diff --git a/demo.py b/demo.py index ab1d99736c43d75bbc0824cc32344db61b5dc6ee..a5f5fb12c5300cc67620e11836e9bbaab645445d 100644 --- a/demo.py +++ b/demo.py @@ -3,8 +3,12 @@ import time def main() -> None: ser = pc.connect() - w = [130, 120, 18, 1, 1, 1, -1]; - pc.set_joint_angles(ser,1.07, 0.21, -0.15, -1.6, -1, 0, 2000) + w = [130, 120, 18, 0, 0, -1]; + print(pc.move_pose(ser, w, 0, 2000)); + print("moving to pose") + time.sleep(2000); + print("done") + #pc.set_joint_angles(ser,1.07, 0.21, -0.15, -1.6, -1, 0, 2000) pc.sleep(ser) pc.disconnect(ser) diff --git a/tictactoe.py b/tictactoe.py index 5e5895b13905502a26ba70a24cd42ac7895a8095..de29f06083f342ebae0e7813af3d6fe026b6d66a 100644 --- a/tictactoe.py +++ b/tictactoe.py @@ -5,6 +5,14 @@ class Player(Enum): undefined = 0 one = 1 two = 2 + grid_full = 3 + +def get_opponent(player:Player) -> Player: + if player == Player.one: + return Player.two + else: + return Player.one + class TicTacToeError(Exception): """TicTacToe Error""" @@ -16,7 +24,11 @@ class TicTacToe(): grid.append([Player.undefined, Player.undefined, Player.undefined]) self.grid = np.array(grid) - def _get_player_from_int(self, player:Player|int) -> Player: + @staticmethod + def _get_player_from_int(player:Player|int) -> Player: + """ + Internal helper methot to return a Player-Object from Int or Player + """ if isinstance(player, int): try: return Player(player) @@ -24,45 +36,57 @@ class TicTacToe(): raise TicTacToeError("The player number is invalid. Only '1' and '2' are accepted") return player - def add(self, row:int, col:int, player:Player|int) -> bool: + def add(self, row:int, col:int, player:Player|int, grid:np.ndarray|None = None) -> bool: """ Change the value of a undefined field. The player can be passed as Player Object or plain Int. Returns True, if executed correctly. Returns False, if the field is already occupied or the player number is invalid. """ + if grid is None: + grid = self.grid player = self._get_player_from_int(player) - if self.check_field(row, col): - self.grid[row][col] = player + if self.check_field(row, col, grid): + grid[row][col] = player return True return False - def check_field(self, row:int, col:int) -> bool: + def check_field(self, row:int, col:int, grid:np.ndarray|None = None) -> bool: """Checks, if a field is occupied""" + if grid is None: + grid = self.grid if self.grid[row][col] != Player.undefined: return False return True - def print_grid(self) -> None: + def print_grid(self, grid:np.ndarray|None=None) -> None: + """ + Prints the current state of a grid. + If no grid is specified, the current game grid (self.grid) is printed + """ + if grid is None: + grid = self.grid symbols = { Player.one: "X", Player.two: "O", Player.undefined: " " } - print("┌───┬───┬───┐") - for i, row in enumerate(self.grid): - print("│", end="") + print("\t┌───┬───┬───┐") + for i, row in enumerate(grid): + print("\t│", end="") for field in row: print(f" {symbols[field]} │", end="") - if i < len(self.grid) - 1: - print("\n├───┼───┼───┤") - print("\n└───┴───┴───┘\n\n") + if i < len(grid) - 1: + print("\n\t├───┼───┼───┤") + print("\n\t└───┴───┴───┘\n\n") def check_winner(self, grid:np.ndarray|None = None) -> Player: - """ Checks, if the game is won. - If the game is won by a player, returns this player. - Else, returns Player.undefined """ + """ + Checks, if the game is won. + If the game is won by a player, returns this player. + Else, returns Player.undefined + """ if grid is None: grid = self.grid @@ -83,21 +107,90 @@ class TicTacToe(): if grid[1, 1] != Player.undefined: return grid[1, 1] - # Base Case - No winner - return Player.undefined - - def is_won(self, player:Player|int) -> bool: - player = self._get_player_from_int(player) - return self.check_winner() == player + # No winner + if Player.undefined in grid: + return Player.undefined + return Player.grid_full + def _maximize(self, grid:np.ndarray) -> float: + """ + Helper Method for the maximizing Player + """ + best = -np.inf + for i in range(3): + for j in range(3): + if grid[i, j] == Player.undefined: + grid[i, j] = self.active_player + score = self._evaluate_recursively(grid, self.opponent) + grid[i, j] = Player.undefined + best = max(best, score) + return best + + def _minimize(self, grid:np.ndarray) -> float: + """ + Helper Method for the minimizing player + """ + best = np.inf + for i in range(3): + for j in range(3): + if grid[i, j] == Player.undefined: + grid[i, j] = self.opponent + score = self._evaluate_recursively(grid, self.active_player) + grid[i, j] = Player.undefined + best = min(best, score) + return best + + def _evaluate_recursively(self, grid:np.ndarray, current:Player) -> float: + """ + Helper Method for calculating the best move. + Recursively evaluate the board state from the perspective of `current`. + """ + winner = self.check_winner(grid) + if winner == self.active_player: + return 1 + elif winner == get_opponent(self.active_player): + return -1 + # Draw + if not any(cell == Player.undefined for row in grid for cell in row): + return 0 + if current == self.active_player: + return self._maximize(grid) + else: + return self._minimize(grid) + + def get_best_move(self, player:Player|int) -> tuple[int, int]: + """ + Returns the best move for the given Player as a tuple of integers. + """ + self.active_player = self._get_player_from_int(player) + self.opponent = get_opponent(self.active_player) + best_score = -np.inf + move = (-1, -1) + for i in range(3): + for j in range(3): + if self.grid[i, j] == Player.undefined: + self.grid[i, j] = self.active_player + score = self._evaluate_recursively(self.grid, self.opponent) + self.grid[i, j] = Player.undefined + if score > best_score: + best_score = score + move = (i, j) + return move if __name__ == "__main__": ttt = TicTacToe() - ttt.add(1, 1, 1) - ttt.print_grid() - ttt.add(0, 1, 2) - ttt.print_grid() - ttt.add(0, 0, 1) - ttt.print_grid() - ttt.add(0, 2, 2) + #ttt.add(1, 1, 1) + #ttt.print_grid() + #ttt.add(2, 1, 2) ttt.print_grid() + current_player = Player.one + while ttt.check_winner() == Player.undefined: + move = ttt.get_best_move(current_player) + ttt.add(move[0], move[1], current_player) + if current_player == Player.one: + current_player = Player.two + else: + current_player = Player.one + ttt.print_grid() + +