def test_game(players, silent):
    """ Go through a whole game by pitting two AutoPlayers against each other """
    # The players parameter is a list of tuples: (playername, constructorfunc)
    # where constructorfunc accepts a State parameter and returns a freshly
    # created AutoPlayer (or subclass thereof) that will generate moves
    # on behalf of the player.

    # Initial, empty game state
    state = State(tileset = NewTileSet, drawtiles = True)

    print(u"After initial draw, bag contains {0} tiles".format(state.bag().num_tiles()))
    print(u"Bag contents are:\n{0}".format(state.bag().contents()))
    print(u"Rack 0 is {0}".format(state.rack(0)))
    print(u"Rack 1 is {0}".format(state.rack(1)))

    # Set player names
    for ix in range(2):
        state.set_player_name(ix, players[ix][0])

    if not silent:
        print(state.__str__()) # This works in Python 2 and 3

    # Generate a sequence of moves, switching player sides automatically

    t0 = time.time()

    while not state.is_game_over():

        # Call the appropriate player creation function
        apl = players[state.player_to_move()][1](state)

        g0 = time.time()
        move = apl.generate_move()
        g1 = time.time()

        legal = state.check_legality(move)
        if legal != Error.LEGAL:
            # Oops: the autoplayer generated an illegal move
            print(u"Play is not legal, code {0}".format(Error.errortext(legal)))
            return

        if not silent:
            print(u"Play {0} scores {1} points ({2:.2f} seconds)".format(move, state.score(move), g1 - g0))

        # Apply the move to the state and switch players
        state.apply_move(move)

        if not silent:
            print(state.__str__())

    # Tally the tiles left and calculate the final score
    state.finalize_score()
    p0, p1 = state.scores()
    t1 = time.time()

    if not silent:
        print(u"Game over, final score {4} {0} : {5} {1} after {2} moves ({3:.2f} seconds)".format(p0, p1,
            state.num_moves(), t1 - t0, state.player_name(0), state.player_name(1)))

    return state.scores()
Exemple #2
0
def test_manual_game():
    """ Manual game test """

    # Initial, empty game state
    state = State(tileset=NewTileSet, manual_wordcheck=True, drawtiles=True)

    print("Manual game")
    print("After initial draw, bag contains {0} tiles".format(
        state.bag().num_tiles()))
    print("Bag contents are:\n{0}".format(state.bag().contents()))
    print("Rack 0 is {0}".format(state.rack(0)))
    print("Rack 1 is {0}".format(state.rack(1)))

    # Set player names
    for ix in range(2):
        state.set_player_name(ix, "Player " + ("A", "B")[ix])

    # print(state.__str__()) # This works in Python 2 and 3

    state.player_rack().set_tiles("stuðinn")
    test_move(state, "H4 stuði")
    state.player_rack().set_tiles("dettsfj")
    test_move(state, "5E detts")
    test_exchange(state, 3)
    state.player_rack().set_tiles("dýsturi")
    test_move(state, "I3 dýs")
    state.player_rack().set_tiles("?xalmen")
    # The question mark indicates a blank tile for the subsequent cover
    test_move(state, "6E ?óx")
    state.player_rack().set_tiles("eiðarps")

    test_move(state, "9F eipar")
    test_challenge(state)
    test_response(state)

    state.player_rack().set_tiles("sóbetis")
    test_move(state, "J3 ós")

    test_move(state, "9F eiðar")
    test_challenge(state)
    test_response(state)

    # Tally the tiles left and calculate the final score
    state.finalize_score()
    p0, p1 = state.scores()

    print(
        "Manual game over, final score {3} {0} : {4} {1} after {2} moves"
        .format(
            p0, p1, state.num_moves(), state.player_name(0), state.player_name(1)
        )
    )
def test_manual_game():
    """ Manual game test """

    # Initial, empty game state
    state = State(tileset = NewTileSet, manual_wordcheck = True, drawtiles = True)

    print(u"Manual game")
    print(u"After initial draw, bag contains {0} tiles".format(state.bag().num_tiles()))
    print(u"Bag contents are:\n{0}".format(state.bag().contents()))
    print(u"Rack 0 is {0}".format(state.rack(0)))
    print(u"Rack 1 is {0}".format(state.rack(1)))

    # Set player names
    for ix in range(2):
        state.set_player_name(ix, "Player " + ("A", "B")[ix])

    # print(state.__str__()) # This works in Python 2 and 3

    state.player_rack().set_tiles(u"stuðinn")
    test_move(state, u"H4 stuði")
    state.player_rack().set_tiles(u"dettsfj")
    test_move(state, u"5E detts")
    test_exchange(state, 3)
    state.player_rack().set_tiles(u"dýsturi")
    test_move(state, u"I3 dýs")
    state.player_rack().set_tiles(u"?xalmen")
    test_move(state, u"6E ?óx") # The question mark indicates a blank tile for the subsequent cover
    state.player_rack().set_tiles(u"eiðarps")

    test_move(state, u"9F eipar")
    test_challenge(state)
    test_response(state)

    state.player_rack().set_tiles(u"sóbetis")
    test_move(state, u"J3 ós")

    test_move(state, u"9F eiðar")
    test_challenge(state)
    test_response(state)

    # Tally the tiles left and calculate the final score
    state.finalize_score()
    p0, p1 = state.scores()

    print(u"Manual game over, final score {3} {0} : {4} {1} after {2} moves".format(p0, p1,
        state.num_moves(), state.player_name(0), state.player_name(1)))
Exemple #4
0
 def state_after_move(self, move_number):
     """ Return a game state after the indicated move, 0=beginning state """
     # Initialize a fresh state object
     s = State(drawtiles = False)
     # Set up the initial state
     for ix in range(2):
         s.set_player_name(ix, self.state.player_name(ix))
         if self.initial_racks[ix] is None:
             # Load the current rack rather than nothing
             s.set_rack(ix, self.state.rack(ix))
         else:
             # Load the initial rack
             s.set_rack(ix, self.initial_racks[ix])
     # Apply the moves up to the state point
     for m in self.moves[0 : move_number]:
         s.apply_move(m.move, shallow = True) # Shallow apply
         if m.rack is not None:
             s.set_rack(m.player, m.rack)
     s.recalc_bag()
     return s
 def state_after_move(self, move_number):
     """ Return a game state after the indicated move, 0=beginning state """
     # Initialize a fresh state object
     s = State(drawtiles=False, tileset=self.tileset)
     # Set up the initial state
     for ix in range(2):
         s.set_player_name(ix, self.state.player_name(ix))
         if self.initial_racks[ix] is None:
             # Load the current rack rather than nothing
             s.set_rack(ix, self.state.rack(ix))
         else:
             # Load the initial rack
             s.set_rack(ix, self.initial_racks[ix])
     # Apply the moves up to the state point
     for m in self.moves[0:move_number]:
         s.apply_move(m.move, shallow=True)  # Shallow apply
         if m.rack is not None:
             s.set_rack(m.player, m.rack)
     s.recalc_bag()
     return s
Exemple #6
0
def test_game(players, silent):
    """ Go through a whole game by pitting two AutoPlayers against each other """
    # The players parameter is a list of tuples: (playername, constructorfunc)
    # where constructorfunc accepts a State parameter and returns a freshly
    # created AutoPlayer (or subclass thereof) that will generate moves
    # on behalf of the player.

    # Initial, empty game state
    state = State(tileset=NewTileSet, drawtiles=True)

    print(u"After initial draw, bag contains {0} tiles".format(
        state.bag().num_tiles()))
    print(u"Bag contents are:\n{0}".format(state.bag().contents()))
    print(u"Rack 0 is {0}".format(state.rack(0)))
    print(u"Rack 1 is {0}".format(state.rack(1)))

    # Set player names
    for ix in range(2):
        state.set_player_name(ix, players[ix][0])

    if not silent:
        print(state.__str__())  # This works in Python 2 and 3

    # Generate a sequence of moves, switching player sides automatically

    t0 = time.time()

    while not state.is_game_over():

        # Call the appropriate player creation function
        apl = players[state.player_to_move()][1](state)

        g0 = time.time()
        move = apl.generate_move()
        g1 = time.time()

        legal = state.check_legality(move)
        if legal != Error.LEGAL:
            # Oops: the autoplayer generated an illegal move
            print(u"Play is not legal, code {0}".format(
                Error.errortext(legal)))
            return

        if not silent:
            print(u"Play {0} scores {1} points ({2:.2f} seconds)".format(
                move, state.score(move), g1 - g0))

        # Apply the move to the state and switch players
        state.apply_move(move)

        if not silent:
            print(state.__str__())

    # Tally the tiles left and calculate the final score
    state.finalize_score()
    p0, p1 = state.scores()
    t1 = time.time()

    if not silent:
        print(
            u"Game over, final score {4} {0} : {5} {1} after {2} moves ({3:.2f} seconds)"
            .format(p0, p1, state.num_moves(), t1 - t0, state.player_name(0),
                    state.player_name(1)))

    return state.scores()
Exemple #7
0
def test_game(players, silent):
    """ Go through a whole game by pitting two AutoPlayers against each other """
    # The players parameter is a list of tuples: (playername, constructorfunc)
    # where constructorfunc accepts a State parameter and returns a freshly
    # created AutoPlayer (or subclass thereof) that will generate moves
    # on behalf of the player.

    # Initial, empty game state
    state = State(drawtiles=True)

    # Set player names
    for ix in range(2):
        state.set_player_name(ix, players[ix][0])

    if not silent:
        print(state.__str__())  # This works in Python 2 and 3

    # test_move(state, u"H4 stuði")
    # test_move(state, u"5E detts")
    # test_exchange(state, 3)
    # test_move(state, u"I3 dýs")
    # test_move(state, u"6E ?óx") # The question mark indicates a blank tile for the subsequent cover
    # state.player_rack().set_tiles(u"ðhknnmn")

    # Generate a sequence of moves, switching player sides automatically

    t0 = time.time()

    while not state.is_game_over():

        # Call the appropriate player creation function
        apl = players[state.player_to_move()][1](state)

        g0 = time.time()
        move = apl.generate_move()
        g1 = time.time()

        # legal = state.check_legality(move)
        # if legal != Error.LEGAL:
        #     # Oops: the autoplayer generated an illegal move
        #     print(u"Play is not legal, code {0}".format(Error.errortext(legal)))
        #     return
        if not silent:
            print(u"Play {0} scores {1} points ({2:.2f} seconds)".format(move, state.score(move), g1 - g0))

        # Apply the move to the state and switch players
        state.apply_move(move)

        if not silent:
            print(state.__str__())

    # Tally the tiles left and calculate the final score
    state.finalize_score()
    p0, p1 = state.scores()
    t1 = time.time()

    if not silent:
        print(
            u"Game over, final score {4} {0} : {5} {1} after {2} moves ({3:.2f} seconds)".format(
                p0, p1, state.num_moves(), t1 - t0, state.player_name(0), state.player_name(1)
            )
        )

    return state.scores()
Exemple #8
0
class Game:

    """ A wrapper class for a particular game that is in process
        or completed. Contains inter alia a State instance.
    """

    # The human-readable name of the computer player
    AUTOPLAYER_NAME = u"Netskrafl"

    def __init__(self, uuid = None):
        # Unique id of the game
        self.uuid = uuid
        # The nickname of the human (local) player
        self.username = None
        # The current game state
        self.state = None
        # Is the human player 0 or 1, where player 0 begins the game?
        self.player_index = 0
        # The last move made by the autoplayer
        self.last_move = None
        # Was the game finished by resigning?
        self.resigned = False
        # History of moves in this game so far
        self.moves = []

    # The current game state held in memory for different users
    # !!! TODO: limit the size of the cache and make it LRU
    _cache = dict()

    def _make_new(self, username):
        # Initialize a new, fresh game
        self.username = username
        self.state = State(drawtiles = True)
        self.player_index = randint(0, 1)
        self.state.set_player_name(self.player_index, username)
        self.state.set_player_name(1 - self.player_index, Game.AUTOPLAYER_NAME)

    @classmethod
    def current(cls):
        """ Obtain the current game state """
        user = User.current()
        user_id = None if user is None else user.id()
        if not user_id:
            # No game state found
            return None
        if user_id in Game._cache:
            return Game._cache[user_id]
        # No game in cache: attempt to find one in the database
        uuid = GameModel.find_live_game(user_id)
        if uuid is None:
            # Not found in persistent storage: create a new game
            return cls.new(user.nickname())
        # Load from persistent storage
        return cls.load(uuid, user.nickname())

    @classmethod
    def new(cls, username):
        """ Start and initialize a new game """
        game = cls(Unique.id()) # Assign a new unique id to the game
        game._make_new(username)
        # 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
        # If AutoPlayer is first to move, generate the first move
        if game.player_index == 1:
            game.autoplayer_move()
        # Store the new game in persistent storage
        game.store()
        return game

    @classmethod
    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 store(self):
        """ Store the game state in persistent storage """
        assert self.uuid is not None
        user = User.current()
        if user is None:
            # No current user: can't store game
            assert False
            return
        gm = GameModel(id = self.uuid)
        gm.set_player(self.player_index, user.id())
        gm.set_player(1 - self.player_index, None)
        gm.rack0 = self.state._racks[0].contents()
        gm.rack1 = self.state._racks[1].contents()
        gm.score0 = self.state.scores()[0]
        gm.score1 = self.state.scores()[1]
        gm.to_move = len(self.moves) % 2
        gm.over = self.state.is_game_over()
        movelist = []
        for player, m in self.moves:
            mm = MoveModel()
            coord, tiles, score = m.summary(self.state.board())
            mm.coord = coord
            mm.tiles = tiles
            mm.score = score
            movelist.append(mm)
        gm.moves = movelist
        gm.put()

    def set_human_name(self, nickname):
        """ Set the nickname of the human player """
        self.state.set_player_name(self.player_index, nickname)

    def resign(self):
        """ The human player is resigning the game """
        self.resigned = True

    def autoplayer_move(self):
        """ Let the AutoPlayer make its move """
        # !!! DEBUG for testing various move types
        # rnd = randint(0,3)
        # if rnd == 0:
        #     print(u"Generating ExchangeMove")
        #     move = ExchangeMove(self.state.player_rack().contents()[0:randint(1,7)])
        # else:
        apl = AutoPlayer(self.state)
        move = apl.generate_move()
        self.state.apply_move(move)
        self.moves.append((1 - self.player_index, move))
        self.last_move = move

    def human_move(self, move):
        """ Register the human move, update the score and move list """
        self.state.apply_move(move)
        self.moves.append((self.player_index, move))
        self.last_move = None # No autoplayer move yet

    def enum_tiles(self):
        """ Enumerate all tiles on the board in a convenient form """
        for x, y, tile, letter in self.state.board().enum_tiles():
            yield (Board.ROWIDS[x] + str(y + 1), tile, letter,
                0 if tile == u'?' else Alphabet.scores[tile])

    BAG_SORT_ORDER = Alphabet.order + u'?'

    def display_bag(self):
        """ Returns the bag as it should be displayed, i.e. including the autoplayer's rack """
        displaybag = self.state.display_bag(1 - self.player_index)
        return u''.join(sorted(displaybag, key=lambda ch: Game.BAG_SORT_ORDER.index(ch)))

    def num_moves(self):
        """ Returns the number of moves in the game so far """
        return len(self.moves)

    def client_state(self):
        """ Create a package of information for the client about the current state """
        reply = dict()
        if self.state.is_game_over():
            # The game is now over - one of the players finished it
            reply["result"] = Error.GAME_OVER # Not really an error
            num_moves = 1
            if self.last_move is not None:
                # Show the autoplayer move if it was the last move in the game
                reply["lastmove"] = self.last_move.details()
                num_moves = 2 # One new move to be added to move list
            newmoves = [(player, m.summary(self.state.board())) for player, m in self.moves[-num_moves:]]
            # Lastplayer is the player who finished the game
            lastplayer = self.moves[-1][0]
            if not self.resigned:
                # If the game did not end by resignation,
                # account for the losing rack
                rack = self.state._racks[1 - lastplayer].contents()
                # Subtract the score of the losing rack from the losing player
                newmoves.append((1 - lastplayer, (u"", rack, -1 * Alphabet.score(rack))))
                # Add the score of the losing rack to the winning player
                newmoves.append((lastplayer, (u"", rack, 1 * Alphabet.score(rack))))
            # Add a synthetic "game over" move
            newmoves.append((1 - lastplayer, (u"", u"OVER", 0)))
            reply["newmoves"] = newmoves
            reply["bag"] = "" # Bag is now empty, by definition
            reply["xchg"] = False # Exchange move not allowed
        else:
            # Game is still in progress
            reply["result"] = 0 # Indicate no error
            reply["rack"] = self.state.player_rack().details()
            reply["lastmove"] = self.last_move.details()
            reply["newmoves"] = [(player, m.summary(self.state.board())) for player, m in self.moves[-2:]]
            reply["bag"] = self.display_bag()
            reply["xchg"] = self.state.is_exchange_allowed()
        reply["scores"] = self.state.scores()
        return reply