From bdfcf76b19eb1424344850653754063814ab4362 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 11 Feb 2018 15:15:17 +0530 Subject: [PATCH 1/7] Create model classes for backgammon --- games.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/games.py b/games.py index 00a2c33d3..a2301aa85 100644 --- a/games.py +++ b/games.py @@ -342,3 +342,33 @@ def __init__(self, h=7, v=6, k=4): def actions(self, state): return [(x, y) for (x, y) in state.moves if y == 1 or (x, y - 1) in state.board] + + +class Backgammon(Game): + def __init__(self): + dice_roll = (random.randint(1, 6), random.randint(1, 6)) + # self.initial = GameState(to_move='W', utility=0, board=Board(), moves=moves) + +class Board: + def __init__(self): + self.points = [Point()]*24 + self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 + self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5 + self.points[7].checkers['W'] = self.points[16].checkers['B'] = 3 + self.points[11].checkers['B'] = self.points[12].checkers['W'] = 5 + self.bar = {'W':0, 'B':0} + +class Point: + def __init__(self): + self.checkers = {'W':0, 'B':0} + + def is_open_for(self, player): + opponent = 'B' if player=='W' else 'W' + return self.checkers[opponent] <= 1 + + def add_checker(self, player): + self.checkers[player] += 1 + + def remove_checker(self, player): + self.checkers[player] -= 1 + From a6a394e0cd94ade6347e15cdd32970dc68f2578a Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 18 Feb 2018 23:41:32 +0530 Subject: [PATCH 2/7] Add game functions to model --- games.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 5 deletions(-) diff --git a/games.py b/games.py index a2301aa85..feb46a2e4 100644 --- a/games.py +++ b/games.py @@ -2,8 +2,9 @@ from collections import namedtuple import random - -from utils import argmax +import itertools +import copy +from utils import argmax, vector_add infinity = float('inf') GameState = namedtuple('GameState', 'to_move, utility, board, moves') @@ -346,17 +347,117 @@ def actions(self, state): class Backgammon(Game): def __init__(self): - dice_roll = (random.randint(1, 6), random.randint(1, 6)) - # self.initial = GameState(to_move='W', utility=0, board=Board(), moves=moves) + self.dice_roll = (random.randint(1, 6), random.randint(1, 6)) + board=Board() + self.initial = GameState(to_move='W', utility=0, board=board, moves=self.get_all_moves(board, 'W')) + + def actions(self, player, state): + moves = state.moves + valid_moves = [] + for move in moves: + board = copy.copy(state.board) + if board.is_valid_move(move, self.dice_roll, player): + valid_moves.append(move) + return valid_moves + + def result(self, state, move): + board = copy.copy(state.board) + player = state.to_move + print(move, " xxxxx ",self.dice_roll) + board.move_checker(move[0], self.dice_roll[0], state.to_move) + board.move_checker(move[1], self.dice_roll[1], state.to_move) + return GameState(to_move=('W' if player == 'B' else 'B'), + utility=self.compute_utility(board, move, player), + board=board, + moves=self.get_all_moves(board, player)) + + + 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 + + def terminal_test(self, state): + """A state is terminal if one player wins.""" + return state.utility != 0 + + def get_all_moves(self, board, player): + all_points = board.points + taken_points = [index for index, point in enumerate(all_points) if point.checkers[player] > 0] + moves = list(itertools.product(taken_points, repeat=2)) + return moves + + def display(self, state): + """Print or otherwise display the state.""" + board = state.board + player = state.to_move + for index, point in enumerate(board.points): + print("Point : ",index) + print("W : ", point.checkers['W']) + print("B : ", point.checkers['B']) + print("player : ", state.to_move ) + + 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 + for idx in range(0, 24): + count = count + board.points[idx].checkers[player] + + if player == 'W' and count == 0: + return 1 + elif player == 'B' and count == 0: + return -1 + else: + return 0 + class Board: def __init__(self): - self.points = [Point()]*24 + self.points = [Point() for index in range(24)] self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5 self.points[7].checkers['W'] = self.points[16].checkers['B'] = 3 self.points[11].checkers['B'] = self.points[12].checkers['W'] = 5 self.bar = {'W':0, 'B':0} + self.allow_bear_off = {'W': False, 'B': False} + + def checkers_at_home(self, player): + sum_range = range(0, 7) if player == 'W' else range(17, 24) + count = 0 + for idx in sum_range: + count = count + self.points[idx].checkers[player] + return count + + def is_valid_move(self, start, steps, player): + dest1, dest2 = vector_add(start, steps) + dest_range = range(0, 24) + move1_valid = move2_valid = False + if dest1 in dest_range: + if self.points[dest1].is_open_for(player): + self.move_checker(start[0], steps[0], player) + move1_valid = True + else: + if self.allow_bear_off[player]: + self.move_checker(start[0], steps[0], player) + move1_valid = True + if not move1_valid: + return False + if dest2 in dest_range: + if self.points[dest2].is_open_for(player): + move2_valid = True + else: + if self.allow_bear_off[player]: + move2_valid = True + return move1_valid and move2_valid + + + def move_checker(self, start, steps, player): + dest = start + steps + dest_range = range(0,24) + self.points[start].remove_checker(player) + if dest in dest_range: + self.points[dest].add_checker(player) + if self.checkers_at_home(player) == 15: + self.allow_bear_off[player] = True class Point: def __init__(self): From d1b570ee990b72da39928b59419ebff1ff01ffad Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 18 Feb 2018 23:43:07 +0530 Subject: [PATCH 3/7] Implement expectiminimax function --- games.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/games.py b/games.py index feb46a2e4..39e2dffdc 100644 --- a/games.py +++ b/games.py @@ -41,6 +41,43 @@ def min_value(state): # ______________________________________________________________________________ +def expectiminimax(state, game): + + player = game.to_move(state) + + def max_value(state): + if game.terminal_test(state): + return game.utility(state, player) + v = -infinity + for a in game.actions('W', state): + v = max(v, chance_node(state, a, 'max')) + + def min_value(state): + if game.terminal_test(state): + return game.utility(state, player) + v = infinity + for a in game.actions('B', state): + v = min(v, chance_node(state, a, 'min')) + return v + + def chance_node(state, action, called_from): + sum_res = 0 + num_res = 21 + dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) + if called_from == 'max': + for val in dice_rolls: + game.dice_roll = (-val[0], -val[1]) + sum_res += min_value(game.result(state, action)) * (1/36 if val[0] == val[1] else 1/18) + elif called_from == 'min': + for val in dice_rolls: + game.dice_roll = val + sum_res += max_value(game.result(state, action)) * (1/36 if val[0] == val[1] else 1/18) + + return sum_res / num_res + + # Body of minimax_decision: + return argmax(game.actions(player, state), + key=lambda a: chance_node(game.result(state, a), a, 'max')) def alphabeta_search(state, game): """Search game to determine best action; use alpha-beta pruning. @@ -156,6 +193,9 @@ def random_player(game, state): def alphabeta_player(game, state): return alphabeta_search(state, game) +def expectiminimax_player(game, state): + return expectiminimax(state, game) + # ______________________________________________________________________________ # Some Sample Games From 16569e2c2d202cd9659bc7bb4f683ecc2061048a Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sun, 25 Feb 2018 22:22:11 +0530 Subject: [PATCH 4/7] Correct logic in some functions --- games.py | 61 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/games.py b/games.py index 39e2dffdc..6f7091ba7 100644 --- a/games.py +++ b/games.py @@ -51,6 +51,7 @@ def max_value(state): v = -infinity for a in game.actions('W', state): v = max(v, chance_node(state, a, 'max')) + return v def min_value(state): if game.terminal_test(state): @@ -66,18 +67,19 @@ def chance_node(state, action, called_from): dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) if called_from == 'max': for val in dice_rolls: - game.dice_roll = (-val[0], -val[1]) + game.dice_roll = val sum_res += min_value(game.result(state, action)) * (1/36 if val[0] == val[1] else 1/18) elif called_from == 'min': for val in dice_rolls: - game.dice_roll = val + game.dice_roll = (-val[0], -val[1]) sum_res += max_value(game.result(state, action)) * (1/36 if val[0] == val[1] else 1/18) return sum_res / num_res # Body of minimax_decision: - return argmax(game.actions(player, state), - key=lambda a: chance_node(game.result(state, a), a, 'max')) + print("Dice roll: ",game.dice_roll) + return max_value(state) + def alphabeta_search(state, game): """Search game to determine best action; use alpha-beta pruning. @@ -241,12 +243,12 @@ def play_game(self, *players): """Play an n-person, move-alternating game.""" state = self.initial while True: - for player in players: - move = player(self, state) - state = self.result(state, move) - if self.terminal_test(state): - self.display(state) - return self.utility(state, self.to_move(self.initial)) + for player in players: + move = player(self, state) + state = self.result(state, move) + if self.terminal_test(state): + self.display(state) + return self.utility(state, self.to_move(self.initial)) class Fig52Game(Game): @@ -387,29 +389,36 @@ def actions(self, state): class Backgammon(Game): def __init__(self): - self.dice_roll = (random.randint(1, 6), random.randint(1, 6)) + self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) board=Board() self.initial = GameState(to_move='W', utility=0, board=board, moves=self.get_all_moves(board, 'W')) def actions(self, player, state): moves = state.moves + print("all moves : ", state.moves) valid_moves = [] for move in moves: - board = copy.copy(state.board) + board = copy.deepcopy(state.board) if board.is_valid_move(move, self.dice_roll, player): valid_moves.append(move) + print(player,"player") + print("valid moves : ", valid_moves) return valid_moves def result(self, state, move): - board = copy.copy(state.board) + board = copy.deepcopy(state.board) player = state.to_move - print(move, " xxxxx ",self.dice_roll) - board.move_checker(move[0], self.dice_roll[0], state.to_move) - board.move_checker(move[1], self.dice_roll[1], state.to_move) - return GameState(to_move=('W' if player == 'B' else 'B'), - utility=self.compute_utility(board, move, player), + self.display(state) + print("Move : ",move) + board.move_checker(move[0], self.dice_roll[0], player) + board.move_checker(move[1], self.dice_roll[1], player) + print("===============================================") + print("Dice roll: ", self.dice_roll) + to_move = ('W' if player == 'B' else 'B') + return GameState(to_move=to_move, + utility=self.compute_utility(board, move, to_move), board=board, - moves=self.get_all_moves(board, player)) + moves=self.get_all_moves(board, to_move)) def utility(self, state, player): @@ -423,19 +432,20 @@ def terminal_test(self, state): def get_all_moves(self, board, player): all_points = board.points taken_points = [index for index, point in enumerate(all_points) if point.checkers[player] > 0] - moves = list(itertools.product(taken_points, repeat=2)) + moves = list(itertools.permutations(taken_points,2)) + moves = moves + [(index, index) for index, point in enumerate(all_points) if point.checkers[player] >= 2] return moves def display(self, state): - """Print or otherwise display the state.""" + """Display state of the game.""" board = state.board player = state.to_move for index, point in enumerate(board.points): - print("Point : ",index) - print("W : ", point.checkers['W']) - print("B : ", point.checkers['B']) + if point.checkers['W'] != 0 or point.checkers['B'] != 0: + print("Point : ",index, " W : ", point.checkers['W'], " B : ", point.checkers['B']) print("player : ", state.to_move ) + 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 @@ -513,3 +523,6 @@ def add_checker(self, player): def remove_checker(self, player): self.checkers[player] -= 1 +if __name__ == "__main__": + bgm = Backgammon() + bgm.play_game(expectiminimax_player, query_player) From 966472b430ab6331f5f95e69486fc8ded41759a0 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Wed, 28 Feb 2018 00:29:23 +0530 Subject: [PATCH 5/7] Correct expectiminimax logic --- games.py | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/games.py b/games.py index 6f7091ba7..0a813aacd 100644 --- a/games.py +++ b/games.py @@ -50,7 +50,7 @@ def max_value(state): return game.utility(state, player) v = -infinity for a in game.actions('W', state): - v = max(v, chance_node(state, a, 'max')) + v = max(v, chance_node(state, a)) return v def min_value(state): @@ -58,27 +58,30 @@ def min_value(state): return game.utility(state, player) v = infinity for a in game.actions('B', state): - v = min(v, chance_node(state, a, 'min')) + v = min(v, chance_node(state, a)) return v - def chance_node(state, action, called_from): + def chance_node(state, action): + res_state = game.result(state, action) + game.display(res_state) sum_res = 0 num_res = 21 dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) - if called_from == 'max': - for val in dice_rolls: - game.dice_roll = val - sum_res += min_value(game.result(state, action)) * (1/36 if val[0] == val[1] else 1/18) - elif called_from == 'min': + if res_state.to_move == 'W': for val in dice_rolls: game.dice_roll = (-val[0], -val[1]) - sum_res += max_value(game.result(state, action)) * (1/36 if val[0] == val[1] else 1/18) + sum_res += max_value(res_state) * (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_res += min_value(res_state) * (1/36 if val[0] == val[1] else 1/18) return sum_res / num_res # Body of minimax_decision: - print("Dice roll: ",game.dice_roll) - return max_value(state) + game.display(state) + return argmax(game.actions(player, state), + key=lambda a: chance_node(state, a)) def alphabeta_search(state, game): @@ -243,12 +246,12 @@ def play_game(self, *players): """Play an n-person, move-alternating game.""" state = self.initial while True: - for player in players: - move = player(self, state) - state = self.result(state, move) - if self.terminal_test(state): - self.display(state) - return self.utility(state, self.to_move(self.initial)) + for player in players: + move = player(self, state) + state = self.result(state, move) + if self.terminal_test(state): + self.display(state) + return self.utility(state, self.to_move(self.initial)) class Fig52Game(Game): @@ -395,25 +398,18 @@ def __init__(self): def actions(self, player, state): moves = state.moves - print("all moves : ", state.moves) valid_moves = [] for move in moves: board = copy.deepcopy(state.board) if board.is_valid_move(move, self.dice_roll, player): valid_moves.append(move) - print(player,"player") - print("valid moves : ", valid_moves) return valid_moves def result(self, state, move): board = copy.deepcopy(state.board) - player = state.to_move - self.display(state) - print("Move : ",move) + player = state.to_move board.move_checker(move[0], self.dice_roll[0], player) board.move_checker(move[1], self.dice_roll[1], player) - print("===============================================") - print("Dice roll: ", self.dice_roll) to_move = ('W' if player == 'B' else 'B') return GameState(to_move=to_move, utility=self.compute_utility(board, move, to_move), From cf732d50cd07ee4c4ace9ef2b588038882a52cba Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Wed, 28 Feb 2018 19:32:48 +0530 Subject: [PATCH 6/7] Refactor code and add docstrings --- games.py | 350 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 188 insertions(+), 162 deletions(-) diff --git a/games.py b/games.py index 0a813aacd..7d29bd647 100644 --- a/games.py +++ b/games.py @@ -42,45 +42,46 @@ def min_value(state): # ______________________________________________________________________________ def expectiminimax(state, game): + """Returns 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) + + def max_value(state): + if game.terminal_test(state): + return game.utility(state, player) + v = -infinity + for a in game.actions(state): + v = max(v, chance_node(state, a)) + return v + + def min_value(state): + if game.terminal_test(state): + return game.utility(state, player) + v = infinity + for a in game.actions(state): + v = min(v, chance_node(state, a)) + return v - player = game.to_move(state) - - def max_value(state): - if game.terminal_test(state): - return game.utility(state, player) - v = -infinity - for a in game.actions('W', state): - v = max(v, chance_node(state, a)) - return v - - def min_value(state): - if game.terminal_test(state): - return game.utility(state, player) - v = infinity - for a in game.actions('B', state): - v = min(v, chance_node(state, a)) - return v - - def chance_node(state, action): - res_state = game.result(state, action) - game.display(res_state) - sum_res = 0 - num_res = 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_res += max_value(res_state) * (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_res += min_value(res_state) * (1/36 if val[0] == val[1] else 1/18) - - return sum_res / num_res + def chance_node(state, action): + res_state = game.result(state, action) + game.display(res_state) + 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) * (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) * (1/36 if val[0] == val[1] else 1/18) + + return sum_chances / num_chances # Body of minimax_decision: - game.display(state) - return argmax(game.actions(player, state), + game.display(state) + return argmax(game.actions(state), key=lambda a: chance_node(state, a)) @@ -391,134 +392,159 @@ def actions(self, state): class Backgammon(Game): - def __init__(self): - self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) - board=Board() - self.initial = GameState(to_move='W', utility=0, board=board, moves=self.get_all_moves(board, 'W')) - - def actions(self, player, state): - moves = state.moves - valid_moves = [] - for move in moves: - board = copy.deepcopy(state.board) - if board.is_valid_move(move, self.dice_roll, player): - valid_moves.append(move) - return valid_moves - - 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) - to_move = ('W' if player == 'B' else 'B') - return GameState(to_move=to_move, - utility=self.compute_utility(board, move, to_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 - - def terminal_test(self, state): - """A state is terminal if one player wins.""" - return state.utility != 0 - - def get_all_moves(self, board, player): - all_points = board.points - taken_points = [index for index, point in enumerate(all_points) if point.checkers[player] > 0] - moves = list(itertools.permutations(taken_points,2)) - moves = moves + [(index, index) for index, point in enumerate(all_points) if point.checkers[player] >= 2] - return moves - - def display(self, state): - """Display state of the game.""" - board = state.board - player = state.to_move - for index, point in enumerate(board.points): - if point.checkers['W'] != 0 or point.checkers['B'] != 0: - print("Point : ",index, " W : ", point.checkers['W'], " B : ", point.checkers['B']) - print("player : ", state.to_move ) - - - 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 - for idx in range(0, 24): - count = count + board.points[idx].checkers[player] - - if player == 'W' and count == 0: - return 1 - elif player == 'B' and count == 0: - return -1 - else: - return 0 + """A two player game where the goal of each player is to move all the + checkers off the board. The moves for each state are determined by + rolling a pair of dice.""" + + def __init__(self): + self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) + board = Board() + self.initial = GameState(to_move='W', + utility=0, board=board, moves=self.get_all_moves(board, 'W')) + + def actions(self, state): + """Returns a list of legal moves for a state.""" + player = state.to_move + moves = state.moves + valid_moves = [] + for move in moves: + board = copy.deepcopy(state.board) + if board.is_valid_move(move, self.dice_roll, player): + valid_moves.append(move) + return valid_moves + + 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) + to_move = ('W' if player == 'B' else 'B') + return GameState(to_move=to_move, + utility=self.compute_utility(board, move, to_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 + + def terminal_test(self, state): + """A state is terminal if one player wins.""" + return state.utility != 0 + + def get_all_moves(self, board, player): + """All possible moves for a player i.e. all possible ways of + choosing two checkers of a player from the board for a move + at a given state.""" + all_points = board.points + taken_points = [index for index, point in enumerate(all_points) + if point.checkers[player] > 0] + moves = list(itertools.permutations(taken_points, 2)) + moves = moves + [(index, index) for index, point in enumerate(all_points) + if point.checkers[player] >= 2] + return moves + + def display(self, state): + """Display state of the game.""" + board = state.board + player = state.to_move + for index, point in enumerate(board.points): + if point.checkers['W'] != 0 or point.checkers['B'] != 0: + print("Point : ", index, " W : ", point.checkers['W'], " B : ", point.checkers['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.""" + count = 0 + for idx in range(0, 24): + count = count + board.points[idx].checkers[player] + if player == 'W' and count == 0: + return 1 + if player == 'B' and count == 0: + return -1 + return 0 class Board: - def __init__(self): - self.points = [Point() for index in range(24)] - self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 - self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5 - self.points[7].checkers['W'] = self.points[16].checkers['B'] = 3 - self.points[11].checkers['B'] = self.points[12].checkers['W'] = 5 - self.bar = {'W':0, 'B':0} - self.allow_bear_off = {'W': False, 'B': False} - - def checkers_at_home(self, player): - sum_range = range(0, 7) if player == 'W' else range(17, 24) - count = 0 - for idx in sum_range: - count = count + self.points[idx].checkers[player] - return count - - def is_valid_move(self, start, steps, player): - dest1, dest2 = vector_add(start, steps) - dest_range = range(0, 24) - move1_valid = move2_valid = False - if dest1 in dest_range: - if self.points[dest1].is_open_for(player): - self.move_checker(start[0], steps[0], player) - move1_valid = True - else: - if self.allow_bear_off[player]: - self.move_checker(start[0], steps[0], player) - move1_valid = True - if not move1_valid: - return False - if dest2 in dest_range: - if self.points[dest2].is_open_for(player): - move2_valid = True - else: - if self.allow_bear_off[player]: - move2_valid = True - return move1_valid and move2_valid - - - def move_checker(self, start, steps, player): - dest = start + steps - dest_range = range(0,24) - self.points[start].remove_checker(player) - if dest in dest_range: - self.points[dest].add_checker(player) - if self.checkers_at_home(player) == 15: - self.allow_bear_off[player] = True + """The board consists of 24 points. Each player('W' and 'B') initially + has 15 checkers on board. Player 'W' moves from point 23 to point 0 + and player 'B' moves from point 0 to 23. Points 0-7 are + home for player W and points 17-24 are home for B.""" + + def __init__(self): + """Initial state of the game""" + # TODO : Add bar to Board class where a blot is placed when it is hit. + self.points = [Point() for index in range(24)] + self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 + self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5 + self.points[7].checkers['W'] = self.points[16].checkers['B'] = 3 + self.points[11].checkers['B'] = self.points[12].checkers['W'] = 5 + self.allow_bear_off = {'W': False, 'B': False} + + def checkers_at_home(self, player): + """Returns 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.points[idx].checkers[player] + return count + + def is_legal_move(self, 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. + It is legal only after a player has moved all his checkers to his home.""" + dest1, dest2 = vector_add(start, steps) + dest_range = range(0, 24) + move1_legal = move2_legal = False + if dest1 in dest_range: + if self.points[dest1].is_open_for(player): + self.move_checker(start[0], steps[0], player) + move1_legal = True + else: + if self.allow_bear_off[player]: + self.move_checker(start[0], steps[0], player) + move1_legal = True + if not move1_legal: + return False + if dest2 in dest_range: + if self.points[dest2].is_open_for(player): + 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): + """Moves a checker from starting point by a given number of steps""" + dest = start + steps + dest_range = range(0, 24) + self.points[start].remove_checker(player) + if dest in dest_range: + self.points[dest].add_checker(player) + if self.checkers_at_home(player) == 15: + self.allow_bear_off[player] = True class Point: - def __init__(self): - self.checkers = {'W':0, 'B':0} - - def is_open_for(self, player): - opponent = 'B' if player=='W' else 'W' - return self.checkers[opponent] <= 1 - - def add_checker(self, player): - self.checkers[player] += 1 - - def remove_checker(self, player): - self.checkers[player] -= 1 - -if __name__ == "__main__": - bgm = Backgammon() - bgm.play_game(expectiminimax_player, query_player) + """A point is one of the 24 triangles on the board where + the players' checkers are placed.""" + + def __init__(self): + self.checkers = {'W':0, 'B':0} + + def is_open_for(self, player): + """A point is open for a player if the no. of opponent's + checkers already present on it is 0 or 1. A player can + move a checker to a point only if it is open.""" + opponent = 'B' if player == 'W' else 'W' + return self.checkers[opponent] <= 1 + + def add_checker(self, player): + """Place a player's checker on a point.""" + self.checkers[player] += 1 + + def remove_checker(self, player): + """Remove a player's checker from a point.""" + self.checkers[player] -= 1 From a21d5f977f78c6dd153ef0810db57625e4bf76b6 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Wed, 28 Feb 2018 23:38:10 +0530 Subject: [PATCH 7/7] Remove print statements --- games.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/games.py b/games.py index 7d29bd647..be9620bd4 100644 --- a/games.py +++ b/games.py @@ -64,7 +64,6 @@ def min_value(state): def chance_node(state, action): res_state = game.result(state, action) - game.display(res_state) sum_chances = 0 num_chances = 21 dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) @@ -79,8 +78,7 @@ def chance_node(state, action): return sum_chances / num_chances - # Body of minimax_decision: - game.display(state) + # Body of expectiminimax: return argmax(game.actions(state), key=lambda a: chance_node(state, a)) @@ -406,12 +404,12 @@ def actions(self, state): """Returns a list of legal moves for a state.""" player = state.to_move moves = state.moves - valid_moves = [] + legal_moves = [] for move in moves: board = copy.deepcopy(state.board) - if board.is_valid_move(move, self.dice_roll, player): - valid_moves.append(move) - return valid_moves + if board.is_legal_move(move, self.dice_roll, player): + legal_moves.append(move) + return legal_moves def result(self, state, move): board = copy.deepcopy(state.board) @@ -475,7 +473,7 @@ class Board: def __init__(self): """Initial state of the game""" - # TODO : Add bar to Board class where a blot is placed when it is hit. + # TODO : Add bar to Board class where a blot is placed when it is hit. self.points = [Point() for index in range(24)] self.points[0].checkers['B'] = self.points[23].checkers['W'] = 2 self.points[5].checkers['W'] = self.points[18].checkers['B'] = 5