commit 5fff58884fbe7a650af907f83036a3c7185a59e9 Author: grovedruid Date: Sat Jul 4 17:46:11 2026 +0200 FSYS Agent platformer — self-contained game project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0a5add --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.vite/ +*.log +.DS_Store +Thumbs.db diff --git a/index.html b/index.html new file mode 100644 index 0000000..db618c3 --- /dev/null +++ b/index.html @@ -0,0 +1,76 @@ + + + + + + FSYS Agent — Platformer + + + + + + + + + + + +
+
📱
+
Rotate your device to landscape for the best experience
+
+
+ + + + + + + + + + diff --git a/preview.png b/preview.png new file mode 100644 index 0000000..b31885e Binary files /dev/null and b/preview.png differ diff --git a/public/assets/sounds/sfx_bump.ogg b/public/assets/sounds/sfx_bump.ogg new file mode 100644 index 0000000..d08750f Binary files /dev/null and b/public/assets/sounds/sfx_bump.ogg differ diff --git a/public/assets/sounds/sfx_coin.ogg b/public/assets/sounds/sfx_coin.ogg new file mode 100644 index 0000000..3a8b830 Binary files /dev/null and b/public/assets/sounds/sfx_coin.ogg differ diff --git a/public/assets/sounds/sfx_disappear.ogg b/public/assets/sounds/sfx_disappear.ogg new file mode 100644 index 0000000..e495f8b Binary files /dev/null and b/public/assets/sounds/sfx_disappear.ogg differ diff --git a/public/assets/sounds/sfx_gem.ogg b/public/assets/sounds/sfx_gem.ogg new file mode 100644 index 0000000..e817e34 Binary files /dev/null and b/public/assets/sounds/sfx_gem.ogg differ diff --git a/public/assets/sounds/sfx_hurt.ogg b/public/assets/sounds/sfx_hurt.ogg new file mode 100644 index 0000000..51eb6a9 Binary files /dev/null and b/public/assets/sounds/sfx_hurt.ogg differ diff --git a/public/assets/sounds/sfx_jump-high.ogg b/public/assets/sounds/sfx_jump-high.ogg new file mode 100644 index 0000000..8481585 Binary files /dev/null and b/public/assets/sounds/sfx_jump-high.ogg differ diff --git a/public/assets/sounds/sfx_jump.ogg b/public/assets/sounds/sfx_jump.ogg new file mode 100644 index 0000000..64e3e8a Binary files /dev/null and b/public/assets/sounds/sfx_jump.ogg differ diff --git a/public/assets/sounds/sfx_magic.ogg b/public/assets/sounds/sfx_magic.ogg new file mode 100644 index 0000000..a826d1c Binary files /dev/null and b/public/assets/sounds/sfx_magic.ogg differ diff --git a/public/assets/sounds/sfx_select.ogg b/public/assets/sounds/sfx_select.ogg new file mode 100644 index 0000000..716e0ca Binary files /dev/null and b/public/assets/sounds/sfx_select.ogg differ diff --git a/public/assets/sounds/sfx_throw.ogg b/public/assets/sounds/sfx_throw.ogg new file mode 100644 index 0000000..1827287 Binary files /dev/null and b/public/assets/sounds/sfx_throw.ogg differ diff --git a/public/assets/sprites/background_clouds.png b/public/assets/sprites/background_clouds.png new file mode 100644 index 0000000..f9894d8 Binary files /dev/null and b/public/assets/sprites/background_clouds.png differ diff --git a/public/assets/sprites/background_color_desert.png b/public/assets/sprites/background_color_desert.png new file mode 100644 index 0000000..d08db85 Binary files /dev/null and b/public/assets/sprites/background_color_desert.png differ diff --git a/public/assets/sprites/background_color_mushrooms.png b/public/assets/sprites/background_color_mushrooms.png new file mode 100644 index 0000000..6295e8e Binary files /dev/null and b/public/assets/sprites/background_color_mushrooms.png differ diff --git a/public/assets/sprites/background_color_trees.png b/public/assets/sprites/background_color_trees.png new file mode 100644 index 0000000..245ccc4 Binary files /dev/null and b/public/assets/sprites/background_color_trees.png differ diff --git a/public/assets/sprites/background_fade_hills.png b/public/assets/sprites/background_fade_hills.png new file mode 100644 index 0000000..a8628f2 Binary files /dev/null and b/public/assets/sprites/background_fade_hills.png differ diff --git a/public/assets/sprites/background_solid_sky.png b/public/assets/sprites/background_solid_sky.png new file mode 100644 index 0000000..0a39a8f Binary files /dev/null and b/public/assets/sprites/background_solid_sky.png differ diff --git a/public/assets/sprites/character_agent_hit.png b/public/assets/sprites/character_agent_hit.png new file mode 100644 index 0000000..0709ca4 Binary files /dev/null and b/public/assets/sprites/character_agent_hit.png differ diff --git a/public/assets/sprites/character_agent_idle.png b/public/assets/sprites/character_agent_idle.png new file mode 100644 index 0000000..93feff8 Binary files /dev/null and b/public/assets/sprites/character_agent_idle.png differ diff --git a/public/assets/sprites/character_agent_jump.png b/public/assets/sprites/character_agent_jump.png new file mode 100644 index 0000000..34cf50b Binary files /dev/null and b/public/assets/sprites/character_agent_jump.png differ diff --git a/public/assets/sprites/character_agent_walk_a.png b/public/assets/sprites/character_agent_walk_a.png new file mode 100644 index 0000000..61999bb Binary files /dev/null and b/public/assets/sprites/character_agent_walk_a.png differ diff --git a/public/assets/sprites/character_agent_walk_b.png b/public/assets/sprites/character_agent_walk_b.png new file mode 100644 index 0000000..0709ca4 Binary files /dev/null and b/public/assets/sprites/character_agent_walk_b.png differ diff --git a/public/assets/sprites/character_green_climb_a.png b/public/assets/sprites/character_green_climb_a.png new file mode 100644 index 0000000..d66a8bb Binary files /dev/null and b/public/assets/sprites/character_green_climb_a.png differ diff --git a/public/assets/sprites/character_green_climb_b.png b/public/assets/sprites/character_green_climb_b.png new file mode 100644 index 0000000..540e88b Binary files /dev/null and b/public/assets/sprites/character_green_climb_b.png differ diff --git a/public/assets/sprites/character_green_duck.png b/public/assets/sprites/character_green_duck.png new file mode 100644 index 0000000..db945f2 Binary files /dev/null and b/public/assets/sprites/character_green_duck.png differ diff --git a/public/assets/sprites/character_green_front.png b/public/assets/sprites/character_green_front.png new file mode 100644 index 0000000..996dfae Binary files /dev/null and b/public/assets/sprites/character_green_front.png differ diff --git a/public/assets/sprites/character_green_hit.png b/public/assets/sprites/character_green_hit.png new file mode 100644 index 0000000..eee3c53 Binary files /dev/null and b/public/assets/sprites/character_green_hit.png differ diff --git a/public/assets/sprites/character_green_idle.png b/public/assets/sprites/character_green_idle.png new file mode 100644 index 0000000..49562df Binary files /dev/null and b/public/assets/sprites/character_green_idle.png differ diff --git a/public/assets/sprites/character_green_jump.png b/public/assets/sprites/character_green_jump.png new file mode 100644 index 0000000..79f990c Binary files /dev/null and b/public/assets/sprites/character_green_jump.png differ diff --git a/public/assets/sprites/character_green_walk_a.png b/public/assets/sprites/character_green_walk_a.png new file mode 100644 index 0000000..547fd6d Binary files /dev/null and b/public/assets/sprites/character_green_walk_a.png differ diff --git a/public/assets/sprites/character_green_walk_b.png b/public/assets/sprites/character_green_walk_b.png new file mode 100644 index 0000000..c2a1d3f Binary files /dev/null and b/public/assets/sprites/character_green_walk_b.png differ diff --git a/public/assets/sprites/character_purple_climb_a.png b/public/assets/sprites/character_purple_climb_a.png new file mode 100644 index 0000000..129e75b Binary files /dev/null and b/public/assets/sprites/character_purple_climb_a.png differ diff --git a/public/assets/sprites/character_purple_climb_b.png b/public/assets/sprites/character_purple_climb_b.png new file mode 100644 index 0000000..bbd3a67 Binary files /dev/null and b/public/assets/sprites/character_purple_climb_b.png differ diff --git a/public/assets/sprites/character_purple_duck.png b/public/assets/sprites/character_purple_duck.png new file mode 100644 index 0000000..b0f75bf Binary files /dev/null and b/public/assets/sprites/character_purple_duck.png differ diff --git a/public/assets/sprites/character_purple_front.png b/public/assets/sprites/character_purple_front.png new file mode 100644 index 0000000..2d55765 Binary files /dev/null and b/public/assets/sprites/character_purple_front.png differ diff --git a/public/assets/sprites/character_purple_hit.png b/public/assets/sprites/character_purple_hit.png new file mode 100644 index 0000000..14225f1 Binary files /dev/null and b/public/assets/sprites/character_purple_hit.png differ diff --git a/public/assets/sprites/character_purple_idle.png b/public/assets/sprites/character_purple_idle.png new file mode 100644 index 0000000..f96e16d Binary files /dev/null and b/public/assets/sprites/character_purple_idle.png differ diff --git a/public/assets/sprites/character_purple_jump.png b/public/assets/sprites/character_purple_jump.png new file mode 100644 index 0000000..b41da14 Binary files /dev/null and b/public/assets/sprites/character_purple_jump.png differ diff --git a/public/assets/sprites/character_purple_walk_a.png b/public/assets/sprites/character_purple_walk_a.png new file mode 100644 index 0000000..7243d7d Binary files /dev/null and b/public/assets/sprites/character_purple_walk_a.png differ diff --git a/public/assets/sprites/character_purple_walk_b.png b/public/assets/sprites/character_purple_walk_b.png new file mode 100644 index 0000000..b8d5b08 Binary files /dev/null and b/public/assets/sprites/character_purple_walk_b.png differ diff --git a/public/assets/tiles/block_blue.png b/public/assets/tiles/block_blue.png new file mode 100644 index 0000000..4ec5cf5 Binary files /dev/null and b/public/assets/tiles/block_blue.png differ diff --git a/public/assets/tiles/block_green.png b/public/assets/tiles/block_green.png new file mode 100644 index 0000000..adce239 Binary files /dev/null and b/public/assets/tiles/block_green.png differ diff --git a/public/assets/tiles/block_plank.png b/public/assets/tiles/block_plank.png new file mode 100644 index 0000000..26a07d5 Binary files /dev/null and b/public/assets/tiles/block_plank.png differ diff --git a/public/assets/tiles/block_planks.png b/public/assets/tiles/block_planks.png new file mode 100644 index 0000000..1f81028 Binary files /dev/null and b/public/assets/tiles/block_planks.png differ diff --git a/public/assets/tiles/block_red.png b/public/assets/tiles/block_red.png new file mode 100644 index 0000000..483bb3d Binary files /dev/null and b/public/assets/tiles/block_red.png differ diff --git a/public/assets/tiles/block_spikes.png b/public/assets/tiles/block_spikes.png new file mode 100644 index 0000000..1bdac71 Binary files /dev/null and b/public/assets/tiles/block_spikes.png differ diff --git a/public/assets/tiles/block_yellow.png b/public/assets/tiles/block_yellow.png new file mode 100644 index 0000000..2e4ce5c Binary files /dev/null and b/public/assets/tiles/block_yellow.png differ diff --git a/public/assets/tiles/bomb.png b/public/assets/tiles/bomb.png new file mode 100644 index 0000000..a725ae8 Binary files /dev/null and b/public/assets/tiles/bomb.png differ diff --git a/public/assets/tiles/bomb_active.png b/public/assets/tiles/bomb_active.png new file mode 100644 index 0000000..e84611e Binary files /dev/null and b/public/assets/tiles/bomb_active.png differ diff --git a/public/assets/tiles/brick_brown.png b/public/assets/tiles/brick_brown.png new file mode 100644 index 0000000..c99b359 Binary files /dev/null and b/public/assets/tiles/brick_brown.png differ diff --git a/public/assets/tiles/brick_grey.png b/public/assets/tiles/brick_grey.png new file mode 100644 index 0000000..35b1d97 Binary files /dev/null and b/public/assets/tiles/brick_grey.png differ diff --git a/public/assets/tiles/bricks_brown.png b/public/assets/tiles/bricks_brown.png new file mode 100644 index 0000000..914f3a2 Binary files /dev/null and b/public/assets/tiles/bricks_brown.png differ diff --git a/public/assets/tiles/bricks_grey.png b/public/assets/tiles/bricks_grey.png new file mode 100644 index 0000000..23c7384 Binary files /dev/null and b/public/assets/tiles/bricks_grey.png differ diff --git a/public/assets/tiles/bridge_logs.png b/public/assets/tiles/bridge_logs.png new file mode 100644 index 0000000..771f542 Binary files /dev/null and b/public/assets/tiles/bridge_logs.png differ diff --git a/public/assets/tiles/bush.png b/public/assets/tiles/bush.png new file mode 100644 index 0000000..7103635 Binary files /dev/null and b/public/assets/tiles/bush.png differ diff --git a/public/assets/tiles/cactus.png b/public/assets/tiles/cactus.png new file mode 100644 index 0000000..7834e90 Binary files /dev/null and b/public/assets/tiles/cactus.png differ diff --git a/public/assets/tiles/chain.png b/public/assets/tiles/chain.png new file mode 100644 index 0000000..0b5d5a0 Binary files /dev/null and b/public/assets/tiles/chain.png differ diff --git a/public/assets/tiles/cloud_tile.png b/public/assets/tiles/cloud_tile.png new file mode 100644 index 0000000..5c11e2d Binary files /dev/null and b/public/assets/tiles/cloud_tile.png differ diff --git a/public/assets/tiles/coin_bronze.png b/public/assets/tiles/coin_bronze.png new file mode 100644 index 0000000..68a5cf7 Binary files /dev/null and b/public/assets/tiles/coin_bronze.png differ diff --git a/public/assets/tiles/coin_gold.png b/public/assets/tiles/coin_gold.png new file mode 100644 index 0000000..344397e Binary files /dev/null and b/public/assets/tiles/coin_gold.png differ diff --git a/public/assets/tiles/coin_silver.png b/public/assets/tiles/coin_silver.png new file mode 100644 index 0000000..f6d57ca Binary files /dev/null and b/public/assets/tiles/coin_silver.png differ diff --git a/public/assets/tiles/door_closed.png b/public/assets/tiles/door_closed.png new file mode 100644 index 0000000..8d0bb88 Binary files /dev/null and b/public/assets/tiles/door_closed.png differ diff --git a/public/assets/tiles/door_closed_top.png b/public/assets/tiles/door_closed_top.png new file mode 100644 index 0000000..5fb7ee0 Binary files /dev/null and b/public/assets/tiles/door_closed_top.png differ diff --git a/public/assets/tiles/door_open.png b/public/assets/tiles/door_open.png new file mode 100644 index 0000000..be39e5f Binary files /dev/null and b/public/assets/tiles/door_open.png differ diff --git a/public/assets/tiles/door_open_top.png b/public/assets/tiles/door_open_top.png new file mode 100644 index 0000000..df3c007 Binary files /dev/null and b/public/assets/tiles/door_open_top.png differ diff --git a/public/assets/tiles/fence.png b/public/assets/tiles/fence.png new file mode 100644 index 0000000..e683f8e Binary files /dev/null and b/public/assets/tiles/fence.png differ diff --git a/public/assets/tiles/fence_broken.png b/public/assets/tiles/fence_broken.png new file mode 100644 index 0000000..bfa66c2 Binary files /dev/null and b/public/assets/tiles/fence_broken.png differ diff --git a/public/assets/tiles/flag_green_a.png b/public/assets/tiles/flag_green_a.png new file mode 100644 index 0000000..25ca9b4 Binary files /dev/null and b/public/assets/tiles/flag_green_a.png differ diff --git a/public/assets/tiles/flag_green_b.png b/public/assets/tiles/flag_green_b.png new file mode 100644 index 0000000..8430097 Binary files /dev/null and b/public/assets/tiles/flag_green_b.png differ diff --git a/public/assets/tiles/flag_red_a.png b/public/assets/tiles/flag_red_a.png new file mode 100644 index 0000000..679196b Binary files /dev/null and b/public/assets/tiles/flag_red_a.png differ diff --git a/public/assets/tiles/flag_red_b.png b/public/assets/tiles/flag_red_b.png new file mode 100644 index 0000000..6c16eb2 Binary files /dev/null and b/public/assets/tiles/flag_red_b.png differ diff --git a/public/assets/tiles/gem_blue.png b/public/assets/tiles/gem_blue.png new file mode 100644 index 0000000..bfeeeea Binary files /dev/null and b/public/assets/tiles/gem_blue.png differ diff --git a/public/assets/tiles/gem_green.png b/public/assets/tiles/gem_green.png new file mode 100644 index 0000000..b0a23cd Binary files /dev/null and b/public/assets/tiles/gem_green.png differ diff --git a/public/assets/tiles/sci-fi-tileset.png b/public/assets/tiles/sci-fi-tileset.png new file mode 100644 index 0000000..0cc20bc Binary files /dev/null and b/public/assets/tiles/sci-fi-tileset.png differ diff --git a/public/images/cards/thumbs/1-gift-of-water-action.png b/public/images/cards/thumbs/1-gift-of-water-action.png new file mode 100644 index 0000000..273e6e4 Binary files /dev/null and b/public/images/cards/thumbs/1-gift-of-water-action.png differ diff --git a/public/images/cards/thumbs/10-tree-planter.png b/public/images/cards/thumbs/10-tree-planter.png new file mode 100644 index 0000000..a4ed5d3 Binary files /dev/null and b/public/images/cards/thumbs/10-tree-planter.png differ diff --git a/public/images/cards/thumbs/11-seed-scout.png b/public/images/cards/thumbs/11-seed-scout.png new file mode 100644 index 0000000..69082bf Binary files /dev/null and b/public/images/cards/thumbs/11-seed-scout.png differ diff --git a/public/images/cards/thumbs/12-natural-spritz.png b/public/images/cards/thumbs/12-natural-spritz.png new file mode 100644 index 0000000..6293730 Binary files /dev/null and b/public/images/cards/thumbs/12-natural-spritz.png differ diff --git a/public/images/cards/thumbs/13-dashboard-designer.png b/public/images/cards/thumbs/13-dashboard-designer.png new file mode 100644 index 0000000..daefd28 Binary files /dev/null and b/public/images/cards/thumbs/13-dashboard-designer.png differ diff --git a/public/images/cards/thumbs/14-onion-bodyguard.png b/public/images/cards/thumbs/14-onion-bodyguard.png new file mode 100644 index 0000000..d96d77d Binary files /dev/null and b/public/images/cards/thumbs/14-onion-bodyguard.png differ diff --git a/public/images/cards/thumbs/15-local-investor.png b/public/images/cards/thumbs/15-local-investor.png new file mode 100644 index 0000000..37e203a Binary files /dev/null and b/public/images/cards/thumbs/15-local-investor.png differ diff --git a/public/images/cards/thumbs/16-poster-designer.png b/public/images/cards/thumbs/16-poster-designer.png new file mode 100644 index 0000000..b99aafd Binary files /dev/null and b/public/images/cards/thumbs/16-poster-designer.png differ diff --git a/public/images/cards/thumbs/17-change-maker.png b/public/images/cards/thumbs/17-change-maker.png new file mode 100644 index 0000000..641f83b Binary files /dev/null and b/public/images/cards/thumbs/17-change-maker.png differ diff --git a/public/images/cards/thumbs/18-waste-remover.png b/public/images/cards/thumbs/18-waste-remover.png new file mode 100644 index 0000000..4733ee1 Binary files /dev/null and b/public/images/cards/thumbs/18-waste-remover.png differ diff --git a/public/images/cards/thumbs/19-manure-maker.png b/public/images/cards/thumbs/19-manure-maker.png new file mode 100644 index 0000000..a1629e4 Binary files /dev/null and b/public/images/cards/thumbs/19-manure-maker.png differ diff --git a/public/images/cards/thumbs/2-grow-greens-action.png b/public/images/cards/thumbs/2-grow-greens-action.png new file mode 100644 index 0000000..f7168f2 Binary files /dev/null and b/public/images/cards/thumbs/2-grow-greens-action.png differ diff --git a/public/images/cards/thumbs/20-mixed-garden.png b/public/images/cards/thumbs/20-mixed-garden.png new file mode 100644 index 0000000..6186178 Binary files /dev/null and b/public/images/cards/thumbs/20-mixed-garden.png differ diff --git a/public/images/cards/thumbs/21-ash-bug-blaster.png b/public/images/cards/thumbs/21-ash-bug-blaster.png new file mode 100644 index 0000000..ae73081 Binary files /dev/null and b/public/images/cards/thumbs/21-ash-bug-blaster.png differ diff --git a/public/images/cards/thumbs/22-dry-and-store.png b/public/images/cards/thumbs/22-dry-and-store.png new file mode 100644 index 0000000..fb4f620 Binary files /dev/null and b/public/images/cards/thumbs/22-dry-and-store.png differ diff --git a/public/images/cards/thumbs/23-seed-distributor.png b/public/images/cards/thumbs/23-seed-distributor.png new file mode 100644 index 0000000..f752e82 Binary files /dev/null and b/public/images/cards/thumbs/23-seed-distributor.png differ diff --git a/public/images/cards/thumbs/24-river-cleaners.png b/public/images/cards/thumbs/24-river-cleaners.png new file mode 100644 index 0000000..14b7a59 Binary files /dev/null and b/public/images/cards/thumbs/24-river-cleaners.png differ diff --git a/public/images/cards/thumbs/25-garden-documenter.png b/public/images/cards/thumbs/25-garden-documenter.png new file mode 100644 index 0000000..4a7f96c Binary files /dev/null and b/public/images/cards/thumbs/25-garden-documenter.png differ diff --git a/public/images/cards/thumbs/26-local-variety-guardian.png b/public/images/cards/thumbs/26-local-variety-guardian.png new file mode 100644 index 0000000..a201439 Binary files /dev/null and b/public/images/cards/thumbs/26-local-variety-guardian.png differ diff --git a/public/images/cards/thumbs/27-nature-regenerator.png b/public/images/cards/thumbs/27-nature-regenerator.png new file mode 100644 index 0000000..1b9f1c4 Binary files /dev/null and b/public/images/cards/thumbs/27-nature-regenerator.png differ diff --git a/public/images/cards/thumbs/28-biodiversity-steward.png b/public/images/cards/thumbs/28-biodiversity-steward.png new file mode 100644 index 0000000..8df9810 Binary files /dev/null and b/public/images/cards/thumbs/28-biodiversity-steward.png differ diff --git a/public/images/cards/thumbs/29-land-negotiator.png b/public/images/cards/thumbs/29-land-negotiator.png new file mode 100644 index 0000000..00ed91b Binary files /dev/null and b/public/images/cards/thumbs/29-land-negotiator.png differ diff --git a/public/images/cards/thumbs/3-fine-dining-compost-dish-action.png b/public/images/cards/thumbs/3-fine-dining-compost-dish-action.png new file mode 100644 index 0000000..0686cd8 Binary files /dev/null and b/public/images/cards/thumbs/3-fine-dining-compost-dish-action.png differ diff --git a/public/images/cards/thumbs/30-tjd-booster-action.png b/public/images/cards/thumbs/30-tjd-booster-action.png new file mode 100644 index 0000000..18bd6f7 Binary files /dev/null and b/public/images/cards/thumbs/30-tjd-booster-action.png differ diff --git a/public/images/cards/thumbs/31-kitchen-garden.png b/public/images/cards/thumbs/31-kitchen-garden.png new file mode 100644 index 0000000..7a4027c Binary files /dev/null and b/public/images/cards/thumbs/31-kitchen-garden.png differ diff --git a/public/images/cards/thumbs/32-balanced-plate-builder.png b/public/images/cards/thumbs/32-balanced-plate-builder.png new file mode 100644 index 0000000..d288311 Binary files /dev/null and b/public/images/cards/thumbs/32-balanced-plate-builder.png differ diff --git a/public/images/cards/thumbs/33-indigenous-tree-lover.png b/public/images/cards/thumbs/33-indigenous-tree-lover.png new file mode 100644 index 0000000..4e3d352 Binary files /dev/null and b/public/images/cards/thumbs/33-indigenous-tree-lover.png differ diff --git a/public/images/cards/thumbs/34-cover-crop-protector.png b/public/images/cards/thumbs/34-cover-crop-protector.png new file mode 100644 index 0000000..98531ed Binary files /dev/null and b/public/images/cards/thumbs/34-cover-crop-protector.png differ diff --git a/public/images/cards/thumbs/35-local-food-producer.png b/public/images/cards/thumbs/35-local-food-producer.png new file mode 100644 index 0000000..5b2fcc6 Binary files /dev/null and b/public/images/cards/thumbs/35-local-food-producer.png differ diff --git a/public/images/cards/thumbs/36-market-price-checker.png b/public/images/cards/thumbs/36-market-price-checker.png new file mode 100644 index 0000000..10ee98f Binary files /dev/null and b/public/images/cards/thumbs/36-market-price-checker.png differ diff --git a/public/images/cards/thumbs/37-community-meeting.png b/public/images/cards/thumbs/37-community-meeting.png new file mode 100644 index 0000000..1ab3cc0 Binary files /dev/null and b/public/images/cards/thumbs/37-community-meeting.png differ diff --git a/public/images/cards/thumbs/38-jar-farm-product-marketer.png b/public/images/cards/thumbs/38-jar-farm-product-marketer.png new file mode 100644 index 0000000..1e20321 Binary files /dev/null and b/public/images/cards/thumbs/38-jar-farm-product-marketer.png differ diff --git a/public/images/cards/thumbs/39-food-donation-hero.png b/public/images/cards/thumbs/39-food-donation-hero.png new file mode 100644 index 0000000..6567d93 Binary files /dev/null and b/public/images/cards/thumbs/39-food-donation-hero.png differ diff --git a/public/images/cards/thumbs/4-buzzing-with-wisdom-action.png b/public/images/cards/thumbs/4-buzzing-with-wisdom-action.png new file mode 100644 index 0000000..7184f1d Binary files /dev/null and b/public/images/cards/thumbs/4-buzzing-with-wisdom-action.png differ diff --git a/public/images/cards/thumbs/40-windbreak-warrior.png b/public/images/cards/thumbs/40-windbreak-warrior.png new file mode 100644 index 0000000..b13f168 Binary files /dev/null and b/public/images/cards/thumbs/40-windbreak-warrior.png differ diff --git a/public/images/cards/thumbs/41-seed-champion.png b/public/images/cards/thumbs/41-seed-champion.png new file mode 100644 index 0000000..0b9f6b7 Binary files /dev/null and b/public/images/cards/thumbs/41-seed-champion.png differ diff --git a/public/images/cards/thumbs/42-fruit-and-tree-team.png b/public/images/cards/thumbs/42-fruit-and-tree-team.png new file mode 100644 index 0000000..013bf6d Binary files /dev/null and b/public/images/cards/thumbs/42-fruit-and-tree-team.png differ diff --git a/public/images/cards/thumbs/43-manure-cycle-drawer.png b/public/images/cards/thumbs/43-manure-cycle-drawer.png new file mode 100644 index 0000000..1e1b8ea Binary files /dev/null and b/public/images/cards/thumbs/43-manure-cycle-drawer.png differ diff --git a/public/images/cards/thumbs/44-animal-hero.png b/public/images/cards/thumbs/44-animal-hero.png new file mode 100644 index 0000000..69ef7cd Binary files /dev/null and b/public/images/cards/thumbs/44-animal-hero.png differ diff --git a/public/images/cards/thumbs/45-land-magic.png b/public/images/cards/thumbs/45-land-magic.png new file mode 100644 index 0000000..14dcb56 Binary files /dev/null and b/public/images/cards/thumbs/45-land-magic.png differ diff --git a/public/images/cards/thumbs/46-local-chicken-lover.png b/public/images/cards/thumbs/46-local-chicken-lover.png new file mode 100644 index 0000000..afaed7c Binary files /dev/null and b/public/images/cards/thumbs/46-local-chicken-lover.png differ diff --git a/public/images/cards/thumbs/47-food-dryer-explorer.png b/public/images/cards/thumbs/47-food-dryer-explorer.png new file mode 100644 index 0000000..725280b Binary files /dev/null and b/public/images/cards/thumbs/47-food-dryer-explorer.png differ diff --git a/public/images/cards/thumbs/48-scrap-tastic-animal-feeder.png b/public/images/cards/thumbs/48-scrap-tastic-animal-feeder.png new file mode 100644 index 0000000..31c8248 Binary files /dev/null and b/public/images/cards/thumbs/48-scrap-tastic-animal-feeder.png differ diff --git a/public/images/cards/thumbs/49-leftover-chef.png b/public/images/cards/thumbs/49-leftover-chef.png new file mode 100644 index 0000000..936a99a Binary files /dev/null and b/public/images/cards/thumbs/49-leftover-chef.png differ diff --git a/public/images/cards/thumbs/5-baking-flatbread.png b/public/images/cards/thumbs/5-baking-flatbread.png new file mode 100644 index 0000000..3ffa38d Binary files /dev/null and b/public/images/cards/thumbs/5-baking-flatbread.png differ diff --git a/public/images/cards/thumbs/50-weather-tracker-checker.png b/public/images/cards/thumbs/50-weather-tracker-checker.png new file mode 100644 index 0000000..ba91fda Binary files /dev/null and b/public/images/cards/thumbs/50-weather-tracker-checker.png differ diff --git a/public/images/cards/thumbs/51-farm-record-keeper.png b/public/images/cards/thumbs/51-farm-record-keeper.png new file mode 100644 index 0000000..73bdb6c Binary files /dev/null and b/public/images/cards/thumbs/51-farm-record-keeper.png differ diff --git a/public/images/cards/thumbs/52-the-water-fertilizers.png b/public/images/cards/thumbs/52-the-water-fertilizers.png new file mode 100644 index 0000000..fadfe40 Binary files /dev/null and b/public/images/cards/thumbs/52-the-water-fertilizers.png differ diff --git a/public/images/cards/thumbs/53-small-farm-big-voice.png b/public/images/cards/thumbs/53-small-farm-big-voice.png new file mode 100644 index 0000000..fc25484 Binary files /dev/null and b/public/images/cards/thumbs/53-small-farm-big-voice.png differ diff --git a/public/images/cards/thumbs/54-nature-educator.png b/public/images/cards/thumbs/54-nature-educator.png new file mode 100644 index 0000000..1df5d2c Binary files /dev/null and b/public/images/cards/thumbs/54-nature-educator.png differ diff --git a/public/images/cards/thumbs/55-teamwork-harvest.png b/public/images/cards/thumbs/55-teamwork-harvest.png new file mode 100644 index 0000000..b8f57ab Binary files /dev/null and b/public/images/cards/thumbs/55-teamwork-harvest.png differ diff --git a/public/images/cards/thumbs/56-sky-garden.png b/public/images/cards/thumbs/56-sky-garden.png new file mode 100644 index 0000000..0ff5a63 Binary files /dev/null and b/public/images/cards/thumbs/56-sky-garden.png differ diff --git a/public/images/cards/thumbs/57-be-the-change-action.png b/public/images/cards/thumbs/57-be-the-change-action.png new file mode 100644 index 0000000..86cbd1d Binary files /dev/null and b/public/images/cards/thumbs/57-be-the-change-action.png differ diff --git a/public/images/cards/thumbs/58-ancient-wisdom-defender-action.png b/public/images/cards/thumbs/58-ancient-wisdom-defender-action.png new file mode 100644 index 0000000..6a59e26 Binary files /dev/null and b/public/images/cards/thumbs/58-ancient-wisdom-defender-action.png differ diff --git a/public/images/cards/thumbs/59-seedling-protector-action.png b/public/images/cards/thumbs/59-seedling-protector-action.png new file mode 100644 index 0000000..0f70f1f Binary files /dev/null and b/public/images/cards/thumbs/59-seedling-protector-action.png differ diff --git a/public/images/cards/thumbs/6-mixed-wastemonster-action.png b/public/images/cards/thumbs/6-mixed-wastemonster-action.png new file mode 100644 index 0000000..758895f Binary files /dev/null and b/public/images/cards/thumbs/6-mixed-wastemonster-action.png differ diff --git a/public/images/cards/thumbs/60-cooperative-builder-action.png b/public/images/cards/thumbs/60-cooperative-builder-action.png new file mode 100644 index 0000000..edd660b Binary files /dev/null and b/public/images/cards/thumbs/60-cooperative-builder-action.png differ diff --git a/public/images/cards/thumbs/7-ingredient-detective-action.png b/public/images/cards/thumbs/7-ingredient-detective-action.png new file mode 100644 index 0000000..b7b5464 Binary files /dev/null and b/public/images/cards/thumbs/7-ingredient-detective-action.png differ diff --git a/public/images/cards/thumbs/8-mindful-eating-action.png b/public/images/cards/thumbs/8-mindful-eating-action.png new file mode 100644 index 0000000..687d5b8 Binary files /dev/null and b/public/images/cards/thumbs/8-mindful-eating-action.png differ diff --git a/public/images/cards/thumbs/9-fermentastic-action.png b/public/images/cards/thumbs/9-fermentastic-action.png new file mode 100644 index 0000000..552f53e Binary files /dev/null and b/public/images/cards/thumbs/9-fermentastic-action.png differ diff --git a/public/images/cloud.png b/public/images/cloud.png new file mode 100644 index 0000000..1b18abf Binary files /dev/null and b/public/images/cloud.png differ diff --git a/public/images/fsys_platform_bg.png b/public/images/fsys_platform_bg.png new file mode 100644 index 0000000..c64571a Binary files /dev/null and b/public/images/fsys_platform_bg.png differ diff --git a/public/images/jump_pad.png b/public/images/jump_pad.png new file mode 100644 index 0000000..ed31fab Binary files /dev/null and b/public/images/jump_pad.png differ diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..02d23cc --- /dev/null +++ b/src/main.js @@ -0,0 +1,31 @@ +var config = { + type: Phaser.AUTO, + width: 960, + height: 600, + parent: 'game-container', + backgroundColor: '#0F0F23', + roundPixels: true, + pixelArt: false, + antialias: true, + resolution: 1, + physics: { + default: 'arcade', + arcade: { + debug: false, + gravity: { y: 600 } + } + }, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH + }, + scene: [ + Boot, + Preloader, + TitleScreen, + GameScene, + GameOver + ] +}; + +var game = new Phaser.Game(config); diff --git a/src/scenes/Boot.js b/src/scenes/Boot.js new file mode 100644 index 0000000..68f1c26 --- /dev/null +++ b/src/scenes/Boot.js @@ -0,0 +1,9 @@ +class Boot extends Phaser.Scene { + constructor() { + super('Boot'); + } + + create() { + this.scene.start('Preloader'); + } +} diff --git a/src/scenes/Game.js b/src/scenes/Game.js new file mode 100644 index 0000000..fac8ead --- /dev/null +++ b/src/scenes/Game.js @@ -0,0 +1,722 @@ +var WORLD_WIDTH = 3500; +var WORLD_HEIGHT = 600; +var MAX_LIVES = 3; + +var CARD_NAMES = [ + '1-gift-of-water-action', '2-grow-greens-action', '3-fine-dining-compost-dish-action', + '4-buzzing-with-wisdom-action', '5-baking-flatbread', '6-mixed-wastemonster-action', + '7-ingredient-detective-action', '8-mindful-eating-action', '9-fermentastic-action', + '10-tree-planter', '11-seed-scout', '12-natural-spritz', + '13-dashboard-designer', '14-onion-bodyguard', '15-local-investor' +]; + +var PLATFORMS = [ + // Ground sections (with gaps between them = bottom holes) + [0, 560, 400, 40], + [500, 560, 300, 40], + [900, 560, 200, 40], + [1200, 560, 400, 40], + [1700, 560, 200, 40], + [2000, 560, 300, 40], + [2400, 560, 200, 40], + [2700, 560, 400, 40], + [3200, 560, 300, 40], + + // Floating platforms (with cards) + [200, 440, 120, 20], + [400, 380, 100, 20], + [600, 320, 120, 20], + [850, 400, 100, 20], + [1050, 340, 120, 20], + [1300, 400, 100, 20], + [1500, 320, 120, 20], + [1750, 380, 100, 20], + [1950, 300, 120, 20], + [2200, 360, 100, 20], + [2450, 280, 120, 20], + [2700, 340, 100, 20], + [2950, 300, 120, 20], + [3200, 260, 100, 20], + + // High platforms (with card) + [300, 240, 100, 20], + + // High platforms (with hazards — no cards) + [700, 200, 120, 20], + [1100, 220, 100, 20], + [1600, 180, 120, 20], + [2100, 200, 100, 20], + [2600, 160, 120, 20], + [3100, 180, 100, 20], +]; + +var CARD_POSITIONS = [ + [240, 410], [440, 350], [640, 290], [890, 370], + [1090, 310], [1340, 370], [1550, 290], [1790, 350], + [1990, 270], [2240, 330], [2490, 250], [2740, 310], + [2990, 270], [3240, 230], [340, 210] +]; + +// Hazard zones — [x, y, w, h] — on platforms with NO cards +var PLATFORM_HAZARDS = [ + // High platforms without cards + [700, 192, 80, 12], + [1100, 212, 80, 12], + [1600, 172, 80, 12], + [2100, 192, 80, 12], + [2600, 152, 80, 12], + [3100, 172, 80, 12], + // On ground sections + [650, 552, 80, 12], + [1400, 552, 80, 12], + [2150, 552, 80, 12], + [2900, 552, 80, 12], +]; + +// Hazard zones at the bottom of ground gaps (bottom holes) +var HOLE_HAZARDS = [ + [450, 588, 60, 24], + [850, 588, 60, 24], + [1150, 588, 60, 24], + [1550, 588, 60, 24], + [1950, 588, 60, 24], + [2350, 588, 60, 24], + [2650, 588, 60, 24], + [3100, 588, 60, 24], +]; + +// Combined hazard list +var HAZARDS = PLATFORM_HAZARDS.concat(HOLE_HAZARDS); + +// Jump pads on ground in the later part of the level +var JUMP_PADS = [ + [2250, 548, 60, 12], + [2500, 548, 60, 12], + [2750, 548, 60, 12], + [3350, 548, 60, 12], +]; + +// Food positions — scattered on ground sections (away from hazards) +var FOOD_POSITIONS = [ + [150, 540], + [550, 540], + [1000, 540], + [1350, 540], + [1850, 540], + [2550, 540], + [3050, 540], +]; + +class GameScene extends Phaser.Scene { + constructor() { + super('GameScene'); + } + + create() { + this.score = 0; + this.totalCards = CARD_POSITIONS.length; + this.canDoubleJump = true; + this.wasOnGround = true; + this.lives = MAX_LIVES; + this.invulnerable = false; + this.gameStarted = false; + this.isDead = false; + this.walkFrame = 0; + this.walkTimer = 0; + this.startTime = 0; + this.elapsed = 0; + this.bestTime = localStorage.getItem('fsys_agent_best_time') + ? parseFloat(localStorage.getItem('fsys_agent_best_time')) : Infinity; + + this.physics.world.setBounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT); + + // Parallax background layers + this.cameras.main.setBackgroundColor(0x0F0F23); + + // Sky/stars layer — procedural gradient with stars + var skyGfx = this.make.graphics({x: 0, y: 0}); + skyGfx.fillGradientStyle(0x0a0a2e, 0x0a0a2e, 0x1a1a4e, 0x1a1a4e, 1); + skyGfx.fillRect(0, 0, 960, 600); + skyGfx.fillStyle(0xffffff, 0.5); + for (var si = 0; si < 20; si++) { + skyGfx.fillCircle( + 10 + Math.random() * 940, + 10 + Math.random() * 580, + 0.5 + Math.random() * 1.5 + ); + } + skyGfx.generateTexture('bg_sky', 960, 600); + skyGfx.destroy(); + + this.add.image(480, 300, 'bg_sky').setScrollFactor(0).setDepth(-12); + + // Main background — two copies for endless tiling, second mirrored + var bgTex = this.textures.get('bg_platform'); + var bgScale = WORLD_HEIGHT / bgTex.getSourceImage().height; + var bgWidth = bgTex.getSourceImage().width * bgScale; + this.add.image(0, 0, 'bg_platform') + .setOrigin(0, 0).setDisplaySize(bgWidth, WORLD_HEIGHT).setDepth(-10); + this.add.image(bgWidth, 0, 'bg_platform') + .setOrigin(0, 0).setDisplaySize(bgWidth, WORLD_HEIGHT).setFlipX(true).setDepth(-10); + + // Platforms — ground=bush+earth, floating=cloud + this.platformGroup = this.physics.add.staticGroup(); + for (var i = 0; i < PLATFORMS.length; i++) { + var p = PLATFORMS[i]; + var cx = p[0] + p[2] / 2; + var cy = p[1] + p[3] / 2; + + // Invisible physics body + var plat = this.add.rectangle(cx, cy, p[2], p[3], 0xffffff, 0.01); + plat.setDepth(0); + this.physics.add.existing(plat, true); + this.platformGroup.add(plat); + + if (p[1] >= 560) { + // Ground sections — earth base + bush tile overlay + var earth = this.add.rectangle(cx, cy, p[2], p[3], 0x6b4423, 0.8); + earth.setDepth(1); + var bush = this.add.tileSprite(cx, cy, p[2], p[3], 'bush_tile'); + bush.setDepth(3); + bush.setTint(0xccbb99); + } else { + // Floating platforms — cloud backdrop + var cloudImg = this.add.image(cx, cy + 6, 'cloud_tile'); + cloudImg.setDepth(2); + cloudImg.setDisplaySize(Math.max(p[2] + 40, 80), 60); + cloudImg.setAlpha(0.8 + Math.random() * 0.2); + } + } + + // Cards + this.cardGroup = this.physics.add.staticGroup(); + for (var j = 0; j < CARD_POSITIONS.length; j++) { + var cp = CARD_POSITIONS[j]; + var cardKey = 'card_' + CARD_NAMES[j % CARD_NAMES.length]; + var texExists = this.textures.exists(cardKey); + + var card = this.cardGroup.create(cp[0], cp[1], texExists ? cardKey : undefined); + card.refreshBody(); + card.setDepth(5); + if (!texExists) card.setTint(0xffd700); + + this.tweens.add({ + targets: card, + y: card.y - 4, + duration: 600 + Math.random() * 400, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + } + + // Hazards — toxic sludge pools with bubbling animation + this.hazardGroup = this.physics.add.staticGroup(); + for (var h = 0; h < HAZARDS.length; h++) { + this.makeHazard(HAZARDS[h]); + } + + // Jump pads — bouncy ground pads in later level + this.jumpPadGroup = this.physics.add.staticGroup(); + for (var jpi = 0; jpi < JUMP_PADS.length; jpi++) { + var jp = JUMP_PADS[jpi]; + var jx = jp[0] + jp[2] / 2; + var jy = jp[1] + jp[3] / 2; + + var pad = this.add.image(jx, jy + 2, 'jump_pad'); + pad.setDepth(8); + this.physics.add.existing(pad, true); + this.jumpPadGroup.add(pad); + + this.tweens.add({ + targets: pad, + alpha: { from: 0.7, to: 1 }, + duration: 500 + Math.random() * 200, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + } + + // Food — generate apple texture and place items + var foodGfx = this.make.graphics({x: 0, y: 0}); + foodGfx.fillStyle(0xff3333); + foodGfx.fillCircle(10, 12, 10); + foodGfx.fillStyle(0x33cc33); + foodGfx.fillRect(8, 2, 4, 6); + foodGfx.generateTexture('food_apple', 20, 22); + foodGfx.destroy(); + + this.foodGroup = this.physics.add.staticGroup(); + for (var fi = 0; fi < FOOD_POSITIONS.length; fi++) { + var fp = FOOD_POSITIONS[fi]; + var food = this.foodGroup.create(fp[0], fp[1], 'food_apple'); + food.setDepth(5); + food.refreshBody(); + + this.tweens.add({ + targets: food, + y: food.y - 3, + duration: 800 + Math.random() * 400, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + } + + // Player — kangaroo + this.player = this.physics.add.sprite(100, 400, 'player_idle'); + this.player.setDepth(100); + this.player.setScale(0.35); + this.player.setCollideWorldBounds(true); + this.player.body.setSize(60, 100); + this.player.body.setOffset(22, 25); + + // Camera + this.cameras.main.setBounds(0, 0, WORLD_WIDTH, WORLD_HEIGHT); + this.cameras.main.startFollow(this.player, true, 0.15, 0.15); + this.cameras.main.setDeadzone(100, 50); + + // Collisions + this.physics.add.collider(this.player, this.platformGroup); + this.physics.add.overlap(this.player, this.cardGroup, this.collectCard, null, this); + this.physics.add.overlap(this.player, this.hazardGroup, this.hitHazard, null, this); + this.physics.add.collider(this.player, this.jumpPadGroup, this.bouncePlayer, null, this); + this.physics.add.overlap(this.player, this.foodGroup, this.collectFood, null, this); + + // Input + this.input.addPointer(2); + this.cursors = this.input.keyboard.createCursorKeys(); + this.wasd = this.input.keyboard.addKeys('W,A,S,D'); + + // Touch controls — native DOM events for reliable multi-touch + this.joystickActive = false; + this.joystickTouchId = -1; + this.joystickDir = 0; + this.touchJump = false; + this.touchJumpJustPressed = false; + this.touchJumpTouchId = -1; + + var jBase = this.add.circle(120, 490, 55, 0xffffff, 0.12).setScrollFactor(0).setDepth(200); + this.jKnob = this.add.circle(120, 490, 22, 0xffffff, 0.35).setScrollFactor(0).setDepth(201); + this.add.text(120, 555, '\u25C0\u25B6', { + fontFamily: 'Arial', fontSize: '13px', color: '#ffffff' + }).setOrigin(0.5).setScrollFactor(0).setDepth(201); + this.add.text(720, 570, 'JUMP', { + fontFamily: 'Arial Black', fontSize: '18px', color: '#ffffff' + }).setOrigin(0.5).setScrollFactor(0).setDepth(201).setAlpha(0.3); + + var canvas = this.sys.game.canvas; + var self = this; + function toGamePos(t) { + var r = canvas.getBoundingClientRect(); + return { x: (t.clientX - r.left) * (960 / r.width), y: (t.clientY - r.top) * (600 / r.height) }; + } + function handleTouch(e) { + e.preventDefault(); + for (var i = 0; i < e.changedTouches.length; i++) { + var t = e.changedTouches[i]; + var pos = toGamePos(t); + if (e.type === 'touchstart') { + if (pos.x < 240 && self.joystickTouchId < 0) { + self.joystickActive = true; + self.joystickTouchId = t.identifier; + self.updateJoystickPos(pos.x, pos.y); + } else if (pos.x >= 480 && self.touchJumpTouchId < 0) { + self.touchJump = true; + self.touchJumpJustPressed = true; + self.touchJumpTouchId = t.identifier; + } + } else if (e.type === 'touchmove' && t.identifier === self.joystickTouchId) { + self.updateJoystickPos(pos.x, pos.y); + } else if (e.type === 'touchend' || e.type === 'touchcancel') { + if (t.identifier === self.joystickTouchId) { + self.joystickActive = false; + self.joystickTouchId = -1; + self.joystickDir = 0; + self.jKnob.setPosition(120, 490); + } + if (t.identifier === self.touchJumpTouchId) { + self.touchJump = false; + self.touchJumpTouchId = -1; + } + } + } + } + canvas.addEventListener('touchstart', handleTouch, { passive: false }); + canvas.addEventListener('touchmove', handleTouch, { passive: false }); + canvas.addEventListener('touchend', handleTouch, { passive: false }); + canvas.addEventListener('touchcancel', handleTouch, { passive: false }); + + // HUD + this.scoreText = this.add.text(16, 16, 'Cards: 0 / ' + this.totalCards, { + fontFamily: 'Arial Black', fontSize: '18px', color: '#52ffff', + stroke: '#000000', strokeThickness: 4 + }).setScrollFactor(0).setDepth(200); + + this.heartsTexts = []; + this.drawHearts(); + + // Timer (top-right) + this.timerText = this.add.text(784, 16, '00:00', { + fontFamily: 'Arial Black', fontSize: '20px', color: '#ffd700', + stroke: '#000000', strokeThickness: 4 + }).setOrigin(1, 0).setScrollFactor(0).setDepth(200); + + if (this.bestTime < Infinity) { + this.add.text(944, 40, 'FSYS Agent: ' + this.formatTime(this.bestTime), { + fontFamily: 'Arial', fontSize: '12px', color: '#b9cbbc', + stroke: '#000000', strokeThickness: 2 + }).setOrigin(1, 0).setScrollFactor(0).setDepth(200); + } + + // Tutorial + var isTouch = this.sys.game.device.input.touch; + var tutMsg = isTouch ? 'Use the stick to move\nTap right side to jump\nCollect all cards!' : 'Arrow keys to move\nSpace to jump\nCollect all cards!'; + this.tutorialText = this.add.text(480, 300, tutMsg, { + fontFamily: 'Arial', fontSize: '20px', color: '#ffffff', + stroke: '#000000', strokeThickness: 4, align: 'center' + }).setOrigin(0.5).setScrollFactor(0).setDepth(200); + + this.input.once('pointerdown', this.startGame, this); + this.input.keyboard.once('keydown', this.startGame, this); + } + + updateJoystickPos(x, y) { + var dx = x - 120; + var dy = y - 490; + var dist = Math.sqrt(dx * dx + dy * dy); + var maxDist = 55; + if (dist > maxDist) { + dx = dx / dist * maxDist; + dy = dy / dist * maxDist; + } + this.jKnob.setPosition(120 + dx, 490 + dy); + this.joystickDir = dx > 15 ? 1 : dx < -15 ? -1 : 0; + } + + makeHazard(hz) { + var cx = hz[0] + hz[2] / 2; + var cy = hz[1] + hz[3] / 2; + + // Glowing pool base + var pool = this.add.rectangle(cx, cy, hz[2], hz[3], 0x55cc33, 0.55); + pool.setDepth(8); + var glow = this.add.rectangle(cx, cy, hz[2] + 6, hz[3] + 6, 0xccff44, 0.15); + glow.setDepth(7); + + // Pool pulse + this.tweens.add({ + targets: [pool, glow], + alpha: { from: 0.4, to: 0.7 }, + duration: 600 + Math.random() * 300, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + + // Bubbles + for (var b = 0; b < 3; b++) { + (function(scene, hzRef) { + var bx = hzRef[0] + 12 + Math.random() * (hzRef[2] - 24); + var bubble = scene.add.circle(bx, hzRef[1] + hzRef[3] - 3, 2 + Math.random() * 2, 0xaaff88, 0.8); + bubble.setDepth(9); + var dur = 700 + Math.random() * 500; + scene.tweens.add({ + targets: bubble, + y: hzRef[1] - 6, + alpha: 0, + scale: 1.8, + duration: dur, + delay: Math.random() * dur, + repeat: -1, + onRepeat: function() { + bubble.y = hzRef[1] + hzRef[3] - 3; + bubble.alpha = 0.8; + bubble.setScale(1); + bubble.x = hzRef[0] + 12 + Math.random() * (hzRef[2] - 24); + } + }); + })(this, hz); + } + + // Physics body + var hazardBody = this.hazardGroup.create(cx, cy); + hazardBody.setDisplaySize(hz[2], hz[3] + 4); + hazardBody.refreshBody(); + hazardBody.setVisible(false); + } + + startGame() { + if (this.gameStarted) return; + this.gameStarted = true; + this.startTime = this.time.now; + this.tutorialText.setVisible(false); + if (this.sys.game.device.input.touch) { + var el = document.documentElement; + if (el.requestFullscreen) el.requestFullscreen(); + else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen(); + } + } + + drawHearts() { + for (var hi = 0; hi < this.heartsTexts.length; hi++) { + this.heartsTexts[hi].destroy(); + } + this.heartsTexts = []; + var full = Math.floor(this.lives); + var hasHalf = (this.lives - full) >= 0.49; + for (var hi = 0; hi < MAX_LIVES; hi++) { + var style = { + fontFamily: 'Arial Black', fontSize: '20px', + stroke: '#000000', strokeThickness: 3 + }; + var t; + if (hi < full) { + style.color = '#ff4444'; + t = this.add.text(16 + hi * 26, 44, '\u2764', style); + } else if (hi === full && hasHalf) { + style.color = '#ff8888'; + t = this.add.text(16 + hi * 26, 44, '\u2764', style); + t.setAlpha(0.5); + } else { + style.color = '#884444'; + t = this.add.text(16 + hi * 26, 44, '\u2661', style); + t.setAlpha(0.35); + } + t.setScrollFactor(0).setDepth(200); + this.heartsTexts.push(t); + } + } + + update(time, delta) { + if (this.isDead) return; + if (!this.gameStarted) return; + + this.elapsed = (this.time.now - this.startTime) / 1000; + this.timerText.setText(this.formatTime(this.elapsed)); + + // Fall into bottom hole — lose life and respawn + if (this.player.y > WORLD_HEIGHT + 50) { + this.takeDamage(); + return; + } + + var speed = 200; + var onGround = this.player.body.blocked.down; + + if (onGround && !this.wasOnGround) { + this.canDoubleJump = true; + } + this.wasOnGround = onGround; + + if (this.cursors.left.isDown || this.wasd.A.isDown || this.joystickDir < 0) { + this.player.setVelocityX(-speed); + this.player.setFlipX(true); + } else if (this.cursors.right.isDown || this.wasd.D.isDown || this.joystickDir > 0) { + this.player.setVelocityX(speed); + this.player.setFlipX(false); + } else { + this.player.setVelocityX(0); + } + + var jumpDown = this.cursors.space.isDown || this.cursors.up.isDown || this.wasd.W.isDown || this.touchJump; + if (jumpDown && onGround) { + this.player.setVelocityY(-420); + this.canDoubleJump = true; + if (this.cache.audio.exists('sfx_jump')) + this.sound.play('sfx_jump', { volume: 0.4 }); + } else if (!onGround && this.canDoubleJump) { + var freshPress = Phaser.Input.Keyboard.JustDown(this.cursors.space) || + Phaser.Input.Keyboard.JustDown(this.cursors.up) || + Phaser.Input.Keyboard.JustDown(this.wasd.W) || + this.touchJumpJustPressed; + this.touchJumpJustPressed = false; + if (freshPress) { + this.player.setVelocityY(-420); + this.canDoubleJump = false; + if (this.cache.audio.exists('sfx_jump')) + this.sound.play('sfx_jump', { volume: 0.4 }); + for (var dj = 0; dj < 8; dj++) { + var dot = this.add.circle(this.player.x + Phaser.Math.Between(-24, 24), this.player.y + 30, 2 + Math.random() * 3, 0xffffff, 0.7); + dot.setDepth(15); + this.tweens.add({ + targets: dot, + y: dot.y + Phaser.Math.Between(30, 70), + x: dot.x + Phaser.Math.Between(-40, 40), + alpha: 0, + scale: 0.1, + duration: 250 + Math.random() * 200, + onComplete: function() { dot.destroy(); } + }); + } + } + } + + this.walkTimer += delta; + if (!onGround) { + this.player.setTexture('player_jump'); + } else if (Math.abs(this.player.body.velocity.x) > 10) { + if (this.walkTimer > 180) { + this.walkTimer = 0; + this.walkFrame = 1 - this.walkFrame; + } + this.player.setTexture(this.walkFrame === 0 ? 'player_walk_a' : 'player_walk_b'); + } else { + this.player.setTexture('player_idle'); + this.walkTimer = 0; + } + } + + takeDamage() { + if (this.invulnerable || this.isDead) return; + this.lives = Math.max(0, this.lives - 1); + this.drawHearts(); + + if (this.cache.audio.exists('sfx_hurt')) + this.sound.play('sfx_hurt', { volume: 0.5 }); + + if (this.lives <= 0) { + this.die(); + return; + } + + this.invulnerable = true; + this.cameras.main.shake(100, 0.01); + this.respawn(); + + var self = this; + this.tweens.add({ + targets: this.player, + alpha: 0.3, + duration: 80, + yoyo: true, + repeat: 7, + onComplete: function() { + self.player.setAlpha(1); + self.invulnerable = false; + } + }); + } + + hitHazard(player, hazard) { + this.takeDamage(); + } + + bouncePlayer(player, pad) { + if (player.body.velocity.y < 0) return; + player.setVelocityY(-580); + this.tweens.add({ + targets: pad, + scaleY: 0.4, + scaleX: 1.2, + duration: 80, + yoyo: true, + ease: 'Quad.easeOut' + }); + if (this.cache.audio.exists('sfx_jump')) + this.sound.play('sfx_jump', { volume: 0.3 }); + } + + respawn() { + this.player.setVelocity(0, 0); + this.player.setPosition(Phaser.Math.Clamp(this.player.x, 100, WORLD_WIDTH - 100), 350); + } + + collectCard(player, card) { + card.destroy(); + this.score++; + this.scoreText.setText('Cards: ' + this.score + ' / ' + this.totalCards); + + if (this.cache.audio.exists('sfx_coin')) + this.sound.play('sfx_coin', { volume: 0.3 }); + + var pop = this.add.text(card.x, card.y - 10, '+1', { + fontFamily: 'Arial Black', fontSize: '16px', color: '#ffd700', + stroke: '#000000', strokeThickness: 3 + }).setOrigin(0.5).setDepth(150); + + this.tweens.add({ + targets: pop, + y: pop.y - 40, + alpha: 0, + duration: 600, + onComplete: function() { pop.destroy(); } + }); + + if (this.score >= this.totalCards) this.win(); + } + + collectFood(player, food) { + if (this.lives >= MAX_LIVES) return; + food.destroy(); + this.lives = Math.min(MAX_LIVES, this.lives + 0.5); + this.drawHearts(); + + if (this.cache.audio.exists('sfx_coin')) + this.sound.play('sfx_coin', { volume: 0.3 }); + + var pop = this.add.text(food.x, food.y - 10, '+0.5', { + fontFamily: 'Arial Black', fontSize: '14px', color: '#ff6666', + stroke: '#000000', strokeThickness: 3 + }).setOrigin(0.5).setDepth(150); + + this.tweens.add({ + targets: pop, + y: pop.y - 30, + alpha: 0, + duration: 600, + onComplete: function() { pop.destroy(); } + }); + } + + die() { + if (this.isDead) return; + this.isDead = true; + this.gameStarted = false; + + this.player.setTint(0xff0000); + this.player.setVelocity(0, -200); + + if (this.cache.audio.exists('sfx_hurt')) + this.sound.play('sfx_hurt', { volume: 0.5 }); + + var self = this; + this.time.delayedCall(1500, function() { + self.scene.start('GameOver', { + won: false, score: self.score, total: self.totalCards, + time: self.elapsed, best: self.bestTime + }); + }); + } + + win() { + this.gameStarted = false; + var isNewBest = false; + if (this.elapsed < this.bestTime) { + this.bestTime = this.elapsed; + localStorage.setItem('fsys_agent_best_time', this.bestTime.toString()); + isNewBest = true; + } + this.tweens.add({ + targets: this.player, + scale: 0.6, + y: this.player.y - 50, + duration: 500, + ease: 'Bounce.easeOut' + }); + var self = this; + this.time.delayedCall(1500, function() { + self.scene.start('GameOver', { + won: true, score: self.score, total: self.totalCards, + time: self.elapsed, best: self.bestTime, newBest: isNewBest + }); + }); + } + + formatTime(seconds) { + var m = Math.floor(seconds / 60); + var s = Math.floor(seconds % 60); + return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; + } +} diff --git a/src/scenes/GameOver.js b/src/scenes/GameOver.js new file mode 100644 index 0000000..5b0cf27 --- /dev/null +++ b/src/scenes/GameOver.js @@ -0,0 +1,140 @@ +class GameOver extends Phaser.Scene { + constructor() { + super('GameOver'); + } + + init(data) { + this.won = data.won || false; + this.score = data.score || 0; + this.total = data.total || 0; + this.timeVal = data.time || 0; + this.best = data.best || 0; + this.newBest = data.newBest || false; + } + + create() { + this.cameras.main.setBackgroundColor(0x0F0F23); + + if (this.won) { + this.add.text(480, 160, 'MISSION COMPLETE!', { + fontFamily: 'Arial Black', + fontSize: '42px', + color: '#00ff9d', + stroke: '#000000', + strokeThickness: 8 + }).setOrigin(0.5); + + this.add.text(480, 230, 'All ' + this.total + ' cards collected!', { + fontFamily: 'Arial', + fontSize: '20px', + color: '#ffd700' + }).setOrigin(0.5); + + this.add.text(480, 270, 'Time: ' + this.formatTime(this.timeVal), { + fontFamily: 'Arial Black', + fontSize: '24px', + color: '#ffffff', + stroke: '#000000', + strokeThickness: 4 + }).setOrigin(0.5); + + if (this.newBest) { + this.add.text(480, 300, 'NEW BEST!', { + fontFamily: 'Arial Black', + fontSize: '18px', + color: '#00ff9d', + stroke: '#000000', + strokeThickness: 3 + }).setOrigin(0.5); + } + + if (this.best > 0) { + this.add.text(480, 325, 'FSYS Agent: ' + this.formatTime(this.best), { + fontFamily: 'Arial', + fontSize: '14px', + color: '#b9cbbc' + }).setOrigin(0.5); + } + + this.add.text(480, 350, 'The food system is restored.', { + fontFamily: 'Arial', + fontSize: '14px', + color: '#849587' + }).setOrigin(0.5); + } else { + this.add.text(480, 200, 'MISSION FAILED', { + fontFamily: 'Arial Black', + fontSize: '42px', + color: '#ffb4ab', + stroke: '#000000', + strokeThickness: 8 + }).setOrigin(0.5); + + this.add.text(480, 270, 'Cards: ' + this.score + ' / ' + this.total, { + fontFamily: 'Arial', + fontSize: '20px', + color: '#b9cbbc' + }).setOrigin(0.5); + + this.add.text(480, 295, 'Time: ' + this.formatTime(this.timeVal), { + fontFamily: 'Arial', + fontSize: '18px', + color: '#ffffff', + stroke: '#000000', + strokeThickness: 3 + }).setOrigin(0.5); + + if (this.best > 0 && this.best < Infinity) { + this.add.text(480, 320, 'FSYS Agent: ' + this.formatTime(this.best), { + fontFamily: 'Arial', + fontSize: '14px', + color: '#b9cbbc' + }).setOrigin(0.5); + } + + this.add.text(480, 350, 'Don\'t give up!', { + fontFamily: 'Arial', + fontSize: '14px', + color: '#849587' + }).setOrigin(0.5); + } + + var retryBtn = this.add.text(480, 420, 'PLAY AGAIN', { + fontFamily: 'Arial Black', + fontSize: '24px', + color: '#52ffff', + stroke: '#000000', + strokeThickness: 4, + padding: { x: 20, y: 10 } + }).setOrigin(0.5).setInteractive({ useHandCursor: true }); + + retryBtn.on('pointerover', function() { retryBtn.setColor('#00ff9d'); }); + retryBtn.on('pointerout', function() { retryBtn.setColor('#52ffff'); }); + retryBtn.on('pointerdown', function() { + this.scene.start('GameScene'); + }, this); + + var border = this.add.rectangle(480, 420, retryBtn.width + 20, retryBtn.height + 10) + .setStrokeStyle(2, 0x52ffff, 0.6); + + this.tweens.add({ + targets: border, + alpha: 0.3, + duration: 1000, + yoyo: true, + repeat: -1 + }); + + this.add.text(480, 550, 'FSYS Agent 2050', { + fontFamily: 'Arial', + fontSize: '12px', + color: '#3b4a3f' + }).setOrigin(0.5); + } + + formatTime(seconds) { + var m = Math.floor(seconds / 60); + var s = Math.floor(seconds % 60); + return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; + } +} diff --git a/src/scenes/Preloader.js b/src/scenes/Preloader.js new file mode 100644 index 0000000..2fe59b3 --- /dev/null +++ b/src/scenes/Preloader.js @@ -0,0 +1,73 @@ +class Preloader extends Phaser.Scene { + constructor() { + super('Preloader'); + } + + init() { + var barW = 400; + var barH = 24; + var barX = (800 - barW) / 2; + var barY = 300; + + this.add.rectangle(400, 280, barW + 4, barH + 4).setStrokeStyle(2, 0xffffff); + + var bar = this.add.rectangle(barX + 2, barY, 4, barH - 4, 0x00ff9d).setOrigin(0, 0.5); + + this.add.text(400, 240, 'FSYS AGENT', { + fontFamily: 'Arial Black', + fontSize: '32px', + color: '#00ff9d' + }).setOrigin(0.5); + + this.add.text(400, 340, 'Loading...', { + fontFamily: 'Arial', + fontSize: '14px', + color: '#b9cbbc' + }).setOrigin(0.5); + + this.load.on('progress', function(progress) { + bar.width = 4 + (barW - 4) * progress; + }); + } + + preload() { + // Player sprites — sci-fi agent + this.load.image('player_idle', 'public/assets/sprites/character_agent_idle.png'); + this.load.image('player_walk_a', 'public/assets/sprites/character_agent_walk_a.png'); + this.load.image('player_walk_b', 'public/assets/sprites/character_agent_walk_b.png'); + this.load.image('player_jump', 'public/assets/sprites/character_agent_jump.png'); + this.load.image('player_hit', 'public/assets/sprites/character_agent_hit.png'); + + // Card images — pre-scaled 56×76 thumbs for crisp rendering + var cardNames = [ + '1-gift-of-water-action', '2-grow-greens-action', '3-fine-dining-compost-dish-action', + '4-buzzing-with-wisdom-action', '5-baking-flatbread', '6-mixed-wastemonster-action', + '7-ingredient-detective-action', '8-mindful-eating-action', '9-fermentastic-action', + '10-tree-planter', '11-seed-scout', '12-natural-spritz', + '13-dashboard-designer', '14-onion-bodyguard', '15-local-investor' + ]; + + for (var i = 0; i < cardNames.length; i++) { + this.load.image('card_' + cardNames[i], 'public/images/cards/thumbs/' + cardNames[i] + '.png'); + } + + // Tile textures + this.load.image('bush_tile', 'public/assets/tiles/bush.png'); + this.load.image('cloud_tile', 'public/images/cloud.png'); + + // Jump pad sprite + this.load.image('jump_pad', 'public/images/jump_pad.png'); + + // Background + this.load.image('bg_platform', 'public/images/fsys_platform_bg.png'); + + // Sound effects + this.load.audio('sfx_jump', 'public/assets/sounds/sfx_jump.ogg'); + this.load.audio('sfx_coin', 'public/assets/sounds/sfx_coin.ogg'); + this.load.audio('sfx_hurt', 'public/assets/sounds/sfx_hurt.ogg'); + } + + create() { + this.scene.start('TitleScreen'); + } +} diff --git a/src/scenes/TitleScreen.js b/src/scenes/TitleScreen.js new file mode 100644 index 0000000..7f73a0b --- /dev/null +++ b/src/scenes/TitleScreen.js @@ -0,0 +1,107 @@ +class TitleScreen extends Phaser.Scene { + constructor() { + super('TitleScreen'); + } + + create() { + this.cameras.main.setBackgroundColor(0x0F0F23); + + var W = 960; + var H = 600; + + // Background — faded into black + this.add.image(W / 2, H / 2, 'bg_platform') + .setDisplaySize(W, H); + this.add.rectangle(W / 2, H / 2, W, H, 0x000000, 0.7); + + // Breathing container — holds title elements + var breathGroup = this.add.container(W / 2, 220); + + // Kangaroo + var roo = this.add.image(0, 0, 'player_idle'); + roo.setScale(1.2); + breathGroup.add(roo); + + // Heading + var heading = this.add.text(0, 80, + 'Help the Collector from Australia\nCollect Cards from the Clouds\nin Kenya!', { + fontFamily: 'Arial', + fontSize: '16px', + color: '#FFD700', + align: 'center', + lineSpacing: 6, + stroke: '#000000', + strokeThickness: 3 + }).setOrigin(0.5); + breathGroup.add(heading); + + // Best time + var bestTime = localStorage.getItem('fsys_agent_best_time'); + if (bestTime) { + bestTime = parseFloat(bestTime); + if (bestTime < Infinity) { + var timeText = this.add.text(0, 130, 'Best Time: ' + this.formatTime(bestTime), { + fontFamily: 'Arial', + fontSize: '14px', + color: '#ffd700' + }).setOrigin(0.5); + breathGroup.add(timeText); + } + } + + // Breathing tween + this.tweens.add({ + targets: breathGroup, + scaleX: 1.03, + scaleY: 1.03, + duration: 2500, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + + // Begin game prompt + var beginText = this.add.text(W / 2, 440, 'CLICK OR PRESS SPACE TO START', { + fontFamily: 'Arial Black', + fontSize: '20px', + color: '#00ff9d', + stroke: '#000000', + strokeThickness: 4 + }).setOrigin(0.5).setInteractive({ useHandCursor: true }); + + var border = this.add.rectangle(W / 2, 440, beginText.width + 30, beginText.height + 14) + .setStrokeStyle(2, 0x00ff9d, 0.6); + + this.tweens.add({ + targets: [beginText, border], + alpha: 0.3, + duration: 800, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + + beginText.on('pointerover', function() { beginText.setColor('#52ffff'); }); + beginText.on('pointerout', function() { beginText.setColor('#00ff9d'); }); + beginText.on('pointerdown', function() { + this.scene.start('GameScene'); + }, this); + + this.input.keyboard.once('keydown-SPACE', function() { + this.scene.start('GameScene'); + }, this); + + // Footer + this.add.text(W / 2, H - 20, 'FSYS Agent 2050', { + fontFamily: 'Arial', + fontSize: '12px', + color: '#3b4a3f' + }).setOrigin(0.5); + } + + formatTime(seconds) { + var m = Math.floor(seconds / 60); + var s = Math.floor(seconds % 60); + return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; + } +}