DEPARTMENT OF COMPUTING

n_queens.py [download]


#!/usr/bin/env python3

import copy
import random

class NQueensBoard:
    """
    Represent a state in the N-Queens problem
    A vector of positions. Each slot corresponds to a column on the board.
    The value stored is the row that the queen is on. 
    -1 means not yet placed.
    """

    def __init__(self, n):
        self._data = [-1] * n
        self._n = n
        return

    def __getitem__(self, key):
        return self._data[key]

    def __setitem__(self, key, value):
        self._data[key] = value
        return

    def __str__(self):
        s = "|"
        for c in range(self._n):
            s += " {} |".format(self._data[c])
        return s

    def __repr__(self):
        return "NQueensBoard<{}>".format(str(self))

NEIGHBORS_ALL_ROWS = 0
NEIGHBORS_NEXT_ROW = 1
class NQueensProblem:
    """
    Represent the N-Queens problem to facilitate search.
    """

    def __init__(self, n):
        self._n = n
        self._neighbors_mode = NEIGHBORS_ALL_ROWS
        return

    def STEP_COST(self, s0, a, s1):
        return 1
        
    def STATE_AS_KEY(self, state):
        key = []
        for c in range(self._n):
            key.append(state[c])
        return tuple(key)

    def ACTIONS(self, state):
        """
        List of legal placements.
        -> [ (row, col), (row, col), ... ]
        
        Only allow placement of queen in first column not yet set
        """
        actions = []
        for col in range(self._n):
            if state[col] != -1: continue
            for row in range(self._n):
                if not self.conflict(state, row, col):
                    actions.append((row, col))
            break
        return actions

    def RESULT(self, state, action):
        """
        place a queen at (row,col) = action.
        return updated state
        """
        (row,col) = action
        state1 = copy.deepcopy(state)
        state1[col] = row
        return state1

    def conflict(self, state, row, col):
        for c0 in range(self._n):
            r0 = state[c0]
            if r0 < 0: continue
            if self.squares_conflict(r0, c0, row, col):
                return True
        return False

    def squares_conflict(self, r0, c0, r1, c1):
        if r0 == r1 or c0 == c1:
            return True
        elif r0 - r1 == c0 - c1 or r0 - r1 == c1 - c0:
            return True
        else:
            return False

    def GOAL_TEST(self, state):
        for c0 in range(self._n):
            if state[c0] < 0:
                return False
        for c0 in range(self._n):
            for c1 in range(c0+1, self._n):
                if self.squares_conflict(c0, state[c0], c1, state[c1]):
                    return False
        return True

    def HEURISTIC(self, state):
        return 0

    # support for local search
    def set_neighbors_all_rows(self):
        self._neighbors_mode = NEIGHBORS_ALL_ROWS
        return

    def set_neighbors_next_row(self):
        self._neighbors_mode = NEIGHBORS_NEXT_ROW
        return

    def _neighbors_all_rows(self, state):
        """We define a neighbor as moving one queen to a different row, for all rows."""
        neighbors = []
        for c in range(self._n):
            for r in range(self._n):
                if state[c] == r: continue
                state1 = copy.deepcopy(state)
                state1[c] = r
                neighbors.append(state1)
        return neighbors

    def _neighbors_next_row(self, state):
        """We define a neighbor as moving one queen up or down a row."""
        neighbors = []
        for c in range(self._n):
            if state[c] > 0:
                state1 = copy.deepcopy(state)
                state1[c] = state1[c]-1
                neighbors.append(state1)
            if state[c] < self._n-1:
                state1 = copy.deepcopy(state)
                state1[c] = state1[c]+1
                neighbors.append(state1)
        return neighbors

    def NEIGHBORS(self, state):
        if self._neighbors_mode == NEIGHBORS_ALL_ROWS:
            return self._neighbors_all_rows(state)
        elif self._neighbors_mode == NEIGHBORS_NEXT_ROW:
            return self._neighbors_next_row(state)
        else:
            raise Exception("Unexpected neighbors mode: {}".format(self._neighbors_mode))
        return []

    def UTILITY(self, state):
        """
        We define utility as negative: the number of piece-piece conflicts.
        The largest utility is 0.0. 
        The more conflicts, the larger the negative utility.
        """
        count = 0.0
        for c0 in range(self._n):
            for c1 in range(c0+1,self._n):
                if self.squares_conflict(state[c0], c0, state[c1], c1):
                    count += 1
        return -count

    def RANDOM_STATE(self):
        state = NQueensBoard(self._n)
        for c in range(self._n):
            state[c] = random.randint(0, self._n-1)
        return state

def main():
    N = 4
    model = NQueensProblem(N)
    board = NQueensBoard(N)
    print(board)
    print(model.ACTIONS(board))

    state = model.RANDOM_STATE()
    print(state)
    print(model.UTILITY(state))
    print(model.NEIGHBORS(state))
    
    return

if __name__ == "__main__":
    main()

Last Updated 09/19/2024