class Display(FloatLayout): '''Tic-tac-toe game display''' grid = ObjectProperty(None) message = ObjectProperty(None) previous = ObjectProperty(None) def __init__(self, *args, **kwargs): super(Display, self).__init__(*args, **kwargs) # 1 and -1 are used to denote the players, 1 is the first player and -1 is the second # player. This makes checking for a win very easy since a row/column/diagonal will add # up to 3 or -3 if someone has won. self.player = Players.player_one self.config_parser = ConfigParser.get_configparser("app") self.game_over = True self.two_player = None self.human_player = None self.player_icons = Bidict() self.player_icons[Players.player_one] = "X" self.player_icons[Players.player_two] = "O" self.player_icons[Players.unplayed] = "" self.player_names = Bidict() self.player_names[Players.player_one] = "Player One" self.player_names[Players.player_two] = "Player Two" self.board = Board() def set_game_mode(self, two_player): '''Set whether the game is in one or two player mode If setting to one player mode, specify which player will be played by a human ''' self.two_player = two_player if not self.two_player: self.human_player = Players.player_one if self.config_parser.get( 'AI', 'ai_player') == "Second" else Players.player_two if self.human_player == Players.player_one: self.player_names[Players.player_one] = self.config_parser.get( 'Player Names', 'player_one_name') self.player_names[Players.player_two] = self.config_parser.get( 'AI', 'ai_name') else: self.player_names[Players.player_one] = self.config_parser.get( 'AI', 'ai_name') self.player_names[Players.player_two] = self.config_parser.get( 'Player Names', 'player_two_name') else: self.human_player = None self.player_names[Players.player_one] = self.config_parser.get( 'Player Names', 'player_one_name') self.player_names[Players.player_two] = self.config_parser.get( 'Player Names', 'player_two_name') self.reset() self.game_over = False self.previous.title = self.player_names[self.player] # If mode is one player and the human is playing as # player two, the ai will make the first move if not self.two_player and self.human_player == Players.player_two: self._ai_make_move(0) def human_make_move(self, row, column): '''Make a move at the tile specified by row, column If the game is in one player mode, the AI will then make its move.''' if (self.two_player or self.player == self.human_player) and self._make_move(row, column): ai_move_delay = self.config_parser.get('AI', 'ai_move_delay') delay = 0 if ai_move_delay == "Random": delay = random.triangular(0.1, 1.5, 0.3) elif ai_move_delay == "2 sec.": delay = 2.0 Clock.schedule_once(self._ai_make_move, delay) def _ai_make_move(self, dt): '''Have the AI make its next move''' if not self.two_player: ai_move = AI.get_next_move( self.board, Players.other_player(self.human_player)) if ai_move: ai_move_row, ai_move_columnn = ai_move self._make_move(ai_move_row, ai_move_columnn) def _make_move(self, row, column): '''Make a move at the tile specified by row, column''' if not self.game_over: # Update internal board state if self.board.make_move(row, column): # Display player's icon on tile self.grid.set_tile_icon( row, column, self.player_icons[self.player]) # Check for win win_status = self.board.check_for_win() return self._handle_move_results(win_status) return False def undo_move(self): '''Undo previous move Can be called multiple times, will only work while the game isn't over. If in one player mode, the ai's move, and the player's move will both be undone. ''' if not self.game_over: if not self.two_player: # Undo ai's move self._undo_move() self._undo_move() def _undo_move(self): '''Internal method to undo the previous move''' # Remove move from internal board move = self.board.undo_move() # If there was a move to undo if move: row, column = move # Remove player's icon from tile self.grid.set_tile_icon( row, column, self.player_icons[Players.unplayed]) # Return to previous turn self.player = Players.other_player(self.player) self.previous.title = self.player_names[self.player] def _handle_move_results(self, win_status): '''Handle the results of a move Based on the results of a move, end a game and display win/tie message or just advance to the next turn ''' gameover, winner = win_status if gameover: self.game_over = True # Cat's game if winner == Results.tie: self.message.display_message("Cat's game!") else: self.grid.display_win(self.board.get_winning_streak()) Clock.schedule_once(self._display_win_message, 0.3) return False # If no win, advance to next turn self.player = Players.other_player(self.player) self.previous.title = self.player_names[self.player] return True def _display_win_message(self, dt): '''Display a win message for the current player''' if self.two_player: self.message.display_message( self.player_names[self.player] + " has won!") elif self.human_player == self.player: self.message.display_message("You won!") else: self.message.display_message("You lost!") def restart(self): '''Restart game into the same gamemode, same players''' self.reset() self.game_over = False # If mode is one player and the human is playing as # player two, the ai will make the first move if not self.two_player and self.human_player == Players.player_two: self._ai_make_move(0) def reset(self): '''Reset board to default state''' self.player = Players.player_one self.previous.title = self.player_names[self.player] self.game_over = True self.board.reset() self.grid.reset() self.message.clear()