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,60 @@
1
+ # Case-Based Scene Dispatch
2
+ # Explicit control flow with validation
3
+ # Source: DragonRuby samples/02_input_basics/07_managing_scenes
4
+
5
+ def tick args
6
+ args.state.current_scene ||= :title
7
+
8
+ case args.state.current_scene
9
+ when :title
10
+ tick_title args
11
+ when :game
12
+ tick_game args
13
+ when :game_over
14
+ tick_game_over args
15
+ else
16
+ # Catch undefined scenes
17
+ args.outputs.labels << {
18
+ x: 640, y: 360,
19
+ text: "Unknown scene: #{args.state.current_scene}",
20
+ anchor_x: 0.5,
21
+ r: 255, g: 0, b: 0
22
+ }
23
+ end
24
+ end
25
+
26
+ def tick_title args
27
+ args.outputs.labels << {
28
+ x: 640, y: 360,
29
+ text: "Title Scene (click to start)",
30
+ anchor_x: 0.5
31
+ }
32
+
33
+ if args.inputs.mouse.click
34
+ args.state.current_scene = :game
35
+ end
36
+ end
37
+
38
+ def tick_game args
39
+ args.outputs.labels << {
40
+ x: 640, y: 360,
41
+ text: "Game Scene (click for game over)",
42
+ anchor_x: 0.5
43
+ }
44
+
45
+ if args.inputs.mouse.click
46
+ args.state.current_scene = :game_over
47
+ end
48
+ end
49
+
50
+ def tick_game_over args
51
+ args.outputs.labels << {
52
+ x: 640, y: 360,
53
+ text: "Game Over (click to restart)",
54
+ anchor_x: 0.5
55
+ }
56
+
57
+ if args.inputs.mouse.click
58
+ args.state.current_scene = :title
59
+ end
60
+ end
@@ -0,0 +1,150 @@
1
+ # Class-Based Scene Management
2
+ # Full OOP approach for complex games
3
+ # Source: DragonRuby samples/02_input_basics/07_managing_scenes_advanced
4
+
5
+ class Game
6
+ attr :args, :hp
7
+
8
+ def initialize
9
+ @hp = 100
10
+ end
11
+
12
+ def take_damage
13
+ @hp -= 10
14
+ end
15
+
16
+ def heal
17
+ @hp += 10
18
+ end
19
+
20
+ def dead?
21
+ @hp <= 0
22
+ end
23
+
24
+ def restart
25
+ @hp = 100
26
+ end
27
+ end
28
+
29
+ class TitleScene
30
+ attr :args
31
+
32
+ def id
33
+ :title
34
+ end
35
+
36
+ def tick
37
+ args.outputs.labels << {
38
+ x: 640, y: 400,
39
+ text: "Click to Start",
40
+ anchor_x: 0.5
41
+ }
42
+
43
+ if args.inputs.mouse.click
44
+ args.state.next_scene = :gameplay
45
+ end
46
+ end
47
+ end
48
+
49
+ class GameplayScene
50
+ attr :game, :args
51
+
52
+ def initialize(game)
53
+ @game = game
54
+ end
55
+
56
+ def id
57
+ :gameplay
58
+ end
59
+
60
+ def tick
61
+ # Damage on Enter
62
+ if args.inputs.keyboard.key_down.enter
63
+ @game.take_damage
64
+ end
65
+
66
+ # Check death
67
+ if @game.dead?
68
+ args.state.next_scene = :game_over
69
+ end
70
+
71
+ # Render
72
+ args.outputs.labels << {
73
+ x: 640, y: 400,
74
+ text: "HP: #{@game.hp} (press Enter to take damage)",
75
+ anchor_x: 0.5
76
+ }
77
+ end
78
+ end
79
+
80
+ class GameOverScene
81
+ attr :game, :args
82
+
83
+ def initialize(game)
84
+ @game = game
85
+ end
86
+
87
+ def id
88
+ :game_over
89
+ end
90
+
91
+ def tick
92
+ args.outputs.labels << {
93
+ x: 640, y: 400,
94
+ text: "Game Over! Click to restart",
95
+ anchor_x: 0.5
96
+ }
97
+
98
+ if args.inputs.mouse.click
99
+ @game.restart
100
+ args.state.next_scene = :title
101
+ end
102
+ end
103
+ end
104
+
105
+ class SceneManager
106
+ attr :args
107
+
108
+ def initialize
109
+ @game = Game.new
110
+ @scenes = [
111
+ TitleScene.new,
112
+ GameplayScene.new(@game),
113
+ GameOverScene.new(@game)
114
+ ]
115
+ end
116
+
117
+ def tick
118
+ args.state.scene ||= :title
119
+
120
+ scene_before = args.state.scene
121
+
122
+ # Find and tick current scene
123
+ scene = @scenes.find { |s| s.id == args.state.scene }
124
+ raise "Scene #{args.state.scene} not found!" unless scene
125
+
126
+ scene.args = args
127
+ scene.tick
128
+
129
+ # Validate no mid-tick change
130
+ if args.state.scene != scene_before
131
+ raise "Don't change scene directly. Use args.state.next_scene"
132
+ end
133
+
134
+ # Apply deferred transition
135
+ if args.state.next_scene
136
+ args.state.scene = args.state.next_scene
137
+ args.state.next_scene = nil
138
+ end
139
+ end
140
+ end
141
+
142
+ def tick args
143
+ $manager ||= SceneManager.new
144
+ $manager.args = args
145
+ $manager.tick
146
+ end
147
+
148
+ def reset args
149
+ $manager = nil
150
+ end
@@ -0,0 +1,100 @@
1
+ # Pause Overlay Pattern
2
+ # Overlay pause menu on frozen gameplay
3
+ # Source: DragonRuby samples/99_genre_arcade/flappy_dragon
4
+
5
+ def tick args
6
+ args.state.scene ||= :gameplay
7
+
8
+ # Toggle pause
9
+ if args.inputs.keyboard.key_down.escape
10
+ if args.state.scene == :gameplay
11
+ args.state.scene = :paused
12
+ args.state.paused_at = Kernel.tick_count
13
+ elsif args.state.scene == :paused
14
+ args.state.scene = :gameplay
15
+ end
16
+ return
17
+ end
18
+
19
+ send("#{args.state.scene}_tick", args)
20
+ end
21
+
22
+ def gameplay_tick args
23
+ args.state.player ||= { x: 640, y: 360, w: 64, h: 64 }
24
+ args.state.score ||= 0
25
+
26
+ # Handle input
27
+ speed = 5
28
+ args.state.player.x += speed if args.inputs.right
29
+ args.state.player.x -= speed if args.inputs.left
30
+ args.state.player.y += speed if args.inputs.up
31
+ args.state.player.y -= speed if args.inputs.down
32
+
33
+ # Clamp to screen
34
+ args.state.player.x = args.state.player.x.clamp(0, 1280 - 64)
35
+ args.state.player.y = args.state.player.y.clamp(0, 720 - 64)
36
+
37
+ args.state.score += 1
38
+
39
+ render_gameplay args
40
+ end
41
+
42
+ def paused_tick args
43
+ # Render frozen gameplay (no updates)
44
+ render_gameplay args
45
+
46
+ # Dark overlay
47
+ args.outputs.primitives << {
48
+ x: 0, y: 0, w: 1280, h: 720,
49
+ r: 0, g: 0, b: 0, a: 180
50
+ }.to_solid
51
+
52
+ # Pause menu
53
+ args.outputs.labels << {
54
+ x: 640, y: 450,
55
+ text: "PAUSED",
56
+ size_enum: 15,
57
+ anchor_x: 0.5,
58
+ r: 255, g: 255, b: 255
59
+ }
60
+ args.outputs.labels << {
61
+ x: 640, y: 350,
62
+ text: "Press ESC to resume",
63
+ anchor_x: 0.5,
64
+ r: 255, g: 255, b: 255
65
+ }
66
+ args.outputs.labels << {
67
+ x: 640, y: 300,
68
+ text: "Press Q to quit",
69
+ anchor_x: 0.5,
70
+ r: 255, g: 255, b: 255
71
+ }
72
+
73
+ if args.inputs.keyboard.key_down.q
74
+ $gtk.reset
75
+ end
76
+ end
77
+
78
+ def render_gameplay args
79
+ # Background
80
+ args.outputs.solids << {
81
+ x: 0, y: 0, w: 1280, h: 720,
82
+ r: 50, g: 80, b: 120
83
+ }
84
+
85
+ # Player (simple box)
86
+ args.outputs.solids << {
87
+ x: args.state.player.x,
88
+ y: args.state.player.y,
89
+ w: args.state.player.w,
90
+ h: args.state.player.h,
91
+ r: 255, g: 200, b: 100
92
+ }
93
+
94
+ # Score
95
+ args.outputs.labels << {
96
+ x: 40, y: 700,
97
+ text: "Score: #{args.state.score}",
98
+ r: 255, g: 255, b: 255
99
+ }
100
+ end
@@ -0,0 +1,68 @@
1
+ # Safe Scene Transitions
2
+ # Prevents mid-tick scene changes for easier debugging
3
+ # Source: DragonRuby samples/02_input_basics/07_managing_scenes
4
+
5
+ def tick args
6
+ args.state.current_scene ||= :title
7
+
8
+ # Capture scene before tick
9
+ scene_before = args.state.current_scene
10
+
11
+ # Dispatch to scene
12
+ case args.state.current_scene
13
+ when :title
14
+ tick_title args
15
+ when :game
16
+ tick_game args
17
+ when :game_over
18
+ tick_game_over args
19
+ end
20
+
21
+ # Validate: scene should not change mid-tick
22
+ if args.state.current_scene != scene_before
23
+ raise "Scene changed mid-tick! Set args.state.next_scene instead."
24
+ end
25
+
26
+ # Apply deferred transition at end of tick
27
+ if args.state.next_scene
28
+ args.state.current_scene = args.state.next_scene
29
+ args.state.next_scene = nil
30
+ end
31
+ end
32
+
33
+ def tick_title args
34
+ args.outputs.labels << {
35
+ x: 640, y: 360,
36
+ text: "Title (click to start)",
37
+ anchor_x: 0.5
38
+ }
39
+
40
+ if args.inputs.mouse.click
41
+ # Use next_scene for deferred transition
42
+ args.state.next_scene = :game
43
+ end
44
+ end
45
+
46
+ def tick_game args
47
+ args.outputs.labels << {
48
+ x: 640, y: 360,
49
+ text: "Game (click for game over)",
50
+ anchor_x: 0.5
51
+ }
52
+
53
+ if args.inputs.mouse.click
54
+ args.state.next_scene = :game_over
55
+ end
56
+ end
57
+
58
+ def tick_game_over args
59
+ args.outputs.labels << {
60
+ x: 640, y: 360,
61
+ text: "Game Over (click to restart)",
62
+ anchor_x: 0.5
63
+ }
64
+
65
+ if args.inputs.mouse.click
66
+ args.state.next_scene = :title
67
+ end
68
+ end
@@ -0,0 +1,98 @@
1
+ # Scene Transition Patterns
2
+ # Various ways to handle scene changes
3
+
4
+ # --- Pattern 1: Instant Transition ---
5
+ def instant_transition args
6
+ args.state.scene = :new_scene
7
+ return # Important: early return
8
+ end
9
+
10
+ # --- Pattern 2: Tracked Transition ---
11
+ # Track when scene changed for animations/effects
12
+ def change_to_scene args, scene
13
+ args.state.scene = scene
14
+ args.state.scene_at = Kernel.tick_count
15
+ args.inputs.keyboard.clear # Prevent input bleed
16
+ end
17
+
18
+ def title_tick_with_fade args
19
+ elapsed = Kernel.tick_count - (args.state.scene_at || 0)
20
+ alpha = [255, elapsed * 8].min # Fade in over ~32 frames
21
+
22
+ args.outputs.labels << {
23
+ x: 640, y: 400,
24
+ text: "Title",
25
+ anchor_x: 0.5,
26
+ a: alpha
27
+ }
28
+ end
29
+
30
+ # --- Pattern 3: State Reset on Transition ---
31
+ def transition_to_gameplay args
32
+ # Reset gameplay state
33
+ args.state.player = nil # Will reinitialize
34
+ args.state.enemies = []
35
+ args.state.score = 0
36
+ args.state.timer = 60 * 60
37
+
38
+ # Keep persistent state
39
+ # args.state.high_score preserved
40
+
41
+ args.state.scene = :gameplay
42
+ end
43
+
44
+ # --- Pattern 4: Full Game Reset ---
45
+ def restart_game
46
+ $gtk.reset # Clears ALL state, tick_count resets
47
+ end
48
+
49
+ # --- Pattern 5: Grace Period ---
50
+ # Prevent accidental input after scene change
51
+ def game_over_tick args
52
+ args.state.grace_timer ||= 60 # 1 second grace period
53
+ args.state.grace_timer -= 1
54
+
55
+ args.outputs.labels << {
56
+ x: 640, y: 400,
57
+ text: "Game Over!",
58
+ anchor_x: 0.5
59
+ }
60
+
61
+ # Don't accept input during grace period
62
+ return if args.state.grace_timer > 0
63
+
64
+ args.outputs.labels << {
65
+ x: 640, y: 300,
66
+ text: "Press SPACE to restart",
67
+ anchor_x: 0.5
68
+ }
69
+
70
+ if args.inputs.keyboard.key_down.space
71
+ $gtk.reset
72
+ end
73
+ end
74
+
75
+ # --- Example: Complete Flow ---
76
+ def tick args
77
+ args.state.scene ||= :title
78
+
79
+ case args.state.scene
80
+ when :title
81
+ args.outputs.labels << { x: 640, y: 400, text: "Title - Press SPACE", anchor_x: 0.5 }
82
+ if args.inputs.keyboard.key_down.space
83
+ transition_to_gameplay args
84
+ end
85
+
86
+ when :gameplay
87
+ args.state.timer ||= 60 * 60
88
+ args.state.timer -= 1
89
+ args.outputs.labels << { x: 640, y: 400, text: "Time: #{(args.state.timer / 60).ceil}", anchor_x: 0.5 }
90
+ if args.state.timer <= 0
91
+ args.state.scene = :game_over
92
+ args.state.grace_timer = nil # Reset for game_over_tick
93
+ end
94
+
95
+ when :game_over
96
+ game_over_tick args
97
+ end
98
+ end
@@ -0,0 +1,88 @@
1
+ # Send-Based Scene Dispatch
2
+ # Simplest pattern using Ruby's dynamic method dispatch
3
+ # Source: DragonRuby book Chapter 11
4
+
5
+ def tick args
6
+ args.state.scene ||= :title
7
+ send("#{args.state.scene}_tick", args)
8
+ end
9
+
10
+ def title_tick args
11
+ args.outputs.labels << {
12
+ x: 640, y: 500,
13
+ text: "My Game",
14
+ size_enum: 10,
15
+ anchor_x: 0.5
16
+ }
17
+ args.outputs.labels << {
18
+ x: 640, y: 300,
19
+ text: "Press SPACE to start",
20
+ anchor_x: 0.5
21
+ }
22
+
23
+ if args.inputs.keyboard.key_down.space
24
+ args.state.scene = :gameplay
25
+ return # Early return prevents old scene code
26
+ end
27
+ end
28
+
29
+ def gameplay_tick args
30
+ args.state.player ||= { x: 640, y: 360, w: 64, h: 64, path: "sprites/player.png" }
31
+ args.state.score ||= 0
32
+ args.state.timer ||= 30 * 60
33
+
34
+ args.state.timer -= 1
35
+
36
+ if args.state.timer <= 0
37
+ args.state.scene = :game_over
38
+ return
39
+ end
40
+
41
+ # Handle input
42
+ speed = 5
43
+ args.state.player.x += speed if args.inputs.right
44
+ args.state.player.x -= speed if args.inputs.left
45
+ args.state.player.y += speed if args.inputs.up
46
+ args.state.player.y -= speed if args.inputs.down
47
+
48
+ # Render
49
+ args.outputs.sprites << args.state.player
50
+ args.outputs.labels << {
51
+ x: 40, y: 700,
52
+ text: "Score: #{args.state.score}"
53
+ }
54
+ args.outputs.labels << {
55
+ x: 1240, y: 700,
56
+ text: "Time: #{(args.state.timer / 60).ceil}",
57
+ anchor_x: 1
58
+ }
59
+ end
60
+
61
+ def game_over_tick args
62
+ args.state.timer -= 1
63
+
64
+ args.outputs.labels << {
65
+ x: 640, y: 450,
66
+ text: "Game Over!",
67
+ size_enum: 10,
68
+ anchor_x: 0.5
69
+ }
70
+ args.outputs.labels << {
71
+ x: 640, y: 350,
72
+ text: "Score: #{args.state.score}",
73
+ size_enum: 4,
74
+ anchor_x: 0.5
75
+ }
76
+ args.outputs.labels << {
77
+ x: 640, y: 200,
78
+ text: "Press SPACE to restart",
79
+ anchor_x: 0.5
80
+ }
81
+
82
+ # Grace period before accepting input
83
+ return if args.state.timer > -30
84
+
85
+ if args.inputs.keyboard.key_down.space
86
+ $gtk.reset
87
+ end
88
+ end