def next(self): '''Apply player moves and update game state. Return a new StateMachine representing the new turn. ''' if self.players != self.moves: players = ', '.join(self.players - self.moves) raise GameError(GameError.NO_MOVES % players) # calculate the new 'true' facts by querying for 'next' state = ASTNode.new('?state') next_query = ASTNode.new('next') next_query.children = [state] next_facts = [d[state.term] for d in self.db.query(next_query)] # delete the 'does', 'true', and derived facts self.db.facts.pop(('does', 2)) self.moves = set() new_db = self.db.copy() new_db.derived_facts = {} new_db.facts.pop(('true', 1)) # replace 'true' facts with 'next' facts for fact in next_facts: new_db.define_fact('true', 1, [fact]) next = StateMachine(new_db) next.players = self.players return next
def test_move_player(self): fsm = StateMachine() data = ''' (role x) (init 1) (<= (legal x ?x) (true ?x)) ''' fsm.store(data=data) fsm.move('x', '1') query = ASTNode.new('does') query.children = [ASTNode.new(x) for x in ('x', '1')] self.assertTrue(fsm.db.query(query))
def test_move_player_complex(self): fsm = StateMachine() data = ''' (role x) (init (cell 1 1 b)) (<= (legal x ?x) (true ?x)) ''' fsm.store(data=data) fsm.move('x', '(cell 1 1 b)') cell = ASTNode.new('cell') cell.children = [ASTNode.new(x) for x in ('1', '1', 'b')] query = ASTNode.new('does') query.children = [ASTNode.new('x'), cell] self.assertTrue(fsm.db.query(query))
def test_store_init(self): fsm = StateMachine() data = ''' (role x) (role o) (init (cell a)) (init (cell b)) (init (cell c)) (init (cell d)) ''' fsm.store(data=data) query = ASTNode.new('true') query.children = [ASTNode.new('?state')] results = [{k: str(d[k]) for k in d} for d in fsm.db.query(query)] for i in ('a', 'b', 'c', 'd'): self.assertIn({'?state': '(cell %s)' % i}, results)
def move(self, player, move): '''Store a does/2 fact in the database representing a player's move.''' if player not in self.players: raise GameError(GameError.NO_SUCH_PLAYER % player) if player in self.moves: raise GameError(GameError.DOUBLE_MOVE % player) move = self._single_move_to_ast(move) player = ASTNode.new(player) if not self._legal(player, move): raise GameError(GameError.ILLEGAL_MOVE % (player, move)) self.db.define_fact('does', 2, [player, move]) self.moves.add(player.term)
def score(self, player='?player'): '''Return the score for a player this turn. If player is not provided, return a dict of all {player: score}. If there is no goal defined for this player/state, then return None. Raise GameError if player does not exist. ''' player = ASTNode.new(player) if not player.is_variable() and player.term not in self.players: raise GameError(GameError.NO_SUCH_PLAYER % player.term) score = ASTNode.new('?score') goal = ASTNode.new('goal') goal.children = [player, score] results = self.db.query(goal) if results is False: return None if not player.is_variable(): return int(results[0][score.term].term) ret = {} for var_dict in results: ret[var_dict[player.term].term] = int(var_dict[score.term].term) return ret
def legal(self, player='?player', move='?move'): '''If player and move are provided, return whether or not the move is legal this turn. If move is not provided, get a list of legal moves for player. If neither is provided, return a dict of moves for all players where player names are keys. ''' move = self._single_move_to_ast(move) player = ASTNode.new(player) results = self._legal(player, move) if type(results) is bool: return results elif not player.is_variable(): return [str(res[move.term]) for res in results] ret = {} for var_dict in results: move_str = str(var_dict[move.term]) ret.setdefault(var_dict[player.term].term, []).append(move_str) return ret
def _legal(self, player, move): '''Query legal/2. Arguments player and move are ASTNodes.''' legal = ASTNode.new('legal') legal.children = [player, move] return self.db.query(legal)
def is_terminal(self): '''Query terminal/0.''' return self.db.query(ASTNode.new('terminal'))