anima-core 0.2.1 → 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.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +19 -0
- data/README.md +213 -43
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +195 -45
- data/app/decorators/user_message_decorator.rb +16 -5
- data/app/jobs/agent_request_job.rb +55 -2
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +15 -4
- data/app/models/concerns/event/broadcasting.rb +81 -0
- data/app/models/event.rb +20 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +366 -21
- data/config/application.rb +2 -0
- data/config/initializers/event_subscribers.rb +0 -1
- data/config/routes.rb +0 -6
- data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
- data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -6
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +5 -40
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/events/subscribers/persister.rb +1 -0
- data/lib/events/user_message.rb +17 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +11 -20
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +226 -0
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +151 -0
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tools/write.rb +86 -0
- data/lib/tui/app.rb +985 -26
- data/lib/tui/cable_client.rb +69 -31
- data/lib/tui/message_store.rb +103 -8
- data/lib/tui/screens/chat.rb +293 -45
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +290 -3
- data/app/controllers/api/sessions_controller.rb +0 -25
- data/lib/events/subscribers/action_cable_bridge.rb +0 -59
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
## args.state Container
|
|
4
|
+
|
|
5
|
+
Central property bag persisting across ticks.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# All state persists automatically between frames
|
|
9
|
+
args.state.player ||= { x: 120, y: 280 }
|
|
10
|
+
args.state.score ||= 0
|
|
11
|
+
args.state.enemies ||= []
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Key characteristics:**
|
|
15
|
+
- Values retained across tick invocations
|
|
16
|
+
- Cleared on `$gtk.reset`
|
|
17
|
+
- Supports arbitrary nesting with automatic Entity backing
|
|
18
|
+
|
|
19
|
+
## Lazy Initialization (||= Pattern)
|
|
20
|
+
|
|
21
|
+
Initialize values only on first access - prevents reset every frame.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# CORRECT: Set once, persist forever
|
|
25
|
+
args.state.player_x ||= 120
|
|
26
|
+
args.state.timer ||= 30 * 60
|
|
27
|
+
|
|
28
|
+
# WRONG: Resets every frame!
|
|
29
|
+
args.state.player_x = 120
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Why critical:** `tick` runs 60 times/second - without `||=`, values reset constantly.
|
|
33
|
+
|
|
34
|
+
## Frame-Based Timing
|
|
35
|
+
|
|
36
|
+
DragonRuby runs at 60 FPS. Convert seconds to frames:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
FPS = 60
|
|
40
|
+
|
|
41
|
+
# 30 seconds = 1800 frames
|
|
42
|
+
args.state.timer ||= 30 * FPS
|
|
43
|
+
|
|
44
|
+
# Display as seconds
|
|
45
|
+
seconds_left = (args.state.timer / FPS).round
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Timer Countdown Pattern
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Decrement each frame
|
|
52
|
+
args.state.timer -= 1
|
|
53
|
+
|
|
54
|
+
# Check expiration
|
|
55
|
+
if args.state.timer < 0
|
|
56
|
+
game_over_tick(args)
|
|
57
|
+
return # Early return critical!
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Periodic Execution with zmod?
|
|
62
|
+
|
|
63
|
+
Execute code every N frames:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
# Every second (60 frames)
|
|
67
|
+
if Kernel.tick_count.zmod?(60)
|
|
68
|
+
spawn_enemy(args)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Every 3 frames
|
|
72
|
+
if Kernel.tick_count.zmod?(3)
|
|
73
|
+
update_animation(args)
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Elapsed Time Tracking
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# Record event timestamp
|
|
81
|
+
args.state.last_click_at ||= 0
|
|
82
|
+
if args.inputs.mouse.click
|
|
83
|
+
args.state.last_click_at = Kernel.tick_count
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check elapsed time
|
|
87
|
+
if args.state.last_click_at.elapsed_time > 120 # 2 seconds
|
|
88
|
+
show_hint(args)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check if expired
|
|
92
|
+
if args.state.spawn_at.elapsed?(60) # 1 second passed
|
|
93
|
+
do_spawn(args)
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Entity created_at (Automatic)
|
|
98
|
+
|
|
99
|
+
Entities track creation time automatically:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
enemy = args.state.new_entity(:enemy, x: 100, y: 100)
|
|
103
|
+
|
|
104
|
+
# Available properties:
|
|
105
|
+
enemy.created_at # Kernel.tick_count at creation
|
|
106
|
+
enemy.created_at_elapsed # Frames since creation
|
|
107
|
+
enemy.global_created_at # Never resets with $gtk.reset
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Grace Periods
|
|
111
|
+
|
|
112
|
+
Use negative timer values for input delays:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# Timer continues counting negative
|
|
116
|
+
args.state.timer -= 1
|
|
117
|
+
|
|
118
|
+
if args.state.timer < 0
|
|
119
|
+
# Show game over
|
|
120
|
+
render_game_over(args)
|
|
121
|
+
|
|
122
|
+
# Wait 30 frames before accepting restart input
|
|
123
|
+
if args.state.timer < -30 && fire_input?(args)
|
|
124
|
+
$gtk.reset
|
|
125
|
+
end
|
|
126
|
+
return
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Why:** Prevents accidental restarts when player is mashing buttons.
|
|
131
|
+
|
|
132
|
+
## Scoring System
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Initialize
|
|
136
|
+
args.state.score ||= 0
|
|
137
|
+
|
|
138
|
+
# Update on events
|
|
139
|
+
if collision?(fireball, target)
|
|
140
|
+
args.state.score += 1
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Display
|
|
144
|
+
args.outputs.labels << {
|
|
145
|
+
x: 40, y: 700,
|
|
146
|
+
text: "Score: #{args.state.score}"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Game State Transitions
|
|
151
|
+
|
|
152
|
+
### Scene Pattern with send
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
def tick(args)
|
|
156
|
+
args.state.scene ||= :title
|
|
157
|
+
send("#{args.state.scene}_tick", args)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def title_tick(args)
|
|
161
|
+
# Title screen logic
|
|
162
|
+
if fire_input?(args)
|
|
163
|
+
args.state.scene = :gameplay
|
|
164
|
+
return # Early return on transition
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def gameplay_tick(args)
|
|
169
|
+
# Main game logic
|
|
170
|
+
if args.state.timer < 0
|
|
171
|
+
args.state.scene = :game_over
|
|
172
|
+
return
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def game_over_tick(args)
|
|
177
|
+
# Game over logic
|
|
178
|
+
if fire_input?(args)
|
|
179
|
+
$gtk.reset
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Early Return Pattern
|
|
185
|
+
|
|
186
|
+
**Critical:** After state changes, `return` to prevent subsequent code execution.
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
def tick(args)
|
|
190
|
+
if args.state.paused
|
|
191
|
+
render_pause_menu(args)
|
|
192
|
+
return # Skip game logic!
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Normal gameplay only runs when not paused
|
|
196
|
+
update_player(args)
|
|
197
|
+
update_enemies(args)
|
|
198
|
+
end
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## State Reset
|
|
202
|
+
|
|
203
|
+
### Full Reset ($gtk.reset)
|
|
204
|
+
|
|
205
|
+
Clears all `args.state` and resets `tick_count` to 0.
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
# Restart game
|
|
209
|
+
if args.inputs.keyboard.key_down.r
|
|
210
|
+
$gtk.reset
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**During development:** Add at file end to force reinitialization on code reload:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
def tick(args)
|
|
218
|
+
# game code
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
$gtk.reset # Dev convenience - remove for production
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Custom Reset Handler
|
|
225
|
+
|
|
226
|
+
Define `reset` method for cleanup when `$gtk.reset` called:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
def reset(args)
|
|
230
|
+
$game = nil # Clear global instances
|
|
231
|
+
puts "Game reset!"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def tick(args)
|
|
235
|
+
$game ||= Game.new
|
|
236
|
+
$game.tick(args)
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Partial Reset
|
|
241
|
+
|
|
242
|
+
Reset specific state without full reset:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
def restart_level(args)
|
|
246
|
+
args.state.player.x = 120
|
|
247
|
+
args.state.player.y = 280
|
|
248
|
+
args.state.enemies = []
|
|
249
|
+
# Keep score, reset position
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## One-Time Initialization
|
|
254
|
+
|
|
255
|
+
Execute code only on first tick:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
def tick(args)
|
|
259
|
+
if Kernel.tick_count == 0
|
|
260
|
+
# First frame only
|
|
261
|
+
args.audio[:music] = { input: "sounds/theme.ogg", looping: true }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Alternative:** Use `boot` method:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
def boot(args)
|
|
270
|
+
args.state = {}
|
|
271
|
+
# One-time setup
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def tick(args)
|
|
275
|
+
# Normal game loop
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Class-Based State (Alternative)
|
|
280
|
+
|
|
281
|
+
For larger games, use classes with `attr_gtk`:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
class Game
|
|
285
|
+
attr_gtk
|
|
286
|
+
|
|
287
|
+
def initialize
|
|
288
|
+
@player = { x: 0, y: 0 }
|
|
289
|
+
@enemies = []
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def tick
|
|
293
|
+
# Access: @player, state, inputs, outputs
|
|
294
|
+
@player.x += 1 if inputs.right
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def tick(args)
|
|
299
|
+
$game ||= Game.new
|
|
300
|
+
$game.args = args
|
|
301
|
+
$game.tick
|
|
302
|
+
end
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Common Antipatterns
|
|
306
|
+
|
|
307
|
+
### Resetting State Every Frame
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
# WRONG
|
|
311
|
+
args.state.score = 0 # Resets to 0 every frame!
|
|
312
|
+
|
|
313
|
+
# CORRECT
|
|
314
|
+
args.state.score ||= 0
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Missing Early Return
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
# WRONG - game logic runs during game over
|
|
321
|
+
if args.state.game_over
|
|
322
|
+
render_game_over(args)
|
|
323
|
+
end
|
|
324
|
+
update_player(args) # Still runs!
|
|
325
|
+
|
|
326
|
+
# CORRECT
|
|
327
|
+
if args.state.game_over
|
|
328
|
+
render_game_over(args)
|
|
329
|
+
return
|
|
330
|
+
end
|
|
331
|
+
update_player(args)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Forgetting Timer Conversion
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
# WRONG - timer is 30 frames (0.5 seconds)
|
|
338
|
+
args.state.timer ||= 30
|
|
339
|
+
|
|
340
|
+
# CORRECT - timer is 30 seconds
|
|
341
|
+
args.state.timer ||= 30 * 60
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Global State Pollution
|
|
345
|
+
|
|
346
|
+
```ruby
|
|
347
|
+
# WRONG - pollutes global namespace
|
|
348
|
+
@player_x = 100
|
|
349
|
+
|
|
350
|
+
# CORRECT
|
|
351
|
+
args.state.player_x ||= 100
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Decision Tree
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
Need to track time?
|
|
358
|
+
├── Countdown to event → examples/game-logic/timers.rb (countdown pattern)
|
|
359
|
+
├── Periodic action (every N frames) → examples/game-logic/timers.rb (zmod? pattern)
|
|
360
|
+
└── Time since event → elapsed_time / elapsed?
|
|
361
|
+
|
|
362
|
+
Need to track score?
|
|
363
|
+
├── Simple increment → examples/game-logic/scoring.rb
|
|
364
|
+
├── With combo/multiplier → examples/game-logic/scoring.rb (combo pattern)
|
|
365
|
+
└── With high score → examples/game-logic/scoring.rb + save_load.rb
|
|
366
|
+
|
|
367
|
+
Need multiple game states?
|
|
368
|
+
├── Title/Gameplay/GameOver → examples/game-logic/state_transitions.rb
|
|
369
|
+
├── Pause menu → Early return pattern
|
|
370
|
+
└── Level transitions → Partial reset pattern
|
|
371
|
+
|
|
372
|
+
Need to reset game?
|
|
373
|
+
├── Full restart → $gtk.reset (examples/game-logic/reset_patterns.rb)
|
|
374
|
+
├── Keep high score → Use ||= in defaults
|
|
375
|
+
├── Restart level only → examples/game-logic/reset_patterns.rb (partial_reset)
|
|
376
|
+
└── Reset position only → examples/game-logic/reset_patterns.rb (soft_reset)
|
|
377
|
+
|
|
378
|
+
Need restart delay?
|
|
379
|
+
└── Grace period → examples/game-logic/state_transitions.rb (game_over_tick)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Examples
|
|
383
|
+
|
|
384
|
+
| File | Demonstrates |
|
|
385
|
+
|------|--------------|
|
|
386
|
+
| `examples/game-logic/timers.rb` | Countdown, zmod? periodic, elapsed_time |
|
|
387
|
+
| `examples/game-logic/scoring.rb` | Score tracking, combo multipliers |
|
|
388
|
+
| `examples/game-logic/state_transitions.rb` | Scene management, send pattern, grace periods |
|
|
389
|
+
| `examples/game-logic/reset_patterns.rb` | Full reset, partial reset, soft reset |
|