From c513a08b28473df909a42b8349a2c4ee9f49c91e Mon Sep 17 00:00:00 2001 From: Luke Esau Date: Thu, 27 Apr 2023 20:52:12 -0700 Subject: [PATCH] ready up feature now launches the game frame --- client.py | 253 ++++++++++++++++++++++++++++++------------------------ server.py | 49 +++++++++-- 2 files changed, 187 insertions(+), 115 deletions(-) diff --git a/client.py b/client.py index 3adf092..9b4e616 100755 --- a/client.py +++ b/client.py @@ -7,15 +7,19 @@ import json class ClientVCKO(wx.App): def OnInit(self): - self.debug_frame = DebugFrame(self) - self.debug_frame.Show() - self.start_frame = StartFrame(self) - self.debug_frame.Show() self.connection_status = False - self.in_lobby = False self.player_id = "" - self.debug_frame.set_connection_status() + self.player_name = "" self.lobby = [] + self.in_lobby = False + self.in_game = False + self.game_id = "" + self.debug_frame = DebugFrame(self) + self.start_frame = StartFrame(self) + self.lobby_frame = LobbyFrame(self) + self.game_frame = GameFrame(self) + self.last_lobby_state = "" + self.debug_frame.set_connection_status() return True def parse_response(self, response): @@ -24,73 +28,118 @@ class ClientVCKO(wx.App): full_command = response.split() match first_word: case "lobby": - full_command = response.split() if full_command[1] == "joined" and len(full_command) == 3: self.player_id = full_command[2] self.in_lobby = True - self.start_frame.show_list_view(None) + self.start_frame.enter_lobby(None) elif full_command[1] == "state": json_response = ' '.join(full_command[2:]) - print(json_response) - self.lobby = json.loads(json_response) + new_lobby_state = json.loads(json_response) + if new_lobby_state != self.lobby: + self.lobby = new_lobby_state + self.lobby_frame.get_lobby_status() else: print("Couldn't understand that response") - case _: - print(full_command[1]) + case "game": + if full_command[1] == "joined" and len(full_command) == 3: + self.game_id = full_command[2] + self.in_game = True + self.in_lobby = False + self.lobby_frame.enter_game() + + def update_lobby_status(self): + return self.in_lobby -class DebugFrame(wx.Frame): +class GameFrame(wx.Frame): def __init__(self, app): - super().__init__(parent=None, title='VCKO Debug Console', size=Constants.default_window_size) + super().__init__(parent=None, title='VCK Online', size=Constants.default_window_size) + self.app = app + self.panel = wx.Panel(self) + self.SetMinSize(Constants.minimum_window_size) + + +class LobbyFrame(wx.Frame): + def __init__(self, app): + super().__init__(parent=None, title='VCK Online Lobby', size=(250, 300)) self.app = app self.panel = wx.Panel(self) self.vertical_sizer = wx.BoxSizer(wx.VERTICAL) - self.status_sizer = wx.BoxSizer(wx.HORIZONTAL) - self.message_field = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER) - self.connection_status_indicator = wx.StaticText(self.panel, label="Connection Status") - self.my_btn = wx.Button(self.panel, label="Send call") - self.my_btn.Bind(wx.EVT_BUTTON, self.on_press) - self.message_field.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter) - # Create a horizontal sizer to hold the connection_status StaticText - self.status_sizer.AddStretchSpacer() - self.status_sizer.Add(wx.StaticText(self.panel), 0, wx.EXPAND | wx.RIGHT, 5) - self.status_sizer.Add(self.connection_status_indicator, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) - self.status_sizer.Add(wx.StaticText(self.panel), 0, wx.EXPAND | wx.LEFT, 5) - # Add the text field, button, and status sizer to the vertical sizer - self.vertical_sizer.Add(self.message_field, 0, wx.ALL | wx.EXPAND, 5) - self.vertical_sizer.Add(self.my_btn, 0, wx.ALL | wx.CENTER, 5) - self.vertical_sizer.AddStretchSpacer() - self.vertical_sizer.Add(self.status_sizer, 0, wx.ALIGN_LEFT | wx.BOTTOM, 5) + self.last_lobby_state = [] + self.current_player_index = None + # Create the list control and columns + self.list_ctrl = wx.ListCtrl(self.panel, style=wx.LC_REPORT) + self.list_ctrl.InsertColumn(0, "Player Name") + self.list_ctrl.InsertColumn(1, "Ready Status", format=wx.LIST_FORMAT_RIGHT) + self.get_lobby_status() + # Create the ready button + ready_button = wx.Button(self.panel, label="Ready Up") + ready_button.Bind(wx.EVT_BUTTON, self.on_ready_up) + self.list_ctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.highlight_current_player) + # Add the list control and ready button to the vertical sizer + self.vertical_sizer.Add(self.list_ctrl, 1, wx.ALL | wx.EXPAND, 5) + self.vertical_sizer.Add(ready_button, 0, wx.ALL | wx.CENTER, 5) self.panel.SetSizer(self.vertical_sizer) self.SetMinSize(Constants.minimum_window_size) - self.Show() - # Create a timer to call the connection_check method every 2 seconds + + # Bind the size event to adjust the column widths + self.Bind(wx.EVT_SIZE, self.on_size) + self.Bind(wx.EVT_CLOSE, self.on_close) + self.timer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.set_connection_status, self.timer) - self.timer.Start(5000) + self.Bind(wx.EVT_TIMER, self.get_lobby_status, self.timer) + self.timer.Start(500) - def set_connection_status(self, event=None): - if connection_check(): - self.connection_status_indicator.SetLabel("Connected") - self.connection_status_indicator.SetForegroundColour(Constants.green) + def on_size(self, event): + # Calculate the width of each column based on the width of the list control + width = self.list_ctrl.GetSize()[0] + col_width = width // 2 + self.list_ctrl.SetColumnWidth(0, col_width) + self.list_ctrl.SetColumnWidth(1, col_width) + event.Skip() + + def get_lobby_status(self, event=None): + if connection_check() and self.app.in_lobby: + 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 + return + self.list_ctrl.DeleteAllItems() + for index, player in enumerate(self.app.lobby): + self.list_ctrl.InsertItem(index, player['name']) + self.list_ctrl.SetItem(index, 1, "Ready" if player['is_ready'] else "Not Ready") + if player['player_id'] == self.app.player_id: + self.current_player_index = index + self.list_ctrl.SetItemState(index, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) + else: + self.list_ctrl.SetItemState(index, 0, wx.LIST_STATE_SELECTED) + # Save the new lobby state + self.last_lobby_state = self.app.lobby + + def highlight_current_player(self): + if self.current_player_index is not None: + self.list_ctrl.Select(self.current_player_index) else: - self.connection_status_indicator.SetLabel("Not Connected") - self.connection_status_indicator.SetForegroundColour(Constants.red) + self.list_ctrl.Select(-1) - def on_press(self, event): - message = self.message_field.GetValue() - if not message: - print("You didn't enter anything!") - else: - self.api_call(message) + def on_ready_up(self, event): + for player in self.app.lobby: + if player['player_id'] == self.app.player_id: + if player['is_ready']: + if connection_check(): + self.app.parse_response(send(f"lobby unready {self.app.player_id}")) + else: + if connection_check(): + self.app.parse_response(send(f"lobby ready {self.app.player_id}")) + break - def on_text_enter(self, event): - self.on_press(event) + def enter_game(self, event=None): + self.app.game_frame.Show() + self.Hide() - def api_call(self, message): - if connection_check(): - self.app.parse_response(send(message)) - self.message_field.SetValue("") + def on_close(self, event): + self.app.parse_response(send(f"lobby leave {self.app.player_id}")) + self.Destroy() class StartFrame(wx.Frame): @@ -126,11 +175,8 @@ class StartFrame(wx.Frame): def on_text_enter(self, event): self.on_submit(event) - def show_list_view(self, event): - list_frame = LobbyFrame(self.app) - list_frame.Show() - - # Refresh the layout of the original panel + def enter_lobby(self, event): + self.app.lobby_frame.Show() self.Hide() def api_call(self, message): @@ -138,72 +184,59 @@ class StartFrame(wx.Frame): self.app.parse_response(send(message)) self.name_field.SetValue("") -class LobbyFrame(wx.Frame): + +class DebugFrame(wx.Frame): def __init__(self, app): - super().__init__(parent=None, title='VCK Online Lobby', size=(250, 300)) + super().__init__(parent=None, title='VCKO Debug Console', size=Constants.default_window_size) self.app = app self.panel = wx.Panel(self) self.vertical_sizer = wx.BoxSizer(wx.VERTICAL) - - # Create the list control and columns - self.list_ctrl = wx.ListCtrl(self.panel, style=wx.LC_REPORT) - self.list_ctrl.InsertColumn(0, "Player Name") - self.list_ctrl.InsertColumn(1, "Ready Status", format=wx.LIST_FORMAT_RIGHT) - self.get_lobby_status() - # Create the ready button - ready_button = wx.Button(self.panel, label="Ready Up") - ready_button.Bind(wx.EVT_BUTTON, self.on_ready_up) - - # Add the list control and ready button to the vertical sizer - self.vertical_sizer.Add(self.list_ctrl, 1, wx.ALL | wx.EXPAND, 5) - self.vertical_sizer.Add(ready_button, 0, wx.ALL | wx.CENTER, 5) + self.status_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.message_field = wx.TextCtrl(self.panel, style=wx.TE_PROCESS_ENTER) + self.connection_status_indicator = wx.StaticText(self.panel, label="Connection Status") + self.my_btn = wx.Button(self.panel, label="Send call") + self.my_btn.Bind(wx.EVT_BUTTON, self.on_press) + self.message_field.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter) + # Create a horizontal sizer to hold the connection_status StaticText + self.status_sizer.AddStretchSpacer() + self.status_sizer.Add(wx.StaticText(self.panel), 0, wx.EXPAND | wx.RIGHT, 5) + self.status_sizer.Add(self.connection_status_indicator, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) + self.status_sizer.Add(wx.StaticText(self.panel), 0, wx.EXPAND | wx.LEFT, 5) + # Add the text field, button, and status sizer to the vertical sizer + self.vertical_sizer.Add(self.message_field, 0, wx.ALL | wx.EXPAND, 5) + self.vertical_sizer.Add(self.my_btn, 0, wx.ALL | wx.CENTER, 5) + self.vertical_sizer.AddStretchSpacer() + self.vertical_sizer.Add(self.status_sizer, 0, wx.ALIGN_LEFT | wx.BOTTOM, 5) self.panel.SetSizer(self.vertical_sizer) self.SetMinSize(Constants.minimum_window_size) - - # Bind the size event to adjust the column widths - self.Bind(wx.EVT_SIZE, self.on_size) - self.Bind(wx.EVT_CLOSE, self.on_close) - self.Show() + # Create a timer to call the connection_check method every 2 seconds self.timer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.get_lobby_status, self.timer) - self.timer.Start(1000) + self.Bind(wx.EVT_TIMER, self.set_connection_status, self.timer) + self.timer.Start(10000) - def on_size(self, event): - # Calculate the width of each column based on the width of the list control - width = self.list_ctrl.GetSize()[0] - col_width = width // 2 - self.list_ctrl.SetColumnWidth(0, col_width) - self.list_ctrl.SetColumnWidth(1, col_width) - - event.Skip() - - def get_lobby_status(self, event=None): + def set_connection_status(self, event=None): if connection_check(): - self.app.parse_response(send("lobby get_status")) - # Clear the list control - self.list_ctrl.DeleteAllItems() + self.connection_status_indicator.SetLabel("Connected") + self.connection_status_indicator.SetForegroundColour(Constants.green) + else: + self.connection_status_indicator.SetLabel("Not Connected") + self.connection_status_indicator.SetForegroundColour(Constants.red) - # Populate the list control with the contents of the lobby - for index, player in enumerate(self.app.lobby): - self.list_ctrl.InsertItem(index, player['name']) - self.list_ctrl.SetItem(index, 1, "Ready" if player['is_ready'] else "Not Ready") + def on_press(self, event): + message = self.message_field.GetValue() + if not message: + print("You didn't enter anything!") + else: + self.api_call(message) - def on_ready_up(self, event): - for player in self.app.lobby: - if player['player_id'] == self.app.player_id: - if player['is_ready']: - if connection_check(): - self.app.parse_response(send(f"lobby unready {self.app.player_id}")) - else: - if connection_check(): - self.app.parse_response(send(f"lobby ready {self.app.player_id}")) - break - - def on_close(self, event): - self.app.parse_response(send(f"lobby leave {self.app.player_id}")) - self.Destroy() + def on_text_enter(self, event): + self.on_press(event) + def api_call(self, message): + if connection_check(): + self.app.parse_response(send(message)) + self.message_field.SetValue("") def connection_check(): diff --git a/server.py b/server.py index f9bb7b8..5155815 100755 --- a/server.py +++ b/server.py @@ -13,6 +13,7 @@ class ServerVCKO: self.server_socket.bind((self.host, Constants.port)) self.game_list = [] self.lobby = [] + self.gamers = [] def handle_client(self, conn, addr): print(f"Connection from: {addr}") @@ -44,18 +45,49 @@ class ServerVCKO: temp_lobby.append(player) self.lobby = temp_lobby self.send_lobby_state(conn) - elif full_command[1] == "get_status" and len(full_command) == 2: - self.send_lobby_state(conn) + elif full_command[1] == "get_status" and len(full_command) == 3: + found = False + for player in self.lobby: + if full_command[2] == player.player_id: + self.send_lobby_state(conn) + found = True + for player in self.gamers: + if full_command[2] == player.player_id: + message = f"game joined {player.game_id}" + conn.send(message.encode(Constants.text_format)) + found = True + # this only runs if we somehow receive an invalid player id + if not found: + message = f"Unable to find any player with player id: {full_command[2]}" + conn.send(message.encode(Constants.text_format)) elif full_command[1] == "ready" and len(full_command) > 2: + ready_check = 0 for player in self.lobby: if player.player_id == full_command[2]: player.is_ready = True - self.send_lobby_state(conn) + if player.is_ready: + ready_check += 1 + print(f"ready check: {ready_check}") + if ready_check == len(self.lobby): + new_game_id = str(uuid.uuid4()) + for player in self.lobby: + print(f"lobby player: {player.name}") + players_to_remove = [] + for player in self.lobby: + if player.is_ready: + new_gamer = GameMember(player.player_id, player.name, new_game_id) + self.gamers.append(new_gamer) + players_to_remove.append(player) + for player in players_to_remove: + self.lobby.remove(player) + message = f"game joined {new_game_id}" + conn.send(message.encode(Constants.text_format)) + else: + self.send_lobby_state(conn) elif full_command[1] == "unready" and len(full_command) > 2: for player in self.lobby: if player.player_id == full_command[2]: player.is_ready = False - self.send_lobby_state(conn) else: conn.send("invalid message".encode(Constants.text_format)) @@ -72,7 +104,7 @@ class ServerVCKO: conn, addr = self.server_socket.accept() thread = threading.Thread(target=self.handle_client, args=(conn, addr)) thread.start() - print(f"Active threads: {threading.active_count() - 1}") + print(f"\nActive threads: {threading.active_count() - 1}") def send_lobby_state(self, conn): lobby_data = [] @@ -94,6 +126,13 @@ class LobbyMember: self.is_ready = False +class GameMember: + def __init__(self, player_id, player_name, game_id): + self.name = player_name + self.player_id = player_id + self.game_id = game_id + + if __name__ == '__main__': print("server starting") server = ServerVCKO()