diff --git a/nim/game.py b/nim/game.py new file mode 100644 index 0000000..242875c --- /dev/null +++ b/nim/game.py @@ -0,0 +1,69 @@ +class NimGame: + "Models a Nim game." + + def get_initial_state(self): + "Returns the game's initial state." + return { + "board": [1, 3, 5, 7], + "first_player": True + } + + def get_next_state(self, state, action): + """Given a state and an action, returns the resulting state. + In the resulting state, the lines have been removed from last + turn, and it is the opposite player's turn. + """ + next_state = { + "board": state["board"].copy(), + "first_player": not state["first_player"], + } + + row, lines_to_remove = action + next_state["board"][row] -= lines_to_remove + + return next_state + + def get_actions(self, state): + "Returns a list of possible moves." + actions = [] + + for row, lines in enumerate(state["board"]): + for lines_to_remove in range(1, 4): + if lines >= lines_to_remove: + actions.append((row, lines_to_remove)) + + return actions + + def get_reward(self, state): + """Determines the reward associated with reaching this state. + For Nim, the two opponents each want a different game outcome. + If the game is over when it is first_player's turn, they lose, so reward is -1 + and the reward for the game being over on Computer's turn as 1. + All other states (unfinished games) are worth 0. + """ + if self.is_over(state): + if state["first_player"]: + return -1 + elif not state["first_player"]: + return 1 + else: + return 0 + + def is_over(self, state): + "Checks whether the game is over." + return self.board_is_empty(state) + + def board_is_empty(self, state): + "Checks whether all the lines in the board are gone." + for lines in state["board"]: + if lines != 0: + return False + return True + + def get_objective(self, state): + """Returns a player's objective, or a function describing what a player wants. + This function should choose the best value from a list. In Nim, the players + want opposite things, so we set first_player's objective to the built-in function `max` + (which chooses the largest number), and we set Computer's objective to the built-in function `min`. + """ + return max if state["first_player"] else min \ No newline at end of file diff --git a/nim/player.py b/nim/player.py index 32fd9fc..6594253 100644 --- a/nim/player.py +++ b/nim/player.py @@ -1,10 +1,12 @@ -from nim.game_stub import NimGameStub +from nim.game import NimGame from strategy.lookahead_strategy import LookaheadStrategy +from strategy.random_strategy import RandomStrategy + class HumanNimPlayer: def __init__(self, name): self.name = name - self.game = NimGameStub() + self.game = NimGame() def choose_action(self, state): actions = self.game.get_actions(state) @@ -26,7 +28,7 @@ class HumanNimPlayer: class ComputerNimPlayer: def __init__(self, name): self.name = name - self.strategy = LookaheadStrategy(NimGameStub(), max_depth=3, deterministic=False) + self.strategy = LookaheadStrategy(NimGame(), max_depth=3, deterministic=False) def choose_action(self, state): action = self.strategy.choose_action(state) diff --git a/nim/view.py b/nim/view.py index 16e3fdf..8bd3d88 100644 --- a/nim/view.py +++ b/nim/view.py @@ -1,9 +1,9 @@ -from nim.game_stub import NimGameStub +from nim.game import NimGame class NimView: def __init__(self, player0, player1): self.players = [player0, player1] - self.game = NimGameStub() + self.game = NimGame() def greet(self): print(f"{self.players[0].name} and {self.players[1].name}, welcome to Nim.") diff --git a/play_nim.py b/play_nim.py index 61f5cf2..293b4ef 100644 --- a/play_nim.py +++ b/play_nim.py @@ -1,11 +1,11 @@ -from nim.game_stub import NimGameStub +from nim.game import NimGame from nim.view import NimView from nim.player import HumanNimPlayer, ComputerNimPlayer player0 = HumanNimPlayer(input("What's your name? ")) player1 = ComputerNimPlayer("Robot") view = NimView(player0, player1) -game = NimGameStub() +game = NimGame() view.greet() state = game.get_initial_state()