Compare commits

...

6 commits

Author SHA1 Message Date
53938f22b5 chore: remove stale GEMINI.md 2026-05-07 09:29:40 +02:00
db9f7714b1 chore(todo): split TODO.md into per-issue files under TODO/
Convert the flat TODO.md into a TODO/ folder where each GAL-N issue is
its own Linear-style markdown document with YAML frontmatter (id, title,
status, parent, labels), and add a README index grouped by section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 09:29:17 +02:00
38c68e6d58 chore(todo): update GAL-39 status and progress 2026-05-06 23:33:25 +02:00
9e6d53867a feat(game): add special stage type every 3rd level
Every 3rd stage (3, 6, 9, ...) is now a special stage with no enemy
dive attacks and no enemy shooting. Enemies still spawn in the default
grid formation (32 enemies) but remain in formation without attacking.

Changes:
- Add SPECIAL_STAGE_INTERVAL constant and is_special_stage() function
- Add special_stage config to StageConfigurations with empty attack_patterns
- Modify for_stage() to route special stages to special config
- Guard enemy_shoot system to skip shooting during special stages
- Show '*' suffix in window title for special stages

Refs: GAL-39
2026-05-06 23:31:12 +02:00
eaff717054 chore(todo): update GAL-27 status and progress 2026-05-06 22:02:13 +02:00
98b74a7ae2 game: award 300 points for Boss kills (3x Grunt)
Boss enemies now award 300 points instead of the same 100 as
Grunts. The scoring match in check_bullet_collisions already
routed by EnemyType; only the BOSS_POINTS constant was wrong.

Refs: GAL-27
2026-05-06 21:59:35 +02:00
64 changed files with 923 additions and 82 deletions

View file

@ -1 +0,0 @@
CLAUDE.md

74
TODO.md
View file

@ -1,74 +0,0 @@
# TODO
**1. Core Gameplay Loop & State Management**
* [x] **GAL-1: Player Lives**
* [x] GAL-2: Add a `PlayerLives` resource.
* [x] GAL-3: Decrement lives on collision.
* [x] GAL-4: Implement player destruction and respawn with temporary invincibility.
* [x] **GAL-5: Game Over State**
* [x] GAL-6: Use Bevy's `States` (`Playing`, `GameOver`).
* [x] GAL-7: Transition to `GameOver` when lives reach zero.
* [x] GAL-8: Display a "Game Over" message.
* [x] **GAL-9: Scoring**
* [x] GAL-10: Add a `Score` resource.
* [x] GAL-11: Increment score when an enemy is hit.
* [x] **GAL-12: Levels/Stages**
* [x] GAL-13: Add `CurrentStage` and `StageConfigurations` resources.
* [x] GAL-14: Define criteria for clearing a stage.
* [x] GAL-15: Advance to the next stage with increasing difficulty.
**2. Enemy Behavior - Formations & Attack Patterns**
* [x] **GAL-16: Enemy Formations**
* [x] GAL-17: Define target positions for formations (`FormationLayout`).
* [x] GAL-18: Enemies have an `Entering` state to fly to their position.
* [x] GAL-19: Enemies have an `InFormation` state.
* [x] **GAL-20: Enemy Attack Dives**
* [x] GAL-21: Enemies have an `Attacking` state with different `AttackPattern`s (Swoop, Direct, Kamikaze).
* [x] GAL-22: Periodically trigger random enemies to start an attack dive.
* [x] GAL-23: Enemies fire bullets during their dives.
* [x] GAL-24: Enemies are despawned when they fly off-screen after an attack.
* [ ] **GAL-25: Enemy Variety**
* [x] GAL-26: `EnemyType` enum (`Grunt`, `Boss`).
* [ ] GAL-27: Different behaviors, points, and colors based on type.
* [x] Colors differ (Grunt red, Boss purple) — `enemy.rs:80-83`
* [x] Behaviors differ (Boss has `CaptureBeam`, distinct SwoopDive) — `enemy.rs:254-318`
* [ ] Points: Boss currently awards same 100 points as Grunt — see `bullet.rs:48-51` (`// Same points as Grunt for now`). Boss should award more.
**3. Advanced Galaga Mechanics**
* [ ] **GAL-28: Boss Galaga & Capture Beam**
* [x] GAL-29: Create a "Boss" enemy type.
* [x] GAL-30: Implement the tractor beam attack logic (`boss_capture_attack` system).
* [x] GAL-31: Player has a `Captured` component when hit by the beam.
* [x] GAL-32: Boss has a `ReturningWithCaptive` state to go back to the formation.
* [x] GAL-33: Improve the tractor beam visual effect (currently a simple rectangle).
* Completed on branch gal-33-improve-tractor-beam-visual, commit 52b0919 — 2-layer glow beam with pulse animation, 8 unit tests
* [ ] **GAL-34: Dual Fighter (Rescuing Captured Ship)**
* [ ] GAL-35: Allow the player to shoot a Boss that is holding a captured ship.
* [ ] GAL-36: Implement the logic to free the captured ship upon shooting the Boss.
* [ ] GAL-37: Implement the dual fighter mode (controlling two ships, firing two bullets).
* [ ] **GAL-38: Challenging Stages**
* [ ] GAL-39: Implement a special stage type (e.g., every 3-4 levels).
* [ ] GAL-40: Design and implement intricate flight patterns for enemies that do not shoot.
* [ ] GAL-41: Award bonus points for destroying all enemies in the stage.
**4. Polish and User Interface**
* [ ] **GAL-42: Visuals**
* [ ] GAL-43: Replace placeholder geometric shapes with actual sprites.
* [x] GAL-44: Add explosion animations/effects.
* **Branch:** `gal-44-add-explosion-effects`
* **Comment:** 2026-05-06 — Implementation complete with commit `2ff561e`
* [x] GAL-45: Implement a scrolling starfield background.
* [ ] **GAL-46: Audio**
* [ ] GAL-47: Integrate `bevy_audio`.
* [ ] GAL-48: Add sound effects (shooting, explosions, player death, tractor beam, etc.).
* [ ] GAL-49: Add background music.
* [ ] **GAL-50: UI**
* [x] GAL-51: Display Score, Lives, and Stage in the window title.
* [ ] GAL-52: Display Score, Lives, and Stage on the screen using `bevy_ui`.
* [ ] GAL-53: Implement a High Score system (saving/loading).
* [x] GAL-54: Create a Start Menu state with a "Start Game" button.
* [ ] GAL-55: Add a "Press R to Restart" message to the `GameOver` screen and implement restart logic.

17
TODO/GAL-1.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-1
title: Player Lives
status: Done
parent: null
labels: [gameplay, core-loop]
---
# GAL-1: Player Lives
Player lives system: lives counter, decrement on hit, destruction and respawn flow.
## Sub-issues
- [x] [GAL-2](GAL-2.md) — Add a `PlayerLives` resource.
- [x] [GAL-3](GAL-3.md) — Decrement lives on collision.
- [x] [GAL-4](GAL-4.md) — Implement player destruction and respawn with temporary invincibility.

11
TODO/GAL-10.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-10
title: Add a Score resource
status: Done
parent: GAL-9
labels: [gameplay, core-loop]
---
# GAL-10: Add a `Score` resource
Add a `Score` resource.

11
TODO/GAL-11.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-11
title: Increment score when an enemy is hit
status: Done
parent: GAL-9
labels: [gameplay, core-loop]
---
# GAL-11: Increment score when an enemy is hit
Increment score when an enemy is hit.

17
TODO/GAL-12.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-12
title: Levels/Stages
status: Done
parent: null
labels: [gameplay, core-loop]
---
# GAL-12: Levels/Stages
Stage progression with increasing difficulty driven by configuration resources.
## Sub-issues
- [x] [GAL-13](GAL-13.md) — Add `CurrentStage` and `StageConfigurations` resources.
- [x] [GAL-14](GAL-14.md) — Define criteria for clearing a stage.
- [x] [GAL-15](GAL-15.md) — Advance to the next stage with increasing difficulty.

11
TODO/GAL-13.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-13
title: Add CurrentStage and StageConfigurations resources
status: Done
parent: GAL-12
labels: [gameplay, core-loop]
---
# GAL-13: Add `CurrentStage` and `StageConfigurations` resources
Add `CurrentStage` and `StageConfigurations` resources.

11
TODO/GAL-14.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-14
title: Define criteria for clearing a stage
status: Done
parent: GAL-12
labels: [gameplay, core-loop]
---
# GAL-14: Define criteria for clearing a stage
Define criteria for clearing a stage.

11
TODO/GAL-15.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-15
title: Advance to the next stage with increasing difficulty
status: Done
parent: GAL-12
labels: [gameplay, core-loop]
---
# GAL-15: Advance to the next stage with increasing difficulty
Advance to the next stage with increasing difficulty.

17
TODO/GAL-16.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-16
title: Enemy Formations
status: Done
parent: null
labels: [enemy, gameplay]
---
# GAL-16: Enemy Formations
Enemies fly into target positions and hold formation.
## Sub-issues
- [x] [GAL-17](GAL-17.md) — Define target positions for formations (`FormationLayout`).
- [x] [GAL-18](GAL-18.md) — Enemies have an `Entering` state to fly to their position.
- [x] [GAL-19](GAL-19.md) — Enemies have an `InFormation` state.

11
TODO/GAL-17.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-17
title: Define target positions for formations (FormationLayout)
status: Done
parent: GAL-16
labels: [enemy, gameplay]
---
# GAL-17: Define target positions for formations (`FormationLayout`)
Define target positions for formations (`FormationLayout`).

11
TODO/GAL-18.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-18
title: Enemies have an Entering state to fly to their position
status: Done
parent: GAL-16
labels: [enemy, gameplay]
---
# GAL-18: Enemies have an `Entering` state to fly to their position
Enemies have an `Entering` state to fly to their position.

11
TODO/GAL-19.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-19
title: Enemies have an InFormation state
status: Done
parent: GAL-16
labels: [enemy, gameplay]
---
# GAL-19: Enemies have an `InFormation` state
Enemies have an `InFormation` state.

11
TODO/GAL-2.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-2
title: Add a PlayerLives resource
status: Done
parent: GAL-1
labels: [gameplay, core-loop]
---
# GAL-2: Add a `PlayerLives` resource
Add a `PlayerLives` resource.

18
TODO/GAL-20.md Normal file
View file

@ -0,0 +1,18 @@
---
id: GAL-20
title: Enemy Attack Dives
status: Done
parent: null
labels: [enemy, gameplay]
---
# GAL-20: Enemy Attack Dives
Enemies leave formation to dive at the player along varied attack patterns.
## Sub-issues
- [x] [GAL-21](GAL-21.md) — Enemies have an `Attacking` state with different `AttackPattern`s (Swoop, Direct, Kamikaze).
- [x] [GAL-22](GAL-22.md) — Periodically trigger random enemies to start an attack dive.
- [x] [GAL-23](GAL-23.md) — Enemies fire bullets during their dives.
- [x] [GAL-24](GAL-24.md) — Enemies are despawned when they fly off-screen after an attack.

11
TODO/GAL-21.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-21
title: Attacking state with different AttackPatterns
status: Done
parent: GAL-20
labels: [enemy, gameplay]
---
# GAL-21: Enemies have an `Attacking` state with different `AttackPattern`s
Enemies have an `Attacking` state with different `AttackPattern`s (Swoop, Direct, Kamikaze).

11
TODO/GAL-22.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-22
title: Periodically trigger random enemies to start an attack dive
status: Done
parent: GAL-20
labels: [enemy, gameplay]
---
# GAL-22: Periodically trigger random enemies to start an attack dive
Periodically trigger random enemies to start an attack dive.

11
TODO/GAL-23.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-23
title: Enemies fire bullets during their dives
status: Done
parent: GAL-20
labels: [enemy, gameplay]
---
# GAL-23: Enemies fire bullets during their dives
Enemies fire bullets during their dives.

11
TODO/GAL-24.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-24
title: Despawn enemies when they fly off-screen after an attack
status: Done
parent: GAL-20
labels: [enemy, gameplay]
---
# GAL-24: Enemies are despawned when they fly off-screen after an attack
Enemies are despawned when they fly off-screen after an attack.

16
TODO/GAL-25.md Normal file
View file

@ -0,0 +1,16 @@
---
id: GAL-25
title: Enemy Variety
status: In Progress
parent: null
labels: [enemy, gameplay]
---
# GAL-25: Enemy Variety
Different enemy types with distinct visuals, behaviors, and point values.
## Sub-issues
- [x] [GAL-26](GAL-26.md) — `EnemyType` enum (`Grunt`, `Boss`).
- [x] [GAL-27](GAL-27.md) — Different behaviors, points, and colors based on type.

11
TODO/GAL-26.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-26
title: EnemyType enum (Grunt, Boss)
status: Done
parent: GAL-25
labels: [enemy, gameplay]
---
# GAL-26: `EnemyType` enum (`Grunt`, `Boss`)
`EnemyType` enum (`Grunt`, `Boss`).

21
TODO/GAL-27.md Normal file
View file

@ -0,0 +1,21 @@
---
id: GAL-27
title: Different behaviors, points, and colors based on type
status: Done
parent: GAL-25
labels: [enemy, gameplay]
---
# GAL-27: Different behaviors, points, and colors based on type
Bosses are visually, behaviorally, and economically distinct from Grunts.
## Acceptance criteria
- [x] Colors differ (Grunt red, Boss purple) — `enemy.rs:80-83`
- [x] Behaviors differ (Boss has `CaptureBeam`, distinct SwoopDive) — `enemy.rs:254-318`
- [x] Points: Boss awards 300 points (3x Grunt) — `bullet.rs:13-14`
## Comments
- Completed on branch GAL-27, commit 98b74a7.

19
TODO/GAL-28.md Normal file
View file

@ -0,0 +1,19 @@
---
id: GAL-28
title: Boss Galaga & Capture Beam
status: In Progress
parent: null
labels: [enemy, advanced-mechanics]
---
# GAL-28: Boss Galaga & Capture Beam
Boss enemy with tractor beam capture mechanic and return-to-formation behavior.
## Sub-issues
- [x] [GAL-29](GAL-29.md) — Create a "Boss" enemy type.
- [x] [GAL-30](GAL-30.md) — Implement the tractor beam attack logic (`boss_capture_attack` system).
- [x] [GAL-31](GAL-31.md) — Player has a `Captured` component when hit by the beam.
- [x] [GAL-32](GAL-32.md) — Boss has a `ReturningWithCaptive` state to go back to the formation.
- [x] [GAL-33](GAL-33.md) — Improve the tractor beam visual effect (currently a simple rectangle).

11
TODO/GAL-29.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-29
title: Create a Boss enemy type
status: Done
parent: GAL-28
labels: [enemy, advanced-mechanics]
---
# GAL-29: Create a "Boss" enemy type
Create a "Boss" enemy type.

11
TODO/GAL-3.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-3
title: Decrement lives on collision
status: Done
parent: GAL-1
labels: [gameplay, core-loop]
---
# GAL-3: Decrement lives on collision
Decrement lives on collision.

11
TODO/GAL-30.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-30
title: Implement the tractor beam attack logic
status: Done
parent: GAL-28
labels: [enemy, advanced-mechanics]
---
# GAL-30: Implement the tractor beam attack logic (`boss_capture_attack` system)
Implement the tractor beam attack logic (`boss_capture_attack` system).

11
TODO/GAL-31.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-31
title: Player has a Captured component when hit by the beam
status: Done
parent: GAL-28
labels: [enemy, advanced-mechanics]
---
# GAL-31: Player has a `Captured` component when hit by the beam
Player has a `Captured` component when hit by the beam.

11
TODO/GAL-32.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-32
title: Boss has a ReturningWithCaptive state
status: Done
parent: GAL-28
labels: [enemy, advanced-mechanics]
---
# GAL-32: Boss has a `ReturningWithCaptive` state to go back to the formation
Boss has a `ReturningWithCaptive` state to go back to the formation.

15
TODO/GAL-33.md Normal file
View file

@ -0,0 +1,15 @@
---
id: GAL-33
title: Improve the tractor beam visual effect
status: Done
parent: GAL-28
labels: [enemy, advanced-mechanics, visuals]
---
# GAL-33: Improve the tractor beam visual effect
Improve the tractor beam visual effect (currently a simple rectangle).
## Comments
- Completed on branch `gal-33-improve-tractor-beam-visual`, commit 52b0919 — 2-layer glow beam with pulse animation, 8 unit tests.

17
TODO/GAL-34.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-34
title: Dual Fighter (Rescuing Captured Ship)
status: Todo
parent: null
labels: [gameplay, advanced-mechanics]
---
# GAL-34: Dual Fighter (Rescuing Captured Ship)
Allow the player to rescue a captured ship and operate in dual-fighter mode.
## Sub-issues
- [ ] [GAL-35](GAL-35.md) — Allow the player to shoot a Boss that is holding a captured ship.
- [ ] [GAL-36](GAL-36.md) — Implement the logic to free the captured ship upon shooting the Boss.
- [ ] [GAL-37](GAL-37.md) — Implement the dual fighter mode (controlling two ships, firing two bullets).

11
TODO/GAL-35.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-35
title: Allow the player to shoot a Boss holding a captured ship
status: Todo
parent: GAL-34
labels: [gameplay, advanced-mechanics]
---
# GAL-35: Allow the player to shoot a Boss that is holding a captured ship
Allow the player to shoot a Boss that is holding a captured ship.

11
TODO/GAL-36.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-36
title: Free the captured ship upon shooting the Boss
status: Todo
parent: GAL-34
labels: [gameplay, advanced-mechanics]
---
# GAL-36: Implement the logic to free the captured ship upon shooting the Boss
Implement the logic to free the captured ship upon shooting the Boss.

11
TODO/GAL-37.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-37
title: Implement the dual fighter mode
status: Todo
parent: GAL-34
labels: [gameplay, advanced-mechanics]
---
# GAL-37: Implement the dual fighter mode (controlling two ships, firing two bullets)
Implement the dual fighter mode (controlling two ships, firing two bullets).

17
TODO/GAL-38.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-38
title: Challenging Stages
status: In Progress
parent: null
labels: [gameplay, advanced-mechanics]
---
# GAL-38: Challenging Stages
Special stages every few levels with intricate non-shooting flight patterns and bonus rewards.
## Sub-issues
- [x] [GAL-39](GAL-39.md) — Implement a special stage type (e.g., every 3-4 levels).
- [ ] [GAL-40](GAL-40.md) — Design and implement intricate flight patterns for enemies that do not shoot.
- [ ] [GAL-41](GAL-41.md) — Award bonus points for destroying all enemies in the stage.

16
TODO/GAL-39.md Normal file
View file

@ -0,0 +1,16 @@
---
id: GAL-39
title: Implement a special stage type
status: Done
parent: GAL-38
labels: [gameplay, advanced-mechanics]
---
# GAL-39: Implement a special stage type (e.g., every 3-4 levels)
Implement a special stage type (e.g., every 3-4 levels).
## Comments
- 2026-05-06 — Status set to In Progress.
- 2026-05-06 — Branch `GAL-39`, commit 9e6d538 — every 3rd level has no enemy attacks.

11
TODO/GAL-4.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-4
title: Player destruction and respawn with temporary invincibility
status: Done
parent: GAL-1
labels: [gameplay, core-loop]
---
# GAL-4: Implement player destruction and respawn with temporary invincibility
Implement player destruction and respawn with temporary invincibility.

11
TODO/GAL-40.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-40
title: Design intricate flight patterns for non-shooting enemies
status: Todo
parent: GAL-38
labels: [gameplay, advanced-mechanics]
---
# GAL-40: Design and implement intricate flight patterns for enemies that do not shoot
Design and implement intricate flight patterns for enemies that do not shoot.

11
TODO/GAL-41.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-41
title: Award bonus points for clearing the special stage
status: Todo
parent: GAL-38
labels: [gameplay, advanced-mechanics]
---
# GAL-41: Award bonus points for destroying all enemies in the stage
Award bonus points for destroying all enemies in the stage.

17
TODO/GAL-42.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-42
title: Visuals
status: In Progress
parent: null
labels: [polish, visuals]
---
# GAL-42: Visuals
Replace placeholder geometry with sprites, add explosions, and add a starfield background.
## Sub-issues
- [ ] [GAL-43](GAL-43.md) — Replace placeholder geometric shapes with actual sprites.
- [x] [GAL-44](GAL-44.md) — Add explosion animations/effects.
- [x] [GAL-45](GAL-45.md) — Implement a scrolling starfield background.

11
TODO/GAL-43.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-43
title: Replace placeholder geometric shapes with actual sprites
status: Todo
parent: GAL-42
labels: [polish, visuals]
---
# GAL-43: Replace placeholder geometric shapes with actual sprites
Replace placeholder geometric shapes with actual sprites.

16
TODO/GAL-44.md Normal file
View file

@ -0,0 +1,16 @@
---
id: GAL-44
title: Add explosion animations/effects
status: Done
parent: GAL-42
labels: [polish, visuals]
---
# GAL-44: Add explosion animations/effects
Add explosion animations/effects.
## Comments
- Branch: `gal-44-add-explosion-effects`.
- 2026-05-06 — Implementation complete with commit `2ff561e`.

11
TODO/GAL-45.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-45
title: Implement a scrolling starfield background
status: Done
parent: GAL-42
labels: [polish, visuals]
---
# GAL-45: Implement a scrolling starfield background
Implement a scrolling starfield background.

17
TODO/GAL-46.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-46
title: Audio
status: Todo
parent: null
labels: [polish, audio]
---
# GAL-46: Audio
Wire up audio plugin, sound effects, and background music.
## Sub-issues
- [ ] [GAL-47](GAL-47.md) — Integrate `bevy_audio`.
- [ ] [GAL-48](GAL-48.md) — Add sound effects (shooting, explosions, player death, tractor beam, etc.).
- [ ] [GAL-49](GAL-49.md) — Add background music.

11
TODO/GAL-47.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-47
title: Integrate bevy_audio
status: Todo
parent: GAL-46
labels: [polish, audio]
---
# GAL-47: Integrate `bevy_audio`
Integrate `bevy_audio`.

11
TODO/GAL-48.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-48
title: Add sound effects
status: Todo
parent: GAL-46
labels: [polish, audio]
---
# GAL-48: Add sound effects (shooting, explosions, player death, tractor beam, etc.)
Add sound effects (shooting, explosions, player death, tractor beam, etc.).

11
TODO/GAL-49.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-49
title: Add background music
status: Todo
parent: GAL-46
labels: [polish, audio]
---
# GAL-49: Add background music
Add background music.

17
TODO/GAL-5.md Normal file
View file

@ -0,0 +1,17 @@
---
id: GAL-5
title: Game Over State
status: Done
parent: null
labels: [gameplay, core-loop]
---
# GAL-5: Game Over State
State machine covering `Playing` and `GameOver`, with a transition trigger and on-screen message.
## Sub-issues
- [x] [GAL-6](GAL-6.md) — Use Bevy's `States` (`Playing`, `GameOver`).
- [x] [GAL-7](GAL-7.md) — Transition to `GameOver` when lives reach zero.
- [x] [GAL-8](GAL-8.md) — Display a "Game Over" message.

19
TODO/GAL-50.md Normal file
View file

@ -0,0 +1,19 @@
---
id: GAL-50
title: UI
status: In Progress
parent: null
labels: [polish, ui]
---
# GAL-50: UI
Score, lives, stage, high-score, start menu, and restart UI.
## Sub-issues
- [x] [GAL-51](GAL-51.md) — Display Score, Lives, and Stage in the window title.
- [ ] [GAL-52](GAL-52.md) — Display Score, Lives, and Stage on the screen using `bevy_ui`.
- [ ] [GAL-53](GAL-53.md) — Implement a High Score system (saving/loading).
- [x] [GAL-54](GAL-54.md) — Create a Start Menu state with a "Start Game" button.
- [ ] [GAL-55](GAL-55.md) — Add a "Press R to Restart" message to the `GameOver` screen and implement restart logic.

11
TODO/GAL-51.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-51
title: Display Score, Lives, and Stage in the window title
status: Done
parent: GAL-50
labels: [polish, ui]
---
# GAL-51: Display Score, Lives, and Stage in the window title
Display Score, Lives, and Stage in the window title.

11
TODO/GAL-52.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-52
title: Display Score, Lives, and Stage on screen using bevy_ui
status: Todo
parent: GAL-50
labels: [polish, ui]
---
# GAL-52: Display Score, Lives, and Stage on the screen using `bevy_ui`
Display Score, Lives, and Stage on the screen using `bevy_ui`.

11
TODO/GAL-53.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-53
title: Implement a High Score system (saving/loading)
status: Todo
parent: GAL-50
labels: [polish, ui]
---
# GAL-53: Implement a High Score system (saving/loading)
Implement a High Score system (saving/loading).

11
TODO/GAL-54.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-54
title: Create a Start Menu state with a Start Game button
status: Done
parent: GAL-50
labels: [polish, ui]
---
# GAL-54: Create a Start Menu state with a "Start Game" button
Create a Start Menu state with a "Start Game" button.

11
TODO/GAL-55.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-55
title: Add Press R to Restart message and restart logic
status: Todo
parent: GAL-50
labels: [polish, ui]
---
# GAL-55: Add a "Press R to Restart" message to the `GameOver` screen and implement restart logic
Add a "Press R to Restart" message to the `GameOver` screen and implement restart logic.

11
TODO/GAL-6.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-6
title: Use Bevy's States (Playing, GameOver)
status: Done
parent: GAL-5
labels: [gameplay, core-loop]
---
# GAL-6: Use Bevy's `States` (`Playing`, `GameOver`)
Use Bevy's `States` (`Playing`, `GameOver`).

11
TODO/GAL-7.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-7
title: Transition to GameOver when lives reach zero
status: Done
parent: GAL-5
labels: [gameplay, core-loop]
---
# GAL-7: Transition to `GameOver` when lives reach zero
Transition to `GameOver` when lives reach zero.

11
TODO/GAL-8.md Normal file
View file

@ -0,0 +1,11 @@
---
id: GAL-8
title: Display a Game Over message
status: Done
parent: GAL-5
labels: [gameplay, core-loop]
---
# GAL-8: Display a "Game Over" message
Display a "Game Over" message.

16
TODO/GAL-9.md Normal file
View file

@ -0,0 +1,16 @@
---
id: GAL-9
title: Scoring
status: Done
parent: null
labels: [gameplay, core-loop]
---
# GAL-9: Scoring
Add a score resource and increment when an enemy is destroyed.
## Sub-issues
- [x] [GAL-10](GAL-10.md) — Add a `Score` resource.
- [x] [GAL-11](GAL-11.md) — Increment score when an enemy is hit.

30
TODO/README.md Normal file
View file

@ -0,0 +1,30 @@
# Project Issues
Linear-style issue tracker for bglga. Each issue lives in its own `GAL-N.md` file in this folder.
Statuses: `Todo`, `In Progress`, `Done`.
## 1. Core Gameplay Loop & State Management
- [x] [GAL-1](GAL-1.md) — Player Lives
- [x] [GAL-5](GAL-5.md) — Game Over State
- [x] [GAL-9](GAL-9.md) — Scoring
- [x] [GAL-12](GAL-12.md) — Levels/Stages
## 2. Enemy Behavior — Formations & Attack Patterns
- [x] [GAL-16](GAL-16.md) — Enemy Formations
- [x] [GAL-20](GAL-20.md) — Enemy Attack Dives
- [ ] [GAL-25](GAL-25.md) — Enemy Variety
## 3. Advanced Galaga Mechanics
- [ ] [GAL-28](GAL-28.md) — Boss Galaga & Capture Beam
- [ ] [GAL-34](GAL-34.md) — Dual Fighter (Rescuing Captured Ship)
- [ ] [GAL-38](GAL-38.md) — Challenging Stages
## 4. Polish and User Interface
- [ ] [GAL-42](GAL-42.md) — Visuals
- [ ] [GAL-46](GAL-46.md) — Audio
- [ ] [GAL-50](GAL-50.md) — UI

View file

@ -11,7 +11,7 @@ use crate::resources::{PlayerLives, PlayerRespawnTimer, Score};
use crate::systems::spawn_explosion;
const GRUNT_POINTS: u32 = 100;
const BOSS_POINTS: u32 = 100; // TODO(GAL-27): differentiate Boss from Grunt scoring
const BOSS_POINTS: u32 = 300;
pub fn move_bullets(
mut query: Query<(Entity, &mut Transform), With<Bullet>>,

View file

@ -47,6 +47,9 @@ pub const BEAM_CORE_COLOR: Color = Color::srgba(0.7, 0.2, 1.0, 0.7);
pub const BEAM_PULSE_FREQ: f32 = 3.0;
pub const BEAM_PULSE_AMPLITUDE: f32 = 0.15;
// Special stages
pub const SPECIAL_STAGE_INTERVAL: u32 = 3;
// Starfield
pub const STAR_COUNT: usize = 150;
pub const STAR_MIN_SIZE: f32 = 1.0;

View file

@ -12,6 +12,7 @@ use crate::constants::{
};
use crate::resources::{
AttackDiveTimer, CurrentStage, EnemySpawnTimer, FormationState, StageConfigurations,
is_special_stage,
};
const BOSS_BASE_CHANCE: f32 = 0.30;
@ -341,7 +342,11 @@ pub fn enemy_shoot(
mut commands: Commands,
time: Res<Time>,
mut enemy_query: Query<(&Transform, &mut Enemy, &EnemyState), Without<FormationTarget>>,
stage: Res<CurrentStage>,
) {
if is_special_stage(stage.number) {
return;
}
for (transform, mut enemy, state) in enemy_query.iter_mut() {
if !matches!(state, EnemyState::Attacking(_)) {
continue;

View file

@ -3,9 +3,14 @@ use bevy::prelude::*;
use crate::components::AttackPattern;
use crate::constants::{
ENEMY_SHOOT_INTERVAL, FORMATION_BASE_Y, FORMATION_COLS, FORMATION_ENEMY_COUNT,
FORMATION_X_SPACING, FORMATION_Y_SPACING, WINDOW_WIDTH,
FORMATION_X_SPACING, FORMATION_Y_SPACING, SPECIAL_STAGE_INTERVAL, WINDOW_WIDTH,
};
/// Returns `true` when `stage_number` is a special stage (every 3rd stage).
pub fn is_special_stage(stage_number: u32) -> bool {
stage_number > 0 && stage_number.is_multiple_of(SPECIAL_STAGE_INTERVAL)
}
#[derive(Resource)]
pub struct EnemySpawnTimer {
pub timer: Timer,
@ -58,13 +63,21 @@ pub struct StageConfig {
#[derive(Resource, Debug, Clone)]
pub struct StageConfigurations {
pub stages: Vec<StageConfig>,
pub special_stage: StageConfig,
}
impl StageConfigurations {
/// Cycles through configured stages once `stage_number` exceeds the count.
/// Returns the config for the given stage, using the special stage config
/// when `stage_number` is a multiple of `SPECIAL_STAGE_INTERVAL`.
pub fn for_stage(&self, stage_number: u32) -> &StageConfig {
let idx = (stage_number.saturating_sub(1) as usize) % self.stages.len();
&self.stages[idx]
if is_special_stage(stage_number) {
return &self.special_stage;
}
// Count how many special stages come before this one
let special_before = stage_number / SPECIAL_STAGE_INTERVAL;
let normal_idx =
(stage_number.saturating_sub(1) - special_before) as usize % self.stages.len();
&self.stages[normal_idx]
}
}
@ -89,8 +102,18 @@ impl Default for StageConfigurations {
enemy_shoot_interval: ENEMY_SHOOT_INTERVAL * 0.8,
};
let special_stage = StageConfig {
formation_layout: FormationLayout::default(),
enemy_count: FORMATION_ENEMY_COUNT,
attack_patterns: vec![],
attack_dive_interval: f32::MAX,
enemy_speed_multiplier: 1.0,
enemy_shoot_interval: ENEMY_SHOOT_INTERVAL,
};
Self {
stages: vec![stage1, stage2],
special_stage,
}
}
}

View file

@ -4,7 +4,7 @@ use std::time::Duration;
use crate::components::{Explosion, Invincible, Player};
use crate::constants::{EXPLOSION_BASE_SIZE, EXPLOSION_COLOR, EXPLOSION_DURATION, EXPLOSION_MAX_SIZE};
use crate::player::spawn_player_ship;
use crate::resources::{CurrentStage, PlayerLives, Score};
use crate::resources::{CurrentStage, PlayerLives, Score, is_special_stage};
use crate::starfield::spawn_starfield;
pub fn setup(mut commands: Commands) {
@ -38,10 +38,15 @@ pub fn update_window_title(
if !(lives.is_changed() || score.is_changed() || stage.is_changed()) {
return;
}
let stage_label = if is_special_stage(stage.number) {
format!("{}*", stage.number)
} else {
stage.number.to_string()
};
if let Ok(mut window) = windows.single_mut() {
window.title = format!(
"Galaga :: Stage: {} Lives: {} Score: {}",
stage.number, lives.count, score.value
stage_label, lives.count, score.value
);
}
}

140
tests/special_stage.rs Normal file
View file

@ -0,0 +1,140 @@
use bglga::constants::SPECIAL_STAGE_INTERVAL;
use bglga::resources::is_special_stage;
use bglga::resources::StageConfigurations;
// ── SPECIAL_STAGE_INTERVAL constant ──
#[test]
fn special_stage_interval_is_three() {
assert_eq!(SPECIAL_STAGE_INTERVAL, 3);
}
// ── is_special_stage function ──
#[test]
fn stage_zero_is_not_special() {
assert!(!is_special_stage(0));
}
#[test]
fn stage_1_is_not_special() {
assert!(!is_special_stage(1));
}
#[test]
fn stage_2_is_not_special() {
assert!(!is_special_stage(2));
}
#[test]
fn stage_3_is_special() {
assert!(is_special_stage(3));
}
#[test]
fn stage_4_is_not_special() {
assert!(!is_special_stage(4));
}
#[test]
fn stage_5_is_not_special() {
assert!(!is_special_stage(5));
}
#[test]
fn stage_6_is_special() {
assert!(is_special_stage(6));
}
#[test]
fn stage_9_is_special() {
assert!(is_special_stage(9));
}
// ── for_stage returns special config for special stages ──
#[test]
fn for_stage_returns_special_config_at_stage_3() {
let configs = StageConfigurations::default();
let config = configs.for_stage(3);
// Special stages have no dive attacks
assert!(
config.attack_patterns.is_empty(),
"Stage 3 (special) should have empty attack_patterns"
);
}
#[test]
fn for_stage_returns_special_config_at_stage_6() {
let configs = StageConfigurations::default();
let config = configs.for_stage(6);
assert!(
config.attack_patterns.is_empty(),
"Stage 6 (special) should have empty attack_patterns"
);
}
#[test]
fn for_stage_returns_normal_config_at_stage_1() {
let configs = StageConfigurations::default();
let config = configs.for_stage(1);
assert!(
!config.attack_patterns.is_empty(),
"Stage 1 (normal) should have attack patterns"
);
}
#[test]
fn for_stage_returns_normal_config_at_stage_2() {
let configs = StageConfigurations::default();
let config = configs.for_stage(2);
assert!(
!config.attack_patterns.is_empty(),
"Stage 2 (normal) should have attack patterns"
);
}
#[test]
fn for_stage_returns_normal_config_at_stage_4() {
let configs = StageConfigurations::default();
let config = configs.for_stage(4);
assert!(
!config.attack_patterns.is_empty(),
"Stage 4 (normal) should have attack patterns"
);
}
// ── Special stage config properties ──
#[test]
fn special_stage_has_full_formation() {
let configs = StageConfigurations::default();
let config = configs.for_stage(3);
assert_eq!(config.enemy_count, 32, "Special stage should have 32 enemies");
assert_eq!(
config.formation_layout.positions.len(),
32,
"Special stage formation should have 32 positions"
);
}
#[test]
fn special_stage_has_max_dive_interval() {
let configs = StageConfigurations::default();
let config = configs.for_stage(3);
assert_eq!(
config.attack_dive_interval,
f32::MAX,
"Special stage should have max dive interval"
);
}
#[test]
fn special_stage_has_base_speed() {
let configs = StageConfigurations::default();
let config = configs.for_stage(3);
assert_eq!(
config.enemy_speed_multiplier, 1.0,
"Special stage should have 1.0x speed multiplier"
);
}