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.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- 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 +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -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 -9
- 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 +4 -0
- 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/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 +8 -7
- 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 +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- 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/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- 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 +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
|