示例#1
0
 def __init__(self):
     self.turn = 0
     self.players = ['w', 'b']
     self.board = HexBoard()
     self.playedPieces = {}
     self.piecesInCell = {}
     self.unplayedPieces = {}
示例#2
0
 def __init__(self, player_num, size, max_time=1, num_samples=100):
     super(MonteCarloPlayer, self).__init__(player_num)
     # the amount of time given for searching.
     self.max_time = max_time
     # the number of rollouts to perform on a leaf node
     self.num_samples = num_samples
     # a list of board states, their visit count, and their children
     self.search_tree = {HexBoard(size).hashable(): [1, 0, set()]}
     # tunable exploration parameter for UCB
     self.C = 1
示例#3
0
def show_answers_hex(trie, input_str):
    row_letters, row_starts = parse_letters_hex(input_str)
    board = HexBoard(row_letters, row_starts)
    print board

    results = board_search(board, trie)

    answers = Answers()
    answers.add(results)

    print answers
示例#4
0
def text_get_rules(default=0):
    size = -1
    while size < 1:
        try:
            if not default:
                size = int(input('Board size: '))
            else:
                size = DEFAULTS[default]['size']
        except ValueError:
            pass

    swap = False
    while swap not in ('y', 'n'):
        if not default:
            swap = input('allow swap rule? (y/n): ')
        else:
            swap = DEFAULTS[default]['swap']
    swap = (swap == 'y')

    if not default:
        player = [None] * 3
    else:
        player = DEFAULTS[default]['players']
    for i in (1, -1):
        if player[i] is not None:
            continue
        player_type = -1
        while not (0 <= player_type <= 5):
            try:
                player_type = int(input(
                    '0 - Text\n1 - Gui\n2 - Random (AI)\n3 - Alpha-Beta Search (AI)\n'
                    '4 - Monte-Carlo Search (AI)\n5 - Charge Heuristic (AI)\nplayer %d type?: ' % (
                                i % 3)))
            except ValueError:
                pass
        if player_type == 0:
            player[i] = TextPlayer(i)
        elif player_type == 1:
            player[i] = GuiPlayer(i)
        elif player_type == 2:
            player[i] = RandomPlayer(i)
        elif player_type == 3:
            player[i] = build_alpha_beta_player(i, size)
        elif player_type == 4:
            player[i] = build_monte_carlo_player(i, size)
        elif player_type == 5:
            player[i] = ChargeHeuristicPlayer(i, size)

    board = HexBoard(size, swap)
    return board, player
示例#5
0
class Hive(object):
    """
    The Hive Game.
    This class enforces the game rules and keeps the state of the game.
    """

    # Directions
    O = HexBoard.HX_O    # origin/on-top
    W = HexBoard.HX_W    # west
    NW = HexBoard.HX_NW  # north-west
    NE = HexBoard.HX_NE  # north-east
    E = HexBoard.HX_E    # east
    SE = HexBoard.HX_SE  # south-east
    SW = HexBoard.HX_SW  # south-west

    # End game status
    UNFINISHED = 0
    WHITE_WIN = 1
    BLACK_WIN = 2
    DRAW = 3


    def __init__(self):
        self.turn = 0
        self.players = ['w', 'b']
        self.board = HexBoard()
        self.playedPieces = {}
        self.piecesInCell = {}
        self.unplayedPieces = {}

    def setup(self):
        """
        Prepare the game to be played
        """
        # Add pieces to the players hands
        self.unplayedPieces['w'] = self._piece_set('w')
        self.unplayedPieces['b'] = self._piece_set('b')
        self.turn = 1


    def action(self, actPiece, refPiece=None, direction=None):
        """Perform the player action.
        return True or ExceptionType
        TODO: elaborate on the exceptions
        """
        player = self.get_active_player()

        piece = self.unplayedPieces[player].get(actPiece, None)
        if piece is not None:
            self.place_piece(piece, refPiece, direction)
            # Remove piece from the unplayed set
            del self.unplayedPieces[player][actPiece]
        else:
            ppiece = self.playedPieces.get(actPiece, None)
            if ppiece is None:
                raise HiveException
            else:
                self.move_piece(ppiece['piece'], refPiece, direction)

        # perform turn increment - TODO:if succesful
        self.turn += 1
        return True

    def get_unplayed_pieces(self, player):
        return self.unplayedPieces[player]


    def get_active_player(self):
        if self.turn <= 0:
            return None

        ap = 1 - (self.turn % 2)
        return self.players[ap]

    def get_board_boundaries(self):
        """returns the coordinates of the board limits."""
        return self.board.get_boundaries()


    def get_pieces(self, cell):
        """return the pieces that are in the cell (x, y)."""
        return self.piecesInCell.get(cell, [])


    def locate(self, pieceName):
        """
        Returns the cell where the piece is positioned.
        pieceName is a piece identifier (string)
        """
        res = None
        pp = self.playedPieces.get(pieceName)
        if pp is not None:
            res = pp['cell']

        return res


    def move_piece(self, piece, refPiece, refDirection):
        """
        Moves a piece on the playing board.
        """

        pieceName = str(piece)
        targetCell = self._poc2cell(refPiece, refDirection)

        # is the move valid
        if not self._validate_turn(piece, 'move'):
            raise HiveException("Invalid Piece Placement")

        if not self._validate_move_piece(piece, targetCell):
            raise HiveException("Invalid Piece Movement")

        pp = self.playedPieces[pieceName]
        startingCell = pp['cell']

        # remove the piece from its current location
        self.piecesInCell[startingCell].remove(pieceName)

        # places the piece at the target location
        self.board.resize(targetCell)
        pp['cell'] = targetCell
        pic = self.piecesInCell.setdefault(targetCell, [])
        pic.append(str(piece))

        return targetCell


    def place_piece(self, piece, refPieceName=None, refDirection=None):
        """
        Place a piece on the playing board.
        """

        # if it's the first piece we put it at cell (0, 0)
        if refPieceName is None and self.turn == 1:
            targetCell = (0, 0)
        else:
            targetCell = self._poc2cell(refPieceName, refDirection)

        # is the placement valid
        if not self._validate_turn(piece, 'place'):
            raise HiveException("Invalid Piece Placement")

        if not self._validate_place_piece(piece, targetCell):
            raise HiveException("Invalid Piece Placement")

        # places the piece at the target location
        self.board.resize(targetCell)
        self.playedPieces[str(piece)] = {'piece': piece, 'cell': targetCell}
        pic = self.piecesInCell.setdefault(targetCell, [])
        pic.append(str(piece))

        return targetCell


    def check_victory(self):
        """
        Check if white wins or black wins or draw or not finished
        """
        white = False
        black = False
        res = self.UNFINISHED

        # if white queen is surrounded => black wins
        queen = self.playedPieces.get('wQ1')
        if(
            queen is not None and
            len(self._occupied_surroundings(queen['cell'])) == 6
        ):
            black = True
            res = self.BLACK_WIN

        # if black queen is surrounded => white wins
        queen = self.playedPieces.get('wB1')
        if(
            queen is not None and
            len(self._occupied_surroundings(queen['cell'])) == 6
        ):
            white = True
            res = self.WHITE_WIN

        # if both queens are surrounded
        if white and black:
            res = self.DRAW

        return res


    def _validate_turn(self, piece, action):
        """
        Verifies if the action is valid on this turn.
        """
        is_even_turn = (self.turn % 2) == 0

        # White player plays on the odd turns
        if (not is_even_turn) and piece.color != 'w':
            return False

        # Black player plays on the even turns
        if is_even_turn and piece.color != 'b':
            return False

        # Tournament rule: no queen in the first move
        if (self.turn == 1 or self.turn == 2) and piece.kind == 'Q':
            return False

        # Move actions are only allowed after the queen is on the board
        if action == 'move':
            if is_even_turn and ('bQ1' not in self.playedPieces):
                return False
            if (not is_even_turn) and ('wQ1' not in self.playedPieces):
                return False

        # White Queen must be placed by turn 7 (4th white action)
        if self.turn == 7:
            if 'wQ1' not in self.playedPieces:
                if str(piece) != 'wQ1' or action != 'place':
                    return False

        # Black Queen must be placed by turn 8 (4th black action)
        if self.turn == 8:
            if 'bQ1' not in self.playedPieces:
                if str(piece) != 'bQ1' or action != 'place':
                    return False

        return True


    def _validate_move_piece(self, moving_piece, targetCell):
        # check if the piece has been placed
        pp = self.playedPieces.get(str(moving_piece))
        if pp is None:
            print "piece was not played yet"
            return False

        # check if the move it's to a different targetCell
        if str(moving_piece) in self.piecesInCell.get(targetCell, []):
            print "moving to the same place"
            return False

        # check if moving this piece won't break the hive
        if not self._one_hive(moving_piece):
            print "break _one_hive rule"
            return False

        validate_fun_map = {
            'A': self._valid_ant_move,
            'B': self._valid_beetle_move,
            'G': self._valid_grasshopper_move,
            'Q': self._valid_queen_move,
            'S': self._valid_spider_move
        }

        return validate_fun_map[moving_piece.kind](
            pp['piece'], pp['cell'], targetCell
        )


    def _validate_place_piece(self, piece, targetCell):
        """
        Verifies if a piece can be played from hand into a given targetCell.
        The piece must be placed touching at least one piece of the same color
        and can only be touching pieces of the same color.
        """

        # targetCell must be free
        if not self._is_cell_free(targetCell):
            return False

        # the piece was already played
        if str(piece) in self.playedPieces:
            return False

        # if it's the first turn we don't need to validate
        if self.turn == 1:
            return True

        # if it's the second turn we put it without validating touching colors
        if self.turn == 2:
            return True

        playedColor = piece.color

        occupiedCells = self._occupied_surroundings(targetCell)
        visiblePieces = [
            self.piecesInCell[oCell][-1] for oCell in occupiedCells
        ]
        res = True
        for pName in visiblePieces:
            if self.playedPieces[pName]['piece'].color != playedColor:
                res = False
                break

        return res


    def _is_cell_free(self, cell):
        pic = self.piecesInCell.get(cell, [])
        return len(pic) == 0


    def _occupied_surroundings(self, cell):
        """
        Returns a list of surrounding cells that contain a piece.
        """
        surroundings = self.board.get_surrounding(cell)
        return [c for c in surroundings if not self._is_cell_free(c)]


    # TODO: rename/remove this function.
    def _poc2cell(self, refPiece, pointOfContact):
        """
        Translates a relative position (piece, point of contact) into
        a board cell (x, y).
        """
        refCell = self.locate(refPiece)
        return self.board.get_dir_cell(refCell, pointOfContact)


    def _bee_moves(self, cell):
        """
        Get possible bee_moves from cell.

        A bee can move to a adjacent target position only if:
        - target position is free
        - and there is a piece adjacent to both the bee and that position
        - and there is a free cell that is adjacent to both the bee and the
          target position.
        """
        available_moves = []
        surroundings = self.board.get_surrounding(cell)
        for i in range(6):
            target = surroundings[i-1]
            # is the target cell free?
            if not self._is_cell_free(target):
                continue
            # does it have an adjacent free and an adjancent occupied cell that
            # is also adjacent to the starting cell?
            if (
                self._is_cell_free(surroundings[i])
                != self._is_cell_free(surroundings[i-2])
            ):
                available_moves.append(target)
        return available_moves


    def _piece_set(self, color):
        """
        Return a full set of hive pieces
        """
        pieceSet = {}
        for i in xrange(3):
            ant = HivePiece(color, 'A', i+1)
            pieceSet[str(ant)] = ant
            grasshopper = HivePiece(color, 'G', i+1)
            pieceSet[str(grasshopper)] = grasshopper
        for i in xrange(2):
            spider = HivePiece(color, 'S', i+1)
            pieceSet[str(spider)] = spider
            beetle = HivePiece(color, 'B', i+1)
            pieceSet[str(beetle)] = beetle
        queen = HivePiece(color, 'Q', 1)
        pieceSet[str(queen)] = queen
        return pieceSet


# +++               +++
# +++ One Hive rule +++
# +++               +++
    def _one_hive(self, piece):
        """
        Check if removing a piece doesn't break the one hive rule.
        Returns False if the hive is broken.
        """
        originalPos = self.locate(str(piece))
        # if the piece is not in the board then moving it won't break the hive
        if originalPos is None:
            return True
        # if there is another piece in the same cell then the one hive rule
        # won't be broken
        pic = self.piecesInCell[originalPos]
        if len(pic) > 1:
            return True

        # temporarily remove the piece
        del self.piecesInCell[originalPos]

        # Get all pieces that are in contact with the removed one and try to
        # reach all of them from one of them.
        occupied = self._occupied_surroundings(originalPos)
        visited = set()
        toExplore = set([occupied[0]])
        toReach = set(occupied[1:])
        res = False

        while len(toExplore) > 0:
            found = []
            for cell in toExplore:
                found += self._occupied_surroundings(cell)
                visited.add(cell)
            toExplore = set(found) - visited
            if toReach.issubset(visited):
                res = True
                break

        # restore the removed piece
        self.piecesInCell[originalPos] = pic
        return res

# --- ---

# +++                +++
# +++ Movement rules +++
# +++                +++
    def _valid_ant_move(self, ant, startCell, endCell):
        # check if ant has no piece on top blocking the move
        if self.piecesInCell[startCell][-1] != str(ant):
            return False
        # temporarily remove ant
        self.piecesInCell[startCell].remove(str(ant))

        toExplore = set([startCell])
        visited = set([startCell])
        res = False

        while len(toExplore) > 0:
            found = set()
            for c in toExplore:
                found.update(self._bee_moves(c))
            found.difference_update(visited)
            # have we found the endCell?
            if endCell in found:
                res = True
                break

            visited.update(found)
            toExplore = found

        # restore ant to it's original position
        self.piecesInCell[startCell].append(str(ant))

        return res


    def _valid_beetle_move(self, beetle, startCell, endCell):
        # check if beetle has no piece on top blocking the move
        if not self.piecesInCell[startCell][-1] == str(beetle):
            return False
        # temporarily remove beetle
        self.piecesInCell[startCell].remove(str(beetle))

        res = False
        # are we on top of the hive?
        if len(self.piecesInCell[startCell]) > 0:
            res = endCell in self.board.get_surrounding(startCell)
        else:
            res = endCell in (
                self._bee_moves(startCell) +
                self._occupied_surroundings(startCell)
            )

        # restore beetle to it's original position
        self.piecesInCell[startCell].append(str(beetle))

        return res


    def _valid_grasshopper_move(self, grasshopper, startCell, endCell):
        # TODO: add function to HexBoard that find cells in a straight line

        # is the move in only one direction?
        (sx, sy) = startCell
        (ex, ey) = endCell
        dx = ex - sx
        dy = ey - sy
        p = sy % 2  # starting from an even or odd line?

        # horizontal jump
        if dy == 0:
            # must jump at least over one piece
            if abs(dx) <= 1:
                return False
        # diagonal jump (dy != 0)
        else:
            # must jump at least over one piece
            if abs(dy) <= 1:
                return False

        moveDir = self.board.get_line_dir(startCell, endCell)
        # must move in a straight line
        if moveDir is None or moveDir == 0:
            return False

        # are all in-between cells occupied?
        c = self.board.get_dir_cell(startCell, moveDir)
        while c != endCell:
            if self._is_cell_free(c):
                return False
            c = self.board.get_dir_cell(c, moveDir)

        # is the endCell free?
        if not self._is_cell_free(endCell):
            return False

        return True


    def _valid_queen_move(self, queen, startCell, endCell):
        return endCell in self._bee_moves(startCell)


    def _valid_spider_move(self, spider, startCell, endCell):
        # check if spider has no piece on top blocking the move
        if not self.piecesInCell[startCell][-1] == str(spider):
            return False
        # temporarily remove spider
        self.piecesInCell[startCell].remove(str(spider))


        visited = set()
        firstStep = set()
        secondStep = set()
        thirdStep = set()

        visited.add(startCell)

        firstStep.update(set(self._bee_moves(startCell)))
        visited.update(firstStep)

        for c in firstStep:
            secondStep.update(set(self._bee_moves(c)))
        secondStep.difference_update(visited)
        visited.update(secondStep)

        for c in secondStep:
            thirdStep.update(set(self._bee_moves(c)))
        thirdStep.difference_update(visited)

        # restore spider to it's original position
        self.piecesInCell[startCell].append(str(spider))

        return endCell in thirdStep
示例#6
0
def solve_board(board, expanded_colors, verbose=False):

    # Identify the input colors and mixing rules used based on the level.
    input_colors = HARD_INPUT_COLORS if expanded_colors else EASY_INPUT_COLORS
    mixing_rules = HARD_MIXING_RULES if expanded_colors else EASY_MIXING_RULES

    try:
        if RectBoard.is_rect_board(board):
            RectBoard.validate(board)
        else:
            HexBoard.validate(board)
        validate_colors(board, input_colors, mixing_rules)
    except ValueError as err:
        print('Board validation failed: %s' % err)
        return

    # Create the solver.
    solver = pywrapcp.Solver('algemy')

    start = time.time()

    grid = {}
    for r, row in enumerate(board):
        for c, el in enumerate(row):
            if el.strip() == '':
                # A solvable position.
                grid[(r, c)] = solver.IntVar(0, len(input_colors),
                                             '<Row %i, Col %i>' % (r, c))
            else:
                grid[(r, c)] = None  # crystal position

    if RectBoard.is_rect_board(board):
        b = RectBoard(grid)
    else:
        b = HexBoard(grid)

    # Helper: converts an input color to the int var space.
    color_i = lambda c: input_colors.index(c) + 1
    # Helper: converts an int var to the input color string.
    i_color = lambda i: input_colors[i - 1]

    # CONSTRAINT - crystal illumination
    for (r, c), el in grid.items():
        if el is not None:
            continue  # only look at crystals
        sight_points = list(b.find_point_sightlines(r, c))

        or_rules = []
        for mix_rule in mixing_rules[board[r][c]]:
            pos_colors = [s[1:] for s in mix_rule if s[0] == '+']

            def get_pos_rules():
                for color in pos_colors:
                    yield solver.Sum(p == color_i(color)
                                     for p in sight_points) >= 1

            neg_colors = [s[1:] for s in mix_rule if s[0] == '-']

            def get_neg_rules():
                for color in neg_colors:
                    yield solver.Sum(p == color_i(color)
                                     for p in sight_points) == 0

            comb_rules = list(get_pos_rules()) + list(get_neg_rules())
            final_rule = solver.Sum(comb_rules) == len(comb_rules)  # ALL
            or_rules.append(final_rule)

        solver.Add(solver.Sum(or_rules) > 0)  # ANY

    # CONSTRAINT - board sightlines
    for sightline in b.find_board_sightlines():
        # At one item can be set (non-zero) in a sightline.
        solver.Add(solver.Sum(x > 0 for x in sightline) <= 1)

    # CONSTRAINT - all points solved
    for (r, c), el in grid.items():
        if el is None:
            continue  # ignore crystals
        # Either the point is non-empty or a point in its sightline is non-empty.
        solver.Add(el + solver.Sum(b.find_point_sightlines(r, c)) > 0)

    all_vars = list(filter(None, grid.values()))
    vars_phase = solver.Phase(all_vars, solver.INT_VAR_DEFAULT,
                              solver.INT_VALUE_DEFAULT)

    if verbose:
        print("Time setting up constraints: %.2fms" %
              ((time.time() - start) * 1000))

    solution = solver.Assignment()
    solution.Add(all_vars)
    collector = solver.FirstSolutionCollector(solution)
    start = time.time()
    solver.Solve(vars_phase, [collector])

    if verbose:
        print("Solve time: %.2fms" % (1000 * (time.time() - start)))

    if collector.SolutionCount() < 1:
        print("\nNO SOLUTION FOUND")
        return None

    # TODO iterate over all solutions instead of using collector.
    # Allow finding first one then prompt to find next or maybe all.

    def lookup(r, c):
        el = grid[(r, c)]
        if el is None:
            return ' '
        s = int(collector.Value(0, el))
        if not s:
            return ' '
        return i_color(s)

    solution = []
    for r, row in enumerate(board):
        solution.append([lookup(r, c) for c in range(len(row))])
    return solution