From ceaf88ef2210e1ea12f98a012390fd461310174f Mon Sep 17 00:00:00 2001 From: Tim McCarthy Date: Sun, 14 Jun 2026 09:30:46 -0700 Subject: [PATCH] Stop auto-assigning the Captain when a ship is stolen Completing Crew Objective 1 (Steal a Ship) no longer anoints the highest-ranked Pi-Rat as Captain (nor vacates the seat when the ship is lost). Captaincy is now earned: the crew chooses one via the Captaincy UI / set_captain and Crew Objective 2. Decoupled captaincy from crew_1 in toggle_objective, dropped the now-unused random import, and updated test_captaincy_and_hand_sizes accordingly. Co-Authored-By: Claude Opus 4.8 --- TODO.md | 2 +- src/pirats/crud_scene.py | 13 ++----------- tests/test_game.py | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index 4f967fe..7cf5036 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,7 @@ - [x] When Deep issues a challenge, stakes should be two fields, success and failure -- [ ] Captain should not be autoassigned. That's something the crew has to earn. +- [x] Captain should not be autoassigned. That's something the crew has to earn. - [ ] The back button on the admin page should go back to the active game, not the join screen diff --git a/src/pirats/crud_scene.py b/src/pirats/crud_scene.py index e7c58b8..99a6100 100644 --- a/src/pirats/crud_scene.py +++ b/src/pirats/crud_scene.py @@ -1,5 +1,4 @@ import json -import random from typing import Tuple, Dict, Any from sqlmodel import Session from .models import Game, Player, Obstacle @@ -310,19 +309,11 @@ def toggle_objective(db: Session, game_id: str, target_id: str, obj_type: str, s if not game: return if obj_type == "crew_1": + # Stealing a ship no longer auto-anoints a Captain — the crew has to + # earn and choose one themselves (Crew Objective 2 + the Captaincy UI). game.completed_crew_1 = status db.add(game) db.commit() - if status and not game.captain_player_id: - # "Once the crew controls a ship, the highest-Ranked Pi-Rat becomes the de facto Captain." - pi_rats = [p for p in game.players if p.role != "deep" and not p.is_dead and not p.is_ghost] - if pi_rats: - max_rank = max(p.rank for p in pi_rats) - candidates = [p for p in pi_rats if p.rank == max_rank] - set_captain(db, game, random.choice(candidates).id, "Highest-ranked Pi-Rat aboard the stolen ship") - elif not status and game.captain_player_id: - # The ship is lost: the Captain's position goes with it. - set_captain(db, game, None, "The ship was lost") elif obj_type == "crew_2": game.completed_crew_2 = status db.add(game) diff --git a/tests/test_game.py b/tests/test_game.py index eb3aef7..725e299 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -357,20 +357,20 @@ def test_captaincy_and_hand_sizes(session): session.commit() assert crud.calculate_max_hand_size(p3, game.players, game.captain_player_id) == 4 - # Completing Crew Objective 1 (Steal a Ship) makes the highest-ranked Pi-Rat the Captain + # Stealing a ship does NOT auto-assign a Captain — the crew must earn and + # choose one. Completing Crew Objective 1 leaves the captaincy vacant. crud.toggle_objective(session, game.id, p1.id, "crew_1", True) session.refresh(game) - assert game.captain_player_id in (p2.id, p3.id) - captain = crud.get_player(session, game.captain_player_id) - assert crud.is_player_captain(captain, game) - # Captain privilege: +1 hand size - assert crud.calculate_max_hand_size(captain, game.players, game.captain_player_id) == 5 - - # Losing the ship vacates the Captaincy - crud.toggle_objective(session, game.id, p1.id, "crew_1", False) - session.refresh(game) assert game.captain_player_id is None + # The crew chooses a Captain explicitly (via the Captaincy UI / set_captain). + crud.set_captain(session, game, p2.id) + session.refresh(game) + assert game.captain_player_id == p2.id + assert crud.is_player_captain(p2, game) + # Captain privilege: +1 hand size + assert crud.calculate_max_hand_size(p2, game.players, game.captain_player_id) == 5 + def test_captain_tax_on_failed_challenge(session): game, deep, (p2, p3) = make_scene_game(session, num_pirats=2) # p3 (rank 2) becomes captain