def test_move(state, movestring): """ Test placing a simple tile move """ coord, word = movestring.split(u' ') rowid = Board.ROWIDS row, col = 0, 0 xd, yd = 0, 0 horiz = True if coord[0] in rowid: row = rowid.index(coord[0]) col = int(coord[1:]) - 1 yd = 1 else: row = rowid.index(coord[-1]) col = int(coord[0:-1]) - 1 xd = 1 horiz = False move = Move(word, row, col, horiz) next_is_blank = False for c in word: if c == u'?': next_is_blank = True continue if not state.board().is_covered(row, col): move.add_cover(row, col, u'?' if next_is_blank else c, c) next_is_blank = False row += xd col += yd legal = state.check_legality(move) if legal != Error.LEGAL: print(u"Play is not legal, code {0}".format(Error.errortext(legal))) return False print(u"Play {0} is legal and scores {1} points".format(move, state.score(move))) state.apply_move(move) print(state.__str__()) return True
def accept(self, matched, final): """ Called to inform the navigator of a match and whether it is a final word """ if final and len(matched) > 1 and (self._index >= Board.SIZE or self._axis.is_empty(self._index)): # Solution found - make a Move object for it and add it to the AutoPlayer's list ix = self._index - len(matched) # The word's starting index within the axis row, col = self._axis.coordinate_of(ix) xd, yd = self._axis.coordinate_step() move = Move(matched, row, col, self._axis.is_horizontal()) # Fetch the rack as it was at the beginning of move generation autoplayer = self._axis._autoplayer rack = autoplayer.rack() tiles = u'' for c in matched: if self._axis.is_empty(ix): # Empty square that is being covered by this move # Find out whether it is a blank or normal letter tile if c in rack: rack = rack.replace(c, u'', 1) tile = c tiles += c else: # Must be a wildcard match rack = rack.replace(u'?', u'', 1) tile = u'?' tiles += tile + c # assert row in range(Board.SIZE) # assert col in range(Board.SIZE) # Add this cover to the Move object move.add_validated_cover(Cover(row, col, tile, c)) else: tiles += c ix += 1 row += xd col += yd # Note the tiles played in the move move.set_tiles(tiles) # Check that we've picked off the correct number of tiles # assert len(rack) == len(self._rack) autoplayer.add_candidate(move)
def test_move(state, movestring): """ Test placing a simple tile move """ coord, word = movestring.split(u' ') rowid = Board.ROWIDS xd, yd = 0, 0 horiz = True if coord[0] in rowid: row = rowid.index(coord[0]) col = int(coord[1:]) - 1 yd = 1 else: row = rowid.index(coord[-1]) col = int(coord[0:-1]) - 1 xd = 1 horiz = False move = Move(word, row, col, horiz) next_is_blank = False for c in word: if c == u'?': next_is_blank = True continue if not state.board().is_covered(row, col): move.add_cover(row, col, u'?' if next_is_blank else c, c) next_is_blank = False row += xd col += yd legal = state.check_legality(move) msg = "" if isinstance(legal, tuple): legal, msg = legal if legal != Error.LEGAL: print(u"Play is not legal, code {0} {1}".format( Error.errortext(legal), msg)) return False print(u"Play {0} is legal and scores {1} points".format( move, state.score(move))) state.apply_move(move) print(state.__str__()) return True
def accept(self, matched, final): """ Called to inform the navigator of a match and whether it is a final word """ # pylint: disable=bad-continuation if (final and len(matched) > 1 and (self._index >= Board.SIZE or self._axis.is_empty(self._index))): # Solution found - make a Move object for it and add it to the AutoPlayer's list ix = self._index - len( matched) # The word's starting index within the axis row, col = self._axis.coordinate_of(ix) xd, yd = self._axis.coordinate_step() move = Move(matched, row, col, self._axis.is_horizontal()) # Fetch the rack as it was at the beginning of move generation autoplayer = self._axis.autoplayer rack = autoplayer.rack() tiles = u"" for c in matched: if self._axis.is_empty(ix): # Empty square that is being covered by this move # Find out whether it is a blank or normal letter tile if c in rack: rack = rack.replace(c, u"", 1) tile = c tiles += c else: # Must be a wildcard match rack = rack.replace(u"?", u"", 1) tile = u"?" tiles += tile + c # assert row in range(Board.SIZE) # assert col in range(Board.SIZE) # Add this cover to the Move object move.add_validated_cover(Cover(row, col, tile, c)) else: tiles += c ix += 1 row += xd col += yd # Note the tiles played in the move move.set_tiles(tiles) # Check that we've picked off the correct number of tiles # assert len(rack) == len(self._rack) autoplayer.add_candidate(move)
def _load_locked(cls, uuid, use_cache = True): """ Load an existing game from cache or persistent storage under lock """ gm = GameModel.fetch(uuid, use_cache) if gm is None: # A game with this uuid is not found in the database: give up return None # Initialize a new Game instance with a pre-existing uuid game = cls(uuid) # Set the timestamps game.timestamp = gm.timestamp game.ts_last_move = gm.ts_last_move if game.ts_last_move is None: # If no last move timestamp, default to the start of the game game.ts_last_move = game.timestamp # Initialize the preferences game._preferences = gm.prefs # Initialize a fresh, empty state with no tiles drawn into the racks game.state = State(drawtiles = False) # A player_id of None means that the player is an autoplayer (robot) game.player_ids[0] = None if gm.player0 is None else gm.player0.id() game.player_ids[1] = None if gm.player1 is None else gm.player1.id() game.robot_level = gm.robot_level # Load the initial racks game.initial_racks[0] = gm.irack0 game.initial_racks[1] = gm.irack1 # Load the current racks game.state.set_rack(0, gm.rack0) game.state.set_rack(1, gm.rack1) # Process the moves player = 0 # mx = 0 # Move counter for debugging/logging for mm in gm.moves: # mx += 1 # logging.info(u"Game move {0} tiles '{3}' score is {1}:{2}".format(mx, game.state._scores[0], game.state._scores[1], mm.tiles).encode("latin-1")) m = None if mm.coord: # Normal tile move # Decode the coordinate: A15 = horizontal, 15A = vertical if mm.coord[0] in Board.ROWIDS: row = Board.ROWIDS.index(mm.coord[0]) col = int(mm.coord[1:]) - 1 horiz = True else: row = Board.ROWIDS.index(mm.coord[-1]) col = int(mm.coord[0:-1]) - 1 horiz = False # The tiles string may contain wildcards followed by their meaning # Remove the ? marks to get the "plain" word formed if mm.tiles is not None: m = Move(mm.tiles.replace(u'?', u''), row, col, horiz) m.make_covers(game.state.board(), mm.tiles) elif mm.tiles[0:4] == u"EXCH": # Exchange move m = ExchangeMove(mm.tiles[5:]) elif mm.tiles == u"PASS": # Pass move m = PassMove() elif mm.tiles == u"RSGN": # Game resigned m = ResignMove(- mm.score) assert m is not None if m: # Do a "shallow apply" of the move, which updates # the board and internal state variables but does # not modify the bag or the racks game.state.apply_move(m, True) # Append to the move history game.moves.append(MoveTuple(player, m, mm.rack, mm.timestamp)) player = 1 - player # Find out what tiles are now in the bag game.state.recalc_bag() # Account for the final tiles in the rack and overtime, if any if game.is_over(): game.finalize_score() if not gm.over: # The game was not marked as over when we loaded it from # the datastore, but it is over now. One of the players must # have lost on overtime. We need to update the persistent state. game._store_locked() return game
def _process_move(movecount, movelist): """ Process a move from the client (the human player) Returns True if OK or False if the move was illegal """ game = Game.current() if game is None: # !!! TODO: more informative error message about relogging in return jsonify(result=Error.NULL_MOVE) # Make sure the client is in sync with the server: # check the move count if movecount != game.num_moves(): return jsonify(result=Error.OUT_OF_SYNC) # Parse the move from the movestring we got back m = Move(u'', 0, 0) try: for mstr in movelist: if mstr == u"pass": # Pass move m = PassMove() break if mstr[0:5] == u"exch=": # Exchange move m = ExchangeMove(mstr[5:]) break if mstr == u"rsgn": # Resign from game, forfeiting all points m = ResignMove(game.state.scores()[game.player_index]) game.resign() break sq, tile = mstr.split(u'=') row = u"ABCDEFGHIJKLMNO".index(sq[0]) col = int(sq[1:]) - 1 if tile[0] == u'?': # If the blank tile is played, the next character contains # its meaning, i.e. the letter it stands for letter = tile[1] tile = tile[0] else: letter = tile # print(u"Cover: row {0} col {1}".format(row, col)) m.add_cover(row, col, tile, letter) except Exception as e: logging.info(u"Exception in _process_move(): {0}".format(e).encode("latin-1")) m = None # Process the move string here err = game.state.check_legality(m) if err != Error.LEGAL: # Something was wrong with the move: # show the user a corresponding error message return jsonify(result=err) # Move is OK: register it and update the state game.human_move(m) # Respond immediately with an autoplayer move # (can be a bit time consuming if rack has one or two blank tiles) if not game.state.is_game_over(): game.autoplayer_move() if game.state.is_game_over(): # If the game is now over, tally the final score game.state.finalize_score() # Make sure the new game state is persistently recorded game.store() # Return a state update to the client (board, rack, score, movelist, etc.) return jsonify(game.client_state())
def load(cls, uuid, username): """ Load an already existing game from persistent storage """ gm = GameModel.fetch(uuid) if gm is None: # A game with this uuid is not found in the database: give up return None # Initialize a new Game instance with a pre-existing uuid game = cls(uuid) game.username = username game.state = State(drawtiles = False) if gm.player0 is None: # Player 0 is an Autoplayer game.player_index = 1 # Human (local) player is 1 else: assert gm.player1 is None game.player_index = 0 # Human (local) player is 0 game.state.set_player_name(game.player_index, username) game.state.set_player_name(1 - game.player_index, u"Netskrafl") # Load the current racks game.state._racks[0].set_tiles(gm.rack0) game.state._racks[1].set_tiles(gm.rack1) # Process the moves player = 0 for mm in gm.moves: m = None if mm.coord: # Normal tile move # Decode the coordinate: A15 = horizontal, 15A = vertical if mm.coord[0] in Board.ROWIDS: row = Board.ROWIDS.index(mm.coord[0]) col = int(mm.coord[1:]) - 1 horiz = True else: row = Board.ROWIDS.index(mm.coord[-1]) col = int(mm.coord[0:-1]) - 1 horiz = False # The tiles string may contain wildcards followed by their meaning # Remove the ? marks to get the "plain" word formed m = Move(mm.tiles.replace(u'?', u''), row, col, horiz) m.make_covers(game.state.board(), mm.tiles) elif mm.tiles[0:4] == u"EXCH": # Exchange move m = ExchangeMove(mm.tiles[5:]) elif mm.tiles == u"PASS": # Pass move m = PassMove() elif mm.tiles == u"RSGN": # Game resigned m = ResignMove(- mm.score) assert m is not None if m: # Do a "shallow apply" of the move, which updates # the board and internal state variables but does # not modify the bag or the racks game.state.apply_move(m, True) # Append to the move history game.moves.append((player, m)) player = 1 - player # If the moves were correctly applied, the scores should match assert game.state._scores[0] == gm.score0 assert game.state._scores[1] == gm.score1 # Find out what tiles are now in the bag game.state.recalc_bag() # Cache the game so it can be looked up by user id user = User.current() if user is not None: Game._cache[user.id()] = game return game
def _load_locked(cls, uuid, use_cache=True): """ Load an existing game from cache or persistent storage under lock """ gm = GameModel.fetch(uuid, use_cache) if gm is None: # A game with this uuid is not found in the database: give up return None # Initialize a new Game instance with a pre-existing uuid game = cls(uuid) # Set the timestamps game.timestamp = gm.timestamp game.ts_last_move = gm.ts_last_move if game.ts_last_move is None: # If no last move timestamp, default to the start of the game game.ts_last_move = game.timestamp # Initialize the preferences game._preferences = gm.prefs # Initialize a fresh, empty state with no tiles drawn into the racks game.state = State(drawtiles=False, tileset=game.tileset) # A player_id of None means that the player is an autoplayer (robot) game.player_ids[0] = None if gm.player0 is None else gm.player0.id() game.player_ids[1] = None if gm.player1 is None else gm.player1.id() game.robot_level = gm.robot_level # Load the initial racks game.initial_racks[0] = gm.irack0 game.initial_racks[1] = gm.irack1 # Load the current racks game.state.set_rack(0, gm.rack0) game.state.set_rack(1, gm.rack1) # Process the moves player = 0 # mx = 0 # Move counter for debugging/logging for mm in gm.moves: # mx += 1 # logging.info(u"Game move {0} tiles '{3}' score is {1}:{2}".format(mx, game.state._scores[0], game.state._scores[1], mm.tiles).encode("latin-1")) m = None if mm.coord: # Normal tile move # Decode the coordinate: A15 = horizontal, 15A = vertical if mm.coord[0] in Board.ROWIDS: row = Board.ROWIDS.index(mm.coord[0]) col = int(mm.coord[1:]) - 1 horiz = True else: row = Board.ROWIDS.index(mm.coord[-1]) col = int(mm.coord[0:-1]) - 1 horiz = False # The tiles string may contain wildcards followed by their meaning # Remove the ? marks to get the "plain" word formed if mm.tiles is not None: m = Move(mm.tiles.replace(u'?', u''), row, col, horiz) m.make_covers(game.state.board(), mm.tiles) elif mm.tiles[0:4] == u"EXCH": # Exchange move m = ExchangeMove(mm.tiles[5:]) elif mm.tiles == u"PASS": # Pass move m = PassMove() elif mm.tiles == u"RSGN": # Game resigned m = ResignMove(-mm.score) assert m is not None if m: # Do a "shallow apply" of the move, which updates # the board and internal state variables but does # not modify the bag or the racks game.state.apply_move(m, True) # Append to the move history game.moves.append(MoveTuple(player, m, mm.rack, mm.timestamp)) player = 1 - player # Find out what tiles are now in the bag game.state.recalc_bag() # Account for the final tiles in the rack and overtime, if any if game.is_over(): game.finalize_score() if not gm.over: # The game was not marked as over when we loaded it from # the datastore, but it is over now. One of the players must # have lost on overtime. We need to update the persistent state. game._store_locked() return game