Esempio n. 1
0
    def getAction(self, number: int) -> "Action Object":
        while self.__moveCount < 5:
            action = AI.Action(random.randrange(1, len(AI.Action)))
            x = random.randrange(self.__colDimension)
            y = random.randrange(self.__rowDimension)
            self.__moveCount += 1
            return Action(action, x, y)

        action = AI.Action(random.randrange(len(AI.Action)))
        x = random.randrange(self.__colDimension)
        y = random.randrange(self.__rowDimension)

        return Action(action, x, y)
Esempio n. 2
0
 def __updateFirstMove(self) -> None:
     """ update the first move in KB"""
     c = self.startX
     r = self.startY
     self.unknown.remove((c, r))
     # self.safeQ.append([c, r])
     self.cLast = c
     self.rLast = r
     # update the 4 instances for this coordinates if necessary
     self.board[c][r].covered = False
     self.board[c][r].nubmer = 0
     # update lastTile instance
     self.lastTile = self.board[c][r]
     self.lastAction = AI.Action(1)
Esempio n. 3
0
    def getAction(self, number: int) -> "Action Object":

        ########################################################################
        #							YOUR CODE BEGINS						   #
        ########################################################################

        #Record the previous turn, update flag neighbors' effective labels
        self.moveCount += 1
        self.recordTileLabel(number)
        #If out of moves OR we believe we won the game, LEAVE
        if self.moveCount >= self.moveCountMax:
            #print("---Leaving... because out of moves - 'AI lost?'---")
            return Action(AI.Action(0))
        elif self.isBoardComplete():
            #print("---Leaving... because the game board was completed - 'AI won?'---")
            return Action(AI.Action(0))

        #print('\nResulting Board:')
        #self.printAIBoard()

        #Check every numbered tile for rule-of-thumb: "EffectiveLabel == NumOfUnmarkedNeighbors -> neighbors are bombs"
        #print('Numbered Tiles =', self.numberedTiles)
        for tile in self.numberedTiles:
            unmarkedNeighbors = set()
            x = tile[0]
            y = tile[1]
            neighbors = [(x, y + 1), (x - 1, y + 1), (x - 1, y),
                         (x - 1, y - 1), (x, y - 1), (x + 1, y - 1),
                         (x + 1, y), (x + 1, y + 1)]
            for n in neighbors:
                if self.isInBounds(n[0], n[1]):
                    if self.board[n[0]][n[1]][0] == '?':
                        unmarkedNeighbors.add(n)
            if self.board[x][y][1] == len(unmarkedNeighbors):
                for n2 in list(unmarkedNeighbors):
                    if n2 not in self.flaggingQueue and n2 not in list(
                            self.flags.keys()):
                        self.flaggingQueue.append(n2)

        #Pop through frontier until coords are a valid tile to UNCOVER
        #print()
        while True:
            #print('iterating frontier... =', self.frontier)

            #Flagging Queue Case: Flag any guaranteed flags found by rule-of-thumb
            #print('\nFlagging Queue =', self.flaggingQueue)
            if len(self.flaggingQueue) != 0:
                #Update every non-zero and non-flag tile for "EffectiveLabel == NumOfCoveredNeighbors"
                #If true, all covered neighbors of tile are bombs.
                if len(self.flaggingQueue) > 0:
                    while True:
                        flag = self.flaggingQueue.pop(0)
                        if flag not in list(self.flags.keys()):
                            break
                    #print('Flagging now:', flag)
                    self.effectiveLabeling(flag[0], flag[1])
                    self.finishFunction(number, 2, flag[0], flag[1])
                    return Action(AI.Action(2), flag[0], flag[1])

            if len(self.frontier) != 0:
                coords = self.frontier.pop(0)
                #Normal case: Found tile to uncover that is valid and not a bomb
                if coords not in self.uncovered and self.isInBounds(
                        coords[0], coords[1]):
                    #print('Breaking out of loop, found valid tile in frontier...')
                    break

            #Flagging Case: Flag possible bomb using right-angle method
            elif len(self.frontier) == 0:
                if len(self.flags) != self.totalMines and len(
                        self.numberedTiles) >= 3:
                    #print('\nFlagging Corner bomb...')
                    flag = None
                    for i in self.numberedTiles:
                        flag = self.flagBombs()
                        if flag == None: continue
                        else: break
                    #If did not find valid flag, do random UNCOVER instead
                    #print('Flag =', flag)
                    if flag != None:
                        if type(flag) == list:
                            #Sometimes returns 2 flags, separate them here
                            self.flaggingQueue.append(flag[0])
                            flag = flag[1]

                        self.effectiveLabeling(flag[0], flag[1])
                        self.finishFunction(number, 2, flag[0], flag[1])
                        return Action(AI.Action(2), flag[0], flag[1])
                    else:
                        #print('\nCorner and Effective Label Flags not found, trying random coords...')
                        coords = self.chooseRandomTile()
                        break

                #Cleanup Case: Bombs have been flagged, UNCOVER all remaining tiles that are not flagged
                elif len(self.flags) == self.totalMines:
                    #print('\nAll bombs flagged, moving remaining tiles to frontier...')
                    self.frontier = self.remaining
                    self.remaining = []
                    coords = self.frontier.pop(0)
                    break

                #Last Resort Case: Try random coords if no other options
                else:
                    #print('\nEverything else failed, trying random coords...')
                    coords = self.chooseRandomTile()
                    break
        #print()

        self.finishFunction(number, 1, coords[0], coords[1])
        return Action(AI.Action(1), coords[0], coords[1])
Esempio n. 4
0
    def getAction(self, number: int) -> "Action Object":
        """ input: an integer number as a perceptnumber from World.py act as a
            hint output: action number, rowCoordinate x, columnCoordinate y

            Total of 5 parts:

                I: Patient 0
                 - Uncover all the surrounding tiles if the center tile is 0

                II: Basic 3x3 deductions
                 - If the number of covered surrending tiles is qual to the percept
                   number for the center tile, then the covered tiles are mines

                III: Multisquare algorithm
                 - Looking at each single tile in the frontier (tiles that are
                   unexplored by adjcent to a percept number), expand the deduction
                   method from part II to multiple surrounding neighbors that have
                   multural affecting tiles.

                IV: Linear algebra
                 - Looking at the entier frontier and simplify it into a matrix. Use
                   the uncovered neighbor tiles' percept number as constraints. Each
                   constraint tells the max possible number of mines among its
                   surrounding tiles. Also, use the entire board's unexplored Tiles
                   and mines left as the final equation in the matrix. Solve or
                   reduce the matrix into row echelon form. Since each tile can
                   either be a mine or not, further deduction can be made.

                V: Guesses
                 - Assign each unexplored tiles a probability based on the current
                   knowledge base (number of minesleft and surrouding constraints)
                   One exception - guess corners first since the probability that
                   a corner being a mines is low. """

        # in the beginning, update KB with the received percept number
        self.logLastMovePercept(number)
        """ Part I - Patient 0 """
        # if the number is 0, then the last tile's surrounding is safe
        if (number == 0):
            """ if in bounds, not the last move,
                not already in safeQ, and covered"""
            for col in range(self.cLast - 1, self.cLast + 2):
                for row in range(self.rLast - 1, self.rLast + 2):
                    if (self.__isInBounds(col, row) and \
                          not (col == self.cLast and row == self.rLast)) and \
                          ((col, row) not in self.safeQ) and \
                          self.board[col][row].covered == True:
                        # add to the safeQ
                        self.safeQ.append((col, row))
        # uncover all the safe ones
        while (self.safeQ != deque([])):
            # get the left most tile in the safe queue
            cr = self.safeQ.popleft()
            # update the knowledge base
            self.logMove(AI.Action(1), cr[0], cr[1])
            # return the action to the World
            return Action(AI.Action(1), cr[0], cr[1])
        """ Part II - Basic 3x3 deductions """
        # locate the mine
        for col in range(0, self.col):
            for row in range(0, self.row):
                """ If the total number of covered surrending the tile
                        is the percept number for the tile,
                    then the covered tile is the mine """
                if (self.board[col][row].covered == False and \
                     self.board[col][row].number != 0 and \
                     self.board[col][row].number == \
                       self.surCovered(col, row)[0]):
                    # flag those as mines
                    mines = self.surCovered(col, row)[1]
                    for _ in mines:
                        self.markMines(_)
        for col in range(0, self.col):
            for row in range(0, self.row):
                # percept == known mines
                # surrounding unexplored - known mines > 0
                if ((self.board[col][row].number == \
                      self.surMines(col, row)[0]) and \
                     (self.surCovered(col, row)[0] - \
                      self.surMines(col, row)[0] > 0)):
                    # get the unexplored tiles and known mines
                    covered = self.surCovered(col, row)[1]
                    mines = self.surMines(col, row)[1]
                    for _ in covered:
                        # if the mines are all discovered, the rest of the tiles
                        # must be safe
                        if (_ not in mines) and (_ not in self.safeQ):
                            self.safeQ.append(_)
        # uncover all the safe ones
        while (self.safeQ != deque([])):
            # get the left most tile in the safe queue
            cr = self.safeQ.popleft()
            # update the knowledge base
            self.logMove(AI.Action(1), cr[0], cr[1])
            # return the action to the World
            return Action(AI.Action(1), cr[0], cr[1])
        """ Part III: neighbor_test (multisquare algorithm) """
        for col in range(self.col):
            for row in range(self.row):
                if self.board[col][row].number > 0 and \
                    self.surUnknown(col, row)[0] > 0:
                    neigh = self.neighbor_test(col, row)
                    if neigh is not None and neigh != []:
                        for _ in neigh:
                            if _ in self.unknown and _ not in self.safeQ:
                                self.safeQ.append(_)

        # uncover all the safe ones
        while (self.safeQ != deque([])):
            # get the left most tile in the safe queue
            cr = self.safeQ.popleft()
            # update the knowledge base
            self.logMove(AI.Action(1), cr[0], cr[1])
            # return the action to the World
            return Action(AI.Action(1), cr[0], cr[1])
        """ Part IV: linear algebra """
        # initialize contraints, frontier, and frontier encoding for the matrix
        constraints = []
        frontier = []
        frontierMap = dict()
        unknown = self.unknown
        totalMinesLeft = self.minesLeft

        # get the current contraints
        constraints = self.constraints()
        constraintsCount = len(constraints)

        # get the current frontier
        frontier = self.frontier()
        frontierCount = len(frontier)

        # each row is a contraint
        # the additional constraint is the entire unexplored tiles
        rowCount = constraintsCount + 1
        # each column is an explored tile on the board
        columnCount = len(unknown) + 1

        # if there are constraints and the variables, construct the matrix
        if columnCount != 1 and rowCount != 1:
            # create a list of column code for each variable tile plus the
            # general rule (unexplored tiles and total mines left)
            columnHeader = [x for x in range(columnCount)]
            frontierHeader = columnHeader[:-1]
            # encode each tile into a dictionary
            col_to_tile = dict(zip(frontierHeader, unknown))
            tile_to_col = dict(zip(unknown, frontierHeader))

            # initialize the matrix
            matrix = [[0 for i in range(columnCount)] for j in range(rowCount)]

            # construct the matrix
            row = 0
            for constraint in constraints:
                # list out the tiles to be explored for each constraint
                sub_frontier = self.surUnknown(constraint[0], constraint[1])[1]
                # mark the coordinates
                for tile in sub_frontier:
                    # encode each column into tile coordinates
                    col = tile_to_col.get(tile)
                    # update the matrix coordinate value
                    matrix[row][col] = 1
                # update the last column with the actual number of mines
                # which is (percept - number of mines already discovered)
                minesCount = self.board[constraint[0]][constraint[1]].number - \
                             self.surMines(constraint[0], constraint[1])[0]
                # update the last number as the effective percept number
                matrix[row][-1] = minesCount
                # move on to the next row
                row += 1

            # update the last row as the general rule
            for i in range(columnCount):
                matrix[row][i] = 1
            matrix[-1][-1] = totalMinesLeft

            # reduce to row echelon form, where the magic happens
            self.reduceRowEchelon(matrix)
            """
            Since each tile is either a mine or not, its value is binary. this
            is useful to solve the matrix or at least some tile's value.
            """
            safe = []
            mines = []
            for row in matrix:
                last = row[-1]
                onesCount = self.countMix(row[:-1])[0]
                onesList = self.countMix(row[:-1])[1]
                neg_onesCount = self.countMix(row[:-1])[2]
                negList = self.countMix(row[:-1])[3]

                # case when the total number of mines is 0
                if last == 0:
                    # case when there are only possitive coefficients on the left
                    if onesCount > 0 and neg_onesCount == 0:
                        for col in onesList:
                            tile = col_to_tile.get(col)
                            # they are safe
                            if tile not in safe:
                                safe.append(tile)
                    # case when there are only negative coefficients on the left
                    if neg_onesCount > 0 and onesCount == 0:
                        for col in negList:
                            tile = col_to_tile.get(col)
                            # they are mines
                            if tile not in mines:
                                mines.append(tile)
                # case when the total number of mines is possitive
                if last > 0:
                    # ignore the negative coefficients
                    if onesCount == last:
                        for col in onesList:
                            tile = col_to_tile.get(col)
                            if tile not in safe:
                                mines.append(tile)
                        for col in negList:
                            tile = col_to_tile.get(col)
                            if tile not in mines:
                                safe.append(tile)
                # case when the total number of mines is negative
                if last < 0:
                    # ignore the possitive coefficients
                    if neg_onesCount == last:
                        for col in onesList:
                            tile = col_to_tile.get(col)
                            if tile not in safe:
                                safe.append(tile)
                        for col in negList:
                            tile = col_to_tile.get(col)
                            if tile not in mines:
                                mines.append(tile)
            # update the knowledge base
            if mines != []:
                for _ in mines:
                    self.markMines(_)

            # update the knowledge base and append to the safe queue
            if safe != []:
                for _ in safe:
                    if _ in self.unknown and _ not in self.safeQ:
                        self.safeQ.append(_)

        # uncover all the safe ones
        while (self.safeQ != deque([])):
            # get the left most tile in the safe queue
            cr = self.safeQ.popleft()
            # update the knowledge base
            self.logMove(AI.Action(1), cr[0], cr[1])
            # return the action to the World
            return Action(AI.Action(1), cr[0], cr[1])
        """ Part V: Guesses """
        # assign heuristics to each tile: number of mines / number of unexplored
        if self.unknown != []:
            keys = self.unknown
            values = [self.minesLeft / len(self.unknown)] * len(self.unknown)
            self.prob = dict(zip(keys, values))
        for col in range(0, self.col):
            for row in range(0, self.row):
                percept = self.board[col][row].number
                num_mines = self.surMines(col, row)[0]
                num_covered = self.surCovered(col, row)[0]
                if ((percept > 0) and \
                    (num_covered - num_mines > 0)):
                    mines = self.surMines(col, row)[1]
                    covered = self.surCovered(col, row)[1]
                    for _ in covered:
                        if (_ not in mines) and (_ not in self.safeQ):
                            # only get the maximum probability of being a mine
                            self.prob[_] = max( ( percept-num_mines ) / \
                                                num_covered,\
                                           self.prob[_])

        # get the corners first
        corners = [(self.col-1, self.row-1), \
                    (0, 0), \
                    (self.col-1, 0), \
                    (0, self.row-1)]
        for _ in corners:
            if _ in self.unknown:
                self.prob[_] = self.prob[_] - 1

        if (self.unknown != []):
            # only uncover the least possible mines
            minList = self.minList(self.prob)
            self.safeQ.append(random.choice(minList))
        if (self.minesLeft == 0):
            return Action(AI.Action(0))

        # uncover all the safe ones
        while (self.safeQ != deque([])):
            # get the left most tile in the safe queue
            cr = self.safeQ.popleft()
            # update the knowledge base
            self.logMove(AI.Action(1), cr[0], cr[1])
            # return the action to the World
            return Action(AI.Action(1), cr[0], cr[1])

        if (self.minesLeft == 0):
            return Action(AI.Action(0))
Esempio n. 5
0
 def logLastMovePercept(self, number):
     """ log the feedback percept number from the world """
     if self.lastAction == AI.Action(1):
         self.lastTile.covered = False
         self.lastTile.number = number