class TicTacToe: """Ui class for TicTacToe game. Attributes: start_menu: Starting menu for setting game parameters tictactoeboard: Tictactoe board data structure screen: Pygame window x_image: X image for player 1 o_image: O image for player 2 square_size: How big is one square in pixels (grid size / number of squares). grid_size: Pygame window size in pixels result_text: Result of the game whose_turn: Whose turn is it (text message) background_color: Background color of the board and the status window grid color: Color of grid lines bottom_height: How long is the window below the grid (Status window and buttons) buttom_width: How long is buttons width running: Is game still running or not """ def __init__(self): """Class constructor, which initializes the variables and final values are set later, because they depend on user input """ self.start_menu = None self.tictactoeboard = None self.screen = None self.x_image = None self.o_image = None self.square_size = 0 self.grid_size = 800 self.result_text = '' self.whose_turn = '' self.background_color = (210, 210, 210) self.grid_color = (0, 0, 0) self.bottom_height = 100 self.button_width = self.grid_size / 3 self.running = True def run(self): """Function where game runs in infinite loop until game ends or player press quit """ self.start_game() if self.tictactoeboard.num_squares == 0: return while self.running and self.tictactoeboard.num_squares > 0: self.play_game() if self.tictactoeboard.result == Result.ONGOING and self.running: pygame.display.flip() pygame.quit() # pylint: disable=no-member def play_game(self): """Infinite loop for reading user mouse events. Break after running is set to False""" for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONDOWN: # pylint: disable=no-member self.check_button_pressed(event.pos[0], event.pos[1]) if self.running is False: break if event.pos[0] > self.grid_size or event.pos[ 1] > self.grid_size: continue self.set_xo(event.pos[0], event.pos[1]) if self.tictactoeboard.result != Result.ONGOING: self.set_result() pygame.quit() # pylint: disable=no-member self.start_game() if self.tictactoeboard.num_squares == 0: self.running = False break def start_game(self): """Call Tkinter Class where the user sets the names of the players and number of squares. If one of player names are incorrect, starts again with error message. Else calls set_game function """ self.start_menu = StartMenu(self.result_text) num_squares, player1, player2 = self.get_game_variables() self.tictactoeboard = TicTacToeBoard(num_squares, player1, player2) if self.tictactoeboard.num_squares == 0: return if self.players_name_ok(self.start_menu.name_max_size) is False: self.result_text = 'Virheellinen pelaajan nimi' self.start_game() else: self.set_game() def get_game_variables(self): """Show Tkinter menu and set num_squares, player1 and player2 to what user gave it in Tkinter window Returns: Number of squares in board, player 1 name and player 2 name """ self.start_menu.show() return self.start_menu.num_squares, self.start_menu.player1, self.start_menu.player2 def players_name_ok(self, max_size): """Check if the player names are the correct size Args: max_size: what is players name max length Returns: True, if names length are correct (1-max_size). Otherwise returns False """ player1_len = len(self.tictactoeboard.player1) player2_len = len(self.tictactoeboard.player2) names_ok = True if player1_len > max_size or player1_len < 1: names_ok = False if player2_len > max_size or player2_len < 1: names_ok = False return names_ok def set_game(self): """Sets game variables right with right num_squares and then calls: - draw_grid function which draws board. - draw_status function which draws whose turn is it (first time player 1) - draw_buttons function which draws save, load and quit buttons """ self.square_size = int(self.grid_size / self.tictactoeboard.num_squares) self.grid_size = self.grid_size - (self.grid_size % self.tictactoeboard.num_squares) self.button_width = math.floor(self.grid_size / 3) self.x_image = pygame.transform.scale(pygame.image.load\ ("src/images_xo/x.png"), (self.square_size, self.square_size)) self.o_image = pygame.transform.scale(pygame.image.load\ ("src/images_xo/o.png"), (self.square_size, self.square_size)) os.environ['SDL_VIDEO_WINDOW_POS'] = "center" pygame.init() # pylint: disable=no-member window_size = [self.grid_size, self.grid_size + self.bottom_height] self.screen = pygame.display.set_mode(window_size, pygame.NOFRAME, pygame.SHOWN) # pylint: disable=no-member self.draw_grid() self.draw_status() self.draw_buttons() def set_xo(self, mouse_x, mouse_y): """Add x or o in tictactoeboard data structure to the square that user clicked Args: mouse_x: X coordinate which position user click with mouse mouse_y: Y coordinate which position user click with mouse """ x_square = math.floor(mouse_x / self.square_size) y_square = math.floor(mouse_y / self.square_size) if self.tictactoeboard.whose_turn == 1 and not \ self.tictactoeboard.is_taken(y_square, x_square): self.tictactoeboard.add_x(x_square, y_square) elif self.tictactoeboard.whose_turn == 2 and not \ self.tictactoeboard.is_taken(y_square, x_square): self.tictactoeboard.add_o(x_square, y_square) self.update_board() self.draw_status() def update_board(self): """Draw empty grid and then draw all x and o to the grid. Function gets x and o positions from the tictactoeboard data structure""" self.draw_grid() for x_square in range(0, self.tictactoeboard.num_squares): for y_square in range(0, self.tictactoeboard.num_squares): x_coordinate = self.square_size * x_square y_coordinate = self.square_size * y_square if self.tictactoeboard.board[y_square][x_square] == 'x': self.screen.blit(self.x_image, (x_coordinate, y_coordinate)) elif self.tictactoeboard.board[y_square][x_square] == 'o': self.screen.blit(self.o_image, (x_coordinate, y_coordinate)) def draw_grid(self): """Draws empty grid for the game""" self.screen.fill((self.background_color), (0, 0, self.grid_size, self.grid_size)) for x_int in range(0, self.grid_size, self.square_size): for y_int in range(0, self.grid_size, self.square_size): rect = pygame.Rect(x_int, y_int, self.square_size, self.square_size) pygame.draw.rect(self.screen, self.grid_color, rect, 1) def draw_status(self): """Draws a status of the game (whose turn is it) and also calls function draw_buttons which draws save, load and quit buttons """ my_font = 'arial' status_font_size = 45 font_color = (0, 0, 0) status_coordinates = (0, self.grid_size, self.grid_size, self.bottom_height / 2) status_text_center = (self.grid_size / 2, self.grid_size + int(self.bottom_height / 4)) if self.tictactoeboard.whose_turn == 1: self.whose_turn = f"Vuoro: {self.tictactoeboard.player1}" else: self.whose_turn = f"Vuoro: {self.tictactoeboard.player2}" font = pygame.font.SysFont(my_font, status_font_size) text = font.render(self.whose_turn, 1, font_color) self.screen.fill((self.background_color), status_coordinates) text_rect = text.get_rect(center=status_text_center) self.screen.blit(text, text_rect) pygame.display.update() def draw_buttons(self): """draws save, load and quit buttons to bottom of window""" save_button_coordinates = (0, self.grid_size + self.bottom_height/2, \ self.button_width, self.bottom_height/2) save_button_center = (0 + self.button_width/2, \ self.grid_size + int(self.bottom_height * 0.75)) self.draw_one_button('Tallenna peli', save_button_coordinates, save_button_center) download_button_coordinates = (self.button_width, self.grid_size + self.bottom_height/2,\ self.button_width, self.bottom_height/2) download_button_center = (self.grid_size / 2,\ self.grid_size + int(self.bottom_height * 0.75)) self.draw_one_button('Lataa peli', download_button_coordinates, download_button_center) quit_button_coordinates = (self.button_width * 2, self.grid_size + self.bottom_height/2,\ self.grid_size - 2 * self.button_width, self.bottom_height/2) quit_button_center = (self.grid_size - self.button_width/2,\ self.grid_size + int(self.bottom_height * 0.75)) self.draw_one_button('Lopeta peli', quit_button_coordinates, quit_button_center) pygame.display.update() def draw_one_button(self, button_text, button_coordinates, button_center): """Auxiliary function for drawing buttons Args: button_text: Button text button_coordinates: Where to draw button and what are buttons width and height button_center: Where is center of button """ button_font = 'arial' button_font_size = 24 button_color = (150, 150, 150) font_color = (50, 50, 50) border_color = (120, 120, 120) font = pygame.font.SysFont(button_font, button_font_size) text = font.render(button_text, 1, font_color) pygame.draw.rect(self.screen, (button_color), button_coordinates) # Draws button borders and text pygame.draw.rect(self.screen, border_color, pygame.Rect(button_coordinates), 4, 0) text_rect = text.get_rect(center=button_center) self.screen.blit(text, text_rect) def check_button_pressed(self, mouse_x, mouse_y): """Check if user press one of buttons Args: mouse_x: Check x coordinate which position user clicked with mouse mouse_y: Check y coordinate which position user clicked with mouse """ if 0 < mouse_x < self.button_width and\ self.grid_size + self.bottom_height/2 < mouse_y < self.grid_size + self.bottom_height: self.save_game() elif self.button_width < mouse_x < self.button_width * 2 and\ self.grid_size + self.bottom_height/2 < mouse_y < self.grid_size + self.bottom_height: self.load_game() elif self.button_width * 2 < mouse_x < self.grid_size and\ self.grid_size + self.bottom_height/2 < mouse_y < self.grid_size + self.bottom_height: self.running = False def save_game(self): """Function for saving game and printing result message""" message = 'Tallennus onnistui' window_title = 'Tallennus' try: save_menu = tk.Tk() save_menu.withdraw() save_filename = tkinter.filedialog.asksaveasfilename(title= "Tallenna tiedosto"\ ,filetypes=[("Tictactoe tiedostot", "*.ttt")]) save_menu.destroy() with open(save_filename, "wb") as save_file: pickle.dump(self.tictactoeboard, save_file) except (IOError, TypeError, AttributeError, pickle.PicklingError): message = 'Tallennus epäonnistui' finally: self.print_message(message, window_title) def load_game(self): """Function for loading game and printing result message""" message = 'Lataus onnistui' window_title = 'Lataus' try: load_menu = tk.Tk() load_menu.withdraw() load_filename = tkinter.filedialog.askopenfilename(parent=load_menu, \ title= "Lataa tiedosto", \ filetypes=[("Tictactoe tiedostot", "*.ttt")]) load_menu.destroy() with open(load_filename, "rb") as load_file: self.tictactoeboard = pickle.load(load_file) except (IOError, TypeError, AttributeError, pickle.UnpicklingError): message = 'Lataus epäonnistui' else: self.set_game() self.update_board() finally: self.print_message(message, window_title) def print_message(self, message, window_title): """Function that print message if needed. Uses Tkinter popup window Args: message: Message that is shown to user window_title: Window title """ errorwindow = tk.Tk() errorwindow.overrideredirect(1) errorwindow.withdraw() tkinter.messagebox.showinfo(window_title, message) errorwindow.destroy() def set_result(self): """Set result text who wins or is it draw""" if self.tictactoeboard.result == Result.FIRST_WIN: self.result_text = f"{self.tictactoeboard.player1} voittaa" elif self.tictactoeboard.result == Result.SECOND_WIN: self.result_text = f"{self.tictactoeboard.player2} voittaa" elif self.tictactoeboard.result == Result.DRAW: self.result_text = 'Tasapeli'
class TestTicTacToeBoard(unittest.TestCase): def setUp(self): self.my_tictactoe_board = TicTacToeBoard(5, '', '') def test_constructor_is_working(self): self.assertEqual(self.my_tictactoe_board.num_squares, 5) boolean = False if all('-' in x for x in self.my_tictactoe_board.board): boolean = True self.assertEqual(boolean, True) self.assertEqual(self.my_tictactoe_board.whose_turn, 1) self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) def test_adding_x_and_o(self): # Add first x to empty square self.my_tictactoe_board.add_x(0, 0) self.assertEqual( sum(x.count("x") for x in self.my_tictactoe_board.board), 1) self.assertEqual( sum(x.count("-") for x in self.my_tictactoe_board.board), (self.my_tictactoe_board.num_squares**2) - 1) # Test to add x into occupied square self.my_tictactoe_board.add_x(0, 0) self.assertEqual( sum(x.count("x") for x in self.my_tictactoe_board.board), 1) self.assertEqual( sum(x.count("-") for x in self.my_tictactoe_board.board), (self.my_tictactoe_board.num_squares**2) - 1) # Add o to an empty square self.my_tictactoe_board.add_o(0, 1) self.assertEqual( sum(x.count("o") for x in self.my_tictactoe_board.board), 1) self.assertEqual( sum(x.count("-") for x in self.my_tictactoe_board.board), (self.my_tictactoe_board.num_squares**2) - 2) # Add o to same square than x self.my_tictactoe_board.add_o(0, 0) self.assertEqual( sum(x.count("o") for x in self.my_tictactoe_board.board), 1) self.assertEqual( sum(x.count("-") for x in self.my_tictactoe_board.board), (self.my_tictactoe_board.num_squares**2) - 2) def test_if_taken(self): self.my_tictactoe_board.add_x(0, 0) self.assertTrue(self.my_tictactoe_board.is_taken(0, 0)) def test_set_winner_right(self): self.my_tictactoe_board.set_winner('x') self.assertEqual(self.my_tictactoe_board.result, Result.FIRST_WIN) self.my_tictactoe_board.set_winner('o') self.assertEqual(self.my_tictactoe_board.result, Result.SECOND_WIN) def test_check_draw(self): self.my_tictactoe_board.add_x(0, 0) self.my_tictactoe_board.add_o(0, 1) self.my_tictactoe_board.add_o(0, 2) self.my_tictactoe_board.add_o(0, 3) self.my_tictactoe_board.add_x(0, 4) self.my_tictactoe_board.add_x(1, 0) self.my_tictactoe_board.add_o(1, 1) self.my_tictactoe_board.add_x(1, 2) self.my_tictactoe_board.add_x(1, 3) self.my_tictactoe_board.add_o(1, 4) self.my_tictactoe_board.add_o(2, 0) self.my_tictactoe_board.add_x(2, 1) self.my_tictactoe_board.add_o(2, 2) self.my_tictactoe_board.add_x(2, 3) self.my_tictactoe_board.add_o(2, 4) self.my_tictactoe_board.add_x(3, 0) self.my_tictactoe_board.add_x(3, 1) self.my_tictactoe_board.add_o(3, 2) self.my_tictactoe_board.add_o(3, 3) self.my_tictactoe_board.add_x(3, 4) self.my_tictactoe_board.add_x(4, 0) self.my_tictactoe_board.add_o(4, 1) self.my_tictactoe_board.add_o(4, 2) self.my_tictactoe_board.add_x(4, 3) self.my_tictactoe_board.add_x(4, 4) self.my_tictactoe_board.check_draw() self.assertEqual(self.my_tictactoe_board.result, Result.DRAW) def test_check_situation_row(self): self.my_tictactoe_board.add_x(0, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(1, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(0, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(1, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(0, 2) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(1, 2) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(0, 3) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.FIRST_WIN) def test_check_situation_col(self): self.my_tictactoe_board.add_x(0, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(0, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(1, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(1, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(2, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(2, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(3, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.FIRST_WIN) def test_check_situation_diagonal(self): self.my_tictactoe_board.add_x(0, 0) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(0, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(1, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(1, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(2, 2) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(2, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(3, 3) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.FIRST_WIN) def test_check_situation_diagonal_other_way(self): self.my_tictactoe_board.add_x(0, 4) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(0, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(1, 3) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(1, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(2, 2) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_o(2, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.ONGOING) self.my_tictactoe_board.add_x(3, 1) self.my_tictactoe_board.check_situation() self.assertEqual(self.my_tictactoe_board.result, Result.FIRST_WIN)