From c4d3abaa539223f6d99ddbe944ec1a9a826fbd2b Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 1 Apr 2018 20:25:58 +0530 Subject: [PATCH 1/5] Resolve recursion issue in Backgammon class --- games.py | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/games.py b/games.py index f129ecd1d..444bcc745 100644 --- a/games.py +++ b/games.py @@ -61,7 +61,9 @@ def min_value(state, dice_roll): return v def chance_node(state, action): + print(action, game.dice_roll) res_state = game.result(state, action) + game.display(res_state) if game.terminal_test(res_state): return game.utility(res_state, player) sum_chances = 0 @@ -79,6 +81,7 @@ def chance_node(state, action): return sum_chances / num_chances # Body of expectiminimax: + print(game.dice_roll) return argmax(game.actions(state), key=lambda a: chance_node(state, a)) @@ -246,6 +249,7 @@ def play_game(self, *players): state = self.initial while True: for player in players: + self.display(state) move = player(self, state) state = self.result(state, move) if self.terminal_test(state): @@ -399,17 +403,17 @@ def __init__(self): self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) # TODO : Add bar to Board class where a blot is placed when it is hit. point = {'W':0, 'B':0} - self.board = [point.copy() for index in range(24)] - self.board[0]['B'] = self.board[23]['W'] = 2 - self.board[5]['W'] = self.board[18]['B'] = 5 - self.board[7]['W'] = self.board[16]['B'] = 3 - self.board[11]['B'] = self.board[12]['W'] = 5 + board = [point.copy() for index in range(24)] + board[0]['B'] = board[23]['W'] = 2 + board[5]['W'] = board[18]['B'] = 5 + board[7]['W'] = board[16]['B'] = 3 + board[11]['B'] = board[12]['W'] = 5 self.allow_bear_off = {'W': False, 'B': False} self.initial = GameState(to_move='W', utility=0, - board=self.board, - moves=self.get_all_moves(self.board, 'W')) + board=board, + moves=self.get_all_moves(board, 'W')) def actions(self, state): """Returns a list of legal moves for a state.""" @@ -420,16 +424,16 @@ def actions(self, state): legal_moves = [] for move in moves: board = copy.deepcopy(state.board) - if self.is_legal_move(move, self.dice_roll, player): + if self.is_legal_move(board, move, self.dice_roll, player): legal_moves.append(move) return legal_moves def result(self, state, move): board = copy.deepcopy(state.board) player = state.to_move - self.move_checker(move[0], self.dice_roll[0], player) + self.move_checker(board, move[0], self.dice_roll[0], player) if len(move) == 2: - self.move_checker(move[1], self.dice_roll[1], player) + self.move_checker(board, move[1], self.dice_roll[1], player) to_move = ('W' if player == 'B' else 'B') return GameState(to_move=to_move, utility=self.compute_utility(board, move, player), @@ -452,7 +456,7 @@ def get_all_moves(self, board, player): all_points = board taken_points = [index for index, point in enumerate(all_points) if point[player] > 0] - if self.checkers_at_home(player) == 1: + if self.checkers_at_home(board, player) == 1: return [(taken_points[0], )] moves = list(itertools.permutations(taken_points, 2)) moves = moves + [(index, index) for index, point in enumerate(all_points) @@ -480,15 +484,15 @@ def compute_utility(self, board, move, player): return -1 return 0 - def checkers_at_home(self, player): + def checkers_at_home(self, board, player): """Return the no. of checkers at home for a player.""" sum_range = range(0, 7) if player == 'W' else range(17, 24) count = 0 for idx in sum_range: - count = count + self.board[idx][player] + count = count + board[idx][player] return count - def is_legal_move(self, start, steps, player): + def is_legal_move(self, board, start, steps, player): """Move is a tuple which contains starting points of checkers to be moved during a player's turn. An on-board move is legal if both the destinations are open. A bear-off move is the one where a checker is moved off-board. @@ -497,31 +501,31 @@ def is_legal_move(self, start, steps, player): dest_range = range(0, 24) move1_legal = move2_legal = False if dest1 in dest_range: - if self.is_point_open(player, self.board[dest1]): - self.move_checker(start[0], steps[0], player) + if self.is_point_open(player, board[dest1]): + self.move_checker(board, start[0], steps[0], player) move1_legal = True else: if self.allow_bear_off[player]: - self.move_checker(start[0], steps[0], player) + self.move_checker(board, start[0], steps[0], player) move1_legal = True if not move1_legal: return False if dest2 in dest_range: - if self.is_point_open(player, self.board[dest2]): + if self.is_point_open(player, board[dest2]): move2_legal = True else: if self.allow_bear_off[player]: move2_legal = True return move1_legal and move2_legal - def move_checker(self, start, steps, player): + def move_checker(self, board, start, steps, player): """Move a checker from starting point by a given number of steps""" dest = start + steps dest_range = range(0, 24) - self.board[start][player] -= 1 + board[start][player] -= 1 if dest in dest_range: - self.board[dest][player] += 1 - if self.checkers_at_home(player) == 15: + board[dest][player] += 1 + if self.checkers_at_home(board, player) == 15: self.allow_bear_off[player] = True def is_point_open(self, player, point): @@ -530,3 +534,7 @@ def is_point_open(self, player, point): move a checker to a point only if it is open.""" opponent = 'B' if player == 'W' else 'W' return point[opponent] <= 1 + +if __name__ == "__main__": + bgm = Backgammon() + print(bgm.play_game(expectiminimax_player, query_player)) From b7632395e1f25cc479cd828ed08bd7feef7a4700 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 1 Apr 2018 22:30:36 +0530 Subject: [PATCH 2/5] Handle empty action list in player functions --- games.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/games.py b/games.py index 444bcc745..d6cfb6284 100644 --- a/games.py +++ b/games.py @@ -83,7 +83,7 @@ def chance_node(state, action): # Body of expectiminimax: print(game.dice_roll) return argmax(game.actions(state), - key=lambda a: chance_node(state, a)) + key=lambda a: chance_node(state, a)) if len(game.actions(state)) > 0 else None def alphabeta_search(state, game): @@ -184,18 +184,21 @@ def query_player(game, state): game.display(state) print("available moves: {}".format(game.actions(state))) print("") - move_string = input('Your move? ') - try: - move = eval(move_string) - except NameError: - move = move_string + move = None + if len(game.actions(state)) > 0: + move_string = input('Your move? ') + try: + move = eval(move_string) + except NameError: + move = move_string + else: + print('No legal moves. Passing turn to next player.') return move def random_player(game, state): """A player that chooses a legal move at random.""" - return random.choice(game.actions(state)) - + return random.choice(game.actions(state)) if len(game.actions(state)) > 0 else None def alphabeta_player(game, state): return alphabeta_search(state, game) @@ -409,7 +412,6 @@ def __init__(self): board[7]['W'] = board[16]['B'] = 3 board[11]['B'] = board[12]['W'] = 5 self.allow_bear_off = {'W': False, 'B': False} - self.initial = GameState(to_move='W', utility=0, board=board, @@ -440,7 +442,6 @@ def result(self, state, move): board=board, moves=self.get_all_moves(board, to_move)) - def utility(self, state, player): """Return the value to player; 1 for win, -1 for loss, 0 otherwise.""" return state.utility if player == 'W' else -state.utility From a07ee02bbfc75d26d32c4b96d5fedb99acb87a46 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Mon, 2 Apr 2018 00:16:28 +0530 Subject: [PATCH 3/5] Add play_game method for backgammon --- games.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/games.py b/games.py index d6cfb6284..0c4804dc3 100644 --- a/games.py +++ b/games.py @@ -61,9 +61,7 @@ def min_value(state, dice_roll): return v def chance_node(state, action): - print(action, game.dice_roll) res_state = game.result(state, action) - game.display(res_state) if game.terminal_test(res_state): return game.utility(res_state, player) sum_chances = 0 @@ -81,9 +79,8 @@ def chance_node(state, action): return sum_chances / num_chances # Body of expectiminimax: - print(game.dice_roll) return argmax(game.actions(state), - key=lambda a: chance_node(state, a)) if len(game.actions(state)) > 0 else None + key=lambda a: chance_node(state, a)) if game.actions(state) else None def alphabeta_search(state, game): @@ -185,20 +182,20 @@ def query_player(game, state): print("available moves: {}".format(game.actions(state))) print("") move = None - if len(game.actions(state)) > 0: + if game.actions(state): move_string = input('Your move? ') try: move = eval(move_string) except NameError: move = move_string else: - print('No legal moves. Passing turn to next player.') + print('no legal moves: passing turn to next player') return move def random_player(game, state): """A player that chooses a legal move at random.""" - return random.choice(game.actions(state)) if len(game.actions(state)) > 0 else None + return random.choice(game.actions(state)) if game.actions(state) else None def alphabeta_player(game, state): return alphabeta_search(state, game) @@ -252,7 +249,6 @@ def play_game(self, *players): state = self.initial while True: for player in players: - self.display(state) move = player(self, state) state = self.result(state, move) if self.terminal_test(state): @@ -413,12 +409,12 @@ def __init__(self): board[11]['B'] = board[12]['W'] = 5 self.allow_bear_off = {'W': False, 'B': False} self.initial = GameState(to_move='W', - utility=0, + utility=0, board=board, moves=self.get_all_moves(board, 'W')) def actions(self, state): - """Returns a list of legal moves for a state.""" + """Return a list of legal moves for a state.""" player = state.to_move moves = state.moves if len(moves) == 1 and len(moves[0]) == 1: @@ -468,11 +464,11 @@ def display(self, state): """Display state of the game.""" board = state.board player = state.to_move - print("Current State : ") + print("current state : ") for index, point in enumerate(board): if point['W'] != 0 or point['B'] != 0: - print("Point : ", index, " W : ", point['W'], " B : ", point['B']) - print("To play : ", player) + print("point : ", index, " W : ", point['W'], " B : ", point['B']) + print("to play : ", player) def compute_utility(self, board, move, player): """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" @@ -536,6 +532,20 @@ def is_point_open(self, player, point): opponent = 'B' if player == 'W' else 'W' return point[opponent] <= 1 -if __name__ == "__main__": - bgm = Backgammon() - print(bgm.play_game(expectiminimax_player, query_player)) + def play_game(self, *players): + """Play backgammon.""" + state = self.initial + while True: + for player in players: + saved_dice_roll = self.dice_roll + move = player(self, state) + self.dice_roll = saved_dice_roll + if move is not None: + state = self.result(state, move) + if state.to_move == 'W': + self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) + else: + self.dice_roll = (random.randint(1, 6), random.randint(1, 6)) + if self.terminal_test(state): + self.display(state) + return self.utility(state, self.to_move(self.initial)) From b7fe9d01ca3b5c4c4e1eec8505d4c87bb8058162 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Thu, 5 Apr 2018 01:33:25 +0530 Subject: [PATCH 4/5] Refactor functions --- games.py | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/games.py b/games.py index 0c4804dc3..275830a6c 100644 --- a/games.py +++ b/games.py @@ -41,6 +41,9 @@ def min_value(state): # ______________________________________________________________________________ +dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) +direction = {'W' : -1, 'B' : 1} + def expectiminimax(state, game): """Return the best move for a player after dice are thrown. The game tree includes chance nodes along with min and max nodes. [Figure 5.11]""" @@ -66,16 +69,14 @@ def chance_node(state, action): return game.utility(res_state, player) sum_chances = 0 num_chances = 21 - dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) - if res_state.to_move == 'W': - for val in dice_rolls: - game.dice_roll = (-val[0], -val[1]) - sum_chances += max_value(res_state, - (-val[0], -val[1])) * (1/36 if val[0] == val[1] else 1/18) - elif res_state.to_move == 'B': - for val in dice_rolls: - game.dice_roll = val - sum_chances += min_value(res_state, val) * (1/36 if val[0] == val[1] else 1/18) + for val in dice_rolls: + game.dice_roll = tuple(map((direction[res_state.to_move]).__mul__, val)) + util = 0 + if res_state.to_move == player: + util = max_value(res_state, game.dice_roll) + else: + util = min_value(res_state, game.dice_roll) + sum_chances += util * (1/36 if val[0] == val[1] else 1/18) return sum_chances / num_chances # Body of expectiminimax: @@ -399,15 +400,15 @@ class Backgammon(Game): def __init__(self): """Initial state of the game""" - self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) + self.dice_roll = tuple(map((direction['W']).__mul__, random.choice(dice_rolls))) # TODO : Add bar to Board class where a blot is placed when it is hit. - point = {'W':0, 'B':0} + point = {'W' : 0, 'B' : 0} board = [point.copy() for index in range(24)] board[0]['B'] = board[23]['W'] = 2 board[5]['W'] = board[18]['B'] = 5 board[7]['W'] = board[16]['B'] = 3 board[11]['B'] = board[12]['W'] = 5 - self.allow_bear_off = {'W': False, 'B': False} + self.allow_bear_off = {'W' : False, 'B' : False} self.initial = GameState(to_move='W', utility=0, board=board, @@ -466,20 +467,16 @@ def display(self, state): player = state.to_move print("current state : ") for index, point in enumerate(board): - if point['W'] != 0 or point['B'] != 0: - print("point : ", index, " W : ", point['W'], " B : ", point['B']) + print("point : ", index, " W : ", point['W'], " B : ", point['B']) print("to play : ", player) def compute_utility(self, board, move, player): """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" - count = 0 + util = {'W' : 1, 'B' : '-1'} for idx in range(0, 24): - count = count + board[idx][player] - if player == 'W' and count == 0: - return 1 - if player == 'B' and count == 0: - return -1 - return 0 + if board[idx][player] > 0: + return 0 + return util[player] def checkers_at_home(self, board, player): """Return the no. of checkers at home for a player.""" @@ -542,10 +539,8 @@ def play_game(self, *players): self.dice_roll = saved_dice_roll if move is not None: state = self.result(state, move) - if state.to_move == 'W': - self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) - else: - self.dice_roll = (random.randint(1, 6), random.randint(1, 6)) + self.dice_roll = tuple(map((direction[player]).__mul__, + random.choice(dice_rolls))) if self.terminal_test(state): self.display(state) return self.utility(state, self.to_move(self.initial)) From a4cb9433f1fd241ea72b6be88097b0591b4b9838 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Fri, 6 Apr 2018 20:01:19 +0530 Subject: [PATCH 5/5] Update argmax function call --- games.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games.py b/games.py index 275830a6c..23e785bab 100644 --- a/games.py +++ b/games.py @@ -81,7 +81,7 @@ def chance_node(state, action): # Body of expectiminimax: return argmax(game.actions(state), - key=lambda a: chance_node(state, a)) if game.actions(state) else None + key=lambda a: chance_node(state, a), default=None) def alphabeta_search(state, game): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy