Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ python missile-defense.py

## Controls

### Keyboard (Default)
- **Arrow Keys**: Move crosshairs
- **Left Ctrl**: Fire from left silo
- **Left Alt**: Fire from center silo
- **Space**: Fire from right silo
- **5**: Insert coin
### Keyboard
- **1**: Start 1-player game
- **P**: Pause/Unpause
- **ESC**: Exit game
Expand Down
27 changes: 2 additions & 25 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,9 @@ class MissileCommandApp:
irq_counter: int = 0
color_cycle_counter: int = 0

# Crosshair position (game coordinates, for keyboard control)
# Crosshair position (game coordinates, updated by mouse)
crosshair_x: int = SCREEN_WIDTH // 2
crosshair_y: int = SCREEN_HEIGHT // 2
crosshair_speed: int = 3 # pixels per frame at game resolution

# Performance tracking
frame_times: list[float] = field(default_factory=list)
Expand Down Expand Up @@ -244,13 +243,8 @@ def _fire_silo(self, silo_index: int) -> None:
def _handle_events(self) -> None:
"""Process pygame events.

Keyboard controls (MAME-style emulation):
Arrow Keys – move crosshair
Left Ctrl – fire from left silo (index 0)
Left Alt – fire from center silo (index 1)
Space – fire from right silo (index 2)
Keyboard controls:
1 – start 1-player game
P – pause / unpause
ESC – exit game

Mouse controls:
Expand All @@ -275,12 +269,6 @@ def _handle_events(self) -> None:
# Start game from attract mode
if self.game.state == GameState.ATTRACT:
self.game.start_wave()
elif event.key == pygame.K_LCTRL:
self._fire_silo(0) # left silo
elif event.key == pygame.K_LALT:
self._fire_silo(1) # center silo
elif event.key == pygame.K_SPACE:
self._fire_silo(2) # right silo

elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # left click
Expand All @@ -290,17 +278,6 @@ def _handle_events(self) -> None:
elif event.button == 3: # right click
self._fire_silo(2) # right silo

# Handle held arrow keys for smooth crosshair movement
if pygame is not None:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.crosshair_x = max(0, self.crosshair_x - self.crosshair_speed)
if keys[pygame.K_RIGHT]:
self.crosshair_x = min(SCREEN_WIDTH - 1, self.crosshair_x + self.crosshair_speed)
if keys[pygame.K_UP]:
self.crosshair_y = max(0, self.crosshair_y - self.crosshair_speed)
if keys[pygame.K_DOWN]:
self.crosshair_y = min(SCREEN_HEIGHT - 1, self.crosshair_y + self.crosshair_speed)

# ── IRQ simulation ──────────────────────────────────────────────────

Expand Down
10 changes: 1 addition & 9 deletions missile-defense.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,7 @@ def main():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
exit_game(screen)
if event.key == K_LCTRL:
# Left Ctrl -> left silo
defense.shoot(missile_list, silo_index=0)
if event.key == K_LALT:
# Left Alt -> center silo
defense.shoot(missile_list, silo_index=1)
if event.key == K_SPACE:
# Space -> right silo
defense.shoot(missile_list, silo_index=2)

if event.key == K_p:
pause_game(screen)
if event.type == KEYUP:
Expand Down
32 changes: 2 additions & 30 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,11 @@ def test_fire_from_each_silo(self):
assert game.missiles.active_abm_count == 3


# ── Crosshair / arrow key controls ────────────────────────────────────────
# ── Crosshair / mouse controls ────────────────────────────────────────


class TestCrosshair:
"""Tests that crosshair position tracking and arrow key clamping work."""
"""Tests that crosshair position tracking works."""

def test_initial_crosshair_position(self):
app = MissileCommandApp()
Expand All @@ -202,34 +202,6 @@ def test_get_target_returns_crosshair_position(self):
app.crosshair_y = 80
assert app._get_target() == (50, 80)

def test_crosshair_left_clamps_at_zero(self):
app = MissileCommandApp()
app.crosshair_x = 1
app.crosshair_x = max(0, app.crosshair_x - app.crosshair_speed)
assert app.crosshair_x == 0

def test_crosshair_right_clamps_at_max(self):
app = MissileCommandApp()
app.crosshair_x = SCREEN_WIDTH - 2
app.crosshair_x = min(SCREEN_WIDTH - 1, app.crosshair_x + app.crosshair_speed)
assert app.crosshair_x == SCREEN_WIDTH - 1

def test_crosshair_up_clamps_at_zero(self):
app = MissileCommandApp()
app.crosshair_y = 1
app.crosshair_y = max(0, app.crosshair_y - app.crosshair_speed)
assert app.crosshair_y == 0

def test_crosshair_down_clamps_at_max(self):
app = MissileCommandApp()
app.crosshair_y = SCREEN_HEIGHT - 2
app.crosshair_y = min(SCREEN_HEIGHT - 1, app.crosshair_y + app.crosshair_speed)
assert app.crosshair_y == SCREEN_HEIGHT - 1

def test_crosshair_speed_default(self):
app = MissileCommandApp()
assert app.crosshair_speed == 3

def test_fire_uses_crosshair_position(self):
app = MissileCommandApp()
app.game.start_wave()
Expand Down