import math # Use these constants to fill in the game board X = "X" O = "O" EMPTY = None def start_state(): """ Returns starting state of the board. """ return [[EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY]] def player(board): """ Returns which player (either X or O) who has the next turn on a board. In the initial game state, X gets the first move. Subsequently, the player alternates with each additional move. Any return value is acceptable if a terminal board is provided as input (i.e., the game is already over). """ count_X = sum(row.count(X) for row in board) count_O = sum(row.count(O) for row in board) if count_X > count_O: return O else: return X def actions(board): """ Returns the set of all possible actions (i, j) available on the board. The actions function should return a set of all the possible actions that can be taken on a given board. Each action should be represented as a tuple (i, j) where i corresponds to the row of the move (0, 1, or 2) and j corresponds to the column of the move (also 0, 1, or 2). Possible moves are any cells on the board that do not already have an X or an O in them. Any return value is acceptable if a terminal board is provided as input. """ actions = set() for i in range(3): for j in range(3): if board[i][j] == EMPTY: actions.add((i, j)) return actions def succ(board, action): """ Returns the board that results from making move (i, j) on the board, without modifying the original board. If `action` is not a valid action for the board, you should raise an exception. The returned board state should be the board that would result from taking the original input board, and letting the player whose turn it is make their move at the cell indicated by the input action. Importantly, the original board should be left unmodified. This means that simply updating a cell in `board` itself is not a correct implementation of this function. You’ll likely want to make a deep copy of the board first before making any changes. """ if action not in actions(board): raise ValueError("Invalid action") new_board = [row[:] for row in board] new_board[action[0]][action[1]] = player(board) return new_board def winner(board): """ Returns the winner of the game, if there is one. - If the X player has won the game, the function should return X. - If the O player has won the game, the function should return O. - If there is no winner of the game (either because the game is in progress, or because it ended in a tie), the function should return None. You may assume that there will be at most one winner (that is, no board will ever have both players with three-in-a-row, since that would be an invalid board state). """ # Check rows for row in board: if row[0] == row[1] == row[2] and row[0] is not None: return row[0] # Check columns for col in range(3): if board[0][col] == board[1][col] == board[2][col] and board[0][col] is not None: return board[0][col] # Check diagonals if board[0][0] == board[1][1] == board[2][2] and board[0][0] is not None: return board[0][0] if board[0][2] == board[1][1] == board[2][0] and board[0][2] is not None: return board[0][2] return None def terminal(board): """ Returns True if game is over, False otherwise. If the game is over, either because someone has won the game or because all cells have been filled without anyone winning, the function should return True. Otherwise, the function should return False if the game is still in progress. """ if winner(board) is not None or all(all(cell is not EMPTY for cell in row) for row in board): return True else: return False def utility(board): """ Returns 1 if X has won the game, -1 if O has won, 0 otherwise. You may assume utility will only be called on a board if terminal(board) is True. """ if winner(board) == X: return 1 elif winner(board) == O: return -1 else: return 0 def minimax(board): """ Returns the optimal action for the current player on the board. The move returned should be the optimal action (i, j) that is one of the allowable actions on the board. If multiple moves are equally optimal, any of those moves is acceptable. If the board is a terminal board, the minimax function should return None. """ if terminal(board): return None if player(board) == X: best_score = float('-inf') best_action = None for action in actions(board): score = min_value(succ(board, action)) if score > best_score: best_score = score best_action = action return best_action else: best_score = float('inf') best_action = None for action in actions(board): score = max_value(succ(board, action)) if score < best_score: best_score = score best_action = action return best_action """ Helper function lol """ def max_value(board): if terminal(board): return utility(board) v = float('-inf') for action in actions(board): v = max(v, min_value(succ(board, action))) return v """ Another helper function lol """ def min_value(board): if terminal(board): return utility(board) v = float('inf') for action in actions(board): v = min(v, max_value(succ(board, action))) return v