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,23 @@
|
|
|
1
|
+
# HD game with high-DPI and linear scaling
|
|
2
|
+
# For modern desktop/web releases
|
|
3
|
+
|
|
4
|
+
devid=youritchusername
|
|
5
|
+
devtitle=Your Studio Name
|
|
6
|
+
gameid=my-hd-game
|
|
7
|
+
gametitle=My HD Game
|
|
8
|
+
version=1.0
|
|
9
|
+
icon=metadata/icon.png
|
|
10
|
+
|
|
11
|
+
# Enable HD rendering (720p, 1080p, 1440p, 4k)
|
|
12
|
+
hd=true
|
|
13
|
+
highdpi=true
|
|
14
|
+
|
|
15
|
+
# Linear scaling for non-pixel-art games
|
|
16
|
+
# 0=nearest (pixel art), 1=linear, 2=anisotropic
|
|
17
|
+
scale_quality=1
|
|
18
|
+
|
|
19
|
+
# Letterbox to maintain 16:9 aspect ratio
|
|
20
|
+
hd_letterbox=true
|
|
21
|
+
|
|
22
|
+
# Maximum scale: 0=stretch, 100=720p, 150=1080p, 200=1440p, 300=4k
|
|
23
|
+
hd_max_scale=0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Mobile-ready game_metadata.txt
|
|
2
|
+
# For Android/iOS deployment
|
|
3
|
+
|
|
4
|
+
devid=youritchusername
|
|
5
|
+
devtitle=Your Studio Name
|
|
6
|
+
gameid=my-mobile-game
|
|
7
|
+
gametitle=My Mobile Game
|
|
8
|
+
version=1.0
|
|
9
|
+
icon=metadata/icon.png
|
|
10
|
+
|
|
11
|
+
# Required for Android/iOS
|
|
12
|
+
packageid=com.yourstudio.mygame
|
|
13
|
+
|
|
14
|
+
# HD mode for modern devices
|
|
15
|
+
hd=true
|
|
16
|
+
highdpi=true
|
|
17
|
+
|
|
18
|
+
# Portrait orientation (540x960 logical)
|
|
19
|
+
orientation=portrait
|
|
20
|
+
# Or landscape,portrait for rotation support
|
|
21
|
+
|
|
22
|
+
# Platform-specific orientation overrides
|
|
23
|
+
orientation_ios=portrait,landscape
|
|
24
|
+
orientation_android=portrait,landscape
|
|
25
|
+
|
|
26
|
+
# Edge-to-edge rendering (no letterbox)
|
|
27
|
+
hd_letterbox=false
|
|
28
|
+
hd_max_scale=400
|
|
29
|
+
|
|
30
|
+
# Exclude dev files from builds
|
|
31
|
+
ignore_directories=saves,debug,tmp
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Platform-specific behavior at runtime
|
|
2
|
+
|
|
3
|
+
def tick(args)
|
|
4
|
+
handle_platform_quirks(args)
|
|
5
|
+
tick_game(args)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def handle_platform_quirks(args)
|
|
9
|
+
# Open app store rating page
|
|
10
|
+
if args.state.show_rating_prompt
|
|
11
|
+
open_rating_page(args)
|
|
12
|
+
args.state.show_rating_prompt = false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def open_rating_page(args)
|
|
17
|
+
if args.gtk.platform?(:ios)
|
|
18
|
+
args.gtk.openurl "itms-apps://itunes.apple.com/app/idYOURGAMEID?action=write-review"
|
|
19
|
+
elsif args.gtk.platform?(:android)
|
|
20
|
+
args.gtk.openurl "https://play.google.com/store/apps/details?id=com.yourstudio.mygame"
|
|
21
|
+
elsif args.gtk.platform?(:web)
|
|
22
|
+
args.gtk.openurl "https://yourusername.itch.io/yourgame/purchase"
|
|
23
|
+
else
|
|
24
|
+
# Desktop (Windows, macOS, Linux)
|
|
25
|
+
args.gtk.openurl "https://yourusername.itch.io/yourgame/rate?source=game"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Platform-specific asset paths
|
|
30
|
+
def get_save_path(args)
|
|
31
|
+
if args.gtk.platform?(:web)
|
|
32
|
+
"save.dat" # Web uses IndexedDB via same path
|
|
33
|
+
else
|
|
34
|
+
"saves/game.dat"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Steam publishing configuration
|
|
2
|
+
# Location: mygame/metadata/steam_metadata.txt
|
|
3
|
+
|
|
4
|
+
# Enable Steam publishing
|
|
5
|
+
steam.publish=true
|
|
6
|
+
|
|
7
|
+
# Steam branch to publish to
|
|
8
|
+
steam.branch=public
|
|
9
|
+
|
|
10
|
+
# Your Steamworks Partner Site login name
|
|
11
|
+
steam.username=YOUR_STEAM_USERNAME
|
|
12
|
+
|
|
13
|
+
# Your Steam AppID (from Steamworks)
|
|
14
|
+
steam.appid=YOUR_APP_ID
|
|
15
|
+
|
|
16
|
+
# Platform-specific Depot IDs (from Steamworks)
|
|
17
|
+
steam.linux_depotid=LINUX_DEPOT_ID
|
|
18
|
+
steam.windows_depotid=WINDOWS_DEPOT_ID
|
|
19
|
+
steam.mac_depotid=MAC_DEPOT_ID
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Collision Detection Patterns
|
|
2
|
+
# Demonstrates intersect_rect? and find_intersect_rect
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
args.state.player ||= { x: 640, y: 360, w: 32, h: 32 }
|
|
6
|
+
args.state.enemies ||= 8.map { |i| { x: i * 150 + 50, y: 300, w: 40, h: 40 } }
|
|
7
|
+
|
|
8
|
+
# Move player with arrow keys
|
|
9
|
+
args.state.player.x += args.inputs.left_right * 5
|
|
10
|
+
args.state.player.y += args.inputs.up_down * 5
|
|
11
|
+
|
|
12
|
+
# Reset hit status
|
|
13
|
+
args.state.enemies.each { |e| e.hit = false }
|
|
14
|
+
|
|
15
|
+
# Method 1: intersect_rect? - check each entity
|
|
16
|
+
args.state.enemies.each do |enemy|
|
|
17
|
+
if args.state.player.intersect_rect?(enemy)
|
|
18
|
+
enemy.hit = true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Method 2: find_intersect_rect - returns first collision (faster)
|
|
23
|
+
collision = Geometry.find_intersect_rect(
|
|
24
|
+
args.state.player,
|
|
25
|
+
args.state.enemies
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Method 3: find_all_intersect_rect - returns all collisions
|
|
29
|
+
all_hits = Geometry.find_all_intersect_rect(
|
|
30
|
+
args.state.player,
|
|
31
|
+
args.state.enemies
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Render player (green)
|
|
35
|
+
args.outputs.solids << args.state.player.merge(r: 0, g: 255, b: 0)
|
|
36
|
+
|
|
37
|
+
# Render enemies (red if hit, gray otherwise)
|
|
38
|
+
args.outputs.borders << args.state.enemies.map do |e|
|
|
39
|
+
e.merge(r: e.hit ? 255 : 128, g: e.hit ? 0 : 128, b: e.hit ? 0 : 128)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
args.outputs.labels << { x: 10, y: 710, text: "Collisions: #{all_hits.length}" }
|
|
43
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Entity Lifecycle Pattern
|
|
2
|
+
# Demonstrates: create -> update -> mark dead -> reject
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
defaults(args)
|
|
6
|
+
spawn_bullets(args)
|
|
7
|
+
update_bullets(args)
|
|
8
|
+
check_collisions(args)
|
|
9
|
+
cleanup_dead(args)
|
|
10
|
+
render(args)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def defaults(args)
|
|
14
|
+
args.state.bullets ||= []
|
|
15
|
+
args.state.targets ||= 5.map { |i| { x: 1000, y: i * 120 + 100, w: 50, h: 50, dead: false } }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Step 1: CREATE entities
|
|
19
|
+
def spawn_bullets(args)
|
|
20
|
+
return unless args.inputs.mouse.click
|
|
21
|
+
|
|
22
|
+
args.state.bullets << {
|
|
23
|
+
x: 0,
|
|
24
|
+
y: args.inputs.mouse.y,
|
|
25
|
+
w: 10,
|
|
26
|
+
h: 10,
|
|
27
|
+
speed: 12,
|
|
28
|
+
dead: false
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Step 2: UPDATE entities
|
|
33
|
+
def update_bullets(args)
|
|
34
|
+
args.state.bullets.each do |bullet|
|
|
35
|
+
bullet.x += bullet.speed
|
|
36
|
+
|
|
37
|
+
# Mark off-screen bullets as dead
|
|
38
|
+
bullet.dead = true if bullet.x > args.grid.w
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Step 3: MARK dead on collision (don't modify arrays during iteration)
|
|
43
|
+
def check_collisions(args)
|
|
44
|
+
args.state.bullets.each do |bullet|
|
|
45
|
+
next if bullet.dead
|
|
46
|
+
|
|
47
|
+
args.state.targets.each do |target|
|
|
48
|
+
next if target.dead
|
|
49
|
+
|
|
50
|
+
if Geometry.intersect_rect?(bullet, target)
|
|
51
|
+
bullet.dead = true
|
|
52
|
+
target.dead = true
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Step 4: REJECT dead entities after all processing
|
|
59
|
+
def cleanup_dead(args)
|
|
60
|
+
args.state.bullets.reject!(&:dead)
|
|
61
|
+
args.state.targets.reject!(&:dead)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render(args)
|
|
65
|
+
args.outputs.solids << args.state.bullets.map { |b| b.merge(r: 255, g: 200, b: 0) }
|
|
66
|
+
args.outputs.borders << args.state.targets
|
|
67
|
+
args.outputs.labels << { x: 10, y: 710, text: "Click to shoot. Targets: #{args.state.targets.length}" }
|
|
68
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Entity Storage Pattern
|
|
2
|
+
# Demonstrates storing entities in args.state arrays
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
# Initialize entity collections as empty arrays
|
|
6
|
+
# The ||= ensures they're only created once
|
|
7
|
+
args.state.enemies ||= []
|
|
8
|
+
args.state.bullets ||= []
|
|
9
|
+
|
|
10
|
+
# Add entities to collections on input
|
|
11
|
+
if args.inputs.mouse.click
|
|
12
|
+
args.state.bullets << {
|
|
13
|
+
x: 0,
|
|
14
|
+
y: args.inputs.mouse.y,
|
|
15
|
+
w: 10,
|
|
16
|
+
h: 10,
|
|
17
|
+
speed: 5
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Spawn enemies every 60 frames (1 second)
|
|
22
|
+
if args.state.tick_count.zmod?(60)
|
|
23
|
+
args.state.enemies << {
|
|
24
|
+
x: 1280,
|
|
25
|
+
y: rand * 720,
|
|
26
|
+
w: 32,
|
|
27
|
+
h: 32
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Render all entities from arrays
|
|
32
|
+
args.outputs.solids << args.state.bullets
|
|
33
|
+
args.outputs.borders << args.state.enemies
|
|
34
|
+
|
|
35
|
+
# Display counts
|
|
36
|
+
args.outputs.labels << { x: 10, y: 710, text: "Enemies: #{args.state.enemies.length}" }
|
|
37
|
+
args.outputs.labels << { x: 10, y: 690, text: "Bullets: #{args.state.bullets.length}" }
|
|
38
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Factory Methods Pattern
|
|
2
|
+
# Demonstrates spawn_* factory methods for entity creation
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
args.state.enemies ||= []
|
|
6
|
+
args.state.projectiles ||= []
|
|
7
|
+
|
|
8
|
+
# Spawn enemies using factory method
|
|
9
|
+
spawn_enemy(args) if args.state.tick_count.zmod?(90)
|
|
10
|
+
|
|
11
|
+
# Spawn projectiles on click
|
|
12
|
+
spawn_projectile(args, args.inputs.mouse) if args.inputs.mouse.click
|
|
13
|
+
|
|
14
|
+
# Update entities
|
|
15
|
+
args.state.projectiles.each { |p| p.x += p.speed }
|
|
16
|
+
|
|
17
|
+
# Render
|
|
18
|
+
args.outputs.borders << args.state.enemies
|
|
19
|
+
args.outputs.solids << args.state.projectiles
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Factory method: encapsulates entity creation logic
|
|
23
|
+
def spawn_enemy(args)
|
|
24
|
+
size = 40
|
|
25
|
+
args.state.enemies << {
|
|
26
|
+
x: args.grid.w + size,
|
|
27
|
+
y: rand(args.grid.h - size * 2) + size,
|
|
28
|
+
w: size,
|
|
29
|
+
h: size,
|
|
30
|
+
path: 'sprites/enemy.png',
|
|
31
|
+
speed: rand(3) + 1
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Factory with parameters
|
|
36
|
+
def spawn_projectile(args, mouse)
|
|
37
|
+
args.state.projectiles << {
|
|
38
|
+
x: 0,
|
|
39
|
+
y: mouse.y,
|
|
40
|
+
w: 8,
|
|
41
|
+
h: 8,
|
|
42
|
+
speed: rand * 8 + 4,
|
|
43
|
+
r: 255, g: 128, b: 0
|
|
44
|
+
}
|
|
45
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Random Spawning with Gutters
|
|
2
|
+
# Demonstrates safe positioning within screen bounds
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
args.state.enemies ||= []
|
|
6
|
+
|
|
7
|
+
# Spawn enemy every 45 frames
|
|
8
|
+
if args.state.tick_count.zmod?(45)
|
|
9
|
+
args.state.enemies << spawn_with_gutters(args)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Render enemies
|
|
13
|
+
args.outputs.sprites << args.state.enemies
|
|
14
|
+
|
|
15
|
+
# Show play area boundary
|
|
16
|
+
gutter = 50
|
|
17
|
+
args.outputs.primitives << { x: gutter, y: gutter, w: 1280 - gutter * 2, h: 720 - gutter * 2, r: 128, g: 128, b: 128, primitive_marker: :border }
|
|
18
|
+
args.outputs.labels << { x: 10, y: 710, text: "Enemies spawn within gray boundary" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Safe spawn: entities never appear partially off-screen
|
|
22
|
+
def spawn_with_gutters(args)
|
|
23
|
+
size = 32
|
|
24
|
+
gutter = 50
|
|
25
|
+
|
|
26
|
+
# Available spawn area (screen minus gutters on all sides)
|
|
27
|
+
play_w = args.grid.w - gutter * 2 - size
|
|
28
|
+
play_h = args.grid.h - gutter * 2 - size
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
x: gutter + rand * play_w,
|
|
32
|
+
y: gutter + rand * play_h,
|
|
33
|
+
w: size,
|
|
34
|
+
h: size,
|
|
35
|
+
path: 'sprites/enemy.png'
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Alternative: spawn only on right side of screen
|
|
40
|
+
def spawn_right_side(args)
|
|
41
|
+
size = 40
|
|
42
|
+
{
|
|
43
|
+
# Right 40% of screen: rand(width * 0.4) + width * 0.6
|
|
44
|
+
x: rand(args.grid.w * 0.4) + args.grid.w * 0.6,
|
|
45
|
+
# Full height with top/bottom gutters
|
|
46
|
+
y: rand(args.grid.h - size * 2) + size,
|
|
47
|
+
w: size,
|
|
48
|
+
h: size
|
|
49
|
+
}
|
|
50
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Reset Patterns in DragonRuby
|
|
2
|
+
# Demonstrates full reset, partial reset, and custom reset handlers
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
defaults(args)
|
|
6
|
+
handle_input(args)
|
|
7
|
+
render(args)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def defaults(args)
|
|
11
|
+
args.state.score ||= 0
|
|
12
|
+
args.state.level ||= 1
|
|
13
|
+
args.state.high_score ||= 0 # Persists across $gtk.reset
|
|
14
|
+
args.state.player ||= { x: 640, y: 360 }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def handle_input(args)
|
|
18
|
+
# Move player
|
|
19
|
+
args.state.player.x -= 5 if args.inputs.left
|
|
20
|
+
args.state.player.x += 5 if args.inputs.right
|
|
21
|
+
args.state.player.y -= 5 if args.inputs.down
|
|
22
|
+
args.state.player.y += 5 if args.inputs.up
|
|
23
|
+
|
|
24
|
+
# Add score
|
|
25
|
+
if args.inputs.keyboard.key_down.space
|
|
26
|
+
args.state.score += 10
|
|
27
|
+
args.state.level += 1 if args.state.score.zmod?(100)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Update high score
|
|
31
|
+
args.state.high_score = [args.state.high_score, args.state.score].max
|
|
32
|
+
|
|
33
|
+
# Pattern 1: Full reset (clears all args.state)
|
|
34
|
+
if args.inputs.keyboard.key_down.r
|
|
35
|
+
$gtk.reset
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Pattern 2: Partial reset (preserve some state)
|
|
39
|
+
if args.inputs.keyboard.key_down.p
|
|
40
|
+
partial_reset(args)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Pattern 3: Soft reset (reset position only)
|
|
44
|
+
if args.inputs.keyboard.key_down.s
|
|
45
|
+
soft_reset(args)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Full reset clears everything - use ||= with values you want to persist
|
|
50
|
+
# High score persists because of ||= pattern in defaults
|
|
51
|
+
|
|
52
|
+
# Pattern 2: Partial reset - keep high score, reset game
|
|
53
|
+
def partial_reset(args)
|
|
54
|
+
# Save what we want to keep
|
|
55
|
+
high_score = args.state.high_score
|
|
56
|
+
|
|
57
|
+
# Clear specific state
|
|
58
|
+
args.state.score = 0
|
|
59
|
+
args.state.level = 1
|
|
60
|
+
args.state.player = { x: 640, y: 360 }
|
|
61
|
+
|
|
62
|
+
# Note: high_score wasn't touched, so it remains
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Pattern 3: Soft reset - minimal state change
|
|
66
|
+
def soft_reset(args)
|
|
67
|
+
args.state.player.x = 640
|
|
68
|
+
args.state.player.y = 360
|
|
69
|
+
# Keep score and level
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def render(args)
|
|
73
|
+
# Player
|
|
74
|
+
args.outputs.solids << {
|
|
75
|
+
x: args.state.player.x - 25,
|
|
76
|
+
y: args.state.player.y - 25,
|
|
77
|
+
w: 50, h: 50, r: 0, g: 150, b: 255
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Stats
|
|
81
|
+
args.outputs.labels << { x: 40, y: 700, text: "Score: #{args.state.score}", size_enum: 5 }
|
|
82
|
+
args.outputs.labels << { x: 40, y: 660, text: "Level: #{args.state.level}", size_enum: 3 }
|
|
83
|
+
args.outputs.labels << { x: 40, y: 620, text: "High Score: #{args.state.high_score}", size_enum: 3, r: 255, g: 215, b: 0 }
|
|
84
|
+
|
|
85
|
+
# Instructions
|
|
86
|
+
args.outputs.labels << { x: 40, y: 100, text: "Arrows: Move | SPACE: Score" }
|
|
87
|
+
args.outputs.labels << { x: 40, y: 70, text: "R: Full reset | P: Partial reset | S: Soft reset" }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Custom reset handler - called when $gtk.reset executes
|
|
91
|
+
def reset(args)
|
|
92
|
+
puts "Game reset! Custom cleanup here."
|
|
93
|
+
# Clear any global variables or class instances
|
|
94
|
+
$game = nil if defined?($game)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Development convenience: uncomment to reset on code reload
|
|
98
|
+
# $gtk.reset
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Save/Load Patterns in DragonRuby
|
|
2
|
+
# Demonstrates file persistence for game data
|
|
3
|
+
|
|
4
|
+
HIGH_SCORE_FILE = "high-score.txt"
|
|
5
|
+
SAVE_FILE = "game-save.txt"
|
|
6
|
+
|
|
7
|
+
def tick(args)
|
|
8
|
+
defaults(args)
|
|
9
|
+
handle_input(args)
|
|
10
|
+
render(args)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def defaults(args)
|
|
14
|
+
args.state.score ||= 0
|
|
15
|
+
args.state.level ||= 1
|
|
16
|
+
args.state.player_name ||= "Player"
|
|
17
|
+
|
|
18
|
+
# Load high score on first tick (nil-safe with .to_i)
|
|
19
|
+
args.state.high_score ||= $gtk.read_file(HIGH_SCORE_FILE).to_i
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def handle_input(args)
|
|
23
|
+
# Increment score for testing
|
|
24
|
+
if args.inputs.keyboard.key_down.space
|
|
25
|
+
args.state.score += 10
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Save high score (simple pattern)
|
|
29
|
+
if args.inputs.keyboard.key_down.s
|
|
30
|
+
save_high_score(args)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Full game save (serialize_state)
|
|
34
|
+
if args.inputs.keyboard.key_down.f5
|
|
35
|
+
save_game(args)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Load game
|
|
39
|
+
if args.inputs.keyboard.key_down.f9
|
|
40
|
+
load_game(args)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Reset
|
|
44
|
+
if args.inputs.keyboard.key_down.r
|
|
45
|
+
args.state.score = 0
|
|
46
|
+
args.state.level = 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Pattern 1: Simple high score save (with save-once flag)
|
|
51
|
+
def save_high_score(args)
|
|
52
|
+
if args.state.score > args.state.high_score
|
|
53
|
+
$gtk.write_file(HIGH_SCORE_FILE, args.state.score.to_s)
|
|
54
|
+
args.state.high_score = args.state.score
|
|
55
|
+
$gtk.notify!("High score saved!")
|
|
56
|
+
else
|
|
57
|
+
$gtk.notify!("Score not high enough")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Pattern 2: Full state serialization
|
|
62
|
+
def save_game(args)
|
|
63
|
+
# Create save data hash (subset of state)
|
|
64
|
+
save_data = {
|
|
65
|
+
score: args.state.score,
|
|
66
|
+
level: args.state.level,
|
|
67
|
+
player_name: args.state.player_name,
|
|
68
|
+
saved_at: Time.now.to_i
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
$gtk.serialize_state(SAVE_FILE, save_data)
|
|
72
|
+
$gtk.notify!("Game saved!")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Pattern 3: Load with validation
|
|
76
|
+
def load_game(args)
|
|
77
|
+
loaded = $gtk.deserialize_state(SAVE_FILE)
|
|
78
|
+
|
|
79
|
+
if loaded
|
|
80
|
+
args.state.score = loaded.score || 0
|
|
81
|
+
args.state.level = loaded.level || 1
|
|
82
|
+
args.state.player_name = loaded.player_name || "Player"
|
|
83
|
+
$gtk.notify!("Game loaded!")
|
|
84
|
+
else
|
|
85
|
+
$gtk.notify!("No save file found")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def render(args)
|
|
90
|
+
args.outputs.labels << { x: 40, y: 700, text: "Score: #{args.state.score}", size_enum: 5 }
|
|
91
|
+
args.outputs.labels << { x: 40, y: 650, text: "Level: #{args.state.level}", size_enum: 3 }
|
|
92
|
+
args.outputs.labels << { x: 40, y: 600, text: "High Score: #{args.state.high_score}", size_enum: 3 }
|
|
93
|
+
|
|
94
|
+
# File status
|
|
95
|
+
save_exists = $gtk.stat_file(SAVE_FILE) ? "Yes" : "No"
|
|
96
|
+
args.outputs.labels << { x: 40, y: 500, text: "Save file exists: #{save_exists}" }
|
|
97
|
+
|
|
98
|
+
# Instructions
|
|
99
|
+
args.outputs.labels << { x: 40, y: 100, text: "SPACE: +10 score | S: Save high score" }
|
|
100
|
+
args.outputs.labels << { x: 40, y: 70, text: "F5: Save game | F9: Load game | R: Reset" }
|
|
101
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Scoring System in DragonRuby
|
|
2
|
+
# Demonstrates score tracking, high score persistence, and display
|
|
3
|
+
|
|
4
|
+
def tick(args)
|
|
5
|
+
defaults(args)
|
|
6
|
+
handle_input(args)
|
|
7
|
+
render(args)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def defaults(args)
|
|
11
|
+
args.state.score ||= 0
|
|
12
|
+
args.state.high_score ||= 0 # Persists across $gtk.reset
|
|
13
|
+
args.state.combo ||= 0
|
|
14
|
+
args.state.multiplier ||= 1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Scoring patterns
|
|
18
|
+
def handle_input(args)
|
|
19
|
+
# Simple score increment
|
|
20
|
+
if args.inputs.keyboard.key_down.space
|
|
21
|
+
add_score(args, 10)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Score with multiplier
|
|
25
|
+
if args.inputs.keyboard.key_down.z
|
|
26
|
+
add_score_with_combo(args, 100)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Break combo
|
|
30
|
+
if args.inputs.keyboard.key_down.x
|
|
31
|
+
reset_combo(args)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Reset game (high score persists)
|
|
35
|
+
if args.inputs.keyboard.key_down.r
|
|
36
|
+
args.state.score = 0
|
|
37
|
+
args.state.combo = 0
|
|
38
|
+
args.state.multiplier = 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Pattern 1: Simple score increment
|
|
43
|
+
def add_score(args, points)
|
|
44
|
+
args.state.score += points
|
|
45
|
+
update_high_score(args)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Pattern 2: Score with combo multiplier
|
|
49
|
+
def add_score_with_combo(args, base_points)
|
|
50
|
+
args.state.combo += 1
|
|
51
|
+
args.state.multiplier = [1 + (args.state.combo / 5), 4].min # Max 4x
|
|
52
|
+
|
|
53
|
+
points = base_points * args.state.multiplier
|
|
54
|
+
args.state.score += points
|
|
55
|
+
update_high_score(args)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Pattern 3: High score tracking
|
|
59
|
+
def update_high_score(args)
|
|
60
|
+
if args.state.score > args.state.high_score
|
|
61
|
+
args.state.high_score = args.state.score
|
|
62
|
+
args.state.new_high_score = true
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def reset_combo(args)
|
|
67
|
+
args.state.combo = 0
|
|
68
|
+
args.state.multiplier = 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render(args)
|
|
72
|
+
# Current score
|
|
73
|
+
args.outputs.labels << {
|
|
74
|
+
x: 40, y: 700, text: "SCORE: #{args.state.score}",
|
|
75
|
+
size_enum: 5
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# High score
|
|
79
|
+
args.outputs.labels << {
|
|
80
|
+
x: 40, y: 650, text: "HIGH SCORE: #{args.state.high_score}",
|
|
81
|
+
size_enum: 3, r: 255, g: 215, b: 0
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Combo/multiplier
|
|
85
|
+
if args.state.combo > 0
|
|
86
|
+
args.outputs.labels << {
|
|
87
|
+
x: 40, y: 600, text: "COMBO: #{args.state.combo} (x#{args.state.multiplier})",
|
|
88
|
+
size_enum: 3, r: 255, g: 100, b: 100
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# New high score notification
|
|
93
|
+
if args.state.new_high_score
|
|
94
|
+
args.outputs.labels << {
|
|
95
|
+
x: 640, y: 400, text: "NEW HIGH SCORE!",
|
|
96
|
+
size_enum: 8, anchor_x: 0.5, r: 255, g: 215, b: 0
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Instructions
|
|
101
|
+
args.outputs.labels << { x: 40, y: 100, text: "SPACE: +10 points" }
|
|
102
|
+
args.outputs.labels << { x: 40, y: 70, text: "Z: +100 with combo" }
|
|
103
|
+
args.outputs.labels << { x: 40, y: 40, text: "X: Break combo | R: Reset score" }
|
|
104
|
+
end
|