From 855cc81a358548d18eb50eebc00ff67b0149db75 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Mon, 12 Mar 2018 20:36:38 +0530 Subject: [PATCH 1/4] Fix expectiminimax and utility issues --- games.py | 64 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/games.py b/games.py index 4868367f8..f1c5d061a 100644 --- a/games.py +++ b/games.py @@ -46,40 +46,46 @@ def expectiminimax(state, game): includes chance nodes along with min and max nodes. [Figure 5.11]""" player = game.to_move(state) - def max_value(state): - if game.terminal_test(state): - return game.utility(state, player) + def max_value(state, depth): v = -infinity for a in game.actions(state): - v = max(v, chance_node(state, a)) + v = max(v, chance_node(copy.deepcopy(state), a, depth+1)) return v - def min_value(state): - if game.terminal_test(state): - return game.utility(state, player) + def min_value(state, depth): v = infinity for a in game.actions(state): - v = min(v, chance_node(state, a)) + v = min(v, chance_node(copy.deepcopy(state), a, depth+1)) return v - def chance_node(state, action): + def chance_node(state, action, depth): res_state = game.result(state, action) + if game.terminal_test(state): + return game.utility(state, player) + print(action, " : Action") 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.display(res_state) game.dice_roll = (-val[0], -val[1]) - sum_chances += max_value(res_state) * (1/36 if val[0] == val[1] else 1/18) + print(depth) + print(game.dice_roll," : Dice roll") + sum_chances += max_value(res_state, depth+1) * (1/36 if val[0] == val[1] else 1/18) elif res_state.to_move == 'B': for val in dice_rolls: + game.display(res_state) game.dice_roll = val - sum_chances += min_value(res_state) * (1/36 if val[0] == val[1] else 1/18) + print(depth) + print(game.dice_roll) + sum_chances += min_value(res_state, depth+1) * (1/36 if val[0] == val[1] else 1/18) + print("chaaaaaaaaaaaaaaaaaaaaaaaaannnnce : ", sum_chances / num_chances) return sum_chances / num_chances # Body of expectiminimax: return argmax(game.actions(state), - key=lambda a: chance_node(state, a)) + key=lambda a: chance_node(state, a, 0)) def alphabeta_search(state, game): @@ -197,6 +203,8 @@ def alphabeta_player(game, state): return alphabeta_search(state, game) def expectiminimax_player(game, state): + game.display(state) + print(game.dice_roll) return expectiminimax(state, game) @@ -246,6 +254,7 @@ def play_game(self, *players): while True: for player in players: move = player(self, state) + print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") state = self.result(state, move) if self.terminal_test(state): self.display(state) @@ -403,6 +412,8 @@ def actions(self, state): """Returns a list of legal moves for a state.""" player = state.to_move moves = state.moves + if len(moves) == 1 and len(moves[0]) == 1: + return moves legal_moves = [] for move in moves: board = copy.deepcopy(state.board) @@ -414,7 +425,8 @@ def result(self, state, move): board = copy.deepcopy(state.board) player = state.to_move board.move_checker(move[0], self.dice_roll[0], player) - board.move_checker(move[1], self.dice_roll[1], player) + if len(move) == 2: + board.move_checker(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, to_move), @@ -437,6 +449,8 @@ def get_all_moves(self, board, player): all_points = board.points taken_points = [index for index, point in enumerate(all_points) if point[player] > 0] + if board.checkers_at_home(player) == 1: + return [(taken_points[0], )] moves = list(itertools.permutations(taken_points, 2)) moves = moves + [(index, index) for index, point in enumerate(all_points) if point[player] >= 2] @@ -448,12 +462,12 @@ def display(self, state): player = state.to_move for index, point in enumerate(board.points): 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("player : ", player) def compute_utility(self, board, move, player): - """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" + """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" count = 0 for idx in range(0, 24): count = count + board.points[idx][player] @@ -531,3 +545,23 @@ 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() + board = BackgammonBoard() + board.points[0]['B'] = board.points[23]['W'] = 0 + board.points[5]['W'] = board.points[18]['B'] = 0 + board.points[7]['W'] = board.points[16]['B'] = 0 + board.points[11]['B'] = board.points[12]['W'] = 0 + board.points[11]['B'] = board.points[0]['W'] = 1 + board.allow_bear_off = {'W': True, 'B': False} + initial = GameState(to_move='W', + utility=0, board=board, moves=bgm.get_all_moves(board, 'W')) + moves = bgm.actions(initial) + print(moves) + print(bgm.get_all_moves(board, 'W')) + bgm.display(initial) + res = bgm.result(initial, moves[0]) + bgm.display(res) + print(bgm.utility(res, res.to_move)) From 4add28fddd89fa21eb89b223fd419cd7fdfc2b16 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Mon, 12 Mar 2018 20:38:48 +0530 Subject: [PATCH 2/4] Correct result function --- games.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/games.py b/games.py index f1c5d061a..702a62178 100644 --- a/games.py +++ b/games.py @@ -429,7 +429,7 @@ def result(self, state, move): board.move_checker(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, to_move), + utility=self.compute_utility(board, move, player), board=board, moves=self.get_all_moves(board, to_move)) @@ -546,7 +546,6 @@ def is_point_open(self, player, point): opponent = 'B' if player == 'W' else 'W' return point[opponent] <= 1 - if __name__ == "__main__": bgm = Backgammon() board = BackgammonBoard() From 4151cbb4b4b7711dddeceb557c35470f938efc7e Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Thu, 15 Mar 2018 20:47:34 +0530 Subject: [PATCH 3/4] Fix issue with dice roll in different states --- games.py | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/games.py b/games.py index 702a62178..2555897fe 100644 --- a/games.py +++ b/games.py @@ -46,46 +46,50 @@ def expectiminimax(state, game): includes chance nodes along with min and max nodes. [Figure 5.11]""" player = game.to_move(state) - def max_value(state, depth): + def max_value(state, dice_roll): v = -infinity for a in game.actions(state): - v = max(v, chance_node(copy.deepcopy(state), a, depth+1)) + v = max(v, chance_node(state, a)) + game.dice_roll = dice_roll + print("qqqqqqqqqqqqqqqqqqqqq") + game.display(state) return v - def min_value(state, depth): + def min_value(state, dice_roll): v = infinity for a in game.actions(state): - v = min(v, chance_node(copy.deepcopy(state), a, depth+1)) + v = min(v, chance_node(state, a)) + game.dice_roll = dice_roll + print("qqqqqqqqqqqqqqqqqqqqq") + game.display(state) return v - def chance_node(state, action, depth): + def chance_node(state, action): res_state = game.result(state, action) - if game.terminal_test(state): - return game.utility(state, player) - print(action, " : Action") + if game.terminal_test(res_state): + return game.utility(res_state, player) + #print("Move checker from :",action) 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.display(res_state) + #game.display(res_state) game.dice_roll = (-val[0], -val[1]) - print(depth) - print(game.dice_roll," : Dice roll") - sum_chances += max_value(res_state, depth+1) * (1/36 if val[0] == val[1] else 1/18) + #print("Player:", res_state.to_move, " threw dice with value:", game.dice_roll) + 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.display(res_state) + #game.display(res_state) game.dice_roll = val - print(depth) - print(game.dice_roll) - sum_chances += min_value(res_state, depth+1) * (1/36 if val[0] == val[1] else 1/18) - print("chaaaaaaaaaaaaaaaaaaaaaaaaannnnce : ", sum_chances / num_chances) + #print("Player:", res_state.to_move, " threw dice with value:", game.dice_roll) + sum_chances += min_value(res_state, val) * (1/36 if val[0] == val[1] else 1/18) + #print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", sum_chances/num_chances) return sum_chances / num_chances # Body of expectiminimax: return argmax(game.actions(state), - key=lambda a: chance_node(state, a, 0)) + key=lambda a: chance_node(state, a)) def alphabeta_search(state, game): @@ -204,7 +208,7 @@ def alphabeta_player(game, state): def expectiminimax_player(game, state): game.display(state) - print(game.dice_roll) + print("Player:", state.to_move, " threw dice with value:", game.dice_roll) return expectiminimax(state, game) @@ -463,7 +467,8 @@ def display(self, state): for index, point in enumerate(board.points): if point['W'] != 0 or point['B'] != 0: print("Point : ", index, " W : ", point['W'], " B : ", point['B']) - print("player : ", player) + print("----------------------------Turn Over---------------------------") + print("Next to play : ", player) def compute_utility(self, board, move, player): @@ -547,6 +552,7 @@ def is_point_open(self, player, point): return point[opponent] <= 1 if __name__ == "__main__": + """ bgm = Backgammon() board = BackgammonBoard() board.points[0]['B'] = board.points[23]['W'] = 0 @@ -563,4 +569,7 @@ def is_point_open(self, player, point): bgm.display(initial) res = bgm.result(initial, moves[0]) bgm.display(res) - print(bgm.utility(res, res.to_move)) + """ + bgm = Backgammon() + bgm.play_game(expectiminimax_player, query_player) + # print(bgm.utility(res, res.to_move)) From ceda8545b25b4dffe2f741bfb7b8fc8bf6276950 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Thu, 15 Mar 2018 21:18:05 +0530 Subject: [PATCH 4/4] Refactor code --- games.py | 56 ++++++++++---------------------------------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/games.py b/games.py index 2555897fe..e71e47aca 100644 --- a/games.py +++ b/games.py @@ -42,7 +42,7 @@ def min_value(state): # ______________________________________________________________________________ def expectiminimax(state, game): - """Returns the best move for a player after dice are thrown. The game tree + """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]""" player = game.to_move(state) @@ -51,40 +51,31 @@ def max_value(state, dice_roll): for a in game.actions(state): v = max(v, chance_node(state, a)) game.dice_roll = dice_roll - print("qqqqqqqqqqqqqqqqqqqqq") - game.display(state) return v - def min_value(state, dice_roll): + def min_value(state, dice_roll): v = infinity for a in game.actions(state): v = min(v, chance_node(state, a)) game.dice_roll = dice_roll - print("qqqqqqqqqqqqqqqqqqqqq") - game.display(state) return v def chance_node(state, action): res_state = game.result(state, action) if game.terminal_test(res_state): return game.utility(res_state, player) - #print("Move checker from :",action) 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.display(res_state) game.dice_roll = (-val[0], -val[1]) - #print("Player:", res_state.to_move, " threw dice with value:", game.dice_roll) - sum_chances += max_value(res_state, (-val[0], -val[1])) * (1/36 if val[0] == val[1] else 1/18) + 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.display(res_state) game.dice_roll = val - #print("Player:", res_state.to_move, " threw dice with value:", game.dice_roll) sum_chances += min_value(res_state, val) * (1/36 if val[0] == val[1] else 1/18) - #print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", sum_chances/num_chances) return sum_chances / num_chances # Body of expectiminimax: @@ -207,8 +198,6 @@ def alphabeta_player(game, state): return alphabeta_search(state, game) def expectiminimax_player(game, state): - game.display(state) - print("Player:", state.to_move, " threw dice with value:", game.dice_roll) return expectiminimax(state, game) @@ -258,7 +247,6 @@ def play_game(self, *players): while True: for player in players: move = player(self, state) - print("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") state = self.result(state, move) if self.terminal_test(state): self.display(state) @@ -454,7 +442,7 @@ def get_all_moves(self, board, player): taken_points = [index for index, point in enumerate(all_points) if point[player] > 0] if board.checkers_at_home(player) == 1: - return [(taken_points[0], )] + return [(taken_points[0], )] moves = list(itertools.permutations(taken_points, 2)) moves = moves + [(index, index) for index, point in enumerate(all_points) if point[player] >= 2] @@ -464,15 +452,14 @@ def display(self, state): """Display state of the game.""" board = state.board player = state.to_move + print("Current State : ") for index, point in enumerate(board.points): if point['W'] != 0 or point['B'] != 0: print("Point : ", index, " W : ", point['W'], " B : ", point['B']) - print("----------------------------Turn Over---------------------------") - print("Next to play : ", player) - + 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.""" + """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" count = 0 for idx in range(0, 24): count = count + board.points[idx][player] @@ -501,7 +488,7 @@ def __init__(self): self.allow_bear_off = {'W': False, 'B': False} def checkers_at_home(self, player): - """Returns the no. of checkers at home for a 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: @@ -535,7 +522,7 @@ def is_legal_move(self, start, steps, player): return move1_legal and move2_legal def move_checker(self, start, steps, player): - """Moves a checker from starting point by a given number of steps""" + """Move a checker from starting point by a given number of steps""" dest = start + steps dest_range = range(0, 24) self.points[start][player] -= 1 @@ -550,26 +537,3 @@ 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() - board = BackgammonBoard() - board.points[0]['B'] = board.points[23]['W'] = 0 - board.points[5]['W'] = board.points[18]['B'] = 0 - board.points[7]['W'] = board.points[16]['B'] = 0 - board.points[11]['B'] = board.points[12]['W'] = 0 - board.points[11]['B'] = board.points[0]['W'] = 1 - board.allow_bear_off = {'W': True, 'B': False} - initial = GameState(to_move='W', - utility=0, board=board, moves=bgm.get_all_moves(board, 'W')) - moves = bgm.actions(initial) - print(moves) - print(bgm.get_all_moves(board, 'W')) - bgm.display(initial) - res = bgm.result(initial, moves[0]) - bgm.display(res) - """ - bgm = Backgammon() - bgm.play_game(expectiminimax_player, query_player) - # print(bgm.utility(res, res.to_move))