diff --git a/client.py b/client.py index a513f8a..5240c6c 100755 --- a/client.py +++ b/client.py @@ -2,6 +2,7 @@ import wx import socket from constants import * import json +import pprint class ClientVCKO(wx.App): @@ -18,6 +19,7 @@ class ClientVCKO(wx.App): self.lobby_frame = LobbyFrame(self) self.game_frame = GameFrame(self) self.last_lobby_state = "" + self.last_game_state = "" self.debug_frame.set_connection_status() return True @@ -45,6 +47,12 @@ class ClientVCKO(wx.App): self.in_game = True self.in_lobby = False self.lobby_frame.enter_game() + elif full_command[1] == "state": + json_response = ' '.join(full_command[2:]) + new_game_state = json.loads(json_response) + if new_game_state == self.last_game_state: + return + self.last_game_state = new_game_state def update_lobby_status(self): return self.in_lobby @@ -55,21 +63,52 @@ class GameFrame(wx.Frame): super().__init__(parent=None, title='VCK Online', size=Constants.default_window_size) self.app = app self.panel = wx.Panel(self) + + # Create a static box sizer with padding + vbox = wx.StaticBoxSizer(wx.StaticBox(self.panel, label=""), wx.VERTICAL) + vbox.AddSpacer(10) # Add a bit of padding at the top + + # Wrap the list control widget inside a scrolled window + sw = wx.ScrolledWindow(vbox.GetStaticBox(), style=wx.VSCROLL) + sw.SetScrollbars(1, 1, 1, 1) # Show the scrollbars + self.game_state_list = wx.ListCtrl(sw, style=wx.LC_REPORT | wx.LC_SINGLE_SEL) + sw.SetSizer(wx.BoxSizer(wx.VERTICAL)) + sw.GetSizer().Add(self.game_state_list, proportion=1, flag=wx.EXPAND | wx.ALL, border=10) + + vbox.Add(sw, proportion=1, flag=wx.EXPAND | wx.ALL, border=10) # Add the scrolled window to the sizer + + vbox.AddSpacer(10) # Add a bit of padding at the bottom + + # Set the sizer for the panel + self.panel.SetSizer(vbox) + self.SetMinSize(Constants.minimum_window_size) - self.last_lobby_state = [] + self.last_pretty_game_state = "" + self.timer_interval = 500 self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.get_game_status, self.timer) - self.timer.Start(500) + self.timer.Start(self.timer_interval) def get_game_status(self, event=None): if connection_check() and self.app.in_game: self.app.parse_response(send(f"game get_status {self.app.game_id}")) - if self.app.lobby == self.last_lobby_state: - # If the current lobby state is the same as the last one, don't update the list control + new_pretty_game_state = pprint.pformat(self.app.last_game_state) + if self.last_pretty_game_state == new_pretty_game_state: + # If the current game state is the same as the last one, don't update the list control + if self.timer_interval < 9500: + self.timer_interval += 500 + self.timer.Start(self.timer_interval) return - - # Save the new lobby state - self.last_lobby_state = self.app.lobby + self.game_state_list.ClearAll() + self.game_state_list.InsertColumn(0, "Game State") + for idx, state in enumerate(new_pretty_game_state.split('\n')): + self.game_state_list.InsertItem(idx, state.strip()) + self.game_state_list.SetColumnWidth(0, wx.LIST_AUTOSIZE) + # Save the new game state + self.last_pretty_game_state = new_pretty_game_state + self.timer_interval = 500 + else: + self.timer.Stop() class LobbyFrame(wx.Frame): @@ -99,9 +138,10 @@ class LobbyFrame(wx.Frame): self.Bind(wx.EVT_SIZE, self.on_size) self.Bind(wx.EVT_CLOSE, self.on_close) + self.timer_interval = 500 self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.get_lobby_status, self.timer) - self.timer.Start(500) + self.timer.Start(self.timer_interval) def on_size(self, event): # Calculate the width of each column based on the width of the list control @@ -116,6 +156,9 @@ class LobbyFrame(wx.Frame): self.app.parse_response(send(f"lobby get_status {self.app.player_id}")) if self.app.lobby == self.last_lobby_state: # If the current lobby state is the same as the last one, don't update the list control + if self.timer_interval < 9500: + self.timer_interval += 500 + self.timer.Start(self.timer_interval) return self.list_ctrl.DeleteAllItems() for index, player in enumerate(self.app.lobby): @@ -266,7 +309,6 @@ def connection_check(): def _send(msg, input_socket): message = msg.encode(Constants.text_format) msg_length = len(message) - print(msg_length) send_length = str(msg_length).encode(Constants.text_format) send_length += b' ' * (Constants.header_size - len(send_length)) input_socket.send(send_length) diff --git a/common.py b/common.py index a5fd3bf..b0634d5 100755 --- a/common.py +++ b/common.py @@ -14,6 +14,13 @@ class Card: self.is_visible = False self.is_accessible = False + def to_dict(self): + return { + "name": self.name, + "is_visible": self.is_visible, + "is_accessible": self.is_accessible, + } + def toggle_visibility(self, toggle: bool = True): self.is_visible = toggle @@ -74,6 +81,25 @@ class Starter(Card): self.specialPayoutOffTurn = special_payout_off_turn self.expansion = expansion + def to_dict(self): + return { + "starter_id": self.starter_id, + "name": self.name, + "roll_match1": self.rollMatch1, + "roll_match2": self.rollMatch2, + "gold_payout_on_turn": self.goldPayoutOnTurn, + "gold_payout_off_turn": self.goldPayoutOffTurn, + "strength_payout_on_turn": self.strengthPayoutOnTurn, + "strength_payout_off_turn": self.strengthPayoutOffTurn, + "magic_payout_on_turn": self.magicPayoutOnTurn, + "magic_payout_off_turn": self.magicPayoutOffTurn, + "has_special_payout_on_turn": self.hasSpecialPayoutOnTurn, + "has_special_payout_off_turn": self.hasSpecialPayoutOffTurn, + "special_payout_on_turn": self.specialPayoutOnTurn, + "special_payout_off_turn": self.specialPayoutOffTurn, + "expansion": self.expansion + } + class Citizen(Card): def __init__(self, citizen_id, name, gold_cost, roll_match1, roll_match2, shadow_count, holy_count, soldier_count, @@ -104,6 +130,30 @@ class Citizen(Card): self.special_citizen = special_citizen self.expansion = expansion + def to_dict(self): + base_dict = super().to_dict() + return {**base_dict, + "citizen_id": self.citizen_id, + "gold_cost": self.gold_cost, + "roll_match1": self.roll_match1, + "roll_match2": self.roll_match2, + "shadow_count": self.shadow_count, + "holy_count": self.holy_count, + "soldier_count": self.soldier_count, + "worker_count": self.worker_count, + "gold_payout_on_turn": self.gold_payout_on_turn, + "gold_payout_off_turn": self.gold_payout_off_turn, + "strength_payout_on_turn": self.strength_payout_on_turn, + "strength_payout_off_turn": self.strength_payout_off_turn, + "magic_payout_on_turn": self.magic_payout_on_turn, + "magic_payout_off_turn": self.magic_payout_off_turn, + "has_special_payout_on_turn": self.has_special_payout_on_turn, + "has_special_payout_off_turn": self.has_special_payout_off_turn, + "special_payout_on_turn": self.special_payout_on_turn, + "special_payout_off_turn": self.special_payout_off_turn, + "special_citizen": self.special_citizen, + "expansion": self.expansion} + class Domain(Card): def __init__(self, domain_id, name, gold_cost, shadow_count, holy_count, soldier_count, worker_count, vp_reward, @@ -124,6 +174,25 @@ class Domain(Card): self.text = text self.expansion = expansion + def to_dict(self): + return { + **super().to_dict(), + "domain_id": self.domain_id, + "name": self.name, + "gold_cost": self.gold_cost, + "shadow_count": self.shadow_count, + "holy_count": self.holy_count, + "soldier_count": self.soldier_count, + "worker_count": self.worker_count, + "vp_reward": self.vp_reward, + "has_activation_effect": self.has_activation_effect, + "has_passive_effect": self.has_passive_effect, + "passive_effect": self.passive_effect, + "activation_effect": self.activation_effect, + "text": self.text, + "expansion": self.expansion + } + class Monster(Card): def __init__(self, monster_id, name, area, monster_type, order, strength_cost, magic_cost, vp_reward, gold_reward, @@ -148,6 +217,28 @@ class Monster(Card): self.is_extra = is_extra self.expansion = expansion + def to_dict(self): + card_dict = super().to_dict() + monster_dict = { + "monster_id": self.monster_id, + "area": self.area, + "monster_type": self.monster_type, + "order": self.order, + "strength_cost": self.strength_cost, + "magic_cost": self.magic_cost, + "vp_reward": self.vp_reward, + "gold_reward": self.gold_reward, + "strength_reward": self.strength_reward, + "magic_reward": self.magic_reward, + "has_special_reward": self.has_special_reward, + "special_reward": self.special_reward, + "has_special_cost": self.has_special_cost, + "special_cost": self.special_cost, + "is_extra": self.is_extra, + "expansion": self.expansion, + } + return {**card_dict, **monster_dict} + def add_strength_cost(self, added_strength): self.strength_cost = self.strength_cost + added_strength @@ -178,6 +269,27 @@ class Duke(Card): self.titan_multiplier = titan_mult self.expansion = expansion + def to_dict(self): + return { + **super().to_dict(), + "duke_id": self.duke_id, + "gold_multiplier": self.gold_multiplier, + "strength_multiplier": self.strength_multiplier, + "magic_multiplier": self.magic_multiplier, + "shadow_multiplier": self.shadow_multiplier, + "holy_multiplier": self.holy_multiplier, + "soldier_multiplier": self.soldier_multiplier, + "worker_multiplier": self.worker_multiplier, + "monster_multiplier": self.monster_multiplier, + "citizen_multiplier": self.citizen_multiplier, + "domain_multiplier": self.domain_multiplier, + "boss_multiplier": self.boss_multiplier, + "minion_multiplier": self.minion_multiplier, + "beast_multiplier": self.beast_multiplier, + "titan_multiplier": self.titan_multiplier, + "expansion": self.expansion + } + class Game: def __init__(self, game_id, player_list_from_lobby, preset="shuffled", number_of_dukes=2): @@ -511,13 +623,7 @@ class Game: class GameObjectEncoder(JSONEncoder): def default(self, obj): - if isinstance(obj, Card): - return { - "name": obj.name, - "is_visible": obj.is_visible, - "is_accessible": obj.is_accessible, - } - elif isinstance(obj, Player): + if isinstance(obj, Player): return { 'player_id': obj.player_id, 'name': obj.name, @@ -536,107 +642,15 @@ class GameObjectEncoder(JSONEncoder): 'worker_count': obj.worker_count } elif isinstance(obj, Duke): - return { - **super().default(obj), - "duke_id": obj.duke_id, - "gold_multiplier": obj.gold_multiplier, - "strength_multiplier": obj.strength_multiplier, - "magic_multiplier": obj.magic_multiplier, - "shadow_multiplier": obj.shadow_multiplier, - "holy_multiplier": obj.holy_multiplier, - "soldier_multiplier": obj.soldier_multiplier, - "worker_multiplier": obj.worker_multiplier, - "monster_multiplier": obj.monster_multiplier, - "citizen_multiplier": obj.citizen_multiplier, - "domain_multiplier": obj.domain_multiplier, - "boss_multiplier": obj.boss_multiplier, - "minion_multiplier": obj.minion_multiplier, - "beast_multiplier": obj.beast_multiplier, - "titan_multiplier": obj.titan_multiplier, - "expansion": obj.expansion, - } + return obj.to_dict() elif isinstance(obj, Monster): - return { - **super().default(obj), - "monster_id": obj.monster_id, - "name": obj.name, - "area": obj.area, - "monster_type": obj.monster_type, - "order": obj.order, - "strength_cost": obj.strength_cost, - "magic_cost": obj.magic_cost, - "vp_reward": obj.vp_reward, - "gold_reward": obj.gold_reward, - "strength_reward": obj.strength_reward, - "magic_reward": obj.magic_reward, - "has_special_reward": obj.has_special_reward, - "special_reward": obj.special_reward, - "has_special_cost": obj.has_special_cost, - "special_cost": obj.special_cost, - "is_extra": obj.is_extra, - "expansion": obj.expansion, - } + return obj.to_dict() elif isinstance(obj, Starter): - return { - **super().default(obj), - "starter_id": obj.starter_id, - "name": obj.name, - "roll_match1": obj.rollMatch1, - "roll_match2": obj.rollMatch2, - "gold_payout_on_turn": obj.goldPayoutOnTurn, - "gold_payout_off_turn": obj.goldPayoutOffTurn, - "strength_payout_on_turn": obj.strengthPayoutOnTurn, - "strength_payout_off_turn": obj.strengthPayoutOffTurn, - "magic_payout_on_turn": obj.magicPayoutOnTurn, - "magic_payout_off_turn": obj.magicPayoutOffTurn, - "has_special_payout_on_turn": obj.hasSpecialPayoutOnTurn, - "has_special_payout_off_turn": obj.hasSpecialPayoutOffTurn, - "special_payout_on_turn": obj.specialPayoutOnTurn, - "special_payout_off_turn": obj.specialPayoutOffTurn, - "expansion": obj.expansion, - } + return obj.to_dict() elif isinstance(obj, Citizen): - return { - **super().default(obj), - "citizen_id": obj.citizen_id, - "name": obj.name, - "gold_cost": obj.gold_cost, - "roll_match1": obj.roll_match1, - "roll_match2": obj.roll_match2, - "shadow_count": obj.shadow_count, - "holy_count": obj.holy_count, - "soldier_count": obj.soldier_count, - "worker_count": obj.worker_count, - "gold_payout_on_turn": obj.gold_payout_on_turn, - "gold_payout_off_turn": obj.gold_payout_off_turn, - "strength_payout_on_turn": obj.strength_payout_on_turn, - "strength_payout_off_turn": obj.strength_payout_off_turn, - "magic_payout_on_turn": obj.magic_payout_on_turn, - "magic_payout_off_turn": obj.magic_payout_off_turn, - "has_special_payout_on_turn": obj.has_special_payout_on_turn, - "has_special_payout_off_turn": obj.has_special_payout_off_turn, - "special_payout_on_turn": obj.special_payout_on_turn, - "special_payout_off_turn": obj.special_payout_off_turn, - "special_citizen": obj.special_citizen, - "expansion": obj.expansion, - } + return obj.to_dict() elif isinstance(obj, Domain): - return { - 'domain_id': obj.domain_id, - 'name': obj.name, - 'gold_cost': obj.gold_cost, - 'shadow_count': obj.shadow_count, - 'holy_count': obj.holy_count, - 'soldier_count': obj.soldier_count, - 'worker_count': obj.worker_count, - 'vp_reward': obj.vp_reward, - 'has_activation_effect': obj.has_activation_effect, - 'has_passive_effect': obj.has_passive_effect, - 'passive_effect': obj.passive_effect, - 'activation_effect': obj.activation_effect, - 'text': obj.text, - 'expansion': obj.expansion - } + return obj.to_dict() elif isinstance(obj, Game): return { "game_id": obj.game_id, @@ -644,9 +658,9 @@ class GameObjectEncoder(JSONEncoder): "preset": obj.preset, "number_of_dukes": obj.number_of_dukes, "player_list": obj.player_list, + "monster_grid": obj.monster_grid, "citizen_grid": obj.citizen_grid, "domain_grid": obj.domain_grid, - "monster_grid": obj.monster_grid, "duke_stack": obj.duke_stack, "domain_stack": obj.domain_stack, "citizen_stack": obj.citizen_stack, diff --git a/server.py b/server.py index 4057c60..bbd86a0 100755 --- a/server.py +++ b/server.py @@ -15,6 +15,16 @@ class ServerVCKO: self.lobby = [] self.gamers = [] + # Start the thread to remove inactive players + self.inactive_player_thread = threading.Thread(target=self.remove_inactive_players, daemon=True) + self.inactive_player_thread.start() + + def remove_inactive_players(self): + while True: + current_time = time.time() + self.lobby = [player for player in self.lobby if current_time - player.last_active_time <= 60] + time.sleep(10) # check for inactive players every 10 seconds + def handle_client(self, conn, addr): print(f"Connection from: {addr}") connected = True @@ -49,6 +59,7 @@ class ServerVCKO: found = False for player in self.lobby: if full_command[2] == player.player_id: + player.last_active_time = time.time() # update last active time self.send_lobby_state(conn) found = True for player in self.gamers: @@ -80,7 +91,8 @@ class ServerVCKO: players_to_remove.append(player) for player in players_to_remove: self.lobby.remove(player) - new_game = Game(new_game_id, self.gamers) + # START GAME + new_game = Game(new_game_id, self.gamers, "base1", 2) self.game_dict[new_game.game_id] = new_game print(f"size of game dict: {len(self.game_dict)}") message = f"game joined {new_game_id}"