From 55f9f1a561682772393f6726e0542525935c6a30 Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 22 Oct 2024 07:01:55 +0100 Subject: [PATCH 1/8] feat: create backend --- src/backend.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 134 insertions(+), 3 deletions(-) diff --git a/src/backend.py b/src/backend.py index b3e380c..45a18d7 100644 --- a/src/backend.py +++ b/src/backend.py @@ -1,9 +1,140 @@ -# This file contains the backend logic of the game, inspired by the Model-View-Controller (MVC) design pattern. +# This file contains the backend logic of the game. +# It is the Model in the Model-View-Controller (MVC) design pattern. +import numpy as np class Dots_and_squares(): """ This class is responsible for the game logic of the dots and squares game. """ - def __init__(self) -> None: - pass \ No newline at end of file + def __init__(self, grid_size, n_players) -> None: + self.n_players = n_players + self.current_player = 0 + self.n_rows = grid_size[0] + self.n_cols = grid_size[1] + self.squares = -1*np.ones((self.n_rows, self.n_cols)) + self.columns = -1*np.ones((self.n_rows, self.n_cols+1)) + self.rows = -1*np.ones((self.n_rows+1, self.n_cols)) + self.scores = np.zeros(n_players) + + + def is_game_over(self)->bool: + """ + This method checks if the game is over. + + Returns: + bool: True if the game is over, False otherwise. + """ + return np.all(self.squares != -1) + + def player_turn(self, player_id, coordinates, action) -> bool: + """ + This method represents the turn of a player. + + Args: + player_id (int): The id of the player. + coordinates (tuple): The coordinates of the action. + action (str) : ["r","c"] The action to perform. + + Returns: + bool: True if the player's turn was successful (they clicked on a previously unoccupied edge), False otherwise. + """ + if action == "r": # If the action is to update a row + if self.update_row(coordinates, player_id): # If the row was updated skip to the next player + self.current_player = (self.current_player + 1) % self.n_players + return True + else: + return False # Ask the player to play again as he made an invalid move + + elif action == "c": # If the action is to update a column + if self.update_column(coordinates, player_id): # If the column was updated skip to the next player + self.current_player = (self.current_player + 1) % self.n_players + return True + else: + return False # Ask the player to play again as he made an invalid move + + + def update_row(self, coordinates, player_id)->bool: + """ + This method updates the row of the game board with the player's id. + + Args: + coordinates (tuple): The coordinates of the row to update. + player_id (int): The id of the player. + + Returns: + bool: True if the row was updated, False otherwise. + """ + + # Check if the position exists + if coordinates[0] < 0 or coordinates[0] >= self.n_rows+1: + return False + if coordinates[1] < 0 or coordinates[1] >= self.n_cols: + return False + + # Check if the position is already taken + if self.rows[coordinates[0], coordinates[1]] != -1: + return False + + # Update the row + self.rows[coordinates[0], coordinates[1]] = player_id + self.update_square((coordinates[0]-1, coordinates[1]), player_id) + self.update_square((coordinates[0], coordinates[1]), player_id) + return True + + def update_column(self, coordinates, player_id)->bool: + """ + This method updates the column of the game board with the player's id. + + Args: + coordinates (tuple): The coordinates of the column to update. + player_id (int): The id of the player. + + Returns: + bool: True if the column was updated, False otherwise. + """ + + # Check if the position exists + if coordinates[0] < 0 or coordinates[0] >= self.n_rows: + return False + if coordinates[1] < 0 or coordinates[1] >= self.n_cols+1: + return False + + # Check if the position is already taken + if self.columns[coordinates[0], coordinates[1]] != -1: + return False + + # Update the column + self.columns[coordinates[0], coordinates[1]] = player_id + self.update_square((coordinates[0], coordinates[1]), player_id) + self.update_square((coordinates[0], coordinates[1]-1), player_id) + return True + + def update_square(self, coordinates, player_id)->bool: + """ + This method updates the square of the game board with the player's id. + + Args: + coordinates (tuple): The coordinates of the square to update. + player_id (int): The id of the player. + + Returns: + bool: True if the square was updated, False otherwise. + """ + + # Check if the position exists + if coordinates[0] < 0 or coordinates[0] >= self.n_rows: + return False + if coordinates[1] < 0 or coordinates[1] >= self.n_cols: + return False + + # Check if the position is already taken + if self.squares[coordinates[0], coordinates[1]] != -1: + return False + + # Update the square + if self.rows[coordinates[0], coordinates[1]] != -1 and self.rows[coordinates[0]+1, coordinates[1]] != -1 and self.columns[coordinates[0], coordinates[1]] != -1 and self.columns[coordinates[0], coordinates[1]+1] != -1: + self.squares[coordinates[0], coordinates[1]] = player_id + self.scores[player_id] += 1 + self.current_player = (self.current_player - 1) % self.n_players + return True \ No newline at end of file From 853823564ba7bbe66d1b218b97df91b1f5e152bd Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 22 Oct 2024 07:02:36 +0100 Subject: [PATCH 2/8] feat: create frontend with GUI --- src/frontend.py | 200 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/src/frontend.py b/src/frontend.py index d2909f1..a198654 100644 --- a/src/frontend.py +++ b/src/frontend.py @@ -1,10 +1,208 @@ # This file contains the GUI of the game. import tkinter as tk +import numpy as np +from backend import Dots_and_squares + +import matplotlib.pyplot as plt class Game_GUI: """ This class is responsible for the game GUI of the dots and squares game. """ - def __init__(self) -> None: + + # ------------------------------------------------------------------ + # Initialization functions + # ------------------------------------------------------------------ + def __init__(self, grid_size, n_players) -> None: + + # Initialize the game instance + self.game_instance = Dots_and_squares(grid_size, n_players) + + # Initialize the GUI + self.grid_size = grid_size + self.size_of_board = 600 + self.players_colors = generate_rainbow_hex_colors(n_players) + symbol_size = (self.size_of_board / 3 - self.size_of_board / 8) / 2 + symbol_thickness = 50 + dot_color = '#7BC043' + #player1_color = '#0492CF' + #player1_color_light = '#67B0CF' + #player2_color = '#EE4035' + #player2_color_light = '#EE7E77' + #Green_color = '#7BC043' + self.dot_width = 0.25*grid_size[0]/grid_size[0] + self.edge_width = 0.075*self.size_of_board/grid_size[0] + self.distance_between_dots = self.size_of_board / (grid_size[0]+1) + + self.window = tk.Tk() + self.window.title('Pypopipette') + self.canvas = tk.Canvas(self.window, width=self.size_of_board, height=self.size_of_board) + self.canvas.pack() + self.window.bind('', self.click) + self.draw_board() + + + def mainloop(self): + self.window.mainloop() + + + # ------------------------------------------------------------------ + # Drawing Functions: + # The modules required to draw required game based object on canvas + # ------------------------------------------------------------------ + + def draw_board(self)->None: + """ + This method draws the game board on the canvas. + + Returns: + None + """ + for i in range(self.grid_size[0]): + for j in range(self.grid_size[1]): + start_x = j * self.distance_between_dots + self.distance_between_dots / 2 + start_y = i * self.distance_between_dots + self.distance_between_dots / 2 + end_x = start_x + self.distance_between_dots + end_y = start_y + self.distance_between_dots + + self.canvas.create_rectangle(start_x, start_y, end_x, end_y, outline='black', width=self.edge_width) + + + def draw_edge(self, player_id, coordinates, action): + """ + This method draws the edge on the canvas. + + Args: + player_id (int): The id of the player. + coordinates (tuple): The coordinates of the edge. + action (str): ['r','c'] whether to draw a row or a column. + + Returns: + None + """ + # Compute the start and end coordinates of the edge + if action == 'r': + start_x = coordinates[1] * self.distance_between_dots + self.distance_between_dots / 2 + start_y = (coordinates[0]+1) * self.distance_between_dots - self.distance_between_dots / 2 + end_x = start_x + self.distance_between_dots + end_y = start_y + + elif action == 'c': + start_x = (coordinates[1]+1) * self.distance_between_dots - self.distance_between_dots / 2 + start_y = coordinates[0] * self.distance_between_dots + self.distance_between_dots / 2 + end_x = start_x + end_y = start_y + self.distance_between_dots + + self.canvas.create_line(start_x, start_y, end_x, end_y, fill=self.players_colors[player_id], width=self.edge_width) + + def shade_boxes(self): + """ + This method shades the boxes on the canvas by filling them with the color of the player who completed the box. + + Returns: + None + """ + + for i in range(len(self.game_instance.squares)): + for j in range(len(self.game_instance.squares[0])): + if self.game_instance.squares[i][j] != -1: + start_x = j * self.distance_between_dots + self.distance_between_dots / 2 + start_y = i * self.distance_between_dots + self.distance_between_dots / 2 + end_x = start_x + self.distance_between_dots + end_y = start_y + self.distance_between_dots + self.canvas.create_rectangle(start_x, start_y, end_x, end_y, fill=self.players_colors[int(self.game_instance.squares[i][j])], outline='') + + #self.canvas.create_rectangle(start_x, start_y, end_x, end_y, outline = self.players_colors[player_id],fill=self.players_colors[player_id], width=self.edge_width) + + def display_gameover(self): + #TODO: Implement this method pass + def refresh_board(self): + #TODO: Implement this method + pass + + def display_turn_text(self): + #TODO: Implement this method + text = 'Next turn: ' + if self.game_instance.player1_turn: + text += 'Player1' + color = player1_color + else: + text += 'Player2' + color = player2_color + + self.canvas.delete(self.turntext_handle) + self.turntext_handle = self.canvas.create_text(size_of_board - 5*len(text), + size_of_board-self.distance_between_dots/8, + font="cmr 15 bold", text=text, fill=color) + + + + def click(self, event): + #TODO: Implement this method + + x, y = event.x, event.y + coordinates, action = self.convert_click_to_action([x, y]) + player = self.game_instance.current_player + + if self.game_instance.player_turn(self.game_instance.current_player, coordinates, action): + if player != self.game_instance.current_player: + self.draw_edge((self.game_instance.current_player-1)%self.game_instance.n_players, coordinates, action) + else: + self.draw_edge(self.game_instance.current_player, coordinates, action) + self.shade_boxes() + + + + def convert_click_to_action(self, grid_position)->tuple: + """ + This method converts the click position to an action. + + Args: + grid_position (list): The grid position of the click. + + Returns: + tuple: The logical coordinates of the edge and action (whether it's a row or a column). + + """ + grid_position = np.array(grid_position) + square_position = (grid_position - self.distance_between_dots / 2) // self.distance_between_dots + grid_position_adjusted = grid_position - (square_position * self.distance_between_dots + self.distance_between_dots / 2) + + if grid_position_adjusted[0] > grid_position_adjusted[1] and grid_position_adjusted[0] > -grid_position_adjusted[1] + self.distance_between_dots: + action = 'c' + coordinates = [int(square_position[1]), int(square_position[0])+1] + elif grid_position_adjusted[0] < grid_position_adjusted[1] and grid_position_adjusted[0] < -grid_position_adjusted[1] + self.distance_between_dots: + action = 'c' + coordinates = [int(square_position[1]), int(square_position[0])] + elif grid_position_adjusted[0] > grid_position_adjusted[1] and grid_position_adjusted[0] < -grid_position_adjusted[1] + self.distance_between_dots: + action = 'r' + coordinates = [int(square_position[1]), int(square_position[0])] + elif grid_position_adjusted[0] < grid_position_adjusted[1] and grid_position_adjusted[0] > -grid_position_adjusted[1] + self.distance_between_dots: + action = 'r' + coordinates = [int(square_position[1])+1, int(square_position[0])] + + return coordinates, action + +import matplotlib.pyplot as plt +from matplotlib.colors import rgb2hex + +# Define a function to generate n colors from blue to red (rainbow spectrum) +def generate_rainbow_hex_colors(n): + # Generate n evenly spaced values from 0 to 1 to represent the positions in the color map + colors = plt.cm.rainbow(np.linspace(0, 1, n)) + + # Convert the RGB colors to hexadecimal + hex_colors = [rgb2hex(color[:3]) for color in colors] + + return hex_colors + + + +if __name__ == '__main__': + gui = Game_GUI((6, 6), 2) + gui.mainloop() + + + \ No newline at end of file From eaba07429563eddfb472a3f236ad55987af7e5b8 Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 22 Oct 2024 07:05:26 +0100 Subject: [PATCH 3/8] build: add run instructions and requirements.txt --- .gitignore | 1 + README.md | 2 ++ requirements.txt | 3 +++ src/main.py | 10 ++++++++++ 4 files changed, 16 insertions(+) create mode 100644 .gitignore create mode 100644 requirements.txt create mode 100644 src/main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/README.md b/README.md index b6f0edf..29c774d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Pypopipette My take on the Dots and Squares game (Pipopipette in French 🇫🇷). +Disclaimer: this is a work in progress. +To run the game, run in CLI `python src/main.py`. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fa1e3d7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy +tkinter +matplotlib \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..961e1c6 --- /dev/null +++ b/src/main.py @@ -0,0 +1,10 @@ +from frontend import Game_GUI + +# Game parameters +grid_size = (6, 6) +n_players = 2 + + + +gui = Game_GUI(grid_size, n_players) +gui.mainloop() From 46e246702fde8924e2206f0069c9f1d7a5471eb8 Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 22 Oct 2024 09:04:02 +0100 Subject: [PATCH 4/8] fix: if two squares are closed at one, you can now still play again --- src/backend.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/backend.py b/src/backend.py index 45a18d7..1b54e66 100644 --- a/src/backend.py +++ b/src/backend.py @@ -78,8 +78,10 @@ def update_row(self, coordinates, player_id)->bool: # Update the row self.rows[coordinates[0], coordinates[1]] = player_id - self.update_square((coordinates[0]-1, coordinates[1]), player_id) - self.update_square((coordinates[0], coordinates[1]), player_id) + uper_square = self.update_square((coordinates[0], coordinates[1]), player_id) + lower_square = self.update_square((coordinates[0]-1, coordinates[1]), player_id) + if uper_square or lower_square: + self.current_player = (self.current_player - 1) % self.n_players return True def update_column(self, coordinates, player_id)->bool: @@ -106,8 +108,10 @@ def update_column(self, coordinates, player_id)->bool: # Update the column self.columns[coordinates[0], coordinates[1]] = player_id - self.update_square((coordinates[0], coordinates[1]), player_id) - self.update_square((coordinates[0], coordinates[1]-1), player_id) + left_square = self.update_square((coordinates[0], coordinates[1]), player_id) + right_square = self.update_square((coordinates[0], coordinates[1]-1), player_id) + if left_square or right_square: + self.current_player = (self.current_player - 1) % self.n_players return True def update_square(self, coordinates, player_id)->bool: @@ -136,5 +140,5 @@ def update_square(self, coordinates, player_id)->bool: if self.rows[coordinates[0], coordinates[1]] != -1 and self.rows[coordinates[0]+1, coordinates[1]] != -1 and self.columns[coordinates[0], coordinates[1]] != -1 and self.columns[coordinates[0], coordinates[1]+1] != -1: self.squares[coordinates[0], coordinates[1]] = player_id self.scores[player_id] += 1 - self.current_player = (self.current_player - 1) % self.n_players - return True \ No newline at end of file + return True + return False \ No newline at end of file From 56560b97803582f5400d6cda52715a09fdc49b45 Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 29 Oct 2024 22:51:15 +0000 Subject: [PATCH 5/8] feat: border of the map now counts as closed ticks --- src/backend.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend.py b/src/backend.py index 1b54e66..6a9833c 100644 --- a/src/backend.py +++ b/src/backend.py @@ -14,10 +14,15 @@ def __init__(self, grid_size, n_players) -> None: self.n_cols = grid_size[1] self.squares = -1*np.ones((self.n_rows, self.n_cols)) self.columns = -1*np.ones((self.n_rows, self.n_cols+1)) + self.columns[:,0] = -2 # The leftmost column is already taken + self.columns[:,-1] = -2 # The rightmost column is already taken self.rows = -1*np.ones((self.n_rows+1, self.n_cols)) + self.rows[0,:] = -2 # The top row is already taken + self.rows[-1,:] = -2 # The bottom row is already taken self.scores = np.zeros(n_players) + def is_game_over(self)->bool: """ This method checks if the game is over. From 09b3a5ad8114c6d14cdf04f25741d756de170227 Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 29 Oct 2024 22:52:07 +0000 Subject: [PATCH 6/8] feat: Interface draws border of the map, displays scores, player turn, and game over screen --- src/frontend.py | 139 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 32 deletions(-) diff --git a/src/frontend.py b/src/frontend.py index a198654..873e274 100644 --- a/src/frontend.py +++ b/src/frontend.py @@ -22,15 +22,15 @@ def __init__(self, grid_size, n_players) -> None: self.grid_size = grid_size self.size_of_board = 600 self.players_colors = generate_rainbow_hex_colors(n_players) - symbol_size = (self.size_of_board / 3 - self.size_of_board / 8) / 2 - symbol_thickness = 50 - dot_color = '#7BC043' + #symbol_size = (self.size_of_board / 3 - self.size_of_board / 8) / 2 + #symbol_thickness = 50 + #dot_color = '#7BC043' #player1_color = '#0492CF' #player1_color_light = '#67B0CF' #player2_color = '#EE4035' #player2_color_light = '#EE7E77' #Green_color = '#7BC043' - self.dot_width = 0.25*grid_size[0]/grid_size[0] + #self.dot_width = 0.25*grid_size[0]/grid_size[0] self.edge_width = 0.075*self.size_of_board/grid_size[0] self.distance_between_dots = self.size_of_board / (grid_size[0]+1) @@ -58,6 +58,7 @@ def draw_board(self)->None: Returns: None """ + # Draw the board for i in range(self.grid_size[0]): for j in range(self.grid_size[1]): start_x = j * self.distance_between_dots + self.distance_between_dots / 2 @@ -65,7 +66,22 @@ def draw_board(self)->None: end_x = start_x + self.distance_between_dots end_y = start_y + self.distance_between_dots - self.canvas.create_rectangle(start_x, start_y, end_x, end_y, outline='black', width=self.edge_width) + self.canvas.create_rectangle(start_x, start_y, end_x, end_y, outline='grey', width=self.edge_width/2, dash=(int(self.size_of_board/100), int(self.size_of_board/50))) + + # Draw the edges of the board as black lines + # Draw the top and bottom edges + self.canvas.create_line(self.distance_between_dots / 2, self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, self.distance_between_dots / 2, fill='black', width=self.edge_width) + self.canvas.create_line(self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, fill='black', width=self.edge_width) + + # Draw the left and right edges + self.canvas.create_line(self.distance_between_dots / 2, self.distance_between_dots / 2, self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, fill='black', width=self.edge_width) + self.canvas.create_line(self.size_of_board - self.distance_between_dots / 2, self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, self.size_of_board - self.distance_between_dots / 2, fill='black', width=self.edge_width) + + # Display the player scores + self.display_players_scores() + + # Display the current player's turn + self.display_player_turn() def draw_edge(self, player_id, coordinates, action): @@ -106,41 +122,94 @@ def shade_boxes(self): for i in range(len(self.game_instance.squares)): for j in range(len(self.game_instance.squares[0])): if self.game_instance.squares[i][j] != -1: - start_x = j * self.distance_between_dots + self.distance_between_dots / 2 - start_y = i * self.distance_between_dots + self.distance_between_dots / 2 - end_x = start_x + self.distance_between_dots - end_y = start_y + self.distance_between_dots + start_x = j * self.distance_between_dots + self.distance_between_dots / 2 + self.edge_width/2 + start_y = i * self.distance_between_dots + self.distance_between_dots / 2 + self.edge_width/2 + end_x = start_x + self.distance_between_dots - self.edge_width/2 + end_y = start_y + self.distance_between_dots - self.edge_width/2 self.canvas.create_rectangle(start_x, start_y, end_x, end_y, fill=self.players_colors[int(self.game_instance.squares[i][j])], outline='') #self.canvas.create_rectangle(start_x, start_y, end_x, end_y, outline = self.players_colors[player_id],fill=self.players_colors[player_id], width=self.edge_width) def display_gameover(self): - #TODO: Implement this method - pass - - def refresh_board(self): - #TODO: Implement this method - pass - - def display_turn_text(self): - #TODO: Implement this method - text = 'Next turn: ' - if self.game_instance.player1_turn: - text += 'Player1' - color = player1_color + """ + This method displays the game over message on the canvas. + + Returns: + None + """ + self.canvas.delete("all") + self.canvas.create_text( + self.size_of_board / 2, + self.size_of_board / 2 - 20, + font=('freemono', 30), + text='Game Over!', + fill='black' + ) + + # Determine the winner(s) + max_score = max(self.game_instance.scores) + winners = [i + 1 for i, score in enumerate(self.game_instance.scores) if score == max_score] + + if len(winners) == 1: + winner_text = f'Player {winners[0]} wins!' else: - text += 'Player2' - color = player2_color + winner_text = f'Players {", ".join(map(str, winners))} tie!' - self.canvas.delete(self.turntext_handle) - self.turntext_handle = self.canvas.create_text(size_of_board - 5*len(text), - size_of_board-self.distance_between_dots/8, - font="cmr 15 bold", text=text, fill=color) - + self.canvas.create_text( + self.size_of_board / 2, + self.size_of_board / 2 + 20, + font=('freemono', 20), + text=winner_text, + fill= self.players_colors[winners[0]-1] + ) + + + def display_player_turn(self)->None: + """ + This method displays the player's turn at the top of the canvas. + """ + + # Clear the previous player's turn + self.canvas.delete("player_turn") + # Display the current player's turn + self.canvas.create_text( + self.size_of_board / 2, + 10, + text=f'Player {self.game_instance.current_player+1}\'s turn', + fill=self.players_colors[self.game_instance.current_player], + tags="player_turn" + ) - def click(self, event): - #TODO: Implement this method + def display_players_scores(self)->None: + """ + This method displays the scores of the players and their colors below the board on the canvas. + It refreshes the score displayed on screen instead of writing the text in front of the previous version of the score. + """ + # Clear the previous scores + self.canvas.delete("score") + + # Display the updated scores + for i in range(self.game_instance.n_players): + self.canvas.create_text( + self.size_of_board / (2 * self.game_instance.n_players) + i * self.size_of_board / self.game_instance.n_players, + self.size_of_board - 10, + text=f'Player {i+1}: {int(self.game_instance.scores[i])}', + fill=self.players_colors[i], + tags="score" + ) + + + def click(self, event: tk.Event)->None: + """ + This method is called when the user clicks on the canvas. + + Args: + event (tk.Event): The event object. + + Returns: + None + """ x, y = event.x, event.y coordinates, action = self.convert_click_to_action([x, y]) @@ -153,6 +222,12 @@ def click(self, event): self.draw_edge(self.game_instance.current_player, coordinates, action) self.shade_boxes() + if self.game_instance.is_game_over(): + self.display_gameover() + else: + self.display_players_scores() + self.display_player_turn() + def convert_click_to_action(self, grid_position)->tuple: @@ -201,7 +276,7 @@ def generate_rainbow_hex_colors(n): if __name__ == '__main__': - gui = Game_GUI((6, 6), 2) + gui = Game_GUI((10,10), 3) gui.mainloop() From 31d8719111b4fff37c025056016aa6c604cc191b Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 29 Oct 2024 23:02:35 +0000 Subject: [PATCH 7/8] feat: add log to game backend --- src/backend.py | 11 +++++++---- src/frontend.py | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/backend.py b/src/backend.py index 6a9833c..c9cb145 100644 --- a/src/backend.py +++ b/src/backend.py @@ -14,12 +14,13 @@ def __init__(self, grid_size, n_players) -> None: self.n_cols = grid_size[1] self.squares = -1*np.ones((self.n_rows, self.n_cols)) self.columns = -1*np.ones((self.n_rows, self.n_cols+1)) - self.columns[:,0] = -2 # The leftmost column is already taken - self.columns[:,-1] = -2 # The rightmost column is already taken + self.columns[:,0] = -2 # -2 means that the column is already taken by a non-player (map feature, in this case the leftmost column) + self.columns[:,-1] = -2 # -2 means that the column is already taken by a non-player (map feature, in this case the rightmost column) self.rows = -1*np.ones((self.n_rows+1, self.n_cols)) - self.rows[0,:] = -2 # The top row is already taken - self.rows[-1,:] = -2 # The bottom row is already taken + self.rows[0,:] = -2 # -2 means that the row is already taken by a non-player (map feature, in this case the top row) + self.rows[-1,:] = -2 # -2 means that the row is already taken by a non-player (map feature, in this case the bottom row) self.scores = np.zeros(n_players) + self.log = [] @@ -44,6 +45,8 @@ def player_turn(self, player_id, coordinates, action) -> bool: Returns: bool: True if the player's turn was successful (they clicked on a previously unoccupied edge), False otherwise. """ + self.log.append({"turn":len(self.log),"player_id": player_id, "coordinates": coordinates, "action": action}) + if action == "r": # If the action is to update a row if self.update_row(coordinates, player_id): # If the row was updated skip to the next player self.current_player = (self.current_player + 1) % self.n_players diff --git a/src/frontend.py b/src/frontend.py index 873e274..b5fc5e4 100644 --- a/src/frontend.py +++ b/src/frontend.py @@ -146,6 +146,7 @@ def display_gameover(self): fill='black' ) + # Determine the winner(s) max_score = max(self.game_instance.scores) winners = [i + 1 for i, score in enumerate(self.game_instance.scores) if score == max_score] @@ -224,6 +225,8 @@ def click(self, event: tk.Event)->None: if self.game_instance.is_game_over(): self.display_gameover() + print("Game Over!") + print(self.game_instance.log) else: self.display_players_scores() self.display_player_turn() From e31fa942b9ff6730c8627d12ccd57b5ca4510a15 Mon Sep 17 00:00:00 2001 From: David GERARD Date: Tue, 29 Oct 2024 23:09:04 +0000 Subject: [PATCH 8/8] feat: set main.py to be a 10x10 3 players game --- src/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index 961e1c6..e5f631b 100644 --- a/src/main.py +++ b/src/main.py @@ -1,8 +1,8 @@ from frontend import Game_GUI # Game parameters -grid_size = (6, 6) -n_players = 2 +grid_size = (10, 10) +n_players = 3