import copy class TicTacToeBoard: """ A class that represents a tic tac toe board. Attributes ---------- size : (int) the width (or height) of the board current_mark: (str) the mark that was played on the board board : (list of str) the board represented as a list of marks Methods ------- add_mark(row, col): (list) adds the current_mark to board[row][col], updates the current_mark for the opponent, and \returns the updated board next_states(): (list) Fills in all the empty spaces we find as candidate next states and returns them in a list has_diagonal_win(): (bool) Checks whether the left-leaning or right-leaning diagonals contains a win (all X or all O) is_diagonal_all_mark(): (bool) Checks whether the left-leaning diagonal contains a win (all positions match mark) is_other_diagonal_all_mark(): (bool) Checks whether the right-leaning diagonal contains a win (all positions match mark) """ def __init__(self, size): self.size = size self.current_mark = "X" # construct a new board that is size by size self.board = [] # initially no moves have been made for i in range(self.size): self.board.append(["_"] * size) def add_mark(self, row, col): """ adds the current_mark to board[row][col], updates the current_mark for the opponent, \ and returns the updated board :param row: (int) the row to mark :param col: (int) the column to mark :return: (list) the updated board """ new_board = copy.deepcopy(self) new_board.board[row][col] = new_board.current_mark #update the mark for the opponent if new_board.current_mark == "X": new_board.current_mark = "O" else: new_board.current_mark = "X" return new_board def next_states(self): """ Fills in all the empty spaces we find as candidate next states and returns them in a list :return: (list) a list of boards that indicate possible moves """ # next_states = [] for r in range(self.size): for c in range(self.size): if self.board[r][c] == "_": next_states.append(self.add_mark(r, c)) return next_states def is_goal(self): return self.has_diagonal_win() def has_diagonal_win(self): """ Checks whether the left-leaning or right-leaning diagonals contains a win (all X or all O) :return: (bool) whether the left-leaning or right-leaning diagonals contains a win (all X or all O) """ return self.is_diagonal_all_mark("X") or \ self.is_diagonal_all_mark("O") or \ self.is_other_diagonal_all_mark("X") or \ self.is_other_diagonal_all_mark("O") def is_diagonal_all_mark(self, mark): """ Checks whether the left-leaning diagonal contains a win (all positions match mark) :param mark: (str) the mark to compare the win against :return: (bool) whether the left-leaning diagonal contains a win (all positions match mark) """ for i in range(self.size): if self.board[i][i] != mark: return False return True def is_other_diagonal_all_mark(self, mark): """ Checks whether the right-leaning diagonal contains a win (all positions match mark) :param mark: (str) the mark to compare the win against :return: (bool) whether the right-leaning diagonal contains a win (all positions match mark) """ for i in range(self.size): if self.board[i][self.size - 1 - i] != mark: return False return True def __str__(self): board_string = " " # add the column indices at the top for i in range(self.size): board_string += str(i) + " " board_string += "\n" for i in range(self.size): board_string += str(i) + " " + str(self.board[i]) + "\n" return board_string def dfs(state): """ Recursive depth first search implementation :param state: (any) the state to start DFS on :return: (list) list of next states as visited by DFS """ # if the current state is a goal state, then return it in a list if state.is_goal(): return [state] else: # else, recurse on the possible next states result = [] for s in state.next_states(): # append all the next states result += dfs(s) return result def main(): start_state = TicTacToeBoard(3) solutions = dfs(start_state) print("There were " + str(len(solutions)) + " solutions.\n") if len(solutions) > 0: print(solutions[0]) if __name__ == "__main__": main()