from __future__ import annotations from .police_actions import tick_blocks from .state import GameState, JunctionCheck from .story_engine import apply_turn_bundle, generate_turn_bundle from .witness_engine import ( corrupt_witnesses_slightly, generate_ambient_witness_batch, generate_patrol_witness_batch, ) from .win_conditions import apply_junction_check, culprit_has_escaped def advance_turn(state: GameState, use_model: bool = False) -> str: if state.result: return "Game is already complete." pre_catch = _search_team_catch(state, phase="stakeout") if pre_catch is not None: return _finalize_capture(state, pre_catch, "A search team was already watching this junction when the turn opened.") move, story, witnesses, venues = generate_turn_bundle(state, use_model=use_model) setattr(move, "previous_disguise", state.culprit.current_disguise) apply_turn_bundle(state, move, story, witnesses, venues) patrol_batch = generate_patrol_witness_batch(state, move) if patrol_batch: state.witness_batches.append(patrol_batch) ambient_batch = generate_ambient_witness_batch(state, [witness.potential_id for witness in witnesses]) if ambient_batch: state.witness_batches.append(ambient_batch) corrupt_witnesses_slightly(state) state.active_blocks = tick_blocks(state.active_blocks) active_block_ids = {block.block_id for block in state.active_blocks} state.placed_tactics = [ tactic for tactic in state.placed_tactics if tactic.linked_block_id is None or tactic.linked_block_id in active_block_ids ] state.turn_number += 1 state.phase = "commissioner_action" post_catch = _search_team_catch(state, phase="intercept") if post_catch is not None: return _finalize_capture(state, post_catch, "A search team intercepted the culprit after their move.") if culprit_has_escaped(state): state.result = "culprit_escape" state.phase = "complete" message = "The culprit avoided detection until the turn limit expired." else: message = f"Turn advanced. Public report: no confirmed capture. Turns remaining: {state.max_turns - state.turn_number + 1}." if ambient_batch: message += f" {ambient_batch.total_witnesses} new witness report(s) surfaced across the city." if patrol_batch: message += f" {patrol_batch.total_witnesses} patrol officer report(s) logged." state.game_log.append({"turn_number": state.turn_number, "kind": "turn_advance", "message": message}) return message def _search_team_catch(state: GameState, phase: str) -> int | None: search_team_junctions = {t.junction_id for t in state.placed_tactics if t.tactic_type == "search_team"} if state.culprit.current_junction in search_team_junctions: return state.culprit.current_junction return None def _finalize_capture(state: GameState, junction_id: int, reason: str) -> str: apply_junction_check(state, junction_id) check_id = f"check_t{state.turn_number:03d}_search_team" state.junction_checks.append(JunctionCheck( check_id=check_id, turn_number=state.turn_number, junction_id=junction_id, result="culprit_found", )) message = f"Commissioner wins. {reason} Junction {junction_id} secured." state.game_log.append({"turn_number": state.turn_number, "kind": "turn_advance", "message": message}) return message