From e17c9f02415893a2c0985c76e8ffa8629881c8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sv=C3=ADtil?= Date: Thu, 16 Oct 2025 20:10:55 +0200 Subject: [PATCH 1/4] track board participation in current game for accurate statistics --- src/main.py | 41 ++++++++++++++--------------------------- src/state.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/main.py b/src/main.py index 844db2f..c636f83 100644 --- a/src/main.py +++ b/src/main.py @@ -169,45 +169,32 @@ def generate_game_statistics(game_state: GameState): # history = [round1_data, round2_data, ...] # where round_data = {"Team A": {'productions': [(Power.NUCLEAR, 1500), ...], 'total_consumption': 1600}, ...} - # First, collect all rounds that were played by any board + # Filter to only include boards that participated in the current game + participating_boards = {board_id: board for board_id, board in game_state.boards.items() + if board.participated_in_current_game} + + debug_print(f"Total boards: {len(game_state.boards)}, Participating boards: {len(participating_boards)}") + + # First, collect all rounds that were played by any participating board all_round_indices = set() - for board in game_state.boards.values(): + for board in participating_boards.values(): all_round_indices.update(board.round_history) if not all_round_indices: - logger.debug("No round history found for any boards") - # Return empty statistics with boards data only - for board_id, board in game_state.boards.items(): - board_stats = board.to_dict() - board_stats["total_energy_produced"] = 0 - board_stats["total_energy_consumed"] = 0 - board_stats["average_production"] = 0 - board_stats["average_consumption"] = 0 - board_stats["energy_balance"] = 0 - board_stats["average_production_by_type"] = {} - statistics["boards"].append(board_stats) - - # Add mock scores since we have no real data - statistics["team_performance"][board_id] = { - "team_name": board.display_name, - "team_number": board_id.replace('board', '') if board_id.startswith('board') else board_id, - "ecology": 50, - "elmix": 50, - "finances": 50, - "popularity": 50 - } + logger.debug("No round history found for any participating boards") + # Return empty statistics - no boards participated return statistics # Sort rounds chronologically sorted_rounds = sorted(all_round_indices) - # Build history for scoring system + # Build history for scoring system - only for participating boards history = [] for round_index in sorted_rounds: round_data = {} - for board_id, board in game_state.boards.items(): + for board_id, board in participating_boards.items(): team_name = board.display_name # Get data for this specific round from board history @@ -278,8 +265,8 @@ def generate_game_statistics(game_state: GameState): logger.debug(f"Full traceback: {traceback.format_exc()}") final_scores = {} - # Process each board's complete data - for board_id, board in game_state.boards.items(): + # Process each participating board's complete data + for board_id, board in participating_boards.items(): board_stats = board.to_dict() # Add calculated statistics diff --git a/src/state.py b/src/state.py index b1fa559..aac77d8 100644 --- a/src/state.py +++ b/src/state.py @@ -251,6 +251,8 @@ def __init__(self, id: str): self.power_generation_by_type: Dict[str, float] = {} # Connected buildings for persistence across board restarts self.connected_buildings: List[Dict[str, Any]] = [] + # Track if board has participated in current game (sent data during game) + self.participated_in_current_game: bool = False def is_connected(self) -> bool: """ @@ -278,7 +280,13 @@ def update_power(self, production: int, consumption: int, script: 'Script' = Non """ Updates the power production and consumption for the board. History is now saved only when explicitly requested (e.g., during next_round). + Marks board as having participated in the current game. """ + # Mark that this board has participated in the current game + if not self.participated_in_current_game: + debug_print(f"Board {self.id}: Marking as participated in game") + self.participated_in_current_game = True + # Determine current round from script and update tracker if script: self.current_round_index = script.current_round_index @@ -413,7 +421,13 @@ def get_connected_production(self) -> List[int]: def update_power_generation_by_type(self, power_type: str, generation: float): """ Updates the power generation for a specific power plant type. + Marks board as having participated in the current game. """ + # Mark that this board has participated in the current game + if not self.participated_in_current_game: + debug_print(f"Board {self.id}: Marking as participated in game (via power_generation)") + self.participated_in_current_game = True + self.power_generation_by_type[power_type] = generation self.update_last_activity() @@ -475,6 +489,7 @@ def reset_for_new_game(self): - power plant generation tracking - connected buildings - current_round_index + - participation flag (board must re-participate in new game) """ self.production = 0 self.consumption = 0 @@ -487,6 +502,7 @@ def reset_for_new_game(self): self.current_round_index = -1 self.power_generation_by_type.clear() self.connected_buildings = [] + self.participated_in_current_game = False # Reset participation flag self.update_last_activity() def to_dict(self): @@ -510,4 +526,5 @@ def to_dict(self): "current_round_index": self.current_round_index, "power_generation_by_type": self.power_generation_by_type, "connected_buildings": self.connected_buildings, + "participated_in_current_game": self.participated_in_current_game, } \ No newline at end of file From 0e535f54eaebfb955e1148224e41ebab4985da8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sv=C3=ADtil?= Date: Thu, 16 Oct 2025 20:37:12 +0200 Subject: [PATCH 2/4] enhance building update logic to preserve state during active games --- src/main.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/main.py b/src/main.py index c636f83..ac4382e 100644 --- a/src/main.py +++ b/src/main.py @@ -849,20 +849,38 @@ def post_values(): if not board: return b'BOARD_NOT_FOUND', 404, {'Content-Type': 'application/octet-stream'} - # Always replace connected buildings list since all boards now send new format - previous_count = len(board.get_connected_buildings()) if hasattr(board, 'get_connected_buildings') else 'n/a' - board.clear_connected_buildings() - if connected_buildings: - for building in connected_buildings: - try: - board.add_connected_building(building['uid'], building['building_type']) - except Exception as e: - print(f"Failed to add building {building}: {e}", file=sys.stderr) - # Debug trace to verify clearing behavior - print(f"Board {board_id}: replaced connected_buildings (prev={previous_count}, new={len(connected_buildings)})", file=sys.stderr) + # Update connected buildings with special logic for game protection + script = user_game_state.get_script() + previous_buildings = board.get_connected_buildings() if hasattr(board, 'get_connected_buildings') else [] + previous_count = len(previous_buildings) + + # During an active game, ignore empty building updates if board previously had buildings + # This prevents loss of building state when board quickly disconnects/reconnects + game_is_active = is_game_active(script) + should_ignore_empty = (game_is_active and + previous_count > 0 and + len(connected_buildings) == 0) + + if should_ignore_empty: + # Keep existing buildings - board is reconnecting and hasn't downloaded state yet + debug_print(f"Board {board_id}: Ignoring empty building list during active game (preserving {previous_count} buildings)") + print(f"Board {board_id}: Ignoring empty building list during active game (preserving {previous_count} buildings)", file=sys.stderr) + else: + # Normal building update - replace with new list + board.clear_connected_buildings() + if connected_buildings: + for building in connected_buildings: + try: + board.add_connected_building(building['uid'], building['building_type']) + except Exception as e: + print(f"Failed to add building {building}: {e}", file=sys.stderr) + # Debug trace to verify behavior + if previous_count > 0 and len(connected_buildings) == 0 and not game_is_active: + print(f"Board {board_id}: Cleared buildings (game not active, prev={previous_count}, new=0)", file=sys.stderr) + else: + print(f"Board {board_id}: Updated connected_buildings (prev={previous_count}, new={len(connected_buildings)})", file=sys.stderr) # Pass the script to track round changes - script = user_game_state.get_script() board.update_power(production, consumption, script) return b'OK', 200, {'Content-Type': 'application/octet-stream'} From 8c832ac60bc37193711aaed6e3c3c6c8a5f1c101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sv=C3=ADtil?= Date: Thu, 16 Oct 2025 20:43:29 +0200 Subject: [PATCH 3/4] refactor building update logic to allow only additions during active games --- src/main.py | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/main.py b/src/main.py index ac4382e..bc02d95 100644 --- a/src/main.py +++ b/src/main.py @@ -849,36 +849,23 @@ def post_values(): if not board: return b'BOARD_NOT_FOUND', 404, {'Content-Type': 'application/octet-stream'} - # Update connected buildings with special logic for game protection + # Update connected buildings - only additions allowed, never subtractions script = user_game_state.get_script() previous_buildings = board.get_connected_buildings() if hasattr(board, 'get_connected_buildings') else [] - previous_count = len(previous_buildings) - - # During an active game, ignore empty building updates if board previously had buildings - # This prevents loss of building state when board quickly disconnects/reconnects - game_is_active = is_game_active(script) - should_ignore_empty = (game_is_active and - previous_count > 0 and - len(connected_buildings) == 0) - - if should_ignore_empty: - # Keep existing buildings - board is reconnecting and hasn't downloaded state yet - debug_print(f"Board {board_id}: Ignoring empty building list during active game (preserving {previous_count} buildings)") - print(f"Board {board_id}: Ignoring empty building list during active game (preserving {previous_count} buildings)", file=sys.stderr) - else: - # Normal building update - replace with new list - board.clear_connected_buildings() - if connected_buildings: - for building in connected_buildings: - try: - board.add_connected_building(building['uid'], building['building_type']) - except Exception as e: - print(f"Failed to add building {building}: {e}", file=sys.stderr) - # Debug trace to verify behavior - if previous_count > 0 and len(connected_buildings) == 0 and not game_is_active: - print(f"Board {board_id}: Cleared buildings (game not active, prev={previous_count}, new=0)", file=sys.stderr) - else: - print(f"Board {board_id}: Updated connected_buildings (prev={previous_count}, new={len(connected_buildings)})", file=sys.stderr) + previous_building_uids = {b['uid'] for b in previous_buildings} + + # Add any new buildings that aren't already in the list + added_count = 0 + for building in connected_buildings: + if building['uid'] not in previous_building_uids: + try: + board.add_connected_building(building['uid'], building['building_type']) + added_count += 1 + except Exception as e: + print(f"Failed to add building {building}: {e}", file=sys.stderr) + + if added_count > 0: + print(f"Board {board_id}: Added {added_count} new building(s) (total now: {len(board.get_connected_buildings())})", file=sys.stderr) # Pass the script to track round changes board.update_power(production, consumption, script) From 9046f0a9a9943061698531a91e13a4646fe0d6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Sv=C3=ADtil?= Date: Thu, 16 Oct 2025 20:59:52 +0200 Subject: [PATCH 4/4] ignore zero power updates during active game reconnects --- src/main.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index bc02d95..bf7b0c0 100644 --- a/src/main.py +++ b/src/main.py @@ -867,8 +867,32 @@ def post_values(): if added_count > 0: print(f"Board {board_id}: Added {added_count} new building(s) (total now: {len(board.get_connected_buildings())})", file=sys.stderr) - # Pass the script to track round changes - board.update_power(production, consumption, script) + # Determine whether to ignore zeroed-out power update during active game reconnects + previous_production = getattr(board, 'production', 0) + previous_consumption = getattr(board, 'consumption', 0) + has_previous_power = (previous_production != 0 or previous_consumption != 0) + has_buildings = len(previous_buildings) > 0 or added_count > 0 + game_is_active = is_game_active(script) + should_ignore_zero_power = ( + game_is_active + and has_buildings + and has_previous_power + and production == 0 + and consumption == 0 + ) + + if should_ignore_zero_power: + debug_print( + f"Board {board_id}: Ignoring zero production/consumption during active game reconnect" + ) + print( + f"Board {board_id}: Ignoring zero production/consumption during active game reconnect (keeping prod={previous_production}, cons={previous_consumption})", + file=sys.stderr + ) + board.update_last_activity() + else: + # Pass the script to track round changes + board.update_power(production, consumption, script) return b'OK', 200, {'Content-Type': 'application/octet-stream'} except BinaryProtocolError as e: