title: "Lecture 5: Chess Problems" author: "Junaid Hasan" date: "June 30, 2021"¶

Today we will discuss Chess Problems. More specifically we will discuss non-attacking problems.

Lets begin.

Question 1: Non-attacking Rooks.¶

Place the most number of non-attacking rooks on a chess board.

  • Idea: If rook is place at $(i,j)$ then another rooks cannot be placed at the same row/column.

  • Answer: Let $x_{ij} = 1$ if rook is placed at row $i$, column $j$ and $0$ otherwise. Then we want to

  • maximize $\sum_{ij} x_{ij}$ subject to

  • $\sum_{i} x_{ij} \leq 1$ non attacking on rows.

  • $\sum_{j} x_{ij} \leq 1$ non attacking on columns.

Question 2: Non-attacking Bishops.¶

How do we translate the same if we want non-attacking bishops instead of rooks.

  • Idea: Bishops attack across diagonals ($i+j = k$) or anti-diagonals ($i-j = k$). Note that $2 \leq i+j \leq 16$ and $ -7 \leq i-j \leq 7$.
  • Answer: Let $x_{ij} = 1$ if bishop is placed at $(i,j)$ and 0 otherwise. We want to
  • maxmimize $\sum_{ij} x_{ij}$ subject to
  • $\sum_{i+j = k} x_{ij} \leq 1$ for $k = 2, \ldots, 16$.
  • $\sum_{i-j = k} x_{ij} \leq 1$ for $k = -7, \ldots, 7$.

Question 3: Non-attacking Queens.¶

A queen is basically a bishop and a rook. Therefore we enforce both constraints.

  • maxmimize $\sum_{ij} x_{ij}$ subject to
  • $\sum_{i+j = k} x_{ij} \leq 1$ for $k = 2, \ldots, 16$.
  • $\sum_{i-j = k} x_{ij} \leq 1$ for $k = -7, \ldots, 7$.
  • $\sum_{i} x_{ij} \leq 1$.
  • $\sum_{j} x_{ij} \leq 1$.

Question 4: Non-attacking Kings¶

There cannot be more than one king among

$x_{i,j}, x_{i\pm1, j}, x_{i, j \pm 1}, x_{i \pm 1, j \pm 1}$ However, note that we can alternatively just enforce one king among $x_{i,j}, x_{i+1,j}, x_{i,j+1}, x_{i+1,j+1}$.

Therefore we get

  • maximize $\sum_{ij} x_{ij}$ subject to
  • $x_{i,j}+ x_{i+1,j}+ x_{i,j+1}+ x_{i+1,j+1} \leq 1$ for $1, \leq i,j \leq 7$.

Question 5: Non-attacking knights¶

Note that similar to kings, looking forward and down is enough. (We do not need to look backward and up). Except at the borders.

Therefore we get

  • maximize $\sum_{ij} x_{ij}$ subject to
  • $x_{i,j} + x_{i',j'} \leq 1$ for $(i',j') \in jump(i,j)$.
In [1]:
I = list(range(1,9))
J = list(range(1,9))
from pyscipopt import Model, quicksum
modelknight = Model("Non attacking knights")
IJ = [(i,j) for i in I for j in J]
IplusJ={}
for k in range(2,17):
    IplusJ[k] = []
for i in I:
    for j in J:
        IplusJ[i+j].append((i,j))
IminusJ={}
for k in range(-7,8):
    IminusJ[k] = []
for i in I:
    for j in J:
        IminusJ[i-j].append((i,j))
jumpIJ = {}
for c in IJ:
    jumpIJ[c] = []
    if 1<= c[0]-2 <=8 and 1<= c[1]+1 <=8:
        jumpIJ[c].append((c[0]-2, c[1]+1))

    if 1<= c[0]-2 <=8 and 1<= c[1]-1 <=8:
        jumpIJ[c].append((c[0]-2, c[1]-1))
    
    if 1<= c[0]-1 <=8 and 1<= c[1]+2 <=8:
        jumpIJ[c].append((c[0]-1, c[1]+2))
    
    if 1<= c[0]-1 <=8 and 1<= c[1]-2 <=8:
        jumpIJ[c].append((c[0]-1, c[1]-2))
    
    if 1<= c[0]+1 <=8 and 1<= c[1]+2 <=8:
        jumpIJ[c].append((c[0]+1, c[1]+2))
    
    if 1<= c[0]+1 <=8 and 1<= c[1]-2 <=8:
        jumpIJ[c].append((c[0]+1, c[1]-2))
    
    if 1<= c[0]+2 <=8 and 1<= c[1]+1 <=8:
        jumpIJ[c].append((c[0]+2, c[1]+1))
    
    if 1<= c[0]+2 <=8 and 1<= c[1]-1 <=8:
        jumpIJ[c].append((c[0]+2, c[1]-1))
In [2]:
IplusJ
Out[2]:
{2: [(1, 1)],
 3: [(1, 2), (2, 1)],
 4: [(1, 3), (2, 2), (3, 1)],
 5: [(1, 4), (2, 3), (3, 2), (4, 1)],
 6: [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)],
 7: [(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)],
 8: [(1, 7), (2, 6), (3, 5), (4, 4), (5, 3), (6, 2), (7, 1)],
 9: [(1, 8), (2, 7), (3, 6), (4, 5), (5, 4), (6, 3), (7, 2), (8, 1)],
 10: [(2, 8), (3, 7), (4, 6), (5, 5), (6, 4), (7, 3), (8, 2)],
 11: [(3, 8), (4, 7), (5, 6), (6, 5), (7, 4), (8, 3)],
 12: [(4, 8), (5, 7), (6, 6), (7, 5), (8, 4)],
 13: [(5, 8), (6, 7), (7, 6), (8, 5)],
 14: [(6, 8), (7, 7), (8, 6)],
 15: [(7, 8), (8, 7)],
 16: [(8, 8)]}
In [3]:
knightdata = {}
for c in IJ:
        knightdata[c] = modelknight.addVar(vtype="B", name = "Knight at %s,%s" %c)
In [4]:
for c in IJ:
    for d in jumpIJ[c]:modelknight.addCons(knightdata[c] + knightdata[d] <=1, "atmost one knight among %s, %s" %c + " and %s, %s" %d)    
In [5]:
modelknight.setObjective(quicksum(knightdata[d] for d in IJ), "maximize")
In [6]:
modelknight.optimize()
In [7]:
modelknight.getObjVal()
Out[7]:
32.0
In [8]:
modelrooks = Model("Non attacking Rooks")
rookdata = {}
for i in I:
    for j in J:
        rookdata[(i,j)] = modelrooks.addVar(vtype="B", name = "Rook at %s,%s" %(i,j))
    
for i in I:
    modelrooks.addCons(quicksum(rookdata[(i,j)] for j in J) <=1)
    
for j in J:
    modelrooks.addCons(quicksum(rookdata[(i,j)] for i in I) <=1)
    
modelrooks.setObjective(quicksum(rookdata[(i,j)] for i in I for j in J), "maximize")

modelrooks.optimize()
In [9]:
modelrooks.getObjVal()
Out[9]:
8.0
In [ ]:
 

Lets draw the knights:

In [10]:
import chess
import chess.svg
import random

This converts from the (i,j) notation to the FEN notation

In [11]:
def getFenChess(chessboard):
    Rank = [8, 7, 6, 5, 4, 3, 2, 1]
    File = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
    string1 = ""
    for r in Rank:
        rowstring = ""
        num_spaces = 0
        for f in File:
            if chessboard[(r,f)] == ' ':
                num_spaces +=1
                if f == 'h':
                    rowstring = rowstring+str(num_spaces)
            else:
                if num_spaces != 0:
                    rowstring = rowstring+str(num_spaces)
                    num_spaces = 0
                rowstring = rowstring+chessboard[(r,f)]
        if r != 1:
            string1 = string1+rowstring+"/"
            # print(rowstring)
        else:
            string1 = string1+ rowstring
    return string1
In [12]:
Filenumber = {1:'a', 2:'b', 3:'c', 4:'d', 5:'e', 6:'f', 7:'g', 8:'h'}
In [13]:
chessboard = {}
for i in I:
    for j in J:
        if modelknight.getVal(knightdata[(i,j)]) == 1:
            chessboard[(i,Filenumber[j])] = 'N'
        else:
            chessboard[(i,Filenumber[j])] = ' '
In [14]:
knightboard = chess.Board(getFenChess(chessboard))
chess.svg.board(knightboard, size=240)
Out[14]:
No description has been provided for this image
In [15]:
chessboard = {}
for i in I:
    for j in J:
        if modelrooks.getVal(rookdata[(i,j)]) == 1:
            chessboard[(i,Filenumber[j])] = 'R'
        else:
            chessboard[(i,Filenumber[j])] = ' '
rookboard = chess.Board(getFenChess(chessboard))
chess.svg.board(rookboard, size=240)
Out[15]:
No description has been provided for this image
In [ ]:
 
In [16]:
modelbishops = Model("Non attacking Bishops")
bishopdata = {}
for i in I:
    for j in J:
        bishopdata[(i,j)] = modelbishops.addVar(vtype="B", name = "Bishop at %s,%s" %(i,j))

for k in IplusJ:
    modelbishops.addCons(quicksum(bishopdata[l] for l in IplusJ[k]) <=1)

for k in IminusJ:
    modelbishops.addCons(quicksum(bishopdata[l] for l in IminusJ[k]) <=1)
    
modelbishops.setObjective(quicksum(bishopdata[(i,j)] for i in I for j in J), "maximize")

modelbishops.optimize()
In [17]:
modelbishops.getObjVal()
Out[17]:
14.0
In [18]:
chessboard = {}
for i in I:
    for j in J:
        if modelbishops.getVal(bishopdata[(i,j)]) == 1:
            chessboard[(i,Filenumber[j])] = 'b'
        else:
            chessboard[(i,Filenumber[j])] = ' '
bishopboard = chess.Board(getFenChess(chessboard))
chess.svg.board(bishopboard, size=240)
Out[18]:
No description has been provided for this image
In [19]:
modelqueens = Model("Non attacking Queen")
queendata = {}
for i in I:
    for j in J:
        queendata[(i,j)] = modelqueens.addVar(vtype="B", name = "Queen at %s,%s" %(i,j))
for i in I:
    modelqueens.addCons(quicksum(queendata[(i,j)] for j in J) <=1)
    
for j in J:
    modelqueens.addCons(quicksum(queendata[(i,j)] for i in I) <=1)
    
for k in IplusJ:
    modelqueens.addCons(quicksum(queendata[l] for l in IplusJ[k]) <=1)

for k in IminusJ:
    modelqueens.addCons(quicksum(queendata[l] for l in IminusJ[k]) <=1)
    
modelqueens.setObjective(quicksum(queendata[(i,j)] for i in I for j in J), "maximize")

modelqueens.optimize()
In [20]:
modelqueens.getObjVal()
Out[20]:
8.0
In [21]:
chessboard = {}
for i in I:
    for j in J:
        if modelqueens.getVal(queendata[(i,j)]) == 1:
            chessboard[(i,Filenumber[j])] = 'q'
        else:
            chessboard[(i,Filenumber[j])] = ' '
queenboard = chess.Board(getFenChess(chessboard))
chess.svg.board(queenboard, size=240)
Out[21]:
No description has been provided for this image
In [ ]:
 
In [ ]:
 
In [ ]:
modelqueens.getSolvingTime