1 /****************************************************************************
2  * Copyright 2018-2020,2021 Thomas E. Dickey                                *
3  * Copyright 1998-2013,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 /*
30  * Knight's Tour - a brain game
31  *
32  * The original of this game was anonymous.  It had an unbelievably bogus
33  * interface, you actually had to enter square coordinates!  Redesign by
34  * Eric S. Raymond <esr@snark.thyrsus.com> July 22 1995.  Mouse support
35  * added September 20th 1995.
36  *
37  * $Id: knight.c,v 1.49 2021/05/08 19:32:15 tom Exp $
38  */
39 
40 #include <test.priv.h>
41 
42 /* board size */
43 #define YLIMIT		8
44 #define XLIMIT		8
45 #define MAXMOVES	(ylimit * xlimit)
46 
47 /* where to start the instructions */
48 #define INSTRY		2
49 #define INSTRX		35
50 
51 /* corner of board */
52 #define BOARDY		2
53 #define BOARDX		0
54 
55 /* notification line */
56 #define NOTIFYY		21
57 
58 /* virtual color values */
59 #define TRAIL_COLOR	1
60 #define PLUS_COLOR	2
61 #define MINUS_COLOR	3
62 
63 #define CX(x)		(2 + 4 * (x))
64 #define CY(y)		(1 + 2 * (y))
65 #define cellmove(y, x)	wmove(boardwin, CY(y), CX(x))
66 #define CXINV(x)	(((x) - 1) / 4)
67 #define CYINV(y)	(((y) - 2) / 2)
68 
69 typedef struct {
70     int x, y;
71 } HISTORY;
72 
73 typedef int SQUARES[YLIMIT][XLIMIT];
74 
75 static WINDOW *boardwin;	/* the board window */
76 static WINDOW *helpwin;		/* the help window */
77 static WINDOW *msgwin;		/* the message window */
78 
79 #if HAVE_USE_DEFAULT_COLORS
80 static bool d_option;
81 #endif
82 
83 static chtype minus = '-';	/* possible-move character */
84 static chtype oldch;
85 static chtype plus = '+';	/* cursor hot-spot character */
86 static chtype trail = '#';	/* trail character */
87 
88 static int ylimit = YLIMIT;
89 static int xlimit = XLIMIT;
90 static int maxmoves = (YLIMIT * XLIMIT);
91 
92 static int count_tries;		/* count of trials so far */
93 static int test_test;		/* FIXME */
94 /* *INDENT-OFF* */
95 static const struct {
96     int y;
97     int x;
98 } offsets[] = {
99     {  2,  1 },
100     {  1,  2 },
101     { -1,  2 },
102     { -2,  1 },
103     { -2, -1 },
104     { -1, -2 },
105     {  1, -2 },
106     {  2, -1 },
107 };
108 #define MAX_OFFSET	(unsigned)SIZEOF(offsets)
109 /* *INDENT-ON* */
110 
111 static void
init_program(void)112 init_program(void)
113 {
114     setlocale(LC_ALL, "");
115 
116     srand((unsigned) getpid());
117     initscr();
118     cbreak();			/* immediate char return */
119     noecho();			/* no immediate echo */
120 
121     maxmoves = MAXMOVES;
122     boardwin = newwin(ylimit * 2 + 1, xlimit * 4 + 1, BOARDY, BOARDX);
123     helpwin = newwin(0, 0, INSTRY, INSTRX);
124     msgwin = newwin(1, INSTRX - 1, NOTIFYY, 0);
125 
126     scrollok(msgwin, TRUE);
127     keypad(boardwin, TRUE);
128 
129     if (has_colors()) {
130 	int bg = COLOR_BLACK;
131 
132 	start_color();
133 #if HAVE_USE_DEFAULT_COLORS
134 	if (d_option && (use_default_colors() == OK))
135 	    bg = -1;
136 #endif
137 
138 	(void) init_pair(TRAIL_COLOR, (short) COLOR_CYAN, (short) bg);
139 	(void) init_pair(PLUS_COLOR, (short) COLOR_RED, (short) bg);
140 	(void) init_pair(MINUS_COLOR, (short) COLOR_GREEN, (short) bg);
141 
142 	trail |= (chtype) COLOR_PAIR(TRAIL_COLOR);
143 	plus |= (chtype) COLOR_PAIR(PLUS_COLOR);
144 	minus |= (chtype) COLOR_PAIR(MINUS_COLOR);
145     }
146 #ifdef NCURSES_MOUSE_VERSION
147     (void) mousemask(BUTTON1_CLICKED, (mmask_t *) NULL);
148 #endif /* NCURSES_MOUSE_VERSION */
149 #if defined(PDCURSES)
150     mouse_set(BUTTON1_RELEASED);
151 #endif
152 
153     oldch = minus;
154 }
155 
156 static void
help1(void)157 help1(void)
158 /* game explanation -- initial help screen */
159 {
160     (void) waddstr(helpwin, "Knight's move is a solitaire puzzle.  Your\n");
161     (void) waddstr(helpwin, "objective is to visit each square of the  \n");
162     (void) waddstr(helpwin, "chessboard exactly once by making knight's\n");
163     (void) waddstr(helpwin, "moves (one square right or left followed  \n");
164     (void) waddstr(helpwin, "by two squares up or down, or two squares \n");
165     (void) waddstr(helpwin, "right or left followed by one square up or\n");
166     (void) waddstr(helpwin, "down).  You may start anywhere.\n\n");
167 
168     (void) waddstr(helpwin, "Use arrow keys to move the cursor around.\n");
169     (void) waddstr(helpwin, "When you want to move your knight to the \n");
170     (void) waddstr(helpwin, "cursor location, press <space> or Enter.\n");
171     (void) waddstr(helpwin, "Illegal moves will be rejected with an  \n");
172     (void) waddstr(helpwin, "audible beep.\n\n");
173     (void) waddstr(helpwin, "The program will detect if you solve the\n");
174     (void) waddstr(helpwin, "puzzle; also inform you when you run out\n");
175     (void) waddstr(helpwin, "of legal moves.\n\n");
176 
177     MvWAddStr(helpwin, NOTIFYY - INSTRY, 0,
178 	      "Press `?' to go to keystroke help.");
179 }
180 
181 static void
help2(void)182 help2(void)
183 /* keystroke help screen */
184 {
185     (void) waddstr(helpwin, "Possible moves are shown with `-'.\n\n");
186 
187     (void) waddstr(helpwin, "You can move around with the arrow keys or\n");
188     (void) waddstr(helpwin, "with the rogue/hack movement keys.  Other\n");
189     (void) waddstr(helpwin, "commands allow you to undo moves or redraw.\n");
190     (void) waddstr(helpwin, "Your mouse may work; try left-button to\n");
191     (void) waddstr(helpwin, "move to the square under the pointer.\n\n");
192 
193     (void) waddstr(helpwin, "x,q -- exit             y k u    7 8 9\n");
194     (void) waddstr(helpwin, "r -- redraw screen       \\|/      \\|/ \n");
195     (void) waddstr(helpwin, "bksp -- undo move       h-+-l    4-+-6\n");
196     (void) waddstr(helpwin, "a -- autojump            /|\\      /|\\ \n");
197     if (ylimit <= 6) {
198 	(void) waddstr(helpwin, "R -- solve (slow)       b j n    1 2 3\n");
199     } else {
200 	(void) waddstr(helpwin, "                        b j n    1 2 3\n");
201     }
202 
203     (void) waddstr(helpwin, "\nYou can place your knight on the selected\n");
204     (void) waddstr(helpwin, "square with spacebar, Enter, or the keypad\n");
205     (void) waddstr(helpwin, "center key.  Use F/B to review the path.\n");
206 
207     MvWAddStr(helpwin, NOTIFYY - INSTRY, 0,
208 	      "Press `?' to go to game explanation");
209 }
210 
211 static void
show_help(bool * keyhelp)212 show_help(bool * keyhelp)
213 {
214     werase(helpwin);
215     if (*keyhelp) {
216 	help1();
217 	*keyhelp = FALSE;
218     } else {
219 	help2();
220 	*keyhelp = TRUE;
221     }
222     wrefresh(helpwin);
223 }
224 
225 static inline bool
isValidYX(int y,int x)226 isValidYX(int y, int x)
227 {
228     return (y >= 0 && y < ylimit && x >= 0 && x < xlimit) ? TRUE : FALSE;
229 }
230 
231 static inline bool
isUnusedYX(SQUARES squares,int y,int x)232 isUnusedYX(SQUARES squares, int y, int x)
233 {
234     return (isValidYX(y, x) && (!squares[y][x]) ? TRUE : FALSE);
235 }
236 
237 static bool
boardIsFilled(SQUARES squares,int y,int x)238 boardIsFilled(SQUARES squares, int y, int x)
239 {
240     unsigned n;
241 
242     for (n = 0; n < MAX_OFFSET; n++) {
243 	if (isUnusedYX(squares, y + offsets[n].y, x + offsets[n].x)) {
244 	    return FALSE;
245 	}
246     }
247     return TRUE;
248 }
249 
250 static void
drawBoard(void)251 drawBoard(void)
252 {
253     int i, j;
254 
255     MvAddStr(0, 20, "KNIGHT'S MOVE -- a logical solitaire");
256 
257     move(BOARDY, BOARDX);
258     waddch(boardwin, ACS_ULCORNER);
259     for (j = 0; j < (ylimit - 1); j++) {
260 	waddch(boardwin, ACS_HLINE);
261 	waddch(boardwin, ACS_HLINE);
262 	waddch(boardwin, ACS_HLINE);
263 	waddch(boardwin, ACS_TTEE);
264     }
265     waddch(boardwin, ACS_HLINE);
266     waddch(boardwin, ACS_HLINE);
267     waddch(boardwin, ACS_HLINE);
268     waddch(boardwin, ACS_URCORNER);
269 
270     for (i = 1; i < ylimit; i++) {
271 	move(BOARDY + i * 2 - 1, BOARDX);
272 	waddch(boardwin, ACS_VLINE);
273 	for (j = 0; j < xlimit; j++) {
274 	    waddch(boardwin, ' ');
275 	    waddch(boardwin, ' ');
276 	    waddch(boardwin, ' ');
277 	    waddch(boardwin, ACS_VLINE);
278 	}
279 	move(BOARDY + i * 2, BOARDX);
280 	waddch(boardwin, ACS_LTEE);
281 	for (j = 0; j < xlimit - 1; j++) {
282 	    waddch(boardwin, ACS_HLINE);
283 	    waddch(boardwin, ACS_HLINE);
284 	    waddch(boardwin, ACS_HLINE);
285 	    waddch(boardwin, ACS_PLUS);
286 	}
287 	waddch(boardwin, ACS_HLINE);
288 	waddch(boardwin, ACS_HLINE);
289 	waddch(boardwin, ACS_HLINE);
290 	waddch(boardwin, ACS_RTEE);
291     }
292 
293     move(BOARDY + i * 2 - 1, BOARDX);
294     waddch(boardwin, ACS_VLINE);
295     for (j = 0; j < xlimit; j++) {
296 	waddch(boardwin, ' ');
297 	waddch(boardwin, ' ');
298 	waddch(boardwin, ' ');
299 	waddch(boardwin, ACS_VLINE);
300     }
301 
302     move(BOARDY + i * 2, BOARDX);
303     waddch(boardwin, ACS_LLCORNER);
304     for (j = 0; j < xlimit - 1; j++) {
305 	waddch(boardwin, ACS_HLINE);
306 	waddch(boardwin, ACS_HLINE);
307 	waddch(boardwin, ACS_HLINE);
308 	waddch(boardwin, ACS_BTEE);
309     }
310     waddch(boardwin, ACS_HLINE);
311     waddch(boardwin, ACS_HLINE);
312     waddch(boardwin, ACS_HLINE);
313     waddch(boardwin, ACS_LRCORNER);
314 }
315 
316 static void
mark_possibles(SQUARES squares,int y,int x,chtype mark)317 mark_possibles(SQUARES squares, int y, int x, chtype mark)
318 {
319     unsigned n;
320 
321     for (n = 0; n < MAX_OFFSET; n++) {
322 	if (isUnusedYX(squares, y + offsets[n].y, x + offsets[n].x)) {
323 	    cellmove(y + offsets[n].y, x + offsets[n].x);
324 	    waddch(boardwin, mark);
325 	}
326     }
327 }
328 
329 static bool
find_next_move(SQUARES squares,HISTORY * doneData,int doneSize,int * y,int * x)330 find_next_move(SQUARES squares, HISTORY * doneData, int doneSize, int *y, int *x)
331 {
332     bool result = FALSE;
333 
334     if (doneSize > 1) {
335 	unsigned j;
336 	int oldy = doneData[doneSize - 1].y;
337 	int oldx = doneData[doneSize - 1].x;
338 	int found = -1;
339 	int first = -1;
340 	int next = -1;
341 
342 	for (j = 0; j < MAX_OFFSET * 2; j++) {
343 	    unsigned k = j % MAX_OFFSET;
344 	    int newy = oldy + offsets[k].y;
345 	    int newx = oldx + offsets[k].x;
346 	    if (isUnusedYX(squares, newy, newx)) {
347 		if (first < 0)
348 		    first = (int) k;
349 		if (newy == *y
350 		    && newx == *x) {
351 		    found = (int) k;
352 		} else if (found >= 0) {
353 		    next = (int) k;
354 		    break;
355 		}
356 	    }
357 	}
358 	if (found < 0)
359 	    next = first;
360 	if (next >= 0) {
361 	    *y = oldy + offsets[next].y;
362 	    *x = oldx + offsets[next].x;
363 	}
364 	result = TRUE;
365     }
366     return result;
367 }
368 
369 static void
count_next_moves(SQUARES squares,int count_moves,int y,int x)370 count_next_moves(SQUARES squares, int count_moves, int y, int x)
371 {
372     int count = 0;
373     unsigned j;
374 
375     wprintw(msgwin, "\nMove %d", count_moves);
376     for (j = 0; j < MAX_OFFSET; j++) {
377 	int newy = y + offsets[j].y;
378 	int newx = x + offsets[j].x;
379 	if (isUnusedYX(squares, newy, newx)) {
380 	    ++count;
381 	}
382     }
383     wprintw(msgwin, ", gives %d choices", count);
384     wclrtoeol(msgwin);
385 }
386 
387 static void
unmarkcell(int row,int column)388 unmarkcell(int row, int column)
389 {
390     cellmove(row, column);
391     waddch(boardwin, '\b');
392     waddch(boardwin, ' ');
393     waddch(boardwin, minus);
394     waddch(boardwin, ' ');
395 }
396 
397 static void
markcell(chtype tchar,int row,int column)398 markcell(chtype tchar, int row, int column)
399 {
400     cellmove(row, column);
401     waddch(boardwin, '\b');
402     waddch(boardwin, tchar);
403     waddch(boardwin, tchar);
404     waddch(boardwin, tchar);
405 }
406 
407 static void
drawMove(SQUARES squares,int count_moves,chtype tchar,int oldy,int oldx,int row,int column)408 drawMove(SQUARES squares, int count_moves, chtype tchar, int oldy, int oldx, int
409 	 row, int column)
410 /* place the stars, update board & currents */
411 {
412     if (count_moves <= 1) {
413 	int i, j;
414 
415 	for (i = 0; i < ylimit; i++) {
416 	    for (j = 0; j < xlimit; j++) {
417 		if (count_moves == 0) {
418 		    unmarkcell(i, j);
419 		} else {
420 		    cellmove(i, j);
421 		    if (winch(boardwin) == minus)
422 			waddch(boardwin, ' ');
423 		}
424 	    }
425 	}
426     } else {
427 	markcell(tchar, oldy, oldx);
428 	mark_possibles(squares, oldy, oldx, ' ');
429     }
430 
431     if (row >= 0 && column >= 0) {
432 	markcell(trail, row, column);
433 	mark_possibles(squares, row, column, minus);
434 	squares[row][column] = TRUE;
435     }
436 
437     wprintw(msgwin, "\nMove %d", count_moves);
438     if (count_tries != count_moves)
439 	wprintw(msgwin, " (%d tries)", count_tries);
440     wclrtoeol(msgwin);
441 }
442 
443 static int
iabs(int num)444 iabs(int num)
445 {
446     if (num < 0)
447 	return (-num);
448     else
449 	return (num);
450 }
451 
452 static bool
evaluate_move(SQUARES squares,HISTORY * doneData,int doneSize,int row,int column)453 evaluate_move(SQUARES squares, HISTORY * doneData, int doneSize, int row, int column)
454 {
455     if (doneSize <= 1)
456 	return (TRUE);
457     else if (squares[row][column] == TRUE) {
458 	waddstr(msgwin, "\nYou've already been there.");
459 	return (FALSE);
460     } else {
461 	int rdif = iabs(row - doneData[doneSize - 1].y);
462 	int cdif = iabs(column - doneData[doneSize - 1].x);
463 
464 	if (!((rdif == 1) && (cdif == 2)) && !((rdif == 2) && (cdif == 1))) {
465 	    waddstr(msgwin, "\nThat's not a legal knight's move.");
466 	    return (FALSE);
467 	}
468     }
469 
470     return (TRUE);
471 }
472 
473 static int
completed(SQUARES squares)474 completed(SQUARES squares)
475 {
476     int i, j, count = 0;
477 
478     for (i = 0; i < ylimit; i++) {
479 	for (j = 0; j < xlimit; j++) {
480 	    if (squares[i][j] != 0) {
481 		count += 1;
482 	    }
483 	}
484     }
485     return ((count == maxmoves) ? -1 : count);
486 }
487 
488 static void
no_previous_move(void)489 no_previous_move(void)
490 {
491     waddstr(msgwin, "\nNo previous move.");
492     beep();
493 }
494 
495 /* Recursively try all possible moves, starting from (y,x) */
496 static int
recurBack(SQUARES squares,int y,int x,int total)497 recurBack(SQUARES squares, int y, int x, int total)
498 {
499     int longest = total;
500     int best_x = x;
501     int best_y = y;
502     int result;
503 
504     if (total < maxmoves) {
505 	unsigned k;
506 
507 	for (k = 0; k < MAX_OFFSET; k++) {
508 	    int try_x = x + offsets[k].x;
509 	    int try_y = y + offsets[k].y;
510 	    if (isUnusedYX(squares, try_y, try_x)) {
511 		++test_test;
512 		squares[try_y][try_x] = total + 1;
513 		result = recurBack(squares, try_y, try_x, total + 1);
514 		if (result > longest) {
515 		    longest = result;
516 		    best_x = try_x;
517 		    best_y = try_y;
518 		}
519 		if (result >= maxmoves)
520 		    break;
521 		squares[try_y][try_x] = 0;	/* allow retry... */
522 	    }
523 	}
524     }
525 
526     result = total;
527     if (longest > total) {
528 	result = longest;
529 	squares[best_y][best_x] = total + 1;
530 	(void) recurBack(squares, best_y, best_x, total + 1);
531 	if (result < maxmoves)
532 	    squares[best_y][best_x] = 0;
533     }
534 
535     return result;
536 }
537 
538 /*
539  * Solve the Knight Tour problem using backtracking, returning the length of
540  * the resulting solution.  If this is invoked from a point where the remaining
541  * choices cannot complete the tour, the result will fall short.
542  */
543 static int
useBacktracking(SQUARES result,HISTORY * doneData,int doneSize)544 useBacktracking(SQUARES result, HISTORY * doneData, int doneSize)
545 {
546     int y = 0, x = 0, n;
547     SQUARES squares;
548     int total;
549     int actual = doneSize - 1;
550 
551     memset(squares, 0, sizeof(squares));
552     for (n = 1; n <= actual; ++n) {
553 	y = doneData[n].y;
554 	x = doneData[n].x;
555 	squares[y][x] = n;
556     }
557 
558     total = recurBack(squares, y, x, actual);
559     if (total > actual) {
560 	for (y = 0; y < ylimit; ++y) {
561 	    for (x = 0; x < xlimit; ++x) {
562 		result[y][x] = squares[y][x];
563 		if ((n = squares[y][x]) != 0) {
564 		    doneData[n].y = y;
565 		    doneData[n].x = x;
566 		}
567 	    }
568 	}
569     }
570     return total;
571 }
572 
573 static int
reviewHistory(HISTORY * history,int count_moves,int review,int * ny,int * nx)574 reviewHistory(HISTORY * history, int count_moves, int review, int *ny, int *nx)
575 {
576     if (review < 0) {
577 	beep();
578 	review = 0;
579     } else if (review > count_moves - 2) {
580 	beep();
581 	review = count_moves - 2;
582     } else {
583 	*ny = history[count_moves - review - 1].y;
584 	*nx = history[count_moves - review - 1].x;
585 	wprintw(msgwin, "\nReview %d:%d.", count_moves - review - 1,
586 		count_moves - 1);
587 	wrefresh(msgwin);
588     }
589     return review;
590 }
591 
592 static void
play(void)593 play(void)
594 /* play the game */
595 {
596     bool keyhelp;		/* TRUE if keystroke help is up */
597     int i, j, count;
598     int lastcol;		/* last location visited */
599     int lastrow;
600     int ny = 0, nx = 0;
601     int review = 0;		/* review history */
602     int test_size;
603     int rw = 0, col = 0;	/* current row and column */
604 
605     do {
606 	SQUARES squares;
607 	HISTORY history[(YLIMIT * XLIMIT) + 1];
608 	int count_moves = 0;	/* count of moves so far */
609 
610 	/* clear screen and draw board */
611 	werase(boardwin);
612 	werase(helpwin);
613 	werase(msgwin);
614 	drawBoard();
615 	help1();
616 	wnoutrefresh(stdscr);
617 	wnoutrefresh(helpwin);
618 	wnoutrefresh(msgwin);
619 	wnoutrefresh(boardwin);
620 	doupdate();
621 
622 	for (i = 0; i < ylimit; i++) {
623 	    for (j = 0; j < xlimit; j++) {
624 		unmarkcell(i, j);
625 	    }
626 	}
627 	memset(squares, 0, sizeof(squares));
628 	memset(history, 0, sizeof(history));
629 	history[0].y = history[0].x = -1;
630 	history[1].y = history[1].x = -1;
631 	lastrow = lastcol = -2;
632 	count_moves = 1;
633 	count_tries = 1;
634 	keyhelp = FALSE;
635 	show_help(&keyhelp);
636 
637 	for (;;) {
638 	    if (rw != lastrow || col != lastcol) {
639 		if (lastrow >= 0 && lastcol >= 0) {
640 		    cellmove(lastrow, lastcol);
641 		    if (squares[lastrow][lastcol])
642 			waddch(boardwin, trail);
643 		    else
644 			waddch(boardwin, oldch);
645 		}
646 
647 		cellmove(rw, col);
648 		oldch = winch(boardwin);
649 
650 		lastrow = rw;
651 		lastcol = col;
652 	    }
653 	    cellmove(rw, col);
654 	    waddch(boardwin, plus);
655 	    cellmove(rw, col);
656 
657 	    wrefresh(msgwin);
658 
659 	    switch (wgetch(boardwin)) {
660 	    case 'k':
661 	    case '8':
662 	    case KEY_UP:
663 		ny = rw + ylimit - 1;
664 		nx = col;
665 		break;
666 	    case 'j':
667 	    case '2':
668 	    case KEY_DOWN:
669 		ny = rw + 1;
670 		nx = col;
671 		break;
672 	    case 'h':
673 	    case '4':
674 	    case KEY_LEFT:
675 		ny = rw;
676 		nx = col + xlimit - 1;
677 		break;
678 	    case 'l':
679 	    case '6':
680 	    case KEY_RIGHT:
681 		ny = rw;
682 		nx = col + 1;
683 		break;
684 	    case 'y':
685 	    case '7':
686 	    case KEY_A1:
687 		ny = rw + ylimit - 1;
688 		nx = col + xlimit - 1;
689 		break;
690 	    case 'b':
691 	    case '1':
692 	    case KEY_C1:
693 		ny = rw + 1;
694 		nx = col + xlimit - 1;
695 		break;
696 	    case 'u':
697 	    case '9':
698 	    case KEY_A3:
699 		ny = rw + ylimit - 1;
700 		nx = col + 1;
701 		break;
702 	    case 'n':
703 	    case '3':
704 	    case KEY_C3:
705 		ny = rw + 1;
706 		nx = col + 1;
707 		break;
708 
709 #ifdef KEY_MOUSE
710 	    case KEY_MOUSE:
711 #ifdef NCURSES_MOUSE_VERSION
712 		{
713 		    MEVENT myevent;
714 
715 		    getmouse(&myevent);
716 		    if (myevent.y >= CY(0) && myevent.y <= CY(ylimit)
717 			&& myevent.x >= CX(0) && myevent.x <= CX(xlimit)) {
718 			nx = CXINV(myevent.x);
719 			ny = CYINV(myevent.y);
720 			ungetch('\n');
721 			break;
722 		    } else {
723 			beep();
724 			continue;
725 		    }
726 		}
727 #endif /* NCURSES_MOUSE_VERSION */
728 #ifdef PDCURSES
729 		{
730 		    int test_y, test_x;
731 		    request_mouse_pos();
732 		    test_y = MOUSE_Y_POS + 0;
733 		    test_x = MOUSE_X_POS + 1;
734 		    if (test_y >= CY(0) && test_y <= CY(ylimit)
735 			&& test_x >= CX(0) && test_x <= CX(xlimit)) {
736 			ny = CYINV(test_y);
737 			nx = CXINV(test_x);
738 			wmove(helpwin, 0, 0);
739 			wrefresh(helpwin);
740 			ungetch('\n');
741 		    }
742 		    break;
743 		}
744 #endif /* PDCURSES */
745 #endif /* KEY_MOUSE */
746 
747 	    case KEY_B2:
748 	    case '\n':
749 	    case ' ':
750 		review = 0;
751 		if (evaluate_move(squares, history, count_moves, rw, col)) {
752 		    drawMove(squares,
753 			     count_moves,
754 			     trail,
755 			     history[count_moves - 1].y,
756 			     history[count_moves - 1].x,
757 			     rw, col);
758 		    history[count_moves].y = (short) rw;
759 		    history[count_moves].x = (short) col;
760 		    count_moves++;
761 		    count_tries++;
762 
763 		    if (boardIsFilled(squares, rw, col)) {
764 			if (completed(squares) < 0) {
765 			    waddstr(msgwin, "\nYou won.");
766 			} else {
767 			    waddstr(msgwin,
768 				    "\nNo further moves are possible.");
769 			}
770 		    }
771 		} else {
772 		    beep();
773 		}
774 		break;
775 
776 	    case KEY_UNDO:
777 	    case KEY_BACKSPACE:
778 	    case '\b':
779 		review = 0;
780 		if (count_moves <= 0) {
781 		    no_previous_move();
782 		} else if (count_moves <= 1) {
783 		    ny = history[count_moves].y;
784 		    nx = history[count_moves].x;
785 		    if (nx < 0 || ny < 0) {
786 			ny = (lastrow >= 0) ? lastrow : 0;
787 			nx = (lastcol >= 0) ? lastcol : 0;
788 		    }
789 		    count_moves = 0;
790 		    squares[ny][nx] = FALSE;
791 		    oldch = minus;
792 		    drawMove(squares, count_moves, ' ', ny, nx, -1, -1);
793 		    count_moves = 1;
794 		    count_tries = 1;
795 		    no_previous_move();
796 		} else {
797 		    int oldy = history[count_moves - 1].y;
798 		    int oldx = history[count_moves - 1].x;
799 
800 		    if (!squares[rw][col]) {
801 			cellmove(rw, col);
802 			waddch(boardwin, ' ');
803 		    }
804 
805 		    squares[oldy][oldx] = FALSE;
806 		    --count_moves;
807 		    ny = history[count_moves - 1].y;
808 		    nx = history[count_moves - 1].x;
809 		    if (nx < 0 || ny < 0) {
810 			ny = oldy;
811 			nx = oldx;
812 		    }
813 		    drawMove(squares, count_moves, ' ', oldy, oldx, ny, nx);
814 
815 		    /* avoid problems if we just changed the current cell */
816 		    cellmove(lastrow, lastcol);
817 		    oldch = winch(boardwin);
818 		}
819 		break;
820 
821 	    case 'a':
822 		nx = col;
823 		ny = rw;
824 		if (find_next_move(squares, history, count_moves, &ny, &nx))
825 		    count_next_moves(squares, count_moves, ny, nx);
826 		else
827 		    beep();
828 		break;
829 
830 	    case 'F':
831 		review = reviewHistory(history, count_moves, review - 1,
832 				       &ny, &nx);
833 		break;
834 
835 	    case 'B':
836 		review = reviewHistory(history, count_moves, review + 1,
837 				       &ny, &nx);
838 		break;
839 
840 	    case 'R':
841 		if (ylimit <= 6) {
842 		    wprintw(msgwin, "\nworking...");
843 		    wrefresh(msgwin);
844 		    test_test = 0;
845 		    test_size = useBacktracking(squares, history, count_moves);
846 		    wprintw(msgwin, "\nOk %d:%d (%d tests)",
847 			    test_size, maxmoves, test_test);
848 		    review = 0;
849 		    while (count_moves <= test_size) {
850 			markcell(trail,
851 				 ny = history[count_moves].y,
852 				 nx = history[count_moves].x);
853 			count_moves++;
854 		    }
855 		} else {
856 		    wprintw(msgwin, "\nBoard is too large.");
857 		}
858 		wrefresh(msgwin);
859 		break;
860 
861 #if HAVE_CURSCR
862 	    case KEY_REDO:
863 	    case '\f':
864 	    case 'r':
865 		clearok(curscr, TRUE);
866 		wnoutrefresh(stdscr);
867 		wnoutrefresh(boardwin);
868 		wnoutrefresh(msgwin);
869 		wnoutrefresh(helpwin);
870 		doupdate();
871 		break;
872 #endif
873 
874 	    case 'q':
875 	    case 'x':
876 		goto dropout;
877 
878 	    case HELP_KEY_1:
879 		show_help(&keyhelp);
880 		break;
881 
882 	    default:
883 		beep();
884 		break;
885 	    }
886 
887 	    col = nx % xlimit;
888 	    rw = ny % ylimit;
889 	}
890 
891       dropout:
892 	if ((count = completed(squares)) < 0)
893 	    wprintw(msgwin, "\nYou won.  Care to try again? ");
894 	else
895 	    wprintw(msgwin, "\n%d squares filled.  Try again? ", count);
896 	wclrtoeol(msgwin);
897     } while
898 	(tolower(wgetch(msgwin)) == 'y');
899 }
900 
901 static void
usage(void)902 usage(void)
903 {
904     static const char *msg[] =
905     {
906 	"Usage: knight [options]"
907 	,""
908 	,"Options:"
909 #if HAVE_USE_DEFAULT_COLORS
910 	," -d       invoke use_default_colors"
911 #endif
912 	," -n NUM   set board-size to NUM*NUM (default 8x8)"
913     };
914     size_t n;
915 
916     for (n = 0; n < SIZEOF(msg); n++)
917 	fprintf(stderr, "%s\n", msg[n]);
918 
919     ExitProgram(EXIT_FAILURE);
920 }
921 
922 int
main(int argc,char * argv[])923 main(int argc, char *argv[])
924 {
925     int ch;
926 
927     while ((ch = getopt(argc, argv, "dn:")) != -1) {
928 	switch (ch) {
929 #if HAVE_USE_DEFAULT_COLORS
930 	case 'd':
931 	    d_option = TRUE;
932 	    break;
933 #endif
934 	case 'n':
935 	    ch = atoi(optarg);
936 	    if (ch < 3 || ch > 8) {
937 		fprintf(stderr, "board size %d is outside [3..8]\n", ch);
938 		usage();
939 	    }
940 	    xlimit = ylimit = ch;
941 	    break;
942 	default:
943 	    usage();
944 	    /* NOTREACHED */
945 	}
946     }
947     if (optind < argc)
948 	usage();
949 
950     init_program();
951 
952     play();
953 
954     endwin();
955     ExitProgram(EXIT_SUCCESS);
956 }
957