n-queens Mexican Standoff

The first day back at work has proved fruitful in the form of someone coming up with the opposite of the traditional n-queens problem: the n-queens Mexican standoff. Instead of minimising the number of mutually attacking queens (to zero), the objective of the standoff version is to maximise the number of attacks.

We haven’t done an exhaustive search yet, but I’ve been running a simulated-annealing-ish script for a while, and it looks like the most attacks is 34 (17 pairs of mutual attacks), in two configurations:

QQ.    .QQ.
QQQ or QQQQ
QQQ    .QQ.

…either of which can be expanded by adding gaps between the queens, or rotating by multiples of 45 degrees.

Q.Q..   .Q.Q.   Q...Q..
.....   Q.Q.Q   .......
Q.Q.Q   .Q.Q.   ..Q...Q
.....   ..Q..   .......
Q.Q.Q           Q...Q..
                .......
                ..Q...Q
Q...Q...

Update: Python code below the fold.

import random
from numpy import array, exp

def print_board((n, queens, board)):
    for row in board:
        for val in row:
            print ('.','#')[val],
        print

def new_board(n=8):
    board = array([[False] * n for i in range(n)])
    queens = []
    while len(queens) < n:
        x = random.randint(0, n-1)
        y = random.randint(0, n-1)
        if not (x,y) in queens:
            queens.append((x, y))
            board[x,y] = True
    return (n, queens, board)

def eval_attack((n, queens, board)):
    attacks = 0
    for (x,y) in queens:
        for (dx,dy) in ((-1,-1),(-1,0),(-1,1),
                        (0,-1),(0,1),
                        (1,-1),(1,0),(1,1)):
            (cx, cy) = (x, y)
            while True:
                cx += dx
                cy += dy
                if cx < 0 or cx >= n or cy < 0 or cy >= n:
                    break
                if board[cx,cy]:
                    attacks += 1
                    break
    return attacks

def move_queen((n, queens, board), q=None, dest=None):
    if q is None:
        q = random.randint(0, n-1)
    if dest is None:
        while True:
            x = random.randint(0, n-1)
            y = random.randint(0, n-1)
            if not board[x,y]:
                dest = (x,y)
                break
    prev = queens[q]
    queens[q] = dest
    board[prev] = False
    board[dest] = True
    return (q, prev)

def find_most_attack(board=None, n=8, temp=2.0, threshold=10000):
    if board is None:
        board = new_board(n)

    best = 0
    meta_best = best
    since_improve = 0
    while True:
        (prev_q, prev_pos) = move_queen(board)
        score = eval_attack(board)
        if score > best:
            best = score
            since_improve = 0
            if best >= meta_best:
                meta_best = best
                print_board(board)
                print score
        else:
            since_improve += 1
            if since_improve > threshold:
                since_improve = 0
                board = new_board(n)
                best = 0
            elif random.random() >= exp((score - best)/temp):
                move_queen(board, prev_q, prev_pos)

Leave a Comment