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,386 @@
|
|
|
1
|
+
# Persistence (Save/Load)
|
|
2
|
+
|
|
3
|
+
## File System Overview
|
|
4
|
+
|
|
5
|
+
DragonRuby uses a sandboxed filesystem:
|
|
6
|
+
|
|
7
|
+
| Environment | Read Location | Write Location |
|
|
8
|
+
|-------------|---------------|----------------|
|
|
9
|
+
| Development | Game directory | Game directory |
|
|
10
|
+
| Windows | Data dir, then game | `C:\Users\<name>\AppData\Roaming\[devtitle]\[gametitle]` |
|
|
11
|
+
| macOS | Data dir, then game | `$HOME/Library/Application Support/[gametitle]` |
|
|
12
|
+
| Linux | Data dir, then game | `$HOME/.local/share/[gametitle]` |
|
|
13
|
+
| HTML5 | IndexedDB | IndexedDB |
|
|
14
|
+
|
|
15
|
+
## Basic File Operations
|
|
16
|
+
|
|
17
|
+
### Reading Files
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
contents = $gtk.read_file("save.txt")
|
|
21
|
+
|
|
22
|
+
if contents
|
|
23
|
+
puts contents
|
|
24
|
+
else
|
|
25
|
+
puts "File does not exist"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Returns:** String content or `nil` if file doesn't exist.
|
|
30
|
+
|
|
31
|
+
### Writing Files
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
$gtk.write_file("save.txt", "Player score: 100")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Behavior:** Overwrites existing file. Creates if doesn't exist.
|
|
38
|
+
|
|
39
|
+
### Appending to Files
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
$gtk.append_file("log.txt", "Event at #{Kernel.tick_count}\n")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Behavior:** Adds to end of file. Creates if doesn't exist.
|
|
46
|
+
|
|
47
|
+
### Deleting Files
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Always check existence first
|
|
51
|
+
if $gtk.stat_file("temp.txt")
|
|
52
|
+
$gtk.delete_file("temp.txt")
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Caution:** Raises exceptions if file open, permissions denied, or directory not empty.
|
|
57
|
+
|
|
58
|
+
### File Info
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
info = $gtk.stat_file("save.txt")
|
|
62
|
+
|
|
63
|
+
if info
|
|
64
|
+
puts info.file_size # Integer
|
|
65
|
+
puts info.mod_time # Integer timestamp
|
|
66
|
+
puts info.file_type # :regular, :directory, :symlink
|
|
67
|
+
puts info.readonly # Boolean
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Listing Directory
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
files = $gtk.list_files("sprites/")
|
|
75
|
+
# Returns: ["player.png", "enemy.png", ...]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Type Conversion Patterns
|
|
79
|
+
|
|
80
|
+
### Reading Numbers (Nil-Safe)
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# .to_i on nil returns 0 - perfect for missing files
|
|
84
|
+
HIGH_SCORE_FILE = "high-score.txt"
|
|
85
|
+
|
|
86
|
+
args.state.high_score ||= $gtk.read_file(HIGH_SCORE_FILE).to_i
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Writing Numbers
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# Convert to string before writing
|
|
93
|
+
$gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Save-Once Pattern
|
|
97
|
+
|
|
98
|
+
**Critical:** Without flags, file writes 60 times/second!
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
def game_over_tick(args)
|
|
102
|
+
# Load high score (once per game over)
|
|
103
|
+
args.state.high_score ||= $gtk.read_file("high-score.txt").to_i
|
|
104
|
+
|
|
105
|
+
# Save new high score (once)
|
|
106
|
+
if !args.state.saved_high_score && args.state.score > args.state.high_score
|
|
107
|
+
$gtk.write_file("high-score.txt", args.state.score.to_s)
|
|
108
|
+
args.state.saved_high_score = true # Prevent repeated saves
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## High Score Pattern (Complete)
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
HIGH_SCORE_FILE = "high-score.txt"
|
|
117
|
+
|
|
118
|
+
def game_over_tick(args)
|
|
119
|
+
# Load lazily (once)
|
|
120
|
+
args.state.high_score ||= $gtk.read_file(HIGH_SCORE_FILE).to_i
|
|
121
|
+
|
|
122
|
+
# Save if beaten (once)
|
|
123
|
+
if !args.state.saved && args.state.score > args.state.high_score
|
|
124
|
+
$gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s)
|
|
125
|
+
args.state.saved = true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Display
|
|
129
|
+
if args.state.score > args.state.high_score
|
|
130
|
+
args.outputs.labels << { x: 260, y: 630, text: "New high-score!" }
|
|
131
|
+
else
|
|
132
|
+
args.outputs.labels << { x: 260, y: 630, text: "Best: #{args.state.high_score}" }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Restart prompt
|
|
136
|
+
if fire_input?(args)
|
|
137
|
+
$gtk.reset
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Complex State Serialization
|
|
143
|
+
|
|
144
|
+
For saving full game state (player, enemies, inventory, etc.):
|
|
145
|
+
|
|
146
|
+
### Serialize State
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
# Save to file
|
|
150
|
+
$gtk.serialize_state("game_state.txt", args.state)
|
|
151
|
+
|
|
152
|
+
# Or get string without saving
|
|
153
|
+
serialized = $gtk.serialize_state(args.state)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Warning:** Serialization over 20KB triggers performance warning.
|
|
157
|
+
|
|
158
|
+
### Deserialize State
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Load from file
|
|
162
|
+
loaded_state = $gtk.deserialize_state("game_state.txt")
|
|
163
|
+
|
|
164
|
+
if loaded_state
|
|
165
|
+
args.state = loaded_state
|
|
166
|
+
else
|
|
167
|
+
puts "No save file found"
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Full Save/Load Implementation
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
def save_game(args)
|
|
175
|
+
$gtk.serialize_state("save.txt", args.state)
|
|
176
|
+
$gtk.notify!("Game saved!")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def load_game(args)
|
|
180
|
+
loaded = $gtk.deserialize_state("save.txt")
|
|
181
|
+
if loaded
|
|
182
|
+
args.state = loaded
|
|
183
|
+
$gtk.notify!("Game loaded!")
|
|
184
|
+
else
|
|
185
|
+
$gtk.notify!("No save found")
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def tick(args)
|
|
190
|
+
if args.inputs.keyboard.key_down.f5
|
|
191
|
+
save_game(args)
|
|
192
|
+
elsif args.inputs.keyboard.key_down.f9
|
|
193
|
+
load_game(args)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Timestamped Backups
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
def save_with_backup(args)
|
|
202
|
+
timestamp = Time.now.to_i
|
|
203
|
+
$gtk.serialize_state("save_#{timestamp}.txt", args.state)
|
|
204
|
+
$gtk.serialize_state("save.txt", args.state) # Main save
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Simple Data Formats
|
|
209
|
+
|
|
210
|
+
### Single Value (Text)
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
# Save
|
|
214
|
+
$gtk.write_file("level.txt", args.state.current_level.to_s)
|
|
215
|
+
|
|
216
|
+
# Load
|
|
217
|
+
args.state.current_level = $gtk.read_file("level.txt").to_i
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Multiple Values (Delimited)
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
# Save
|
|
224
|
+
data = "#{args.state.score},#{args.state.level},#{args.state.lives}"
|
|
225
|
+
$gtk.write_file("progress.txt", data)
|
|
226
|
+
|
|
227
|
+
# Load
|
|
228
|
+
contents = $gtk.read_file("progress.txt")
|
|
229
|
+
if contents
|
|
230
|
+
parts = contents.split(",")
|
|
231
|
+
args.state.score = parts[0].to_i
|
|
232
|
+
args.state.level = parts[1].to_i
|
|
233
|
+
args.state.lives = parts[2].to_i
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### JSON-Like (Using serialize_state)
|
|
238
|
+
|
|
239
|
+
For structured data, prefer `serialize_state`/`deserialize_state` - handles complex nested objects automatically.
|
|
240
|
+
|
|
241
|
+
## File Path Conventions
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
# Simple filenames in root
|
|
245
|
+
"high-score.txt"
|
|
246
|
+
"save.txt"
|
|
247
|
+
|
|
248
|
+
# Subdirectories (created automatically)
|
|
249
|
+
"saves/slot1.txt"
|
|
250
|
+
"data/progress.txt"
|
|
251
|
+
|
|
252
|
+
# Descriptive extensions
|
|
253
|
+
"player-data.txt" # Simple text
|
|
254
|
+
"game-state.dat" # Serialized state
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Development vs Production
|
|
258
|
+
|
|
259
|
+
### Development Mode
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
# Writes directly to mygame/ directory
|
|
263
|
+
$gtk.write_file("debug.txt", "test")
|
|
264
|
+
|
|
265
|
+
# Useful for level editors, debug output
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Get Data Directory
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
path = $gtk.get_game_dir
|
|
272
|
+
# Development: mygame/
|
|
273
|
+
# Production: OS-specific data path
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Development-Only Functions
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
# Write outside sandbox (dev only!)
|
|
280
|
+
$gtk.write_file_root("../external.txt", "data")
|
|
281
|
+
$gtk.append_file_root("../log.txt", "entry\n")
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Common Antipatterns
|
|
285
|
+
|
|
286
|
+
### Missing Save-Once Flag
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
# WRONG - writes 60 times/second!
|
|
290
|
+
if args.state.score > args.state.high_score
|
|
291
|
+
$gtk.write_file("high-score.txt", args.state.score.to_s)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# CORRECT
|
|
295
|
+
if !args.state.saved && args.state.score > args.state.high_score
|
|
296
|
+
$gtk.write_file("high-score.txt", args.state.score.to_s)
|
|
297
|
+
args.state.saved = true
|
|
298
|
+
end
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Unchecked File Read
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
# WRONG - crashes on nil (actually OK, nil.to_i = 0)
|
|
305
|
+
score = $gtk.read_file("score.txt").to_i
|
|
306
|
+
|
|
307
|
+
# WRONG - crashes if file missing
|
|
308
|
+
name = $gtk.read_file("name.txt").strip # NoMethodError!
|
|
309
|
+
|
|
310
|
+
# CORRECT
|
|
311
|
+
contents = $gtk.read_file("name.txt")
|
|
312
|
+
name = contents ? contents.strip : "Player"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Deleting Without Check
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
# WRONG - raises exception if missing
|
|
319
|
+
$gtk.delete_file("temp.txt")
|
|
320
|
+
|
|
321
|
+
# CORRECT
|
|
322
|
+
if $gtk.stat_file("temp.txt")
|
|
323
|
+
$gtk.delete_file("temp.txt")
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Saving Every Frame
|
|
328
|
+
|
|
329
|
+
```ruby
|
|
330
|
+
# WRONG - massive I/O overhead
|
|
331
|
+
def tick(args)
|
|
332
|
+
$gtk.write_file("autosave.txt", args.state.to_s)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# CORRECT - periodic saves
|
|
336
|
+
def tick(args)
|
|
337
|
+
if Kernel.tick_count.zmod?(60 * 30) # Every 30 seconds
|
|
338
|
+
save_game(args)
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Quick Reference
|
|
344
|
+
|
|
345
|
+
| Operation | Method | Returns |
|
|
346
|
+
|-----------|--------|---------|
|
|
347
|
+
| Read | `$gtk.read_file(path)` | String or nil |
|
|
348
|
+
| Write | `$gtk.write_file(path, data)` | - |
|
|
349
|
+
| Append | `$gtk.append_file(path, data)` | - |
|
|
350
|
+
| Delete | `$gtk.delete_file(path)` | - |
|
|
351
|
+
| Info | `$gtk.stat_file(path)` | Hash or nil |
|
|
352
|
+
| List | `$gtk.list_files(dir)` | Array |
|
|
353
|
+
| Serialize | `$gtk.serialize_state(path, state)` | String |
|
|
354
|
+
| Deserialize | `$gtk.deserialize_state(path)` | Object or nil |
|
|
355
|
+
|
|
356
|
+
## Decision Tree
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
What to save?
|
|
360
|
+
├── Single value (high score, level) → examples/game-logic/save_load.rb (simple pattern)
|
|
361
|
+
│ └── Use write_file + .to_s / read_file + .to_i
|
|
362
|
+
├── Multiple values → Delimited string or serialize_state
|
|
363
|
+
└── Full game state → examples/game-logic/save_load.rb (serialize pattern)
|
|
364
|
+
└── Use serialize_state / deserialize_state
|
|
365
|
+
|
|
366
|
+
When to save?
|
|
367
|
+
├── On event (game over, checkpoint) → Save-once flag pattern
|
|
368
|
+
├── Periodically → zmod?(60 * 30) for every 30 seconds
|
|
369
|
+
└── On user action (F5 key) → examples/game-logic/save_load.rb
|
|
370
|
+
|
|
371
|
+
How to handle missing files?
|
|
372
|
+
├── Numeric data → .to_i handles nil (returns 0)
|
|
373
|
+
├── String data → Check for nil: contents ? contents : "default"
|
|
374
|
+
└── Complex data → deserialize_state returns nil if missing
|
|
375
|
+
|
|
376
|
+
File not saving correctly?
|
|
377
|
+
├── Writing 60x/second? → Add save-once flag
|
|
378
|
+
├── File empty? → Check .to_s conversion
|
|
379
|
+
└── Wrong location? → Development vs production paths differ
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Examples
|
|
383
|
+
|
|
384
|
+
| File | Demonstrates |
|
|
385
|
+
|------|--------------|
|
|
386
|
+
| `examples/game-logic/save_load.rb` | High score, serialize/deserialize, save-once flag |
|