/// <summary> Creates an empty queen set with bitboards initialized. </summary> public static QueenSet Empty() { var bbCount = Board.BitboardCount; var dim = Board.Dimension; var tFilesInv = ~BitBase.TrailingBitFiles; var tRanksInv = ~BitBase.TrailingBitRanks; var emptySet = new QueenSet(new ulong[bbCount], new ulong[bbCount]); // Exclude all files beyond N: for (var i = dim - 1; i < bbCount; i += dim) { emptySet.Fill[i] = tFilesInv; } // Exclude all ranks beyond N: for (var i = bbCount - dim; i < bbCount; i++) { emptySet.Fill[i] |= tRanksInv; } return(emptySet); }
protected override void Solve() { /* We don't need to check every position in the first rank. * In fact, we only need to check half of them (+1 if N is odd). * This is because placing the first queen on the other half * of the rank will produce the same fundamental results - only * mirrored horizontally. */ var halfWay = Master.N / 2 + Master.N % 2; /* The main search stage begins after all root nodes are collected. * A root node is basically a description of an incomplete set * containing the first 2 queens placed on the first 2 ranks. The * reason for the whole concept is parallelism. Each thread will * handle one path starting from some root node. Why 2 queens and * not just the first one? Well, that way there are more starting * points meaning more iterations, and therefore the parallel loop * will work more efficiently. */ var nodes = new ConcurrentBag <Node>(); /* Also we need some original fill mask that takes redundant utmost * bits into consideration. Those are bits that need to be excluded * from the leftmost and topmost bitboards in case N is not a power of 8. * By doing that right away, we don't need to worry about it anymore. * The Empty() function handles the redundant bits. */ var fillMask = QueenSet.Empty().Fill; // Gather the root nodes: Parallel.For(0, halfWay, Master.ThreadingOptions, i => { // The index of the bitboard containing the examined square: var bbIdx = i / 8; // The first queen's bit-position: var bitPos = 1UL << (i % 8); // The first queen's attack mask: var attacks = BitBase.Attacks[bbIdx][bitPos]; // The root fill mask will combine the attack mask and the original fill mask: var rootFill = new ulong[Board.BitboardCount]; for (var j = 0; j < Board.BitboardCount; j++) { rootFill[j] = attacks[j] | fillMask[j]; } // Now go through the 2nd rank and get all the ways of placing // the 2nd queen, effectively collecting a set of root nodes: for (var j = 0; j < Board.Dimension; j++) { AddNode(nodes, rootFill, j, bbIdx, bitPos, 0xFF00); } }); // Perform the search per each root node: Parallel.ForEach(nodes, Master.ThreadingOptions, node => { Search(node, 0, 2, 0xFF0000); // Notify the user that something is going on (helpful in case of big N): Events.OnProgress(); }); // All solutions should be found at this point. Events.OnAllSolutionsFound(); }