Knight's Tour Generator
Tourneys and the Fast Generation and Obfuscation of Closed Knight's Tours
Warnsdorff.cpp
Go to the documentation of this file.
1 
4 // MIT License
5 //
6 // Copyright (c) 2019 Ian Parberry
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 
26 #include "Warnsdorff.h"
27 #include "Defines.h"
28 
29 extern std::atomic_bool g_bFinished;
30 extern MoveDeltas g_vecDeltas;
31 
34 
36  ::srand(seed); //seed the default PRNG
37  m_cRandom.srand(); //seed our PRNG
38 } //constructor
39 
43 
45  const int w = b.GetWidth();
46  const int n = b.GetSize();
47 
48  b.Clear();
49 
50  int target = m_cRandom.randn(0, n - 1);
51  int current = target;
52 
53  int next;
54  int nVisited = 1;
55 
56  std::set<int> m_bEgress;
57 
58  for(MoveDelta delta: g_vecDeltas)
59  if(b.IsOnBoard(target, delta))
60  m_bEgress.insert(target + delta.second*w + delta.first);
61 
62  int available[8];
63  int preferred[8];
64  int preferredcount = 0;
65 
66  int exitcount[8];
67  int count = 0;
68 
69  do{
70  count = 0;
71 
72  //enumerate all possible places you could jump to from current and
73  //record them in array "available", setting "count" to the number of them
74 
75  for(MoveDelta delta: g_vecDeltas){
76  int next = current + delta.second*w + delta.first;
77  if(b.IsOnBoard(current, delta) && b[next] == UNUSED)
78  available[count++] = next;
79  } //for
80 
81  //count the number of moves out of all possible squares
82  //in array "available" and record them in array "exitcount"
83  //also record the smallest of these in "min"
84 
85  int prefcount = 0;
86 
87  int min = 9999;
88 
89  for(int i=0; i<count; i++){
90  exitcount[i] = b.GetAvailableMoveCount(available[i]);
91 
92  if(exitcount[i] < min)
93  min = exitcount[i];
94  } //for
95 
96  //transfer to array "preferred" all of those entries in "available"
97  //that have exitcount equal to the minimum
98  //record the number of these in "prefcount"
99 
100  for(int i=0; i<count; i++)
101  if(exitcount[i] <= min && b[available[i]] != current)
102  preferred[prefcount++] = available[i];
103 
104  //choose a random one of the minima
105 
106  if(prefcount > 0){
107  if(prefcount == 1)
108  next = preferred[0]; //unique minimum
109 
110  else do{
111  const int index = m_cRandom.randn(0, prefcount - 1);
112  next = preferred[index]; //pick preferred next move
113  }while(next == target && prefcount > 1); //can't be target unless no choice
114 
115  //move there
116 
117  b.InsertUndirectedMove(current, next);
118  current = next; //move from current to next
119  nVisited++; //one more square visited
120  } //if
121 
122  m_bEgress.erase(current); //we've used up 1 exit point
123  }while(count > 0 && m_bEgress.size() > 0 && !g_bFinished);
124 
125  if(!g_bFinished && b.IsKnightMove(current, target) && nVisited >= n){
126  b.InsertUndirectedMove(current, target);
127  return true;
128  } //if
129 
130  return false;
131 } //GenerateTour
132 
136 
138  const int w = b.GetWidth();
139  const int n = b.GetSize();
140 
141  bool bCycleCover = false;
142 
143  b.Clear();
144 
145  for(int i=0; i<4*w && !bCycleCover; i++){
146  bool bFinished = false;
147  int first = 0;
148 
149  //find an unused cell
150  while(b[first] != UNUSED && first < n)
151  first++;
152 
153  bCycleCover = first >= n;
154  bFinished = bCycleCover;
155 
156  if(!bFinished){
157  int last = RandomClosedWalk(b, first); //do a random walk
158 
159  //finished if we can close the cycle and it has length > 1
160  bFinished = b.IsKnightMove(first, last) &&
161  b[first] != last && b[last] != first;
162 
163  if(bFinished)
164  b.InsertUndirectedMove(last, first);
165 
166  else{ //erase attempt at cycle
167  int cur = first;
168  int next = b[cur];
169 
170  while(cur != last){
171  b.DeleteMove(cur, next);
172  cur = next;
173  next = b[cur];
174  } //while
175  } //else
176  } //if
177  } //while
178 
179  return bCycleCover;
180 } //GenerateTourney
181 
186 
188  const int w = b.GetWidth();
189  const int n = b.GetSize();
190 
191  int next = 0;
192  int current = start;
193  int exitcount[8];
194 
195  int nVisited = 1;
196  int nNextMoveCount = 1;
197 
198  int nNumTrials = 0;
199 
200  int available[8];
201 
202  while(nNumTrials < 4*w && (!b.IsKnightMove(current, start) || nVisited < 6)){
203  nNumTrials++;
204  nNextMoveCount = 0;
205 
206  //enumerate all possible places you could jump to from current and
207  //record them in array "available", setting "count" to the number of them
208 
209  for(MoveDelta delta: g_vecDeltas) {
210  int next = current + delta.second*w + delta.first;
211  if(b.IsOnBoard(current, delta) && b[next] == UNUSED)
212  available[nNextMoveCount++] = next;
213  } //for
214 
215  //count the number of moves out of all possible squares
216  //in array "available" and record them in array "exitcount"
217  //also record the smallest of these in "min"
218 
219  int min = 9999;
220 
221  for(int i=0; i<nNextMoveCount; i++){
222  exitcount[i] = b.GetAvailableMoveCount(available[i]);
223  if(exitcount[i] < min)
224  min = exitcount[i];
225  } //for
226 
227  //transfer to array "preferred" all of those entries in "available"
228  //that have exitcount equal to the minimum
229  //record the number of these in "preferredcount"
230 
231  int preferredcount = 0;
232  int preferred[8];
233 
234  for(int i=0; i<nNextMoveCount; i++){
235  if(exitcount[i] <= min && b[available[i]] != current)
236  preferred[preferredcount++] = available[i];
237  } //for
238 
239  //choose a random one of the minima
240 
241  if(preferredcount > 0){
242  if(preferredcount == 1)
243  next = preferred[0]; //unique minimum
244 
245  else{ //pick a preferred move
246  const int n = m_cRandom.randn(0, preferredcount - 1);
247  next = preferred[n];
248  } //else
249 
250  //move there
251 
252  b.InsertUndirectedMove(current, next);
253  current = next;
254  nVisited++;
255  } //if
256  } //while
257 
258  return current;
259 } //RandomClosedWalk
260 
264 
266  switch(t){
267  case CycleType::Tour:
268  while(!GenerateTour(b) && !g_bFinished); //generate tour
269  break;
270 
271  case CycleType::Tourney:
272  while(!GenerateTourney(b) && !g_bFinished); //generate tourney
273  break;
274 
275  case CycleType::TourFromTourney:
276  while(!GenerateTourney(b) && !g_bFinished); //generate tourney
277  b.JoinUntilTour(); //make tour from tourney
278  break;
279  } //switch
280 } //Generate
int RandomClosedWalk(CBoard &b, int start)
Create closed random walk.
Definition: Warnsdorff.cpp:187
Header for Warnsdorff's generator CWarnsdorff.
Defines, enumerated types, and typedefs.
bool DeleteMove(int src, int dest)
Delete a move.
Definition: BaseBoard.cpp:528
void Generate(CBoard &b, CycleType t)
Generate a tour or tourney.
Definition: Warnsdorff.cpp:265
CRandom m_cRandom
PRNG.
Definition: Warnsdorff.h:59
UINT randn()
Get a random unsigned integer.
Definition: Random.cpp:49
int GetSize()
Get size.
Definition: BaseBoard.cpp:808
std::pair< int, int > MoveDelta
Move delta for a knight's move.
Definition: Helpers.h:42
CWarnsdorff(int seed)
Constructor.
Definition: Warnsdorff.cpp:35
bool IsKnightMove(int i, int j)
Knight's move test.
Definition: BaseBoard.cpp:138
bool IsOnBoard(int pos, const MoveDelta &delta)
Move stays on board.
Definition: BaseBoard.cpp:190
void JoinUntilTour()
Join cycles to reduce tourney size.
Definition: Board.cpp:305
std::atomic_bool g_bFinished
Search termination flag.
bool GenerateTourney(CBoard &b)
Generate a tourney.
Definition: Warnsdorff.cpp:137
#define UNUSED
Contents of unused square on the chessboard.
Definition: Defines.h:31
Chessboard.
Definition: Board.h:42
int GetAvailableMoveCount(int index)
Get number of moves from a cell.
Definition: BaseBoard.cpp:205
void srand()
Seed the random number generator.
Definition: Random.cpp:39
std::vector< MoveDelta > MoveDeltas
Move deltas for knight's moves.
Definition: Helpers.h:43
bool GenerateTour(CBoard &b)
Generate a knight's tour.
Definition: Warnsdorff.cpp:44
MoveDeltas g_vecDeltas
Move deltas for all possible knight's moves.
Definition: Helpers.cpp:68
CycleType
Cycle type.
Definition: Defines.h:58
int GetWidth()
Get width.
Definition: BaseBoard.cpp:794
bool InsertUndirectedMove(int src, int dest)
Insert an undirected move.
Definition: BaseBoard.cpp:482
void Clear()
Clear the board of moves.
Definition: BaseBoard.cpp:89