anima-core 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +219 -25
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +76 -28
  12. data/app/jobs/agent_request_job.rb +24 -0
  13. data/app/jobs/analytical_brain_job.rb +33 -0
  14. data/app/jobs/count_event_tokens_job.rb +1 -1
  15. data/app/models/concerns/event/broadcasting.rb +20 -2
  16. data/app/models/event.rb +1 -1
  17. data/app/models/goal.rb +91 -0
  18. data/app/models/session.rb +347 -22
  19. data/config/application.rb +2 -0
  20. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  21. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  22. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  23. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  24. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  25. data/db/migrate/20260315140843_create_goals.rb +16 -0
  26. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  27. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  28. data/lib/agent_loop.rb +65 -9
  29. data/lib/agents/definition.rb +116 -0
  30. data/lib/agents/registry.rb +106 -0
  31. data/lib/analytical_brain/runner.rb +276 -0
  32. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  33. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  34. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  35. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  36. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  37. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  38. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  39. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  40. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  41. data/lib/analytical_brain.rb +23 -0
  42. data/lib/anima/cli/mcp/secrets.rb +76 -0
  43. data/lib/anima/cli/mcp.rb +197 -0
  44. data/lib/anima/cli.rb +4 -0
  45. data/lib/anima/installer.rb +168 -0
  46. data/lib/anima/settings.rb +226 -0
  47. data/lib/anima/version.rb +1 -1
  48. data/lib/anima.rb +9 -0
  49. data/lib/credential_store.rb +103 -0
  50. data/lib/environment_probe.rb +232 -0
  51. data/lib/llm/client.rb +29 -10
  52. data/lib/mcp/client_manager.rb +86 -0
  53. data/lib/mcp/config.rb +213 -0
  54. data/lib/mcp/health_check.rb +77 -0
  55. data/lib/mcp/secrets.rb +73 -0
  56. data/lib/mcp/stdio_transport.rb +206 -0
  57. data/lib/providers/anthropic.rb +8 -7
  58. data/lib/shell_session.rb +11 -10
  59. data/lib/skills/definition.rb +97 -0
  60. data/lib/skills/registry.rb +105 -0
  61. data/lib/tools/edit.rb +3 -4
  62. data/lib/tools/mcp_tool.rb +114 -0
  63. data/lib/tools/read.rb +15 -16
  64. data/lib/tools/registry.rb +14 -12
  65. data/lib/tools/request_feature.rb +121 -0
  66. data/lib/tools/return_result.rb +81 -0
  67. data/lib/tools/spawn_specialist.rb +109 -0
  68. data/lib/tools/spawn_subagent.rb +111 -0
  69. data/lib/tools/subagent_prompts.rb +12 -0
  70. data/lib/tools/web_get.rb +8 -9
  71. data/lib/tui/app.rb +332 -43
  72. data/lib/tui/message_store.rb +20 -0
  73. data/lib/tui/screens/chat.rb +207 -20
  74. data/lib/workflows/definition.rb +97 -0
  75. data/lib/workflows/registry.rb +89 -0
  76. data/skills/activerecord/SKILL.md +255 -0
  77. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  78. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  79. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  80. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  81. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  82. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  83. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  84. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  85. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  86. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  87. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  88. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  89. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  90. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  91. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  92. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  93. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  94. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  95. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  96. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  97. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  98. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  99. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  100. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  101. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  102. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  103. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  104. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  105. data/skills/activerecord/references/associations.md +709 -0
  106. data/skills/activerecord/references/basics.md +622 -0
  107. data/skills/activerecord/references/callbacks.md +738 -0
  108. data/skills/activerecord/references/migrations.md +657 -0
  109. data/skills/activerecord/references/querying.md +655 -0
  110. data/skills/activerecord/references/validations.md +596 -0
  111. data/skills/dragonruby/SKILL.md +250 -0
  112. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  113. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  114. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  115. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  116. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  117. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  118. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  119. data/skills/dragonruby/examples/core/labels.rb +22 -0
  120. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  121. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  122. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  123. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  124. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  125. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  126. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  127. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  128. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  129. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  130. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  131. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  132. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  133. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  134. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  135. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  136. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  137. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  138. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  139. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  140. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  141. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  142. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  143. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  144. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  145. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  146. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  147. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  148. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  149. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  150. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  151. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  152. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  153. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  154. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  155. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  156. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  157. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  158. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  159. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  160. data/skills/dragonruby/references/audio.md +396 -0
  161. data/skills/dragonruby/references/core.md +385 -0
  162. data/skills/dragonruby/references/distribution.md +434 -0
  163. data/skills/dragonruby/references/entities.md +516 -0
  164. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  165. data/skills/dragonruby/references/game-logic/state.md +389 -0
  166. data/skills/dragonruby/references/input.md +414 -0
  167. data/skills/dragonruby/references/rendering/animation.md +467 -0
  168. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  169. data/skills/dragonruby/references/scenes.md +443 -0
  170. data/skills/draper-decorators/SKILL.md +344 -0
  171. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  172. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  173. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  174. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  175. data/skills/draper-decorators/references/patterns.md +507 -0
  176. data/skills/draper-decorators/references/testing.md +559 -0
  177. data/skills/gh-issue.md +182 -0
  178. data/skills/mcp-server/SKILL.md +177 -0
  179. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  180. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  181. data/skills/mcp-server/examples/http_client.rb +48 -0
  182. data/skills/mcp-server/examples/http_server.rb +97 -0
  183. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  184. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  185. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  186. data/skills/mcp-server/references/gotchas.md +183 -0
  187. data/skills/mcp-server/references/prompts.md +98 -0
  188. data/skills/mcp-server/references/resources.md +53 -0
  189. data/skills/mcp-server/references/server.md +140 -0
  190. data/skills/mcp-server/references/tools.md +146 -0
  191. data/skills/mcp-server/references/transport.md +104 -0
  192. data/skills/ratatui-ruby/SKILL.md +315 -0
  193. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  194. data/skills/ratatui-ruby/references/events.md +387 -0
  195. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  196. data/skills/ratatui-ruby/references/layout.md +423 -0
  197. data/skills/ratatui-ruby/references/styling.md +268 -0
  198. data/skills/ratatui-ruby/references/testing.md +433 -0
  199. data/skills/ratatui-ruby/references/widgets.md +532 -0
  200. data/skills/rspec/SKILL.md +340 -0
  201. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  202. data/skills/rspec/examples/core/configuration.rb +126 -0
  203. data/skills/rspec/examples/core/hooks.rb +126 -0
  204. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  205. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  206. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  207. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  208. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  209. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  210. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  211. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  212. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  213. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  214. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  215. data/skills/rspec/examples/matchers/change.rb +115 -0
  216. data/skills/rspec/examples/matchers/collections.rb +154 -0
  217. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  218. data/skills/rspec/examples/matchers/composing.rb +155 -0
  219. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  220. data/skills/rspec/examples/matchers/equality.rb +58 -0
  221. data/skills/rspec/examples/matchers/errors.rb +136 -0
  222. data/skills/rspec/examples/matchers/output.rb +103 -0
  223. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  224. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  225. data/skills/rspec/examples/matchers/types.rb +82 -0
  226. data/skills/rspec/examples/matchers/yield.rb +147 -0
  227. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  228. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  229. data/skills/rspec/examples/mocks/constants.rb +177 -0
  230. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  231. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  232. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  233. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  234. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  235. data/skills/rspec/examples/mocks/responses.rb +223 -0
  236. data/skills/rspec/examples/mocks/spies.rb +149 -0
  237. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  238. data/skills/rspec/examples/rails/channels.rb +250 -0
  239. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  240. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  241. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  242. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  243. data/skills/rspec/examples/rails/matchers.rb +374 -0
  244. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  245. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  246. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  247. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  248. data/skills/rspec/examples/rails/transactions.rb +254 -0
  249. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  250. data/skills/rspec/references/core.md +816 -0
  251. data/skills/rspec/references/factory_bot.md +641 -0
  252. data/skills/rspec/references/matchers.md +516 -0
  253. data/skills/rspec/references/mocks.md +381 -0
  254. data/skills/rspec/references/rails.md +528 -0
  255. data/templates/soul.md +40 -0
  256. data/workflows/commit.md +45 -0
  257. data/workflows/create_handoff.md +98 -0
  258. data/workflows/create_note.md +82 -0
  259. data/workflows/create_plan.md +457 -0
  260. data/workflows/decompose_ticket.md +109 -0
  261. data/workflows/feature.md +91 -0
  262. data/workflows/implement_plan.md +87 -0
  263. data/workflows/iterate_plan.md +247 -0
  264. data/workflows/research_codebase.md +210 -0
  265. data/workflows/resume_handoff.md +217 -0
  266. data/workflows/review_pr.md +320 -0
  267. data/workflows/thoughts_init.md +71 -0
  268. data/workflows/validate_plan.md +166 -0
  269. metadata +284 -1
@@ -0,0 +1,23 @@
1
+ # HD game with high-DPI and linear scaling
2
+ # For modern desktop/web releases
3
+
4
+ devid=youritchusername
5
+ devtitle=Your Studio Name
6
+ gameid=my-hd-game
7
+ gametitle=My HD Game
8
+ version=1.0
9
+ icon=metadata/icon.png
10
+
11
+ # Enable HD rendering (720p, 1080p, 1440p, 4k)
12
+ hd=true
13
+ highdpi=true
14
+
15
+ # Linear scaling for non-pixel-art games
16
+ # 0=nearest (pixel art), 1=linear, 2=anisotropic
17
+ scale_quality=1
18
+
19
+ # Letterbox to maintain 16:9 aspect ratio
20
+ hd_letterbox=true
21
+
22
+ # Maximum scale: 0=stretch, 100=720p, 150=1080p, 200=1440p, 300=4k
23
+ hd_max_scale=0
@@ -0,0 +1,9 @@
1
+ # Minimal game_metadata.txt - All required fields
2
+ # Location: mygame/metadata/game_metadata.txt
3
+
4
+ devid=youritchusername
5
+ devtitle=Your Name
6
+ gameid=my-game-slug
7
+ gametitle=My Game Title
8
+ version=0.1
9
+ icon=metadata/icon.png
@@ -0,0 +1,31 @@
1
+ # Mobile-ready game_metadata.txt
2
+ # For Android/iOS deployment
3
+
4
+ devid=youritchusername
5
+ devtitle=Your Studio Name
6
+ gameid=my-mobile-game
7
+ gametitle=My Mobile Game
8
+ version=1.0
9
+ icon=metadata/icon.png
10
+
11
+ # Required for Android/iOS
12
+ packageid=com.yourstudio.mygame
13
+
14
+ # HD mode for modern devices
15
+ hd=true
16
+ highdpi=true
17
+
18
+ # Portrait orientation (540x960 logical)
19
+ orientation=portrait
20
+ # Or landscape,portrait for rotation support
21
+
22
+ # Platform-specific orientation overrides
23
+ orientation_ios=portrait,landscape
24
+ orientation_android=portrait,landscape
25
+
26
+ # Edge-to-edge rendering (no letterbox)
27
+ hd_letterbox=false
28
+ hd_max_scale=400
29
+
30
+ # Exclude dev files from builds
31
+ ignore_directories=saves,debug,tmp
@@ -0,0 +1,36 @@
1
+ # Platform-specific behavior at runtime
2
+
3
+ def tick(args)
4
+ handle_platform_quirks(args)
5
+ tick_game(args)
6
+ end
7
+
8
+ def handle_platform_quirks(args)
9
+ # Open app store rating page
10
+ if args.state.show_rating_prompt
11
+ open_rating_page(args)
12
+ args.state.show_rating_prompt = false
13
+ end
14
+ end
15
+
16
+ def open_rating_page(args)
17
+ if args.gtk.platform?(:ios)
18
+ args.gtk.openurl "itms-apps://itunes.apple.com/app/idYOURGAMEID?action=write-review"
19
+ elsif args.gtk.platform?(:android)
20
+ args.gtk.openurl "https://play.google.com/store/apps/details?id=com.yourstudio.mygame"
21
+ elsif args.gtk.platform?(:web)
22
+ args.gtk.openurl "https://yourusername.itch.io/yourgame/purchase"
23
+ else
24
+ # Desktop (Windows, macOS, Linux)
25
+ args.gtk.openurl "https://yourusername.itch.io/yourgame/rate?source=game"
26
+ end
27
+ end
28
+
29
+ # Platform-specific asset paths
30
+ def get_save_path(args)
31
+ if args.gtk.platform?(:web)
32
+ "save.dat" # Web uses IndexedDB via same path
33
+ else
34
+ "saves/game.dat"
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # Steam publishing configuration
2
+ # Location: mygame/metadata/steam_metadata.txt
3
+
4
+ # Enable Steam publishing
5
+ steam.publish=true
6
+
7
+ # Steam branch to publish to
8
+ steam.branch=public
9
+
10
+ # Your Steamworks Partner Site login name
11
+ steam.username=YOUR_STEAM_USERNAME
12
+
13
+ # Your Steam AppID (from Steamworks)
14
+ steam.appid=YOUR_APP_ID
15
+
16
+ # Platform-specific Depot IDs (from Steamworks)
17
+ steam.linux_depotid=LINUX_DEPOT_ID
18
+ steam.windows_depotid=WINDOWS_DEPOT_ID
19
+ steam.mac_depotid=MAC_DEPOT_ID
@@ -0,0 +1,43 @@
1
+ # Collision Detection Patterns
2
+ # Demonstrates intersect_rect? and find_intersect_rect
3
+
4
+ def tick(args)
5
+ args.state.player ||= { x: 640, y: 360, w: 32, h: 32 }
6
+ args.state.enemies ||= 8.map { |i| { x: i * 150 + 50, y: 300, w: 40, h: 40 } }
7
+
8
+ # Move player with arrow keys
9
+ args.state.player.x += args.inputs.left_right * 5
10
+ args.state.player.y += args.inputs.up_down * 5
11
+
12
+ # Reset hit status
13
+ args.state.enemies.each { |e| e.hit = false }
14
+
15
+ # Method 1: intersect_rect? - check each entity
16
+ args.state.enemies.each do |enemy|
17
+ if args.state.player.intersect_rect?(enemy)
18
+ enemy.hit = true
19
+ end
20
+ end
21
+
22
+ # Method 2: find_intersect_rect - returns first collision (faster)
23
+ collision = Geometry.find_intersect_rect(
24
+ args.state.player,
25
+ args.state.enemies
26
+ )
27
+
28
+ # Method 3: find_all_intersect_rect - returns all collisions
29
+ all_hits = Geometry.find_all_intersect_rect(
30
+ args.state.player,
31
+ args.state.enemies
32
+ )
33
+
34
+ # Render player (green)
35
+ args.outputs.solids << args.state.player.merge(r: 0, g: 255, b: 0)
36
+
37
+ # Render enemies (red if hit, gray otherwise)
38
+ args.outputs.borders << args.state.enemies.map do |e|
39
+ e.merge(r: e.hit ? 255 : 128, g: e.hit ? 0 : 128, b: e.hit ? 0 : 128)
40
+ end
41
+
42
+ args.outputs.labels << { x: 10, y: 710, text: "Collisions: #{all_hits.length}" }
43
+ end
@@ -0,0 +1,68 @@
1
+ # Entity Lifecycle Pattern
2
+ # Demonstrates: create -> update -> mark dead -> reject
3
+
4
+ def tick(args)
5
+ defaults(args)
6
+ spawn_bullets(args)
7
+ update_bullets(args)
8
+ check_collisions(args)
9
+ cleanup_dead(args)
10
+ render(args)
11
+ end
12
+
13
+ def defaults(args)
14
+ args.state.bullets ||= []
15
+ args.state.targets ||= 5.map { |i| { x: 1000, y: i * 120 + 100, w: 50, h: 50, dead: false } }
16
+ end
17
+
18
+ # Step 1: CREATE entities
19
+ def spawn_bullets(args)
20
+ return unless args.inputs.mouse.click
21
+
22
+ args.state.bullets << {
23
+ x: 0,
24
+ y: args.inputs.mouse.y,
25
+ w: 10,
26
+ h: 10,
27
+ speed: 12,
28
+ dead: false
29
+ }
30
+ end
31
+
32
+ # Step 2: UPDATE entities
33
+ def update_bullets(args)
34
+ args.state.bullets.each do |bullet|
35
+ bullet.x += bullet.speed
36
+
37
+ # Mark off-screen bullets as dead
38
+ bullet.dead = true if bullet.x > args.grid.w
39
+ end
40
+ end
41
+
42
+ # Step 3: MARK dead on collision (don't modify arrays during iteration)
43
+ def check_collisions(args)
44
+ args.state.bullets.each do |bullet|
45
+ next if bullet.dead
46
+
47
+ args.state.targets.each do |target|
48
+ next if target.dead
49
+
50
+ if Geometry.intersect_rect?(bullet, target)
51
+ bullet.dead = true
52
+ target.dead = true
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Step 4: REJECT dead entities after all processing
59
+ def cleanup_dead(args)
60
+ args.state.bullets.reject!(&:dead)
61
+ args.state.targets.reject!(&:dead)
62
+ end
63
+
64
+ def render(args)
65
+ args.outputs.solids << args.state.bullets.map { |b| b.merge(r: 255, g: 200, b: 0) }
66
+ args.outputs.borders << args.state.targets
67
+ args.outputs.labels << { x: 10, y: 710, text: "Click to shoot. Targets: #{args.state.targets.length}" }
68
+ end
@@ -0,0 +1,38 @@
1
+ # Entity Storage Pattern
2
+ # Demonstrates storing entities in args.state arrays
3
+
4
+ def tick(args)
5
+ # Initialize entity collections as empty arrays
6
+ # The ||= ensures they're only created once
7
+ args.state.enemies ||= []
8
+ args.state.bullets ||= []
9
+
10
+ # Add entities to collections on input
11
+ if args.inputs.mouse.click
12
+ args.state.bullets << {
13
+ x: 0,
14
+ y: args.inputs.mouse.y,
15
+ w: 10,
16
+ h: 10,
17
+ speed: 5
18
+ }
19
+ end
20
+
21
+ # Spawn enemies every 60 frames (1 second)
22
+ if args.state.tick_count.zmod?(60)
23
+ args.state.enemies << {
24
+ x: 1280,
25
+ y: rand * 720,
26
+ w: 32,
27
+ h: 32
28
+ }
29
+ end
30
+
31
+ # Render all entities from arrays
32
+ args.outputs.solids << args.state.bullets
33
+ args.outputs.borders << args.state.enemies
34
+
35
+ # Display counts
36
+ args.outputs.labels << { x: 10, y: 710, text: "Enemies: #{args.state.enemies.length}" }
37
+ args.outputs.labels << { x: 10, y: 690, text: "Bullets: #{args.state.bullets.length}" }
38
+ end
@@ -0,0 +1,45 @@
1
+ # Factory Methods Pattern
2
+ # Demonstrates spawn_* factory methods for entity creation
3
+
4
+ def tick(args)
5
+ args.state.enemies ||= []
6
+ args.state.projectiles ||= []
7
+
8
+ # Spawn enemies using factory method
9
+ spawn_enemy(args) if args.state.tick_count.zmod?(90)
10
+
11
+ # Spawn projectiles on click
12
+ spawn_projectile(args, args.inputs.mouse) if args.inputs.mouse.click
13
+
14
+ # Update entities
15
+ args.state.projectiles.each { |p| p.x += p.speed }
16
+
17
+ # Render
18
+ args.outputs.borders << args.state.enemies
19
+ args.outputs.solids << args.state.projectiles
20
+ end
21
+
22
+ # Factory method: encapsulates entity creation logic
23
+ def spawn_enemy(args)
24
+ size = 40
25
+ args.state.enemies << {
26
+ x: args.grid.w + size,
27
+ y: rand(args.grid.h - size * 2) + size,
28
+ w: size,
29
+ h: size,
30
+ path: 'sprites/enemy.png',
31
+ speed: rand(3) + 1
32
+ }
33
+ end
34
+
35
+ # Factory with parameters
36
+ def spawn_projectile(args, mouse)
37
+ args.state.projectiles << {
38
+ x: 0,
39
+ y: mouse.y,
40
+ w: 8,
41
+ h: 8,
42
+ speed: rand * 8 + 4,
43
+ r: 255, g: 128, b: 0
44
+ }
45
+ end
@@ -0,0 +1,50 @@
1
+ # Random Spawning with Gutters
2
+ # Demonstrates safe positioning within screen bounds
3
+
4
+ def tick(args)
5
+ args.state.enemies ||= []
6
+
7
+ # Spawn enemy every 45 frames
8
+ if args.state.tick_count.zmod?(45)
9
+ args.state.enemies << spawn_with_gutters(args)
10
+ end
11
+
12
+ # Render enemies
13
+ args.outputs.sprites << args.state.enemies
14
+
15
+ # Show play area boundary
16
+ gutter = 50
17
+ args.outputs.primitives << { x: gutter, y: gutter, w: 1280 - gutter * 2, h: 720 - gutter * 2, r: 128, g: 128, b: 128, primitive_marker: :border }
18
+ args.outputs.labels << { x: 10, y: 710, text: "Enemies spawn within gray boundary" }
19
+ end
20
+
21
+ # Safe spawn: entities never appear partially off-screen
22
+ def spawn_with_gutters(args)
23
+ size = 32
24
+ gutter = 50
25
+
26
+ # Available spawn area (screen minus gutters on all sides)
27
+ play_w = args.grid.w - gutter * 2 - size
28
+ play_h = args.grid.h - gutter * 2 - size
29
+
30
+ {
31
+ x: gutter + rand * play_w,
32
+ y: gutter + rand * play_h,
33
+ w: size,
34
+ h: size,
35
+ path: 'sprites/enemy.png'
36
+ }
37
+ end
38
+
39
+ # Alternative: spawn only on right side of screen
40
+ def spawn_right_side(args)
41
+ size = 40
42
+ {
43
+ # Right 40% of screen: rand(width * 0.4) + width * 0.6
44
+ x: rand(args.grid.w * 0.4) + args.grid.w * 0.6,
45
+ # Full height with top/bottom gutters
46
+ y: rand(args.grid.h - size * 2) + size,
47
+ w: size,
48
+ h: size
49
+ }
50
+ end
@@ -0,0 +1,98 @@
1
+ # Reset Patterns in DragonRuby
2
+ # Demonstrates full reset, partial reset, and custom reset handlers
3
+
4
+ def tick(args)
5
+ defaults(args)
6
+ handle_input(args)
7
+ render(args)
8
+ end
9
+
10
+ def defaults(args)
11
+ args.state.score ||= 0
12
+ args.state.level ||= 1
13
+ args.state.high_score ||= 0 # Persists across $gtk.reset
14
+ args.state.player ||= { x: 640, y: 360 }
15
+ end
16
+
17
+ def handle_input(args)
18
+ # Move player
19
+ args.state.player.x -= 5 if args.inputs.left
20
+ args.state.player.x += 5 if args.inputs.right
21
+ args.state.player.y -= 5 if args.inputs.down
22
+ args.state.player.y += 5 if args.inputs.up
23
+
24
+ # Add score
25
+ if args.inputs.keyboard.key_down.space
26
+ args.state.score += 10
27
+ args.state.level += 1 if args.state.score.zmod?(100)
28
+ end
29
+
30
+ # Update high score
31
+ args.state.high_score = [args.state.high_score, args.state.score].max
32
+
33
+ # Pattern 1: Full reset (clears all args.state)
34
+ if args.inputs.keyboard.key_down.r
35
+ $gtk.reset
36
+ end
37
+
38
+ # Pattern 2: Partial reset (preserve some state)
39
+ if args.inputs.keyboard.key_down.p
40
+ partial_reset(args)
41
+ end
42
+
43
+ # Pattern 3: Soft reset (reset position only)
44
+ if args.inputs.keyboard.key_down.s
45
+ soft_reset(args)
46
+ end
47
+ end
48
+
49
+ # Full reset clears everything - use ||= with values you want to persist
50
+ # High score persists because of ||= pattern in defaults
51
+
52
+ # Pattern 2: Partial reset - keep high score, reset game
53
+ def partial_reset(args)
54
+ # Save what we want to keep
55
+ high_score = args.state.high_score
56
+
57
+ # Clear specific state
58
+ args.state.score = 0
59
+ args.state.level = 1
60
+ args.state.player = { x: 640, y: 360 }
61
+
62
+ # Note: high_score wasn't touched, so it remains
63
+ end
64
+
65
+ # Pattern 3: Soft reset - minimal state change
66
+ def soft_reset(args)
67
+ args.state.player.x = 640
68
+ args.state.player.y = 360
69
+ # Keep score and level
70
+ end
71
+
72
+ def render(args)
73
+ # Player
74
+ args.outputs.solids << {
75
+ x: args.state.player.x - 25,
76
+ y: args.state.player.y - 25,
77
+ w: 50, h: 50, r: 0, g: 150, b: 255
78
+ }
79
+
80
+ # Stats
81
+ args.outputs.labels << { x: 40, y: 700, text: "Score: #{args.state.score}", size_enum: 5 }
82
+ args.outputs.labels << { x: 40, y: 660, text: "Level: #{args.state.level}", size_enum: 3 }
83
+ args.outputs.labels << { x: 40, y: 620, text: "High Score: #{args.state.high_score}", size_enum: 3, r: 255, g: 215, b: 0 }
84
+
85
+ # Instructions
86
+ args.outputs.labels << { x: 40, y: 100, text: "Arrows: Move | SPACE: Score" }
87
+ args.outputs.labels << { x: 40, y: 70, text: "R: Full reset | P: Partial reset | S: Soft reset" }
88
+ end
89
+
90
+ # Custom reset handler - called when $gtk.reset executes
91
+ def reset(args)
92
+ puts "Game reset! Custom cleanup here."
93
+ # Clear any global variables or class instances
94
+ $game = nil if defined?($game)
95
+ end
96
+
97
+ # Development convenience: uncomment to reset on code reload
98
+ # $gtk.reset
@@ -0,0 +1,101 @@
1
+ # Save/Load Patterns in DragonRuby
2
+ # Demonstrates file persistence for game data
3
+
4
+ HIGH_SCORE_FILE = "high-score.txt"
5
+ SAVE_FILE = "game-save.txt"
6
+
7
+ def tick(args)
8
+ defaults(args)
9
+ handle_input(args)
10
+ render(args)
11
+ end
12
+
13
+ def defaults(args)
14
+ args.state.score ||= 0
15
+ args.state.level ||= 1
16
+ args.state.player_name ||= "Player"
17
+
18
+ # Load high score on first tick (nil-safe with .to_i)
19
+ args.state.high_score ||= $gtk.read_file(HIGH_SCORE_FILE).to_i
20
+ end
21
+
22
+ def handle_input(args)
23
+ # Increment score for testing
24
+ if args.inputs.keyboard.key_down.space
25
+ args.state.score += 10
26
+ end
27
+
28
+ # Save high score (simple pattern)
29
+ if args.inputs.keyboard.key_down.s
30
+ save_high_score(args)
31
+ end
32
+
33
+ # Full game save (serialize_state)
34
+ if args.inputs.keyboard.key_down.f5
35
+ save_game(args)
36
+ end
37
+
38
+ # Load game
39
+ if args.inputs.keyboard.key_down.f9
40
+ load_game(args)
41
+ end
42
+
43
+ # Reset
44
+ if args.inputs.keyboard.key_down.r
45
+ args.state.score = 0
46
+ args.state.level = 1
47
+ end
48
+ end
49
+
50
+ # Pattern 1: Simple high score save (with save-once flag)
51
+ def save_high_score(args)
52
+ if args.state.score > args.state.high_score
53
+ $gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s)
54
+ args.state.high_score = args.state.score
55
+ $gtk.notify!("High score saved!")
56
+ else
57
+ $gtk.notify!("Score not high enough")
58
+ end
59
+ end
60
+
61
+ # Pattern 2: Full state serialization
62
+ def save_game(args)
63
+ # Create save data hash (subset of state)
64
+ save_data = {
65
+ score: args.state.score,
66
+ level: args.state.level,
67
+ player_name: args.state.player_name,
68
+ saved_at: Time.now.to_i
69
+ }
70
+
71
+ $gtk.serialize_state(SAVE_FILE, save_data)
72
+ $gtk.notify!("Game saved!")
73
+ end
74
+
75
+ # Pattern 3: Load with validation
76
+ def load_game(args)
77
+ loaded = $gtk.deserialize_state(SAVE_FILE)
78
+
79
+ if loaded
80
+ args.state.score = loaded.score || 0
81
+ args.state.level = loaded.level || 1
82
+ args.state.player_name = loaded.player_name || "Player"
83
+ $gtk.notify!("Game loaded!")
84
+ else
85
+ $gtk.notify!("No save file found")
86
+ end
87
+ end
88
+
89
+ def render(args)
90
+ args.outputs.labels << { x: 40, y: 700, text: "Score: #{args.state.score}", size_enum: 5 }
91
+ args.outputs.labels << { x: 40, y: 650, text: "Level: #{args.state.level}", size_enum: 3 }
92
+ args.outputs.labels << { x: 40, y: 600, text: "High Score: #{args.state.high_score}", size_enum: 3 }
93
+
94
+ # File status
95
+ save_exists = $gtk.stat_file(SAVE_FILE) ? "Yes" : "No"
96
+ args.outputs.labels << { x: 40, y: 500, text: "Save file exists: #{save_exists}" }
97
+
98
+ # Instructions
99
+ args.outputs.labels << { x: 40, y: 100, text: "SPACE: +10 score | S: Save high score" }
100
+ args.outputs.labels << { x: 40, y: 70, text: "F5: Save game | F9: Load game | R: Reset" }
101
+ end
@@ -0,0 +1,104 @@
1
+ # Scoring System in DragonRuby
2
+ # Demonstrates score tracking, high score persistence, and display
3
+
4
+ def tick(args)
5
+ defaults(args)
6
+ handle_input(args)
7
+ render(args)
8
+ end
9
+
10
+ def defaults(args)
11
+ args.state.score ||= 0
12
+ args.state.high_score ||= 0 # Persists across $gtk.reset
13
+ args.state.combo ||= 0
14
+ args.state.multiplier ||= 1
15
+ end
16
+
17
+ # Scoring patterns
18
+ def handle_input(args)
19
+ # Simple score increment
20
+ if args.inputs.keyboard.key_down.space
21
+ add_score(args, 10)
22
+ end
23
+
24
+ # Score with multiplier
25
+ if args.inputs.keyboard.key_down.z
26
+ add_score_with_combo(args, 100)
27
+ end
28
+
29
+ # Break combo
30
+ if args.inputs.keyboard.key_down.x
31
+ reset_combo(args)
32
+ end
33
+
34
+ # Reset game (high score persists)
35
+ if args.inputs.keyboard.key_down.r
36
+ args.state.score = 0
37
+ args.state.combo = 0
38
+ args.state.multiplier = 1
39
+ end
40
+ end
41
+
42
+ # Pattern 1: Simple score increment
43
+ def add_score(args, points)
44
+ args.state.score += points
45
+ update_high_score(args)
46
+ end
47
+
48
+ # Pattern 2: Score with combo multiplier
49
+ def add_score_with_combo(args, base_points)
50
+ args.state.combo += 1
51
+ args.state.multiplier = [1 + (args.state.combo / 5), 4].min # Max 4x
52
+
53
+ points = base_points * args.state.multiplier
54
+ args.state.score += points
55
+ update_high_score(args)
56
+ end
57
+
58
+ # Pattern 3: High score tracking
59
+ def update_high_score(args)
60
+ if args.state.score > args.state.high_score
61
+ args.state.high_score = args.state.score
62
+ args.state.new_high_score = true
63
+ end
64
+ end
65
+
66
+ def reset_combo(args)
67
+ args.state.combo = 0
68
+ args.state.multiplier = 1
69
+ end
70
+
71
+ def render(args)
72
+ # Current score
73
+ args.outputs.labels << {
74
+ x: 40, y: 700, text: "SCORE: #{args.state.score}",
75
+ size_enum: 5
76
+ }
77
+
78
+ # High score
79
+ args.outputs.labels << {
80
+ x: 40, y: 650, text: "HIGH SCORE: #{args.state.high_score}",
81
+ size_enum: 3, r: 255, g: 215, b: 0
82
+ }
83
+
84
+ # Combo/multiplier
85
+ if args.state.combo > 0
86
+ args.outputs.labels << {
87
+ x: 40, y: 600, text: "COMBO: #{args.state.combo} (x#{args.state.multiplier})",
88
+ size_enum: 3, r: 255, g: 100, b: 100
89
+ }
90
+ end
91
+
92
+ # New high score notification
93
+ if args.state.new_high_score
94
+ args.outputs.labels << {
95
+ x: 640, y: 400, text: "NEW HIGH SCORE!",
96
+ size_enum: 8, anchor_x: 0.5, r: 255, g: 215, b: 0
97
+ }
98
+ end
99
+
100
+ # Instructions
101
+ args.outputs.labels << { x: 40, y: 100, text: "SPACE: +10 points" }
102
+ args.outputs.labels << { x: 40, y: 70, text: "Z: +100 with combo" }
103
+ args.outputs.labels << { x: 40, y: 40, text: "X: Break combo | R: Reset score" }
104
+ end