diff --git a/ora6/amoba.py b/ora6/amoba.py new file mode 100644 index 0000000..caa9d7a --- /dev/null +++ b/ora6/amoba.py @@ -0,0 +1,166 @@ +import random +from game_search_algorithms import alfabeta_search, minimax_search + +# GAME BASE CLASS +class Game: + def legal_steps(self, state: dict[str, any]): + """Steps that can be made in given state.""" + raise NotImplementedError() + + def take_step(self, step, state): # NOQA + """Result of taking step in given state.""" + raise NotImplementedError + + def goodness(self, state, player): + """Goodness measure of the state for the player.""" + raise NotImplementedError() + + def is_leaf(self, state): + """Is the node a terminal node.""" + return not self.legal_steps(state) + + def next(self, state): + """Return next player.""" + return state['next'] + + def print(self, state): + """Print current state.""" + print(state) + + def next_state(self, state): + """Return next (step, state) list.""" + return [(step, self.take_step(step, state)) + for step in self.legal_steps(state)] + + def __repr__(self): + """Print the name of the game.""" + return '<%s>' % self.__class__.__name__ + + + + + +class TicTacToe(Game): + def __init__(self, h=3, v=3, k=3): + #super().__init__() + self.h = h + self.v = v + self.k = k + + steps = [(i,j) for i in range(1, h+1) for j in range(1, v+1)] + print(steps) + + self.initial = { + #ki kezd + 'next': 'X', + 'steps': steps, + 'result': 0, + 'board': dict() + } + + def print(self, state): + """Let's see the current state.""" + board = state['board'] + for x in range(1, self.h + 1): + for y in range(1, self.v + 1): + print(board.get((x, y), '.'), end=" ") + print() + print('Result of the game: ', state['result']) + print() + + def legal_steps(self, state): + return state['steps'] + + def take_step(self, step, state): + if step not in state['steps']: + return state #ha galiba van ujra lephetunk (??????) + + board = state['board'].copy() + board[step] = self.next(state) #adot pozba beirjuk a jatekost + #modositjuk a lepeseket + steps = state['steps'].copy() + steps.remove(step) #ezzel nem tud foglalt helyre lepni + + return { + 'next': 'X' if state['next'] == 'O' else 'O', + 'steps': steps, + 'result': self.result(board, step, state['next']), + 'board': board + } + + def result(self, board, step, player): + if (self.check_triplets(board, step, player, (0,1)) or self.check_triplets(board, step, player, (1,0)) or + self.check_triplets(board, step, player, (1,1)) or self.check_triplets(board, step, player, (-1,1))): + if player == 'X': + return 1 + else: + return -1 + return 0 + + def check_triplets(self, board, step, player, direction): + n = -1 + dy, dx = direction + + x,y = step + while board.get(x,y) == player: + x,y = dx + x,dy + y + n += 1 + + x,y = step + while board.get(x,y) == player: + x,y = x - dx,y - dy + n += 1 + + return n >= self.k + + def goodness(self, state, player): + return state["result"] if player == 'X' else -state['result'] + + def is_leaf(self, state): + return state['steps'] == [] or state['result'] != 0 #elfogytak a lepesek vagy valaki nyert + + + +# PLAYERS +def random_player(game, state): + """Randomly choose between options""" + return random.choice(game.legal_steps(state)) + + +def alfabeta_player(game, state): + """Search in game tree""" + return alfabeta_search(state, game) + + +def minimax_player(game, state): + """Search in game tree""" + return minimax_search(state, game) + + +def play_game(game, *players): + state = game.initial + game.print(state) + while True: + for player in players: + step = player(game, state) + state = game.take_step(step, state) + game.print(state) + if game.is_leaf(state): + end_result = game.goodness(state, 'X') + return "X wins" if end_result == 1 else "O wins" if end_result == -1 else "Draw" + + +tto = TicTacToe() + +# Test if playing works +play_game(tto, random_player, random_player) + +# Demonstrate the power of the search algorithms +# you can comment out the game.print(state) lines in the play_game function for this +for i in range(1): + print(play_game(tto, random_player, random_player)) # outcome will be random (starting player has the edge) + print(play_game(tto, alfabeta_player, random_player)) # X will always win + print(play_game(tto, minimax_player, alfabeta_player)) # O will most likely win + print(play_game(tto, alfabeta_player, alfabeta_player)) # game will always end in draw + print(play_game(tto, alfabeta_player, minimax_player)) + \ No newline at end of file diff --git a/ora6/game_search_algorithms.py b/ora6/game_search_algorithms.py new file mode 100644 index 0000000..e71ba73 --- /dev/null +++ b/ora6/game_search_algorithms.py @@ -0,0 +1,59 @@ +# SEARCHES +# Minimax search +def minimax_search(state, game): + player = game.next(state) + + # define labels on each level of the tree + def max_value(state): + if game.is_leaf(state): + return game.goodness(state, player) + return max([min_value(s) for (_, s) in game.next_state(state)]) + + def min_value(state): + if game.is_leaf(state): + return game.goodness(state, player) + return min([max_value(s) for (_, s) in game.next_state(state)]) + + # minimax method + children_values = [(a, min_value(s)) for (a, s) in game.next_state(state)] + step, value = max(children_values, key=lambda a_s: a_s[1]) + return step + + +def alfabeta_search(state, game, d=4, cut_test=None, expand=None): + """Search game tree until defined depth""" + player = game.next(state) + + def max_value(state, alfa, beta, depth): + if cut_test(state, depth): + return expand(state) + v = float("-inf") + for (a, s) in game.next_state(state): + v = max(v, min_value(s, alfa, beta, depth+1)) + if v >= beta: + return v + alfa = max(alfa, v) + return v + + def min_value(state, alfa, beta, depth): + if cut_test(state, depth): + return expand(state) + v = float("inf") + for (a, s) in game.next_state(state): + v = min(v, max_value(s, alfa, beta, depth+1)) + if v <= alfa: + return v + beta = min(beta, v) + return v + + # Alfabeta search + cut_test = cut_test or (lambda state, depth: depth > d or game.is_leaf(state)) + expand = expand or (lambda state: game.goodness(state, player)) + alfa = float("-inf") + best_step = None + for a, s in game.next_state(state): + v = min_value(s, alfa, float("inf"), 0) + if v > alfa: + alfa = v + best_step = a + return best_step diff --git a/ora6/tictactoe.py b/ora6/tictactoe.py new file mode 100644 index 0000000..d927080 --- /dev/null +++ b/ora6/tictactoe.py @@ -0,0 +1,160 @@ +import random +from game_search_algorithms import alfabeta_search, minimax_search + + +# GAME BASE CLASS +class Game: + def legal_steps(self, state: dict[str, any]): + """Steps that can be made in given state.""" + raise NotImplementedError() + + def take_step(self, step, state): # NOQA + """Result of taking step in given state.""" + raise NotImplementedError + + def goodness(self, state, player): + """Goodness measure of the state for the player.""" + raise NotImplementedError() + + def is_leaf(self, state): + """Is the node a terminal node.""" + return not self.legal_steps(state) + + def next(self, state): + """Return next player.""" + return state['next'] + + def print(self, state): + """Print current state.""" + print(state) + + def next_state(self, state): + """Return next (step, state) list.""" + return [(step, self.take_step(step, state)) + for step in self.legal_steps(state)] + + def __repr__(self): + """Print the name of the game.""" + return '<%s>' % self.__class__.__name__ + + +# TIC-TAC TOE CLASS +class TicTacToe(Game): + """3x3 version.""" + + def __init__(self, h=3, v=3, k=3): + """Base of the game""" + self.h = h + self.v = v + self.k = k + steps = [(x, y) for x in range(1, h + 1) for y in range(1, v + 1)] + print(steps) + print(type(steps)) + print(steps[0]) + self.initial = {'next': 'X', + 'result': 0, + 'board': {}, + 'steps': steps} + + def legal_steps(self, state): + """We can step on every empty cell""" + return state['steps'] + + def take_step(self, step, state): + """Effect of the step""" + if step not in state['steps']: + return state + board = state['board'].copy() + board[step] = state['next'] + steps = list(state['steps']) + steps.remove(step) + return { + 'next': 'X' if state['next'] == 'O' else 'O', + 'result': self.result(board, step, state['next']), # need to change + 'board': board, + 'steps': steps + } + + def result(self, board, step, player): + """If X wins with this step then return 1. If O wins with this then return -1. Else return 0.""" + if (self.check_triples(board, step, player, (0, 1)) or self.check_triples(board, step, player, (1, 0)) or + self.check_triples(board, step, player, (1, -1)) or self.check_triples(board, step, player, (1, 1))): + return 1 if player == 'X' else -1 + return 0 + + def check_triples(self, board, step, player, direction): + """Check for triples in a direction.""" + delta_x, delta_y = direction + x, y = step + n = 0 + while board.get((x, y)) == player: + n += 1 + x, y = x + delta_x, y + delta_y + x, y = step + while board.get((x, y)) == player: + n += 1 + x, y = x - delta_x, y - delta_y + n -= 1 + return n >= self.k + + def goodness(self, state, player): + """X goodness: 1, if it wins; -1, if it loses, 0 if draw.""" + return state['result'] if player == "X" else -state['result'] + + def is_leaf(self, state): + """If someone won or the table is full it will be the end of the game.""" + return state['result'] != 0 or len(state['steps']) == 0 + + def print(self, state): + """Let's see the current state.""" + board = state['board'] + for x in range(1, self.h + 1): + for y in range(1, self.v + 1): + print(board.get((x, y), '.'), end=" ") + print() + print('Result of the game: ', state['result']) + print() + + +# PLAYERS +def random_player(game, state): + """Randomly choose between options""" + return random.choice(game.legal_steps(state)) + + +def alfabeta_player(game, state): + """Search in game tree""" + return alfabeta_search(state, game) + + +def minimax_player(game, state): + """Search in game tree""" + return minimax_search(state, game) + + +def play_game(game, *players): + state = game.initial + game.print(state) + while True: + for player in players: + step = player(game, state) + state = game.take_step(step, state) + game.print(state) + if game.is_leaf(state): + end_result = game.goodness(state, 'X') + return "X wins" if end_result == 1 else "O wins" if end_result == -1 else "Draw" + + +tto = TicTacToe() + +# Test if playing works +play_game(tto, random_player, random_player) + +# Demonstrate the power of the search algorithms +# you can comment out the game.print(state) lines in the play_game function for this +for i in range(1): + print(play_game(tto, random_player, random_player)) # outcome will be random (starting player has the edge) + print(play_game(tto, alfabeta_player, random_player)) # X will always win + print(play_game(tto, minimax_player, alfabeta_player)) # O will most likely win + print(play_game(tto, alfabeta_player, alfabeta_player)) # game will always end in draw + print(play_game(tto, alfabeta_player, minimax_player))