1 /* cmd.cc
2 
3    GNU Chess frontend
4 
5    Copyright (C) 2001-2021 Free Software Foundation, Inc.
6 
7    GNU Chess is based on the two research programs
8    Cobalt by Chua Kong-Sian and Gazebo by Stuart Cracraft.
9 
10    This program is free software: you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation, either version 3 of the License, or
13    (at your option) any later version.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 
23    Contact Info:
24      bug-gnu-chess@gnu.org
25      cracraft@ai.mit.edu, cracraft@stanfordalumni.org, cracraft@earthlink.net
26 */
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <errno.h>
34 
35 #include "version.h"
36 #include "common.h"
37 #include "gettext.h"
38 
39 #define _(str) gettext (str)
40 
41 static char logfile[MAXSTR];
42 static char gamefile[MAXSTR];
43 
44 /*
45  * Splitting input is actually not neccessary, but we find
46  * tokens separated by whitespace and put pointers on them.
47  * How many do we need? We just take 3 for now. Check if the
48  * fact that tokens are not 0-terminated but whitespace-terminated
49  * generates bugs. (Already killed one bug in move.c)
50  * We also kill trailing whitespace. (The trailing '\n' might
51  * be really annoying otherwise.)
52  */
53 
54 #define TOKENS 3
55 
56 static char *token[TOKENS];
57 char *endptr;
58 
59 static int hardFlag=0;
60 static int postFlag=0;
61 
62 static const char setboard_cmd[] = "setboard ";
63 
split_input(void)64 static void split_input(void)
65 {
66   /* r points to the last non-space character */
67   char *s, *r;
68   int k;
69 
70   for (k = 0, s = r = inputstr; k < TOKENS; ++k) {
71     /* Skip leading whitespace */
72     while (isspace(*s)) s++;
73     token[k] = s;
74     /* Skip token */
75     while (*s && !isspace(*s)) r = s++;
76   }
77   while (*s) {
78     while (isspace(*s)) s++;
79     while (*s && !isspace(*s)) r = s++;
80   }
81   r[1] = '\0';
82 }
83 
84 /*
85  * Compares two tokens, returns 1 on equality. Tokens
86  * are separated by whitespace.
87  */
tokeneq(const char * s,const char * t)88 static int tokeneq(const char *s, const char *t)
89 {
90   while (*s && *t && !isspace(*s) && !isspace(*t)) {
91     if (*s++ != *t++) return 0;
92   }
93   return (!*s || isspace(*s)) && (!*t || isspace(*t));
94 }
95 
96 /*
97  * Remove a trailing \n and return NULL if last character is not \n.
98  */
trim_newline(char * line)99 static char *trim_newline(char *line)
100 {
101   if (line == NULL) {
102     return NULL;
103   }
104   const size_t line_len = strlen(line);
105   const size_t last_char_index = strlen(line) - 1;
106   if (line_len <= 0 || line[last_char_index] != '\n') {
107     return NULL;
108   }
109   line[last_char_index] = '\0';
110   return line;
111 }
112 
113 /*
114  * Takes an EPD filename as input and returns the contents as a
115  * 'setboard <epd-position>' command.
116  */
build_setboard_cmd_from_epd_file(char * data,const char * epd_filename,unsigned int data_len)117 static char *build_setboard_cmd_from_epd_file(char *data, const char *epd_filename, unsigned int data_len)
118 {
119   char *result = NULL;
120   char *epdline = (char *)calloc(data_len, sizeof(char));
121 
122   if (epdline == NULL) {
123     return NULL;
124   }
125   FILE *epdfile = fopen(epd_filename, "r");
126   if (epdfile == NULL) {
127     return NULL;
128   }
129   if (fgets(epdline, data_len, epdfile) && trim_newline(epdline) && strlen(setboard_cmd) + strlen(epdline) < data_len) {
130     strcpy(data, setboard_cmd);
131     strcat(data, epdline);
132     result = data;
133   }
134   fclose(epdfile);
135   free(epdline);
136 
137   return result;
138 }
139 
140 /*
141  * Takes a PGN filename as input and returns the contents as a
142  * 'setboard <epd-position>' command.
143  */
build_setboard_cmd_from_pgn_file(char * data,const char * pgn_filename,unsigned int data_len)144 static char *build_setboard_cmd_from_pgn_file(char *data, const char *pgn_filename, unsigned int data_len)
145 {
146   char *result = NULL;
147   char *epdline = (char *)calloc(data_len, sizeof(char));
148 
149   if (epdline == NULL) {
150     return NULL;
151   }
152   PGNReadFromFile (pgn_filename, 0);
153   EPD2str(epdline);
154   if (strlen(setboard_cmd) + strlen(epdline) < data_len) {
155     strcpy(data, setboard_cmd);
156     strcat(data, epdline);
157     result = data;
158   }
159   free(epdline);
160 
161   return result;
162 }
163 
164 /*
165  * Loads a PGN file. Returns 1 on success, 0 on error.
166  */
pgnload(const char * pgn_filename)167 static int pgnload(const char *pgn_filename)
168 {
169   int success;
170   char data[MAXSTR]="";
171 
172   if (build_setboard_cmd_from_pgn_file(data, pgn_filename, sizeof(data))) {
173     SetDataToEngine( data );
174     SetAutoGo( true );
175     success = 1;
176   } else {
177     printf( _("Error loading PGN file '%s'.\n"), pgn_filename );
178     success = 0;
179   }
180   return success;
181 }
182 
cmd_accepted(void)183 void cmd_accepted(void)
184 {
185   SetDataToEngine( token[0] );
186 }
187 
cmd_activate(void)188 void cmd_activate(void)
189 {
190   printf( _("Command 'activate' is currently not supported.\n") );
191 }
192 
cmd_analyze(void)193 void cmd_analyze(void)
194 {
195   /*
196    * "analyze" mode is similar to force, hard and post together
197    * in that it produces a text output like post, but must
198    * think indefinitely like ponder.
199    *
200    * Some additional output is expected in command ".\n" but if ignored
201    * this should not be sent any more
202    */
203 
204 /* TODO correct output, add fail high low */
205 
206   SET (flags, ANALYZE);
207   SetDataToEngine( "hard\npost\nanalyze" );
208 }
209 
cmd_bk(void)210 void cmd_bk(void)
211 {
212   SetDataToEngine( "bk" );
213 }
214 
cmd_black(void)215 void cmd_black(void)
216 {
217  /*
218   * No longer used by Xboard but requested as a feature
219   */
220   printf( _("Command 'black' is currently not supported.\n") );
221 }
222 
cmd_book(void)223 void cmd_book(void)
224 {
225   char data[MAXSTR]="";
226   strcpy( data, "book " );
227   if (tokeneq(token[1], "add")) {
228     if (access(token[2], F_OK) < 0) {
229       printf(_("The syntax to add a new book is:\n\n\tbook add file.pgn\n"));
230     } else {
231       strcat( data, "add " );
232       strcat( data, token[2] );
233     }
234   } else if (tokeneq (token[1], "on") || tokeneq(token[1], "prefer")) {
235     strcpy( data, "book on" );
236     printf(_("Book is now on.\n"));
237   } else if (tokeneq (token[1], "off")) {
238     strcpy( data, "book off" );
239     printf(_("Book is now off.\n"));
240   } else if (tokeneq (token[1], "best")) {
241     strcpy( data, "book best" );
242     printf(_("Book is now best.\n"));
243   } else if (tokeneq (token[1], "worst")) {
244     strcpy( data, "book worst" );
245     printf(_("Book is now worst.\n"));
246   } else if (tokeneq (token[1], "random")) {
247     strcpy( data, "book random" );
248     printf(_("Book is now random.\n"));
249   } else {
250     printf( _("Incorrect book option: '%s'.\n"), token[1] );
251     return;
252   }
253   SetDataToEngine( data );
254 }
255 
cmd_depth(void)256 void cmd_depth(void)
257 {
258   char data[MAXSTR]="";
259   int searchDepth=0;
260   searchDepth = atoi( token[1] );
261   sprintf( data, "sd %d", searchDepth );
262   SetDataToEngine( data );
263   printf(_("Search to a depth of %d.\n"),searchDepth);
264 }
265 
cmd_easy(void)266 void cmd_easy(void)
267 {
268   SetDataToEngine( token[0] );
269 }
270 
271 /* Predecessor to setboard */
cmd_edit(void)272 void cmd_edit(void)
273 {
274   if ( flags & XBOARD ) {
275     printf("tellusererror command 'edit' not implemented\n");
276     fflush(stdout);
277   }
278 }
279 
cmd_exit(void)280 void cmd_exit(void)
281 {
282   /*
283    * "exit" is a synonym for quit except in engine mode
284    * when it means leave analyze mode
285    */
286 
287   if ( flags & ANALYZE ){
288     flags = preanalyze_flags ; /* this implicitly clears ANALYZE flag */
289     SetDataToEngine( token[0] );
290   } else {
291     cmd_quit();
292   }
293 }
294 
cmd_force(void)295 void cmd_force(void)
296 {
297   SET (flags, MANUAL);
298   SetDataToEngine( token[0] );
299 }
300 
cmd_go(void)301 void cmd_go(void)
302 {
303   SET (flags, THINK);
304   CLEAR (flags, MANUAL);
305   CLEAR (flags, TIMEOUT);
306   CLEAR (flags, ENDED);
307   computer = board.side;
308   ExpectAnswerFromEngine( true );
309   ChangeColor( true );
310   SetDataToEngine( token[0] );
311   pgnloaded = 0;
312 }
313 
cmd_hard(void)314 void cmd_hard(void)
315 {
316   SetDataToEngine( token[0] );
317 }
318 
cmd_hash(void)319 void cmd_hash(void)
320 {
321   if (tokeneq (token[1], "off")) {
322     CLEAR (flags, USEHASH);
323     SetDataToEngine( "hashoff" );
324   } else if (tokeneq (token[1], "on")) {
325     SET (flags, USEHASH);
326     SetDataToEngine( "hashon" );
327   }
328   if ( flags & USEHASH ) {
329     printf( _("Hashing is on.\n") );
330   } else {
331     printf( _("Hashing is off.\n") );
332   }
333 }
334 
335 /* Give a possible move for the player to play */
cmd_hint(void)336 void cmd_hint(void)
337 {
338   /* An answer is received only if book on - TODO change this in adapter */
339   SetDataToEngine( token[0] );
340   /* TODO if no hint, inform on stdout */
341 }
342 
cmd_ics(void)343 void cmd_ics(void)
344 {
345   SetDataToEngine( token[0] );
346 }
347 
cmd_level(void)348 void cmd_level(void)
349 {
350   SetDataToEngine( token[0] );
351   sscanf (token[1], "%d %f %d", &TCMove, &TCTime, &TCinc);
352   if (TCMove == 0) {
353     TCMove =  35 /* MIN((5*(GameCnt+1)/2)+1,60) */;
354     printf("TCMove = %d\n",TCMove);
355   }
356   if (TCTime == 0) {
357     SearchTime = TCinc / 2.0f ;
358     printf(_("Fischer increment of %d seconds.\n"),TCinc);
359   } else {
360     MoveLimit[white] = MoveLimit[black] = TCMove - (GameCnt+1)/2;
361     TimeLimit[white] = TimeLimit[black] = TCTime * 60;
362     if (!(flags & XBOARD)) {
363       /* TRANSLATORS: Please be aware that the word 'move' is sometimes
364          used as a synonym of 'ply', and sometimes in the sense of a
365          full 2-ply move. */
366       printf (_("Time control: %d moves in %.2f secs.\n"),
367       MoveLimit[white], TimeLimit[white]);
368       printf(_("Fischer increment of %d seconds.\n"),TCinc);
369     }
370   }
371 }
372 
cmd_list(void)373 void cmd_list(void)
374 {
375   if (token[1][0] == '?') {
376     printf(_("name    - list known players alphabetically\n"));
377     printf(_("score   - list by GNU best result first\n"));
378     printf(_("reverse - list by GNU worst result first\n"));
379   } else {
380     if (token[1][0] == '\0') DBListPlayer("rscore");
381     else DBListPlayer(token[1]);
382   }
383 }
384 
cmd_load(void)385 void cmd_load(void)
386 {
387   char *epd_filename = token[1];
388   char data[MAXSTR]="";
389   LoadEPD (epd_filename);
390   pgnloaded = 0;
391   check_board();
392   if (!ValidateBoard()) {
393     SET (flags, ENDED);
394     printf (_("Board is wrong!\n"));
395   } else {
396     /* Read EPD file and send contents to engine */
397     if (build_setboard_cmd_from_epd_file(data, epd_filename, strlen(data))) {
398       SetDataToEngine( data );
399       SetAutoGo( true );
400     } else {
401       printf( _("Error loading EPD file '%s'.\n"), epd_filename );
402     }
403   }
404 }
405 
cmd_manual(void)406 void cmd_manual(void)
407 {
408   SET (flags, MANUAL);
409   ExpectAnswerFromEngine( false );
410   SetDataToEngine( "force" );
411 }
412 
cmd_memory(void)413 void cmd_memory(void)
414 {
415   if (token[1][0] == 0) {
416     ExpectAnswerFromEngine( true );
417     SetDataToEngine( "memory" );
418   } else {
419     unsigned int memory;
420     if ( sscanf( token[1], "%d", &memory ) == 1 ) {
421       char data[MAXSTR]="";
422       sprintf( data, "memory %d\nmemory", memory );
423       SetDataToEngine( data );
424     }
425 /* TODO Handle error */
426   }
427 }
428 
429 /* Move now, not applicable */
cmd_movenow(void)430 void cmd_movenow(void)
431 {
432   SetDataToEngine( "?" );
433 }
434 
435 /*
436  * TODO: Add a logpath variable or macro, not always dump into current
437  * dir. Again, how does one handle paths portably across Unix/Windows?
438  *   -- Lukas
439  */
cmd_name(void)440 void cmd_name(void)
441 {
442   SetDataToEngine( token[0] );
443   int suffix = 0;
444 
445   /* name[sizeof name - 1] should always be 0 */
446   strncpy(name, token[1], sizeof name - 1);
447   for (suffix = 0; suffix < 1000; suffix++) {
448     sprintf(logfile,"log.%03d",suffix);
449     sprintf(gamefile,"game.%03d",suffix);
450     /*
451      * There is an obvious race condition here but who cares, we just
452      * bail out in case of failure... --Lukas
453      */
454     if (access(logfile,F_OK) < 0) {
455       ofp = fopen(logfile,"w");
456       if (ofp == NULL) {
457         ofp = stdout;
458         fprintf(stderr, _("Failed to open %s for writing: %s\n"),
459         logfile, strerror(errno));
460       }
461       return;
462     }
463   }
464   fprintf(stderr, _("Could not create logfile, all slots occupied.\n"));
465   fprintf(stderr, _("You may consider deleting or renaming your existing logfiles.\n"));
466 }
467 
cmd_new(void)468 void cmd_new(void)
469 {
470   InitVars ();
471   NewPosition ();
472   /* Protocol specification for ANALYZE says "new" does not end analysis */
473   if (!(flags & ANALYZE))
474     CLEAR (flags, MANUAL);
475   CLEAR (flags, THINK);
476   myrating = opprating = 0;
477   SetDataToEngine( token[0] );
478 }
479 
cmd_nopost(void)480 void cmd_nopost(void)
481 {
482   CLEAR (flags, POST);
483   postFlag = 0;
484   ExpectAnswerFromEngine( false );
485   SetDataToEngine( token[0] );
486 }
487 
cmd_null(void)488 void cmd_null(void)
489 {
490   if (tokeneq (token[1], "off")) {
491     CLEAR (flags, USENULL);
492     SetDataToEngine( "nulloff" );
493   } else if (tokeneq (token[1], "on")) {
494     SET (flags, USENULL);
495     SetDataToEngine( "nullon" );
496   }
497   if ( flags & USENULL ) {
498     printf( _("Null-move heuristic is on.\n") );
499   } else {
500     printf( _("Null-move heuristic is off.\n") );
501   }
502 }
503 
cmd_otim(void)504 void cmd_otim(void)
505 {
506    SetDataToEngine( token[0] );
507 }
508 
509 /*
510  * Load a file containing a game in PGN format.
511  *
512  * The file contents will be passed on to the adapter
513  * in EPD notation (the adapter expectes FEN actually,
514  * but EPD and FEN are similar (possible issue here?),
515  * hence a PGN -> EPD conversion in done first.
516  */
cmd_pgnload(void)517 void cmd_pgnload(void)
518 {
519   pgnload(token[1]);
520 }
521 
522 /* See comment above in cmd_pgnload about PGN -> EPD conversion. */
cmd_pgnreplay(void)523 void cmd_pgnreplay(void)
524 {
525   if (!pgnload(token[1])) {
526     return;
527   }
528   pgnloaded = 1;
529   pgncnt = GameCnt;
530 
531   while (GameCnt >= 0) {
532     if (GameCnt >= 0) {
533       CLEAR (flags, ENDED);
534       CLEAR (flags, TIMEOUT);
535       ChangeColor( true );
536       SetAutoGo( true );
537       UnmakeMove (board.side, &Game[GameCnt].move);
538       if (GameCnt >= 0) {
539         UnmakeMove (board.side, &Game[GameCnt].move);
540       }
541     }
542   }
543 
544   cmd_first();
545 }
546 
cmd_next(void)547 void cmd_next(void)
548 {
549   if (!pgnloaded) {
550     printf(_("Error: PGN file not loaded!\n"));
551     return;
552   }
553 
554   if ((GameCnt+1) <= pgncnt) {
555     ChangeColor( true );
556     SetAutoGo( true );
557     MakeMove (board.side, &Game[GameCnt+1].move);
558   } else {
559     printf(_("No more moves. Game reached the end.\n"));
560     return;
561   }
562 
563   printf("%d. ",GameCnt/2+1);
564   printf("%s\n", Game[GameCnt].SANmv);
565   ShowBoard ();
566 }
567 
cmd_previous(void)568 void cmd_previous(void)
569 {
570   if (!pgnloaded) {
571     printf(_("Error: PGN file not loaded!\n"));
572     return;
573   }
574 
575   if (GameCnt >= 0) {
576     ChangeColor( true );
577     SetAutoGo( true );
578     UnmakeMove (board.side, &Game[GameCnt].move);
579   }
580   else {
581     printf(_("Initial position reached. There are no earlier moves.\n"));
582     return;
583   }
584 
585   printf("%d. ",GameCnt/2+1);
586   printf("%s\n", Game[GameCnt].SANmv);
587   ShowBoard ();
588 }
589 
cmd_last(void)590 void cmd_last(void)
591 {
592   if (!pgnloaded) {
593     printf(_("Error: PGN file not loaded!\n"));
594     return;
595   }
596 
597   while (GameCnt+1 <= pgncnt) {
598     ChangeColor( true );
599     SetAutoGo( true );
600     MakeMove (board.side, &Game[GameCnt+1].move);
601   }
602 
603   printf("%d. ",GameCnt/2+1);
604   printf("%s\n", Game[GameCnt].SANmv);
605   ShowBoard ();
606 }
607 
cmd_first(void)608 void cmd_first(void)
609 {
610   if (!pgnloaded) {
611     printf(_("Error: PGN file not loaded!\n"));
612     return;
613   }
614 
615   while (GameCnt >= 0) {
616     if (GameCnt >= 0) {
617       CLEAR (flags, ENDED);
618       CLEAR (flags, TIMEOUT);
619       ChangeColor( true );
620       SetAutoGo( true );
621       UnmakeMove (board.side, &Game[GameCnt].move);
622       if (GameCnt >= 0) {
623         UnmakeMove (board.side, &Game[GameCnt].move);
624       }
625     }
626   }
627 
628   ShowBoard ();
629 }
630 
631 /*
632  * XXX - Filenames with spaces will break here,
633  * do we want to support them? I vote for "no"
634  *   - Lukas
635  */
cmd_pgnsave(void)636 void cmd_pgnsave(void)
637 {
638   if ( strlen(token[1]) > 0 )
639     PGNSaveToFile (token[1], "");
640   else
641     printf(_("Invalid filename.\n"));
642 }
643 
cmd_graphic(void)644 void cmd_graphic(void)
645 {
646   graphicmodeoutput = 1;
647   printf(_("Graphic mode is enabled.\n"));
648 }
649 
cmd_nographic(void)650 void cmd_nographic(void)
651 {
652   graphicmodeoutput = 0;
653   printf(_("Graphic mode is disabled.\n"));
654 }
655 
cmd_ping(void)656 void cmd_ping(void)
657 {
658   /* TODO cf. 5.08 */
659   SetDataToEngine( token[0] );
660   /* If ping is received when we are on move, we are supposed to
661      reply only after moving.  In this version of GNU Chess, we
662      never read commands while we are on move, so we don't have to
663      worry about that here. */
664   printf("pong %s\n", token[1]);
665   fflush(stdout);
666 }
667 
cmd_post(void)668 void cmd_post(void)
669 {
670   /* TODO State makes no sense */
671   SET (flags, POST);
672   postFlag = 1;
673   if ( hardFlag && postFlag )
674     ExpectAnswerFromEngine( true );
675   if ( flags & XBOARD )
676     ExpectAnswerFromEngine( true );
677   else
678     ExpectAnswerFromEngine( false );
679   SetDataToEngine( token[0] );
680 }
681 
cmd_protover(void)682 void cmd_protover(void)
683 {
684   SetDataToEngine( token[0] );
685   return;
686   if (flags & XBOARD) {
687     /* Note: change this if "draw" command is added, etc. */
688     printf("feature setboard=1 analyze=1 ping=1 draw=0 sigint=0\
689  variants=\"normal\" myname=\"%s %s\" done=1\n",
690       PROGRAM, VERSION);
691     fflush(stdout);
692   }
693 }
694 
cmd_quit(void)695 void cmd_quit(void) { SET (flags, QUIT); }
696 
cmd_random(void)697 void cmd_random(void)
698 {
699   printf( _("Command 'random' is currently not supported.\n") );
700   //SetDataToEngine( token[0] );
701 }
702 
cmd_rating(void)703 void cmd_rating(void)
704 {
705   myrating = atoi(token[1]);
706   opprating = atoi(token[2]);
707   fprintf(ofp,_("my rating = %d, opponent rating = %d\n"),myrating,opprating);
708   /* Change randomness of book based on opponent rating. */
709   /* Basically we play narrower book the higher the opponent */
710   if (opprating >= 1700) bookfirstlast = 2;
711   else if (opprating >= 1700) bookfirstlast = 2;
712   else bookfirstlast = 2;
713 }
714 
cmd_rejected(void)715 void cmd_rejected(void) {}
716 
cmd_remove(void)717 void cmd_remove(void)
718 {
719   SetDataToEngine( token[0] );
720   if (GameCnt >= 0) {
721     CLEAR (flags, ENDED);
722     CLEAR (flags, TIMEOUT);
723     UnmakeMove (board.side, &Game[GameCnt].move);
724     if (GameCnt >= 0) {
725       UnmakeMove (board.side, &Game[GameCnt].move);
726       if (!(flags & XBOARD))
727            ShowBoard ();
728     }
729     PGNSaveToFile ("game.log","");
730   } else
731     printf (_("No moves to undo!\n"));
732 }
733 
cmd_result(void)734 void cmd_result(void)
735 {
736   /* TODO Do not send to engine */
737   SetDataToEngine( token[0] );
738   if (ofp != stdout) {
739     fprintf(ofp, "result: %s\n",token[1]);
740     fclose(ofp);
741     ofp = stdout;
742     printf(_("Save to %s\n"),gamefile);
743     PGNSaveToFile (gamefile, token[1]);
744     DBUpdatePlayer (name, token[1]);
745   }
746 }
747 
cmd_save(void)748 void cmd_save(void)
749 {
750   if ( strlen(token[1]) > 0 )
751     SaveEPD (token[1]);
752   else
753     printf(_("Invalid filename.\n"));
754 }
755 
cmd_setboard(void)756 void cmd_setboard(void)
757 {
758   /* setboard uses FEN, not EPD, but ParseEPD will accept FEN too */
759   char data[MAXSTR]="";
760   ParseEPD (token[1]);
761   NewPosition();
762   check_board();
763   snprintf(data, sizeof(data), "setboard %s", token[1]);
764   SetDataToEngine(data);
765 }
766 
cmd_solve(void)767 void cmd_solve(void)
768 {
769   Solve (token[1]);
770 }
771 
772 /* Set total time for move to be N seconds is "st N" */
cmd_st(void)773 void cmd_st(void)
774 {
775   char data[MAXSTR]="";
776   /* Approximately level 1 0 N */
777   sscanf(token[1],"%d",&TCinc);
778   /* Allow a little fussiness for failing low etc */
779   SearchTime = TCinc * 0.90f ;
780   sprintf( data, "st %d", atoi( token[1] ) );
781   SetDataToEngine( data );
782 }
783 
cmd_switch(void)784 void cmd_switch(void)
785 {
786   printf( _("Command 'switch' is currently not supported.\n") );
787 }
788 
cmd_time(void)789 void cmd_time(void)
790 {
791   /* TODO send what? */
792   SetDataToEngine( token[0] );
793   TimeLimit[1^board.side] = atoi(token[1]) / 100.0f ;
794 }
795 
cmd_undo(void)796 void cmd_undo(void)
797 {
798   SetDataToEngine( "force\nundo" );
799   ChangeColor( true );
800   SetAutoGo( !(flags & MANUAL) );
801   if (GameCnt >= 0)
802     UnmakeMove (board.side, &Game[GameCnt].move);
803   else
804     printf (_("No moves to undo!\n"));
805   MoveLimit[board.side]++;
806   TimeLimit[board.side] += Game[GameCnt+1].et;
807   if (!(flags & XBOARD)) ShowBoard ();
808 }
809 
cmd_usage(void)810 void cmd_usage(void)
811 {
812       printf ( "\n" );
813       printf ( _("\
814 Usage: %s [OPTION]...\n\n"), progname );
815       fputs( _("\
816 Play the game of chess.\n\n"), stdout );
817       fputs( _("Options:\n"), stdout );
818       fputs( _("\
819  -h, --help         display this help and exit\n"), stdout );
820       fputs( _("\
821  -v, --version      display version information and exit\n"), stdout );
822       fputs( _("\
823  -q, --quiet        make the program silent on startup\n"), stdout );
824       fputs( _("\
825      --silent       same as -q\n"), stdout );
826       fputs( _("\
827 \n"), stdout );
828       fputs( _("\
829  -x, --xboard       start in engine mode\n"), stdout );
830       fputs( _("\
831  -p, --post         start up showing thinking\n"), stdout );
832       fputs( _("\
833  -e, --easy         disable thinking in opponents time\n"), stdout );
834       fputs( _("\
835  -m, --manual       enable manual mode\n"), stdout );
836       fputs( _("\
837  -u, --uci          enable UCI protocol (externally behave as UCI engine)\n"), stdout );
838       fputs( _("\
839  -M size, --memory=size   specify memory usage in MB for hashtable\n"), stdout );
840       fputs( _("\
841  -a filename, --addbook=filename   compile book.bin from pgn book 'filename'\n"), stdout );
842       fputs( _("\
843  -g, --graphic      enable graphic mode\n"), stdout );
844       fputs( _("\
845 \n"), stdout );
846       fputs( _("\
847  Options xboard and post are accepted without leading dashes\n\
848  for backward compatibility.\n\
849 \n"), stdout );
850       fputs( _("\
851  Moves are accepted either in standard algebraic notation (SAN) or\n\
852  in coordinate algebraic notation.\n\
853 \n"), stdout );
854       fputs( _("\
855  The file 'gnuchess.ini' allows setting config options if --uci is not\n\
856  used. See 'info gnuchess' for details. The file is looked for in three\n\
857  locations according to this precedence: current directory, the\n\
858  directory pointed to by environment variable GNUCHESS_PKGDATADIR,\n\
859  or the package data directory stated at configure time.\n\
860 \n"), stdout );
861       fputs( _("\
862 Report bugs to <bug-gnu-chess@gnu.org>.\n\
863 \n"), stdout );
864      }
865 
866 
867 /* Play variant, we instruct interface in protover we play normal */
cmd_variant(void)868 void cmd_variant(void) {}
869 
870 /* TODO Not in 5.08 */
cmd_usermove(void)871 void cmd_usermove(void)
872 {
873   /* TODO: Remove the first SetDataToEngine */
874   /*SetDataToEngine( token[0] );*/
875    leaf *ptr;
876    ptr = ValidateMove (token[1]);
877    if (ptr != NULL) {
878      /* Since the user entered a move:
879       * 1. The move must be sent to the engine.
880       * 2. A reply is expected from the engine.
881       */
882      SetUserInputValidMove( 1 );
883      SetDataToEngine( token[0] );
884      pgnloaded = 0;
885      ExpectAnswerFromEngine( true );
886      SANMove (ptr->move, 1);
887      MakeMove (board.side, &ptr->move);
888      strcpy (Game[GameCnt].SANmv, SANmv);
889      printf("%d. ",GameCnt/2+1);
890      printf("%s",token[1]);
891      if (ofp != stdout) {
892        fprintf(ofp,"%d. ",GameCnt/2+1);
893        fprintf(ofp,"%s",token[1]);
894      }
895      putchar('\n');
896      fflush(stdout);
897      if (ofp != stdout) {
898        fputc('\n',ofp);
899        fflush(ofp);
900      }
901      if (!(flags & XBOARD)) ShowBoard ();
902      SET (flags, THINK);
903    }
904    else {
905      /*
906       * Must Output Illegal move to prevent Xboard accepting illegal
907       * en passant captures and other subtle mistakes
908       */
909      printf(_("Invalid move: %s\n"),token[1]);
910      fflush(stdout);
911    }
912 }
913 
cmd_version(void)914 void cmd_version(void)
915 {
916    if (!(flags & XBOARD))
917      printf ("%s %s\n", PROGRAM, VERSION);
918    else
919      printf ("Chess\n");
920 }
921 
cmd_coords(void)922 void cmd_coords(void) {
923     printf(_("Coordinate display enabled.\n"));
924     coords = 1;
925 }
926 
cmd_nocoords(void)927 void cmd_nocoords(void) {
928     printf(_("Coordinate display disabled.\n"));
929     coords = 0;
930 }
931 
cmd_white(void)932 void cmd_white(void)
933 {
934  /*
935   * No longer used by Xboard but requested as a feature
936   */
937   printf( _("Command 'white' is currently not supported.\n") );
938 }
939 
cmd_xboard(void)940 void cmd_xboard(void)
941 {
942   SetDataToEngine( "xboard" );
943   if (tokeneq (token[1], "off"))
944     CLEAR (flags, XBOARD);
945   else if (tokeneq (token[1], "on"))
946     SET (flags, XBOARD);
947   else if (!(flags & XBOARD)) { /* set if unset and only xboard called */
948     SET (flags, XBOARD);        /* like in xboard/winboard usage */
949   }
950 }
951 
952 /*
953  * Command with subcommands, could write secondary method
954  * tables here
955  */
956 
cmd_show(void)957 void cmd_show (void)
958 /************************************************************************
959  *
960  *  The show command driver section.
961  *
962  ************************************************************************/
963 {
964    /* TODO Remove gettext support */
965    if (tokeneq (token[1], "board"))
966       ShowBoard ();
967    else if (tokeneq (token[1], "rating"))
968    {
969       printf(_("My rating = %d\n"),myrating);
970       printf(_("Opponent rating = %d\n"),opprating);
971    }
972    else if (tokeneq (token[1], _("time")))
973       ShowTime ();
974    else if (tokeneq (token[1], _("moves"))) {
975       GenCnt = 0;
976       TreePtr[2] = TreePtr[1];
977       GenMoves (1);
978       ShowMoveList (1);
979       printf (_("No. of moves generated = %ld\n"), GenCnt);
980    }
981    else if (tokeneq (token[1], "escape")) {
982       GenCnt = 0;
983       TreePtr[2] = TreePtr[1];
984       GenCheckEscapes (1);
985       ShowMoveList (1);
986       printf (_("No. of moves generated = %ld\n"), GenCnt);
987    }
988    else if (tokeneq (token[1], "noncapture"))
989    {
990       GenCnt = 0;
991       TreePtr[2] = TreePtr[1];
992       GenNonCaptures (1);
993       FilterIllegalMoves (1);
994       ShowMoveList (1);
995       printf (_("No. of moves generated = %ld\n"), GenCnt);
996    }
997    else if (tokeneq (token[1], "capture"))
998    {
999       GenCnt = 0;
1000       TreePtr[2] = TreePtr[1];
1001       GenCaptures (1);
1002       FilterIllegalMoves (1);
1003       ShowMoveList (1);
1004       printf (_("No. of moves generated = %ld\n"), GenCnt);
1005    }
1006    else if (tokeneq (token[1], "eval") || tokeneq (token[1], "score"))
1007    {
1008       printf( _("Command 'show eval/score' is currently not supported.\n") );
1009       return;
1010    }
1011    else if (tokeneq (token[1], "game"))
1012      ShowGame ();
1013    else if (tokeneq (token[1], "pin"))
1014    {
1015       printf( _("Command 'show pin' is currently not supported.\n") );
1016       return;
1017    }
1018 }
1019 
cmd_test(void)1020 void cmd_test (void)
1021 /*************************************************************************
1022  *
1023  *  The test command driver section.
1024  *
1025  *************************************************************************/
1026 {
1027   printf( _("Command 'test' is currently not supported.\n") );
1028 }
1029 
1030 /*
1031  * This is more or less copied from the readme, and the
1032  * parser is not very clever, so the lines containing
1033  * command names should not be indented, the lines with
1034  * explanations following them should be indented. Do not
1035  * use tabs for indentation, only spaces. CAPITALS are
1036  * reserved for parameters in the command names. The
1037  * array must be terminated by two NULLs.
1038  *
1039  * This one should be integrated in the method table.
1040  * (Very much like docstrings in Lisp.)
1041  */
1042 
1043 static const char * const helpstr[] = {
1044    "quit",
1045    gettext_noop(" Quits the program."),
1046    "exit",
1047    gettext_noop(" In analysis mode this stops analysis, otherwise it quits the program."),
1048    "help",
1049    gettext_noop(" Produces a help blurb corresponding to this list of commands."),
1050    "book",
1051    gettext_noop(" add - compiles book.bin from a pgn book file"),
1052    gettext_noop(" on - enables use of book (default)"),
1053    gettext_noop(" off - disables use of book"),
1054    gettext_noop(" worst - plays worst move from book"),
1055    gettext_noop(" best - plays best move from book"),
1056    gettext_noop(" prefer - same as 'book on' (default)"),
1057    gettext_noop(" random - plays any move from book"),
1058    "version",
1059    gettext_noop(" Prints out the version of this program."),
1060    "previous",
1061    "p",
1062    gettext_noop(" Backs up one move in pgn loaded game."),
1063    "pgnsave FILENAME",
1064    gettext_noop(" Saves the game so far from memory to the file."),
1065    "pgnload FILENAME",
1066    gettext_noop(" Loads the game in the file into memory."),
1067    "pgnreplay FILENAME",
1068    gettext_noop(" Loads the game in the file into memory, and enables\n"
1069                 " the commands 'first', 'last', 'next', 'previous'."),
1070    "next",
1071    "n",
1072    gettext_noop(" Advances one move in pgn loaded game."),
1073    "first",
1074    gettext_noop(" Goes to begin position of pgn loaded game."),
1075    "last",
1076    gettext_noop(" Goes to end position of pgn loaded game."),
1077    "force",
1078    "manual",
1079    gettext_noop(" Makes the program stop moving. You may now enter moves\n"
1080                 " to reach some position in the future."),
1081    " ",
1082    "white",
1083    gettext_noop(" Program plays white."),
1084    "black",
1085    gettext_noop(" Program plays black."),
1086    "go",
1087    gettext_noop(" Computer takes whichever side is on move and begins its\n"
1088                 " thinking immediately."),
1089    "post",
1090    gettext_noop(" Arranges for verbose thinking output showing variation, score,\n"
1091                 " time, depth, etc."),
1092    "nopost",
1093    gettext_noop(" Turns off verbose thinking output."),
1094    "name NAME",
1095    gettext_noop(" Lets you input your name. Also writes the log.nnn and\n"
1096                 " corresponding game.nnn files. For details please see\n"
1097                 " the auxiliary file format sections."),
1098    "result",
1099    gettext_noop(" Mostly used by Internet Chess Server."),
1100    "activate",
1101    gettext_noop(" This command reactivates a game that has been terminated automatically\n"
1102                 " due to checkmate or no more time on the clock. However, it does not\n"
1103                 " alter those conditions. So you would have to undo a move or two, or\n"
1104                 " add time to the clock with 'level' or 'time'."),
1105    "rating COMPUTERRATING OPPONENTRATING",
1106    gettext_noop(" Inputs the estimated rating for computer and for its opponent."),
1107    "new",
1108    gettext_noop(" Sets up a new game (i.e. pieces in original positions)."),
1109    "time",
1110    gettext_noop(" Inputs time left in game for computer in hundredths of a second.\n"
1111                 " Mostly used by Internet Chess Server."),
1112    "hash",
1113    gettext_noop(" on - enables using the memory hash table to speed up search"),
1114    gettext_noop(" off - disables the memory hash table"),
1115    "memory N",
1116    gettext_noop(" Sets the hash table to permit storage of N MB."),
1117    "null",
1118    gettext_noop(" on - enables using the null-move heuristic to speed up search"),
1119    gettext_noop(" off - disables using the null-move heuristic"),
1120    "xboard",
1121    gettext_noop(" on - enables use of xboard/winboard"),
1122    gettext_noop(" off - disables use of xboard/winboard"),
1123    "depth N",
1124    gettext_noop(" Sets the program to look N ply (half-moves) deep for every\n"
1125                 " search it performs. If there is a checkmate or other condition\n"
1126                 " that does not allow that depth, then it will not be."),
1127    "level MOVES MINUTES INCREMENT",
1128    gettext_noop(" Sets time control to be MOVES in MINUTES, with each move giving\n"
1129                 " an INCREMENT (in seconds, i.e. a Fischer-style clock)."),
1130    "load",
1131    "epdload",
1132    gettext_noop(" Loads a position in EPD format from disk into memory."),
1133    "save",
1134    "epdsave",
1135    gettext_noop(" Saves game position into EPD format from memory to disk."),
1136    "switch",
1137    gettext_noop(" Switches side to move."),
1138    "solve FILENAME",
1139    "solveepd FILENAME",
1140    gettext_noop(" Solves the positions in FILENAME."),
1141    "remove",
1142    gettext_noop(" Backs up two moves in game history."),
1143    "undo",
1144    gettext_noop(" Backs up one move in game history."),
1145    "usage",
1146    gettext_noop(" Displays command line syntax."),
1147    "show",
1148    gettext_noop(" board - displays the current board"),
1149    gettext_noop(" time - displays the time settings"),
1150    gettext_noop(" moves - shows all moves using one call to routine"),
1151    gettext_noop(" escape - shows moves that escape from check using one call to routine"),
1152    gettext_noop(" noncapture - shows non-capture moves"),
1153    gettext_noop(" capture - shows capture moves"),
1154    gettext_noop(" eval [or score] - shows the evaluation per piece and overall"),
1155    gettext_noop(" game - shows moves in game history"),
1156    gettext_noop(" pin - shows pinned pieces"),
1157    "test",
1158    gettext_noop(" movelist - reads in an epd file and shows legal moves for its entries"),
1159    gettext_noop(" capture - reads in an epd file and shows legal captures for its entries"),
1160    gettext_noop(" movegenspeed - tests speed of move generator"),
1161    gettext_noop(" capturespeed - tests speed of capture move generator"),
1162    gettext_noop(" eval - reads in an epd file and shows evaluation for its entries"),
1163    gettext_noop(" evalspeed - tests speed of the evaluator"),
1164    "bk",
1165    gettext_noop(" Shows moves from opening book."),
1166    "graphic",
1167    gettext_noop(" Enables display board in graphic mode."),
1168    "nographic",
1169    gettext_noop(" Disables graphic mode and display classical view."),
1170    "coords",
1171    gettext_noop(" Displays the chessboard rank and file in both graphic and classical views."),
1172    "nocoords",
1173    gettext_noop(" Does not display the chessboard rank nor file in either mode (graphic nor classical)."),
1174    NULL,
1175    NULL
1176 };
1177 
cmd_help(void)1178 void cmd_help (void)
1179 /**************************************************************************
1180  *
1181  *  Display all the help commands.
1182  *
1183  **************************************************************************/
1184 {
1185    const char * const *p;
1186    int count, len;
1187 
1188    if (strlen(token[1])>0) {
1189       for (p=helpstr, count=0; *p; p++) {
1190         if (strncmp(*p, token[1], strlen(token[1])) == 0) {
1191            puts(*p);
1192            while (*++p && **p != ' ') /* Skip aliases */ ;
1193            for (; *p && **p == ' '; p++) {
1194               puts(_(*p));
1195            }
1196            return;
1197         }
1198       }
1199       printf(_("Help for command '%s' not found.\n\n"), token[1]);
1200    }
1201    printf(_("List of commands: (help COMMAND to get more help)\n"));
1202    for (p=helpstr, count=0; *p; p++) {
1203       len = strcspn(*p, " ");
1204       if (len > 0) {
1205         count += printf("%.*s  ", len, *p);
1206         if (count > 60) {
1207            count = 0;
1208            puts("");
1209         }
1210       }
1211    }
1212    puts("");
1213 }
1214 
1215 /*
1216  * Try a method table, one could also include the documentation
1217  * strings here
1218  */
1219 
1220 struct methodtable {
1221   const char *name;
1222   void (*method) (void);
1223 };
1224 
1225 /* Last entry contains two NULL pointers */
1226 
1227 /* List commands we don't implement to avoid illegal moving them */
1228 
1229 const struct methodtable commands[] = {
1230   { "?", cmd_movenow },
1231   { "accepted", cmd_accepted },
1232   { "activate", cmd_activate },
1233   { "analyze", cmd_analyze },
1234   { "bk", cmd_bk },
1235   { "black", cmd_black },
1236   { "book", cmd_book },
1237   { "depth", cmd_depth },
1238   { "easy", cmd_easy },
1239   { "edit", cmd_edit },
1240   { "epdload", cmd_load },
1241   { "epdsave", cmd_save },
1242   { "exit", cmd_exit },
1243   { "force", cmd_force },
1244   { "go", cmd_go },
1245   { "graphic", cmd_graphic },
1246   { "hard", cmd_hard },
1247   { "hash", cmd_hash },
1248   { "help", cmd_help },
1249   { "hint", cmd_hint },
1250   { "ics", cmd_ics },
1251   { "last", cmd_last },
1252   { "level", cmd_level },
1253   { "list", cmd_list },
1254   { "load", cmd_load },
1255   { "manual", cmd_manual },
1256   { "memory", cmd_memory },
1257   { "name", cmd_name },
1258   { "new", cmd_new },
1259   { "next", cmd_next },
1260   { "n", cmd_next },
1261   { "nographic", cmd_nographic  },
1262   { "nopost", cmd_nopost },
1263   { "null", cmd_null },
1264   { "otim", cmd_otim },
1265   { "pgnload", cmd_pgnload },
1266   { "pgnreplay", cmd_pgnreplay },
1267   { "pgnsave", cmd_pgnsave },
1268   { "ping", cmd_ping },
1269   { "post", cmd_post },
1270   { "previous", cmd_previous },
1271   { "p", cmd_previous },
1272   { "first", cmd_first },
1273   { "protover", cmd_protover },
1274   { "quit", cmd_quit },
1275   { "random", cmd_random },
1276   { "rating", cmd_rating },
1277   { "rejected", cmd_rejected },
1278   { "remove", cmd_remove },
1279   { "result", cmd_result },
1280   { "save", cmd_save },
1281   { "setboard", cmd_setboard },
1282   { "show", cmd_show },
1283   { "solve", cmd_solve },
1284   { "solveepd", cmd_solve },
1285   { "st", cmd_st },
1286   { "switch", cmd_switch },
1287   { "test", cmd_test },
1288   { "time", cmd_time },
1289   { "undo", cmd_undo },
1290   { "usage", cmd_usage },
1291   { "usermove", cmd_usermove },
1292   { "variant", cmd_variant },
1293   { "version", cmd_version },
1294   { "white", cmd_white },
1295   { "xboard", cmd_xboard },
1296   { "coords", cmd_coords},
1297   { "nocoords", cmd_nocoords},
1298   { NULL, NULL }
1299 };
1300 
parse_input(void)1301 void parse_input(void)
1302 /*************************************************************************
1303  *
1304  *  This is the main user command interface driver.
1305  *
1306  *************************************************************************/
1307 {
1308    leaf *ptr;
1309    const struct methodtable * meth;
1310 
1311    dbg_printf("parse_input() called, inputstr = *%s*\n", inputstr);
1312 
1313    /* Initialize variables used to send messages to the engine */
1314    SetDataToEngine( "" );
1315    ExpectAnswerFromEngine( false );
1316    SetUserInputValidMove( 0 );
1317    ChangeColor( false );
1318 
1319    split_input();
1320 
1321    for (meth = commands; meth->name != NULL; meth++) {
1322      if (tokeneq(token[0], meth->name)) {
1323        meth->method();
1324        return;
1325      }
1326    }
1327 
1328    /* OK, no known command, this should be a move */
1329    char cleanMove[MAXSTR]="";
1330    ptr = ValidateMove (token[0], cleanMove);
1331    if (ptr != NULL) {
1332      /* Since the user entered a move:
1333       * 1. The move must be sent to the engine.
1334       * 2. A reply is expected from the engine.
1335       */
1336      SetUserInputValidMove( 1 );
1337      SetDataToEngine( cleanMove );
1338      pgnloaded = 0;
1339      ExpectAnswerFromEngine( true );
1340      SANMove (ptr->move, 1);
1341      MakeMove (board.side, &ptr->move);
1342      strcpy (Game[GameCnt].SANmv, SANmv);
1343      printf("%d. ",GameCnt/2+1);
1344      printf("%s",token[0]);
1345      if (ofp != stdout) {
1346        fprintf(ofp,"%d. ",GameCnt/2+1);
1347        fprintf(ofp,"%s",token[0]);
1348      }
1349      putchar('\n');
1350      fflush(stdout);
1351      if (ofp != stdout) {
1352        fputc('\n',ofp);
1353        fflush(ofp);
1354      }
1355      if (!(flags & XBOARD)) ShowBoard ();
1356      SET (flags, THINK);
1357    }
1358    else {
1359      /*
1360       * Must Output Illegal move to prevent Xboard accepting illegal
1361       * en passant captures and other subtle mistakes
1362       */
1363      printf(_("Invalid move: %s\n"),token[0]);
1364      fflush(stdout);
1365    }
1366 }
1367 
check_board()1368 void check_board()
1369 /*************************************************************************
1370  *
1371  *  When the board is changed by commands, call the Validation
1372  *  routine, and if it fails set flags to prevent the analysis of
1373  *  illegal positions, as the code is not robust against the
1374  *  analysis of such positions (To Do).
1375  *
1376  *************************************************************************/
1377 {
1378   if (!ValidateBoard()) {
1379     SET (flags, ENDED);
1380     if (flags & XBOARD) {
1381     printf ("telluser Board is wrong!\n");
1382         fflush(stdout);
1383     } else {
1384         printf (_("Board is wrong!\n"));
1385     }
1386   }
1387 }
1388 
1389