18

I've been searching for hours and haven't found a fully working solution for this kind of puzzle yet. So I followed similar problem with bishops.

What I need to do is place 12 knights on the chess board in such a way, that all free squares of the board are attacked by at least one piece.

The final result should look like this:

enter image description here

The problem is that my program only tries different combinations with two last pieces and then somehow crashes. EDITED

What I have done so far:

#include <iostream>
using namespace std;
#define N 8

void fillChessBoard(int (&chessBoard)[N][N], int num);
void printChessBoard(int (&chessBoard)[N][N]);
void removeKnight(int (&chessBoard)[N][N], int i, int j);
void placeKnight(int (&chessBoard)[N][N], int i, int j);
bool allSpaceDominated(int (&chessBoard)[N][N]);
bool backtracking(int (&chessBoard)[N][N], int pos);

int main()
{
    int chessBoard[N][N];

    fillChessBoard(chessBoard, 0);
    backtracking(chessBoard, 0);

    return 0;
}

bool backtracking(int (&chessBoard)[N][N], int knightNum)
{
    if(knightNum==12)
    {
        if(allSpaceDominated(chessBoard))
        {
            printChessBoard(chessBoard);
            return true;
        }
        else return false;
    }
    else
    {
        for(int i=0; i<N; i++)
        {
            for(int j=0; j<N; j++)
            {
                if(chessBoard[i][j]!=2)
                {
                    placeKnight(chessBoard, i, j);
                    printChessBoard(chessBoard); //step by step

                    if(backtracking(chessBoard, knightNum+1)) return true;
                    removeKnight(chessBoard, i, j);
                }
            }
        }
    }
}
void fillChessBoard(int (&chessBoard)[N][N], int num)
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            chessBoard[i][j]=num;
        }
    }
}
void printChessBoard(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            cout<<chessBoard[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;
    cout<<endl;
    cin.get();
}
void removeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=0;
    chessBoard[i][j]=num;

    if(i+2 <= N && j+1 <= N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 <= N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 <= N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 <= N && j+2 <= N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 <= N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 <= N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;

    for(int k=0; k<N; k++) //correct overlapping dominations
    {
        for(int x=0; x<N; x++)
        {
            if(chessBoard[k][x]==2)
            {
                placeKnight(chessBoard, k, x);
            }
        }
    }
}
void placeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=1;
    chessBoard[i][j]=2;

    if(i+2 <= N && j+1 <= N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 <= N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 <= N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 <= N && j+2 <= N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 <= N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 <= N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;
}
bool allSpaceDominated(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            if(chessBoard[i][j]==0) return false;
        }
    }
    return true;
}

Edit:

  • Fixed array boundaries in placeKnight and removeKnight (like, j+2 < N instead of j+2 <= N)
  • Added return false; in backtracking

Problem: Now it loops forever. Tries tons of combinations, but never finds the one I need (Brute-force?)

#include <iostream>
using namespace std;
#define N 8

void fillChessBoard(int (&chessBoard)[N][N], int num);
void printChessBoard(int (&chessBoard)[N][N]);
void removeKnight(int (&chessBoard)[N][N], int i, int j);
void placeKnight(int (&chessBoard)[N][N], int i, int j);
bool allSpaceDominated(int (&chessBoard)[N][N]);
bool backtracking(int (&chessBoard)[N][N], int pos);

int main()
{
    int chessBoard[N][N];

    fillChessBoard(chessBoard, 0);
    backtracking(chessBoard, 0);
    printChessBoard(chessBoard);

    return 0;
}
bool backtracking(int (&chessBoard)[N][N], int knightNum)
{
    if(knightNum==12)
    {
        if(allSpaceDominated(chessBoard))
        {
            printChessBoard(chessBoard);
            return true;
        }
        else return false;
    }
    else
    {
        for(int i=0; i<N; i++)
        {
            for(int j=0; j<N; j++)
            {
                if(chessBoard[i][j]!=2)
                {
                    placeKnight(chessBoard, i, j);
                    printChessBoard(chessBoard);// step by step
                    if(backtracking(chessBoard, knightNum+1)) return true;

                    removeKnight(chessBoard, i, j);
                }
            }
        }
        return false; //ADDED LINE
    }
}
void fillChessBoard(int (&chessBoard)[N][N], int num)
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            chessBoard[i][j]=num;
        }
    }
}
void printChessBoard(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            cout<<chessBoard[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;
    cout<<endl;
    //cin.get();
}
void removeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=0;
    chessBoard[i][j]=num;

    if(i+2 < N && j+1 < N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 < N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 < N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 < N && j+2 < N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 < N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 < N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;

    for(int k=0; k<N; k++)
    {
        for(int x=0; x<N; x++)
        {
            if(chessBoard[k][x]==2)
            {
                placeKnight(chessBoard, k, x);
            }
        }
    }
}
void placeKnight(int (&chessBoard)[N][N], int i, int j)
{
    int num=1;
    chessBoard[i][j]=2;

    if(i+2 < N && j+1 < N && chessBoard[i+2][j+1]!=2) chessBoard[i+2][j+1]=num;
    if(i+2 < N && j-1 >= 0 && chessBoard[i+2][j-1]!=2) chessBoard[i+2][j-1]=num;

    if(i-2 >= 0 && j+1 < N && chessBoard[i-2][j+1]!=2) chessBoard[i-2][j+1]=num;
    if(i-2 >= 0 && j-1 >= 0 && chessBoard[i-2][j-1]!=2) chessBoard[i-2][j-1]=num;

    if(i+1 < N && j+2 < N && chessBoard[i+1][j+2]!=2) chessBoard[i+1][j+2]=num;
    if(i+1 < N && j-2 >= 0 && chessBoard[i+1][j-2]!=2) chessBoard[i+1][j-2]=num;

    if(i-1 >= 0 && j+2 < N && chessBoard[i-1][j+2]!=2) chessBoard[i-1][j+2]=num;
    if(i-1 >= 0 && j-2 >= 0 && chessBoard[i-1][j-2]!=2) chessBoard[i-1][j-2]=num;
}
bool allSpaceDominated(int (&chessBoard)[N][N])
{
    for(int i=0; i<N; i++)
    {
        for(int j=0; j<N; j++)
        {
            if(chessBoard[i][j]==0) return false;
        }
    }
    return true;
}
RendoJack
  • 366
  • 1
  • 5
  • 17
  • Does it crash during **fillChessBoard()**, or **backtracking()**, and if so, which one? – agc Apr 10 '16 at 08:08
  • It does crash on backtracking. – RendoJack Apr 10 '16 at 08:10
  • 2
    In `placeKnight` and `removeKnight` you are using `<= N` instead of `< N` so exceeding the array bounds. – David Conneely Apr 10 '16 at 08:34
  • How important is performance in your solution? – Chiel Apr 10 '16 at 09:51
  • It shouldn't be a brute-force as it is now if I understand right. The only requirements for this task are recursion and backtracking method. – RendoJack Apr 10 '16 at 09:54
  • 1
    Not 10^165 positions! There was an error in my calculation, there are "only" 3*10^12 positions. Still if you are printing them it will take a very long time. You still should try to reduce the search space – n. m. could be an AI Apr 10 '16 at 10:06
  • Any suggestions how to improve code? – RendoJack Apr 10 '16 at 11:03
  • Try to remove printing first :) You can also reject bad positions early. Eaxh knight dominates up to 9 fields, so if you need to add k knights while having more than 9×k non-dominated fields, you can backtrack right away. – n. m. could be an AI Apr 10 '16 at 11:47
  • 3
    To find the first solution faster, try placing knights starting from the center of the board and not from the corner. – n. m. could be an AI Apr 10 '16 at 11:51
  • @n.m. Indeed, 64! / (52! * 12!) = 3284214703056 – Chiel Apr 10 '16 at 12:37
  • If you divide the board into 4 quadrants and you solve the 1st quadrant using only 3 pieces then you have already solved the 4th quadrant because it is a diagonal line of symmetry and the same goes for quadrant 2. If you solve that then you already solved quadrant 3. Also due to the nature of this problem, you can pre-eliminate any cells that are on the border, the 4 inner diagonal cells from the corners of the board as well as the 4 inner most center cells. Refer to my answer as I have demonstrated a visual algorithm. – Francis Cugler Apr 16 '16 at 06:26
  • In truth if you solve the first quadrant of the board with the 3 knights, you have already found the exact same solutions for the other 3. So if the first board found say 8 solutions, then there would be 8^4 total solutions. If it found 2 solutions it is 2^4 total solutions. This works because if you rotate the first quadrant by 90 degrees about the center cell you have the next quadrant the only difference is the colors are opposite until you rotate it another 90 degrees. – Francis Cugler Apr 17 '16 at 01:15
  • Btw: 12 ! 64 = 3.284.214.703.056 - the number of combinations if brute force. Equals 38 days if 1 million combinations calc'ed per second. – Stormwind Apr 19 '16 at 00:05

5 Answers5

15

Your attempt is very inefficient, so it might be just because of the inefficiency that you can't find a solution.

First, it's pointless to try to place 12 knights. Place 6 knights onto white fields. Find all solutions. Then any solution with 6 knights on white fields can be mirrored and gives 6 knights on black fields, and you combine that.

Second, you are trying to place the knights in any order. But the order is arbitrary. So place them in some sorted order, like a1, c1, e1, g1, b2, d2, f2, h2, a3... etc. That reduces the number of choices by a factor 6! or 720 (in your original case 12! = billions).

To be efficient: Number the white fields from 0 to 31. Number the black fields from 0 to 31. For each black field, find the indices of the white fields that can be reached by a knight on that field, and create a 32 bit bitmap representing those fields.

Then:

for (int k1 = 0; k1 < 27; ++k1)
    for (int k2 = k1+1, k2 < 28; ++k2)
        for (int k3 = k2+1; k3 < 29; ++k3)
            for (int k4 = k3+1; k4 < 30; ++k4)
                for (int k5 = k4+1; k5 < 31; ++k5)
                   for (int k6 = k5+1; k6 < 32; ++k6)
                       if ((bits [k1] | bits [k2] | bits [k3] | bits [k4] | bits [k5] | bits [k6]) == 0xffffffff)
                           // got a solution!!!

That's less than a million checks, so it would take a few milliseconds.

PS. Your combination of placeKnight / removeKnight doesn't work. For example, c3 is covered both by a knight on b1 or on a2. If you place a knight on a2, then on b1, then remove the knight on b1, you set c3 to "not covered".

PS. If you had a bigger chessboard, you would take shortcuts to reduce the number of possibilities. For example, field a1 must be covered by a knight on the first row, second row, or b3 on the third row. So if you try to put a knight on a field c3 or later, and a1 isn't covered, there is no need to try putting a knight on that field or a later field at all.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • The mirroring trick is nice with a board size of an even number, such as 8x8. However, if you use an odd-numbered board size, such as 9x9, this trick won't work. – johnwbyrd Apr 17 '16 at 07:07
3

As pointed out by @gnasher729 trying to place all the 12 knights would be very inefficient, so we can try to place 6 knights on white blocks instead, but using this approach we can only attack a maximum of 30 black blocks out of 32.

So from the above we can take 2 approaches :

1) We can fix 2 knights on the remaining 2 black blocks, and then try to place the remaining 4 knights on the remaining 30 black blocks, notice now we only need to attack the remaining 26 white blocks.

@gnasher729 said that we could mirror the solution, but i was not able to come up with the logic of fixing 2 places and then finding the mirror, because only 30 blocks were being attacked, had the no of knights been 14, then all the 32 blocks would have been attacked, and finding a mirror maybe would have been possible.

2) The second would be to brute force the remaining 6 knights whenever we find a solution for the first 6 knights that attacked more than 26 black blocks, which i implemented but that was still not finding a solution.

So as @n.m. said we can try to find solutions from the center to reduce the search space, so i tried to find solution by placing the knights in the 6 X 6 center square, and further only looked for solutions when 30 out of the 32 black blocks were being attacked instead of 26, and finally was able to find 2 solutions to the problem which were both symmetric, there might be more solutions available to the problem with a better approach.

Code in c++ :

#include <iostream>
#include <ctime>

using namespace std;

#define N 8

int board[N][N], mark[N][N];

void placeOnBoard(int i, int j){
    int count = 0;
    if(mark[i][j] == 0) mark[i][j] = 1;
    if(i+2 < N && j+1 < N && mark[i+2][j+1] == 0) mark[i+2][j+1] = 1;
    if(i+2 < N && j-1 >= 0 && mark[i+2][j-1] == 0) mark[i+2][j-1] = 1;
    if(i-2 >= 0 && j+1 < N && mark[i-2][j+1] == 0) mark[i-2][j+1] = 1;
    if(i-2 >= 0 && j-1 >= 0 && mark[i-2][j-1] == 0) mark[i-2][j-1] = 1;

    if(j+2 < N && i+1 < N && mark[i+1][j+2] == 0) mark[i+1][j+2] = 1;
    if(j+2 < N && i-1 >= 0 && mark[i-1][j+2] == 0) mark[i-1][j+2] = 1;
    if(j-2 >= 0 && i+1 < N && mark[i+1][j-2] == 0) mark[i+1][j-2] = 1;
    if(j-2 >= 0 && i-1 >= 0 && mark[i-1][j-2] == 0) mark[i-1][j-2] = 1;
}

void printBoard(){
    for(int i = 0;i < N;i++){
        for(int j = 0;j < N;j++){
            if(board[i][j] != 0) cout << "K ";
            else cout << board[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

void backtrackBlack(int knightNum, int currX, int currY){
    if(knightNum == 7){
        int count = 0;
        for(int i = 0;i < N;i++) for(int j = 0;j < N;j++) mark[i][j] = 0;
        for(int i = 0;i < N;i++){
            for(int j = 0;j < N;j++) if(board[i][j] != 0) placeOnBoard(i, j);
        }
        for(int i = 0;i < N;i++){
            for(int j = 0;j < N;j++) if(mark[i][j] != 0) count++;
        }
        if(count == 64) printBoard();
        return;
    }
    if(currX == N-1 && currY == N) return;
    int newX, newY;     //new place in the board to move to
    if(currY == N) newY = 0,newX = currX + 1;
    else newY = currY + 1,newX = currX;

    //do not place the current knight at (currX, currY)
    backtrackBlack(knightNum, newX, newY);
    //try to place the current knight at (currX, currY)
    if((currX + currY) % 2 == 1 && currX > 0 && currX < N-1 && currY > 0 && currY < N-1){
        board[currX][currY] = knightNum;
        backtrackBlack(knightNum+1, newX, newY);
        board[currX][currY] = 0;
    }
}

void backtrackWhite(int knightNum, int currX, int currY){
    if(knightNum == 7){
        int count = 0;
        for(int i = 0;i < N;i++) for(int j = 0;j < N;j++) mark[i][j] = 0;
        for(int i = 0;i < N;i++){
            for(int j = 0;j < N;j++) if(board[i][j] != 0) placeOnBoard(i, j);
        }
        for(int i = 0;i < N;i++){
            for(int j = 0;j < N;j++) if(mark[i][j] != 0) count++;
        }
        if(count >= 32){
            backtrackBlack(1, 0, 0);
            //printBoard();
        }
        return;
    }
    if(currX == N-1 && currY == N) return;
    int newX, newY;     //new place in the board to move to
    if(currY == N) newY = 0,newX = currX + 1;
    else newY = currY + 1,newX = currX;

    //do not place the current knight at (currX, currY)
    backtrackWhite(knightNum, newX, newY);
    //try to place the current knight at (currX, currY)
    if((currX + currY) % 2 == 0 && currX > 0 && currX < N-1 && currY > 0 && currY < N-1){
        board[currX][currY] = knightNum;
        backtrackWhite(knightNum+1, newX, newY);
        board[currX][currY] = 0;
    }
}

int main(){
    time_t t = clock();
    backtrackWhite(1, 0, 0);
    t = clock() - t;
        double time_taken = ((double)t)/CLOCKS_PER_SEC;
        cout << "Time Taken : " << time_taken<< endl;
    return 0;
}

It only finds 2 solution in about 89 seconds.

Output :

0 0 0 0 0 0 0 0
0 0 K 0 0 0 0 0
0 0 K K 0 K K 0
0 0 0 0 0 K 0 0
0 0 K 0 0 0 0 0
0 K K 0 K K 0 0
0 0 0 0 0 K 0 0
0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0
0 0 0 0 0 K 0 0
0 K K 0 K K 0 0
0 0 K 0 0 0 0 0
0 0 0 0 0 K 0 0
0 0 K K 0 K K 0
0 0 K 0 0 0 0 0
0 0 0 0 0 0 0 0

Time Taken : 89.2418
uSeemSurprised
  • 1,826
  • 2
  • 15
  • 18
  • So another solution is possible as the first backtrack that try's to place the 6 knights gives 23425 candidate answers, when we take the knights as attacking 26 or more black blocks, a similar thing could be done for the remaining 6 knights, then we can try all possible pairs to yield a solution, but i do not think that it would be better than this one in finding the solution or would be capable of finding more than 2 solutions to the problem. – uSeemSurprised Apr 12 '16 at 18:36
2

Here is a fairly efficient approach; your board is an array of [8][8] or a single array of [64]. You have 12 pieces to place. Before we begin to write any code to implement a program to solve this problem, let's first assess the situation and design an algorithm.

There are 2 things we can do first, we can omit or eliminate cells that we already know are places that a knight can not be to satisfy the solution/s to this problem. These are the outer cells or boarder of the board, the four diagonally adjacent inner corners, and the 4 cells or tiles that make up the dead center of the board. If any one knight is placed on any one of these squares then the solution will not work. We can also look at the lowest common denominator which is 4 and with this we can divide the board into 4 quadrants and work with only 3 pieces in that quadrant.

This does two things; one it makes the algorithm easier to define and more efficient, and it also satisfies another condition. The other conditions is this, we must have 3 knights in each quadrant for the solution to be valid. If there are 4 or more in any one quadrant and there are less than 3 in anyone quadrant, the solution again will fail.

If we look at each Quadrant we can see this:

  • Top Left Quadrant - Bottom Right Cell is one of the center cells.
  • Top Right Quadrant - Bottom Left Cell is one of the center cells.
  • Bottom Left Quadrant - Top Right Cell is one of the center cells.
  • Bottom Right Quadrant - Top Left Cell is one of the center cells.

What makes this vital? We know that when placing a knight on the board for any open cell to be set to attack that these 4 squares at the very center of the board that joins these 4 quadrants can not have a Knight in their locations and that at least one of the outward adjacent cells either in the horizontal or vertical direction of it must have a Knight. Knowing this we can work on 1 Quadrant to place 3 Knights with the immediate exclusion of the cell that we just marked and the other cell that is adjacent to the same center cell.

If you solve one of these quadrants then the other three are just translations of them. So with this approach it would only take so many computations to solve a 4x4 grid with its inner corner listed as a starting point and either its horizontal or vertical neighbor will have a knight placed, and which ever one has a knight placed the other adjacent will be left empty. Here is a diagram to visually see this elimination process before hand so that you know how to properly construct or implement your checking, searching and placement algorithms.

Knights

So once being able to see this and know what is happening with the problem. These are the steps to take in your algorithm.

  • Divide - 4 Quadrants - 3 Knights per quadrant
  • Eliminate or skip over invalid cells (Border, inner corners and center cells)
  • Place adjacent from center cell either at the vertical or horizontal position.
  • There was 7 possible cells to choose for placement, but since one is selected and the other adjacent won't have a placement we now have 5 cells left to place 2 more knights.
  • Solve the rest of board for this quadrant.
  • Perform Symmetry On The Above solution. If this is quadrant 1 then we don't need to solve quadrant 4, we can take all of the solutions and perform a +diagonal symmetry. If this is quadrant 2 then we don't need to solve for quadrant 3, we can perform the -diagonal symmetry.
  • Now stitch all 4 quadrants back together for all of the solutions and send each solution to your checker to verify if it satisfy the attackable condition. This should be a linear search through an array of [64] with a few comparisons, fairly quick.
  • Remove any solution that doesn't fit the requirements.
  • Print the results.

Edit

Here is a sample program demonstrating how to set up a predefined board that is prepared before you begin to start solving the problem and to verify if the solutions are correct.

#include <conio.h>
#include <iostream>
#include <iomanip>

// These Enums Are Only A Visual Reference And Are Not Used
enum CellPlacement {
    EMPTY_CELL     = 0, 
    BLOCKED_BORDER = 1, 
    BLOCKED_CENTER = 2,
    KNIGHT_PLACED  = 3, 
};

enum CellColor {
    WHITE = 0,
    WHITE = 1,
};

enum IsAttacked {
    NO = 0,
    YES = 1,
};

struct Cell {
    unsigned char row : 3;      // [0x00, 0x07] 
    unsigned char col : 3;      // [0x00, 0x07]
    unsigned char color : 1;    // [0x00, 0x01] - Refer to CellColor
    unsigned char attacked : 1; // [0x00, 0x01] - Refer to IsAttacked
    unsigned char quad : 3;     // [0x01, 0x04] 
    unsigned char state : 3;    // [0x00, 0x03] - Refer to CellPlacement    
};

struct Board {
    Cell cell[8][8];    
};

struct Quad {
    Cell cell[4][4];
};

struct DividedBoard {
    Quad quad[4];
};


int main() {
    Board board;
    DividedBoard divBoard;

    // Temporary
    unsigned char state = 0x00;
    unsigned char quad  = 0x00;

    for ( unsigned char row = 0; row < 8; row++ ) {
        for ( unsigned char col = 0; col < 8; col++ ) {
            // Place Coords
            board.cell[row][col].row = row;
            board.cell[row][col].col = col;

            // Mark Borders, Inner Corners & Center
            if ( row == 0x00 || row == 0x07 || col == 0x00 || col == 0x07 ) {  // Outer Boarders
                state = 0x01;
                board.cell[row][col].state = state;

            } else if ( (row == 0x01 && col == 0x01) || (row == 0x01 && col == 0x06) ||   // Top Left & Right Inner Adjacent Corners
                        (row == 0x06 && col == 0x01) || (row == 0x06 && col == 0x06) ) {  // Bottom Left & Right Inner Adjacent Corners
                state = 0x01;
                board.cell[row][col].state = state;
            } else if ( (row == 0x03 && col == 0x03) || (row == 0x03 && col == 0x04) ||   // Top Left & Right Centers
                        (row == 0x04 && col == 0x03) || (row == 0x04 && col == 0x04) ) {  // Bottom Left & Right Centers
                state = 0x02;
                board.cell[row][col].state = state;
            } else {
                state = 0x00;
                board.cell[row][col].state = state;  // Empty Cells
            }

            // Mark Wich Quadrant They Belong To And Populate Our Divided Board
            if ( (row >= 0x00 && row < 0x04) && (col >= 0x00 && col < 0x04) ) {
                quad = 0x01;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[0].cell[row][col].row   = row;
                divBoard.quad[0].cell[row][col].col   = col;                
                divBoard.quad[0].cell[row][col].state = state;
                divBoard.quad[0].cell[row][col].quad  = quad;
            }
            if ( (row >= 0x00 && row < 0x04) && (col >= 0x04) ) {
                quad = 0x02;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[1].cell[row][col-4].row   = row;
                divBoard.quad[1].cell[row][col-4].col   = col;          
                divBoard.quad[1].cell[row][col-4].state = state;
                divBoard.quad[1].cell[row][col-4].quad  = quad;
            }
            if ( (row >= 0x04) && (col >= 0x00 && col < 0x04) ) {
                quad = 0x03;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[2].cell[row-4][col].row   = row;
                divBoard.quad[2].cell[row-4][col].col   = col;      
                divBoard.quad[2].cell[row-4][col].state = state;
                divBoard.quad[2].cell[row-4][col].quad  = quad;
            }
            if ( row >= 0x04 && col >= 0x04 ) {
                quad = 0x04;
                board.cell[row][col].quad = quad;

                // Set Divided Board To This Quads State
                divBoard.quad[3].cell[row-4][col-4].row   = row;
                divBoard.quad[3].cell[row-4][col-4].col   = col;            
                divBoard.quad[3].cell[row-4][col-4].state = state;
                divBoard.quad[3].cell[row-4][col-4].quad  = quad;
            }       
        }
    }

    // Display Board With Blocked & Empty Squares
    std::cout << std::setw(19) << std::setfill('\0') << "Full Board:\n";
    std::cout << std::setw(20) << std::setfill('\0') << "-----------\n\n";
    for ( unsigned char row = 0x00; row < 0x08; row++ ) {
        for ( unsigned char col = 0x00; col < 0x08; col++ ) {
            std::cout << std::setw(2) << +board.cell[row][col].state << " ";
        }
        std::cout << "\n\n";
    }
    std::cout << "\n";


    // Now Print Our Divided Board By Each Quadrant
    for ( unsigned quad = 0; quad < 4; quad++ ) {
        std::cout << std::setw(6) << "Quad" << quad << ":\n\n";
        for ( unsigned row = 0; row < 4; row++ ) {
            for ( unsigned col = 0; col < 4; col++ ) {
                std::cout << std::setw(2) << +divBoard.quad[quad].cell[row][col].state << " ";
            }
            std::cout << "\n\n";
        }
        std::cout << "\n";
    }   

    std::cout << "\nPress any key to quit.\n" << std::endl;
    _getch();
    return 0;
} // main

If you run this program through the console it will basically print out the image diagram that I had previously displayed. As you can see the structure here is already created. In the code I marked the outer board with a value of 1, the 4 inner cells with 2 and empty cells with 0. From This point now on it is a matter of taking your first quad and start by choosing one of two points that are adjacent to the center which is the cell that has a value of 2. This grid location in our [8][8] is [3][3] so you can use either location 2[3] or location 3 to start with and if you set a Knight there a value of 3, then the other will remain a 0 for this possible solution. As you can see there are only 7 empty cells and after making your first choice there are only 5 cells left to chose to place your 2nd Knight, then there are 4 locations left to place your third and final knight.

Once this step is done, you can do the reflection of the +symmetry to have to same solution pattern for Quad 4. Once you generate all these solutions for Quad 1 Quad 4 is also completed. Then you have to do the same for Quad 2 & 3.

So if we do the math where 1 Knight is placed leaving 2 knights to place and 5 locations That means there are 10 possible solutions to the first knights placement. If we take into the account that the first nice was placed in the other location then that gives us a total of 20 possible solutions for 1 quadrant. We know that there are 4 quadrants so when you have your container that holds all the quads there is a total of 20^4 different possible solutions to choose from. Which is 160,000 total permutations that count for all the different possible placement.

I had actually mentioned that the solutions of Quad1 are the reflection of Qaud4 and that the solutions of Qaud2 are the reflection of Quad3. This is true when testing all of the solutions because of the square being marked as black or white. However when it comes to placing the knights to find possible solutions, none of that is relevant so instead of doing symmetry at this stage, we can just find all permutations for 1 quadrant and just rotate them from its marked center cell to be able to map those solutions to the other 3 quadrants. So once we find the 20 possible layouts for Quadrant 1, it is just a matter of performing 3 rotations on all 20 layouts to give us our 80 different layouts.

Then it is a matter of mixing and matching each of these layouts and testing it against our rule board.

Now this doesn't solve the problem; but this is an efficient way to break down this problem to minimize the amount of permutations of setting the characters on the board. And you can use this approach to design your algorithm to test all the possible answers to find all the correct solutions. Now these structures that I have shown is a good start, but you can also add to the cell structure. You can add an identifier for what color the square is, and another identifier that if it it's position is under attack. I used unsigned char's because the memory foot print is much smaller when working with a byte as opposed to an int, or even a short, and since the range of values for what is needed only goes from 0-7 I also decided to use a bit field within my Cell structure. So stead of 1 cell being 6 bytes a single cell is now only 2 bytes with a couple of bits left over. You need to take caution when using bit fields due to endianess, however, since all values are of unsigned char, this shouldn't be an issue. This helps to conserve and save on memory space when building the arrays of these cells in the quad results when we begin to do all the permutations of the possible solutions to find a working solution. Also by setting the cells values with numbers as opposed to an actual letter allows the math calculations to be much easier.

One thing that I did not mention is this: I'm not 100% sure about this, but from looking at the board long enough because I am a Chess Player, When you go to do the process of generating all the possible solutions, you can eliminate a majority of them before you even send them to the function to verify if they satisfy the final condition, and that is, I think that the 3 Knights in one quadrant within their arrangement would also have to be in the same shape in which they attack. Another words they will form an L shape on the board. Which means that if one of these three knights does not have another knight that is adjacent both in the horizontal and vertical position, the we could conclude that this layout is not going to be a valid layout. You could incorporate this step when you are doing the placement of your knights with in a single quadrant and then this way when you do the rotations for the other 3 quadrants you will have a fraction of the total amount of permutations to solve for.

And because of applying that adjacent rule to a center cell and the additional condition that I believe that the three knights that are placed have to make the shape of how it attacks, here is an image showing all the valid locations a knight can be in.

Knights2

Due to the adjacent to the center rule of placement where if you choose the cell vertical then the center's horizontal cell will be emptied, then that leaves me to believe that are at least 8 possible solutions, which only maybe 2 or 4 of them are valid. So we even narrowed down our search and permutations even more. One thing we can also conclude to narrowing down our search because of the previous rule is we can apply the "Bingo" Rule here as well. Just like in Bingo the center cell is "Free", well for us here with in each quadrant there is no center cell, however from the patter of the cross from all the possible placements we now know that the center of this cross will always have a knight. Using the coordinates that I used and going by row col on the full board, these would be [2,2], [2,5], [5,2] & [5,5]. So during the placement phase these can automatically be added first, then you have the choice of the adjacent to center, then finally you have two choices left with your last piece that will not be the other cell that is also adjacent to your center cell of that Quadrant.

And with this additional case we have reduced our total permutations from 160,000 down to 4 per quadrant for 4^4 total permutations across the entire board. So if you have a pre-populated look up table of all these possible solutions then the function to check for validity will only have to be called 256 times as opposed to 160,000 or billions if you run it for all board placements. Pre-elimination is one of the steps that many people do not take into account before solving a complex problem. What is so nice about this, is that if there are 256 total possible permutations that can generate a valid answer to be tested if it passes the requirements is that each of these can be index from 0-255. The indexing for all these solutions using an unsigned char in hex values first into 1 byte of memory.

Now as for your function to check these 256 permutations of possible solutions this can be done in a simple for & while loop in a linear process just checking each cell to see if it is attacked by doing a basic collision detection process and if anyone one of these fails, we can break out of the iteration of that while loop and discard this solution, then go to the next iteration of our for loop and continue that process. If a container of a solution does satisfy it then you want to mark the occurrence of that solution and save it into another container that holds all valid solutions for each iteration of the loops. This can also eliminate the need or use for recursion.

I know that this is quite long, but it takes that much to explain it in detail and it took me several few hours to examine the problem, draw up the graphics, write the small program and to explain what I did and why I did it, so please feel free to leave a comment and let me know what you think of this approach.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • The suppositions in this algorithm are wrong. A counterexample: for a 6x6 board, one knight domination has four knights at the corners and four knights in the center. Source: http://www.slideshare.net/DanFreeman1/chessboard-puzzles-part-1-domination-42701514 , page 17. – johnwbyrd Apr 17 '16 at 07:00
  • @johnwbyrd yes on a 6x6 board, but for not for an 8x8 board. If they are placed on the boarder, centers or inner corners even with 12, you wouldn't be able to cover every location. Even if my suppositions are not completely accurate, I was demonstrating how to methodically break down such a problem and the steps to take. I was demonstrating instead of using brute force and recursion, to try divide and eliminate, solve for smaller sample and then use that smaller sample as a reference since other sections would have the same results since they are rotations or reflections of the former. – Francis Cugler Apr 17 '16 at 14:28
  • "we can omit or eliminate cells that we already know are places that a knight can not be to satisfy the solution/s to this problem. These are the outer cells or boarder of the board, the four diagonally adjacent inner corners, and the 4 cells or tiles that make up the dead center of the board. If any one knight is placed on any one of these squares then the solution will not work." Can you please explain why that is the case? I don't see any obvious reason that placing any knight on one of those squares would make the problem unsolvable. – Fabio says Reinstate Monica Apr 19 '16 at 00:18
  • @Fabio Turati; I'm an avid chess player, I know the board well and from both experience and examining the board I can tell you that it won't work. Also mathematically speaking if you take the board and divide it into quadrants which is dividing an 8x8 board into 4 4x4 boards this same factor of 4 can be used to divide the number nights which is 12 and this gives you 3 knights per quadrant. If they are placed on the boarders or in the dead center of the board, their attacking patters can not cover all squares. I know this by knowing the board and the piece. – Francis Cugler Apr 19 '16 at 01:49
  • @FabioTurati I don't think like a bitwise logical machine that needs to use brute force, hashing algorithms, and heuristics does to make this kind of assessment. I may use pattern recognition and intuition in my proposal. You can consider what I have said in my answer as being like an axiom in mathematical concepts. I can not exactly prove how it works or show you proof, but I can tell you that it just is with acceptance. Knowing this first hand when trying to add a knight to the board by eliminating such cells will speed up the process. Now the testing algorithm should be the same. – Francis Cugler Apr 19 '16 at 01:53
  • @FabioTurati What I have proposed in my answer is more of a guideline of how to break down such a problem and what steps can be taken before the process of writing the implementation to these algorithms begins. The one I have described above is centered about the placement algorithm. I'm not 100% sure, but I have a strong feeling that there are only 4 possible solutions to this problem but of those 4, 2 are reflections of each other while the other 2 are reflections as well, so the total solution count would be 2. Now as the implementation for testing such a solution should be the same. – Francis Cugler Apr 19 '16 at 02:04
  • @FabioTurati now if you want to get into the process of writing bit boards which is quite a bit more difficult then that is a different approach. – Francis Cugler Apr 19 '16 at 02:05
  • 1
    @FrancisCugler Then you should say that your intuition and experience suggest that a knight can't be placed on those squares, but you have no hard proof for it and you are just assuming it. In my opinion it's fine, but it must be said clearly. Moreover, you are assuming that you can split the board in 4 parts and exploit its symmetry. Sure, it works, but there could be some asymmetrical solutions that this method can't find. So, it's a heuristic. Again: fine, but you should clearly state the limits of your approach. – Fabio says Reinstate Monica Apr 19 '16 at 22:25
2

First I define my basic concept attack-ability. Attack-ability is how many knights can attack given cell.

Ex: Corner cells can be attacked by only two knights so attack-ability is two. Attack-ability of middle cell is 8.

Attack-ability of cells

| 2 | 3 | 4 | 4 | 4 | 4 | 3 | 2 |

| 3 | 4 | 6 | 6 | 6 | 6 | 4 | 3 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 4 | 6 | 8 | 8 | 8 | 8 | 6 | 4 |

| 3 | 4 | 6 | 6 | 6 | 6 | 4 | 3 |

| 2 | 3 | 4 | 4 | 4 | 4 | 3 | 2 |

Calculating attackability

AttackingNodes::AttackingNodes(int x, int y)
{
    target = new Element(x, y);
    CreateAtackList();
}

void AttackingNodes::CreateAtackList()
{
    for(int diffx = -2; diffx <=2; ++diffx)
    {

        for(int diffy = -2; diffy <=2; ++diffy)
        {
            if((diffx*diffx + diffy* diffy) == 5)
            {
                AddAttack(target->_X + diffx, target->_Y + diffy);
            }
        }
    }
}

void AttackingNodes::AddAttack( int x, int y )
{
    if(x >= 0 && y >= 0 && x < BOARD_SIZE && y < BOARD_SIZE)
    {
        Element* element = new Element(x, y);
        attackers.push_back(element);
    }
}

size of the attackers in attacking nodes is equal to attackability.

Then multimap is created attackability against attackingNodes

for(int x = 0; x < BOARD_SIZE; ++x)
{
    for(int y = 0; y < BOARD_SIZE; ++y)
    {
        AttackingNodes* nodes = new AttackingNodes(x, y);
        attackNodes[x][y] = nodes;
        mapAttackPriority.insert(std::make_pair(nodes->attackers.size(), nodes));
    }
}

If attackability is low then there is lesser options for attacking given cell.

So first node from multimap is chosen which has lesser attacking options.

First cell will be 0, 0. There is two options to attack 0, 0

1, 2 or 2, 1

Lets choose 1, 2 and attack cells if they are empty. It can attack 6 cells.

attack(..) is placing knight in given cell. Atackers and targets are same way related. So data generated while calculating attack-ability is used here.

bool Solution::attack( Element* nodes )
{
    ++knightCount;
    AttackingNodes* attackList = PriorityTargets::inst->attackNodes[nodes->_X][nodes->_Y];
    std::list<Element*>::iterator itr;

    board[nodes->_X][nodes->_Y] = CC_KNIGHT;

    for(itr = attackList->attackers.begin(); itr != attackList->attackers.end(); ++itr)
    {
        Element* attackNode = *itr;

        if(board[attackNode->_X][attackNode->_Y] == CC_EMPTY)
        {
            board[attackNode->_X][attackNode->_Y] = CC_ATTACKED;
        }
    }

    return false;
}

| A | 0 | A | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | A | 0 | 0 | 0 | 0 |

| 0 | K | 0 | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | A | 0 | 0 | 0 | 0 |

| A | 0 | A | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

Then algorithm search for next empty cell (no Knight, no Attacked) with lowest attackability attack it with available options.

AttackingNodes* PriorityTargets::GetNextNode( Solution* solution )
{

    std::multimap<size_t, AttackingNodes*>::iterator priorityItr;
    for(priorityItr  = mapAttackPriority.begin(); priorityItr != mapAttackPriority.end(); ++priorityItr)
    {
        AttackingNodes* attackNodes = priorityItr->second;
        if(solution->board[attackNodes->target->_X][attackNodes->target->_Y] == CC_EMPTY)
        {
            return attackNodes;
        }
    }

    return NULL;
}

It will be an another corner node for second options and it will go on until knight count is larger than 12 or no empty cells.

knight count is larger than 12 it is a fail attempt and backed tracked. If there is no empty cell then it is a solution.

Solution::Solution()
{
    Clear();
}

void Solution::Print()
{

    std::cout << std::endl ;
    for(int x = 0; x < BOARD_SIZE; ++x)
    {
        for(int y = 0; y < BOARD_SIZE; ++y)
        {
            std::cout << (int)board[x][y] << " ";
        }
        std::cout << std::endl ;
    }


    std::cout << std::endl ;

}

bool Solution::Solve( Solution* solution )
{
    AttackingNodes* nextAttackingNode = PriorityTargets::inst->GetNextNode(solution);

    if(nextAttackingNode != NULL)
    {
        Solution* newSolutioon = new Solution();
        std::list<Element*>::iterator itr;
        for(itr = nextAttackingNode->attackers.begin(); itr != nextAttackingNode->attackers.end(); ++itr)
        {
            Element* attack = *itr;


                *newSolutioon = *solution;
                newSolutioon->attack(attack);
                if(newSolutioon->knightCount < 13)
                {
                    Solve(newSolutioon);
                }
                else
                {
                    //Fail
                    newSolutioon->Clear();
                }
        }

        delete newSolutioon;
    }
    else
    {
        std::cout << "Solved" << std::endl;
        solution->Print();
    }

    return false;
}


void Solution::Clear()
{
    memset(board, 0, BOARD_SIZE*BOARD_SIZE);
    knightCount = 0;
}

And I got the answer in lesser than 500 ms in visual studio 2008 release mode. I have used 2 for Knight and 1 for attacked.

enter image description here

amald
  • 236
  • 1
  • 8
  • I don't know it is appropriate to attach whole code. If it is fine I can provide it. – amald Apr 17 '16 at 16:33
  • Nice, your algorithm to place the attacking knights to an empty cells until all 12 knights have been exhausted while looking to see if all cells are accounted for completely complements the answer I have given in a way to algorithmically break down this problem before any code is written. And the 2 solutions that your implementation has returned correlates the stipulations I had made in my answer that no knight can be on the boarder of the board, nor in the 4 inner corners adjacent to the corner of the board and the 4 cells that make up the middle. It also confirms 3 knights to 1/4 board. – Francis Cugler Apr 17 '16 at 20:52
  • Thank you and minor modification. In my algorithm knights are not placed in empty cells. They are placed to attack empty cells and they can be placed at already attacked cells. – amald Apr 20 '16 at 06:34
0

A lot of work has been done on bounding the knights domination problem for various board sizes. Here is an article that seems to summarize all the previous work and adds a few new twists. And here is an article that claims to demonstrate a linear-time algorithm for knights domination. I've even found references to a constant-time knights domination algorithm, but I don't see where anyone has taken the trouble to write it out. Engage brain first, write code second.

johnwbyrd
  • 3,432
  • 2
  • 29
  • 25
  • 1
    Yeah. And somehow i am not ready to accept the assumptions people have made in their answers, like assuming symmetry, excluding the edge band, etc. Each knight is able to cover 8 locations plus itself, 12*9=108. There are 64 locations to cover with a whole 108 in the pocket. 108-64=44 can be doublettes or go outside the board. It seems to me that there could well be irregular, assymmetric solutions as well. Nothing proven, i say! – Stormwind Apr 19 '16 at 01:11
  • @Stromwind if you have time please check my answer. I did not use symmetricity and assumptions in my logic. It is obvious if we take a corner cell there is two option to attack it. But even that options are not hard coded and calculated using simple algorithm. – amald Apr 20 '16 at 06:13
  • 1
    @Stromwind You're totally correct. Many solutions for the m x n case are not pretty and symmetrical. For the 6x6 case, you actually put knights in the corners. I consider most of the assumptions made in this thread to be bogus. – johnwbyrd Apr 20 '16 at 07:47