1
/* GNU Chess 5.0 - cmd.c - command parser
2
Copyright (c) 1999-2002 Free Software Foundation, Inc.
4
GNU Chess is based on the two research programs
5
Cobalt by Chua Kong-Sian and Gazebo by Stuart Cracraft.
7
GNU Chess is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2, or (at your option)
12
GNU Chess is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with GNU Chess; see the file COPYING. If not, write to
19
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20
Boston, MA 02111-1307, USA.
24
cracraft@ai.mit.edu, cracraft@stanfordalumni.org, cracraft@earthlink.net
39
static char logfile[MAXSTR];
40
static char gamefile[MAXSTR];
43
* Splitting input is actually not neccessary, but we find
44
* tokens separated by whitespace and put pointers on them.
45
* How many do we need? We just take 3 for now. Check if the
46
* fact that tokens are not 0-terminated but whitespace-terminated
47
* generates bugs. (Already killed one bug in move.c)
48
* We also kill trailing whitespace. (The trailing '\n' might
49
* be really annoying otherwise.)
54
static char *token[TOKENS];
57
static void split_input(void)
59
/* r points to the last non-space character */
63
for (k = 0, s = r = inputstr; k < TOKENS; ++k) {
64
/* Skip leading whitespace */
65
while (isspace(*s)) s++;
68
while (*s && !isspace(*s)) r = s++;
71
while (isspace(*s)) s++;
72
while (*s && !isspace(*s)) r = s++;
78
* Compares two tokens, returns 1 on equality. Tokens
79
* are separated by whitespace.
81
static int tokeneq(const char *s, const char *t)
83
while (*s && *t && !isspace(*s) && !isspace(*t)) {
84
if (*s++ != *t++) return 0;
86
return (!*s || isspace(*s)) && (!*t || isspace(*t));
89
void cmd_accepted(void) {}
91
void cmd_activate(void)
93
CLEAR (flags, TIMEOUT);
97
void cmd_analyze(void)
100
* "analyze" mode is similar to force, hard and post together
101
* in that it produces a text output like post, but must
102
* think indefinitely like ponder.
104
* Some additional output is expected in command ".\n" but if ignored
105
* this should not be sent any more
108
/* TODO correct output, add fail high low */
110
if (!(flags & ANALYZE)){
111
preanalyze_flags=flags; /* save these flags for exit */
112
SET (flags, ANALYZE);
121
/* Print moves from Open Book for Xboard/WinBoard */
122
/* Lines must start with " " and end with blank line */
123
/* No need to test for xboard as it is generally useful */
125
printf("\n"); /* Blank line */
132
* No longer used by Xboard but requested as a feature
135
NewPosition(); /* Reset some flags and settings */
136
CLEAR ( flags, THINK);
137
CLEAR ( flags, MANUAL);
138
CLEAR (flags, TIMEOUT);
140
if ( board.side == white ) {
148
if (tokeneq(token[1], "add")) {
149
if (access(token[2], F_OK) < 0) {
150
printf("The syntax to add a new book is:\n\n\tbook add file.pgn\n");
152
BookPGNReadFromFile (token[2]);
154
} else if (tokeneq (token[1], "on") || tokeneq(token[1], "prefer")) {
155
bookmode = BOOKPREFER;
156
printf("book now on.\n");
157
} else if (tokeneq (token[1], "off")) {
159
printf("book now off.\n");
160
} else if (tokeneq (token[1], "best")) {
162
printf("book now best.\n");
163
} else if (tokeneq (token[1], "worst")) {
164
bookmode = BOOKWORST;
165
printf("book now worst.\n");
166
} else if (tokeneq (token[1], "random")) {
168
printf("book now random.\n");
172
/* Our opponent is a computer */
173
void cmd_computer(void) {}
177
SearchDepth = atoi (token[1]);
178
printf("Search to a depth of %d\n",SearchDepth);
181
/* Ignore draw offers */
182
void cmd_draw(void) {}
184
void cmd_easy(void) { CLEAR (flags, HARD); }
186
/* Predecessor to setboard */
189
if ( flags & XBOARD ) {
190
printf("tellusererror command 'edit' not implemented\n");
200
printf ("\n%s : Best move = %s\n", id, solution);
206
* "exit" is a synonym for quit except in engine mode
207
* when it means leave analyze mode
210
if ( flags & ANALYZE ){
211
flags = preanalyze_flags ; /* this implicitly clears ANALYZE flag */
219
void cmd_force(void) { SET (flags, MANUAL); }
224
CLEAR (flags, MANUAL);
225
CLEAR (flags, TIMEOUT);
226
CLEAR (flags, ENDED);
227
computer = board.side;
230
void cmd_hard(void) { SET (flags, HARD); }
234
if (tokeneq (token[1], "off"))
235
CLEAR (flags, USEHASH);
236
else if (tokeneq (token[1], "on"))
237
SET (flags, USEHASH);
238
printf ("Hashing %s\n", flags & USEHASH ? "on" : "off");
241
void cmd_hashsize(void)
243
if (token[1][0] == 0) {
244
printf("Current HashSize is %d slots\n", HashSize);
247
/* i = atoi (token[1]); */
249
i = strtol (token[1], &endptr, 10);
250
if ( errno != 0 || *endptr != '\0' ){
251
printf("Hashsize out of Range or Invalid\n");
260
/* Give a possible move for the player to play */
263
if ( flags & ENDED ){
264
printf("The game is over.\n");
267
HintMove = TreePtr[1]->move; /* Pick first move in tree */
268
if (IsLegalMove(HintMove)){
270
SANMove(TreePtr[1]->move,1);
271
printf("Hint: %s\n", SANmv);
273
printf("No hint available at this time\n");
282
sscanf (token[1], "%d %f %d", &TCMove, &TCTime, &TCinc);
284
TCMove = 35 /* MIN((5*(GameCnt+1)/2)+1,60) */;
285
printf("TCMove = %d\n",TCMove);
290
SET (flags, TIMECTL);
291
SearchTime = TCinc / 2.0f ;
292
printf("Fischer increment of %d seconds\n",TCinc);
294
SET (flags, TIMECTL);
295
MoveLimit[white] = MoveLimit[black] = TCMove - (GameCnt+1)/2;
296
TimeLimit[white] = TimeLimit[black] = TCTime * 60;
297
if (!(flags & XBOARD)) {
298
printf ("Time Control: %d moves in %.2f secs\n",
299
MoveLimit[white], TimeLimit[white]);
300
printf("Fischer increment of %d seconds\n",TCinc);
307
if (token[1][0] == '?') {
308
printf("name - list known players alphabetically\n");
309
printf("score - list by GNU best result first \n");
310
printf("reverse - list by GNU worst result first\n");
312
if (token[1][0] == '\0') DBListPlayer("rscore");
313
else DBListPlayer(token[1]);
320
if (!ValidateBoard()) {
322
printf ("Board is wrong!\n");
326
void cmd_manual(void) { SET (flags, MANUAL); }
328
/* Move now, not applicable */
329
void cmd_movenow(void) {}
332
* TODO: Add a logpath variable or macro, not always dump into current
333
* dir. Again, how does one handle paths portably across Unix/Windows?
340
/* name[sizeof name - 1] should always be 0 */
341
strncpy(name, token[1], sizeof name - 1);
342
for (suffix = 0; suffix < 1000; suffix++) {
343
sprintf(logfile,"log.%03d",suffix);
344
sprintf(gamefile,"game.%03d",suffix);
346
* There is an obvious race condition here but who cares, we just
347
* bail out in case of failure... --Lukas
349
if (access(logfile,F_OK) < 0) {
350
ofp = fopen(logfile,"w");
353
fprintf(stderr, "Failed to open %s for writing: %s\n",
354
logfile, strerror(errno));
359
fprintf(stderr, "Could not create logfile, all slots occupied.\n");
360
fprintf(stderr, "You may consider deleting or renaming your existing logfiles.\n");
367
/* Protocol specification for ANALYZE says "new" does not end analysis */
368
if (!(flags & ANALYZE))
369
CLEAR (flags, MANUAL);
370
CLEAR (flags, THINK);
371
myrating = opprating = 0;
374
void cmd_nopost(void) { CLEAR (flags, POST); }
378
if (tokeneq (token[1], "off"))
379
CLEAR (flags, USENULL);
380
else if (tokeneq (token[1], "on"))
381
SET (flags, USENULL);
382
printf ("Null moves %s\n", flags & USENULL ? "on" : "off");
385
void cmd_otim(void) {}
387
void cmd_pgnload(void) { PGNReadFromFile (token[1]); }
390
* XXX - Filenames with spaces will break here,
391
* do we want to support them? I vote for "no"
394
void cmd_pgnsave(void)
396
if ( strlen(token[1]) > 0 )
397
PGNSaveToFile (token[1], "");
399
printf("Invalid filename.\n");
404
/* If ping is received when we are on move, we are supposed to
405
reply only after moving. In this version of GNU Chess, we
406
never read commands while we are on move, so we don't have to
407
worry about that here. */
408
printf("pong %s\n", token[1]);
412
void cmd_post(void) { SET (flags, POST); }
414
void cmd_protover(void)
416
if (flags & XBOARD) {
417
/* Note: change this if "draw" command is added, etc. */
418
printf("feature setboard=1 analyze=1 ping=1 draw=0 sigint=0"
419
" variants=\"normal\" myname=\"%s %s\" done=1\n",
425
void cmd_quit(void) { SET (flags, QUIT); }
427
void cmd_random(void) {}
429
void cmd_rating(void)
431
myrating = atoi(token[1]);
432
opprating = atoi(token[2]);
433
fprintf(ofp,"my rating = %d, opponent rating = %d\n",myrating,opprating);
434
/* Change randomness of book based on opponent rating. */
435
/* Basically we play narrower book the higher the opponent */
436
if (opprating >= 1700) bookfirstlast = 2;
437
else if (opprating >= 1700) bookfirstlast = 2;
438
else bookfirstlast = 2;
441
void cmd_rejected(void) {}
443
void cmd_remove(void)
446
CLEAR (flags, ENDED);
447
CLEAR (flags, TIMEOUT);
448
UnmakeMove (board.side, &Game[GameCnt].move);
450
UnmakeMove (board.side, &Game[GameCnt].move);
451
if (!(flags & XBOARD))
454
PGNSaveToFile ("game.log","");
456
printf ("No moves to undo! \n");
459
void cmd_result(void)
462
fprintf(ofp, "result: %s\n",token[1]);
465
printf("Save to %s\n",gamefile);
466
PGNSaveToFile (gamefile, token[1]);
467
DBUpdatePlayer (name, token[1]);
473
if ( strlen(token[1]) > 0 )
476
printf("Invalid filename.\n");
479
void cmd_setboard(void)
481
/* setboard uses FEN, not EPD, but ParseEPD will accept FEN too */
486
void cmd_solve(void) { Solve (token[1]); }
488
/* Set total time for move to be N seconds is "st N" */
491
/* Approximately level 1 0 N */
492
sscanf(token[1],"%d",&TCinc);
494
/* Allow a little fussiness for failing low etc */
495
SearchTime = TCinc * 0.90f ;
496
CLEAR (flags, TIMECTL);
499
void cmd_switch(void)
501
board.side = 1^board.side;
502
board.ep = -1 ; /* Enpassant doesn't apply after switch */
503
printf ("%s to move\n", board.side == white ? "White" : "Black");
508
TimeLimit[1^board.side] = atoi(token[1]) / 100.0f ;
514
UnmakeMove (board.side, &Game[GameCnt].move);
516
printf ("No moves to undo! \n");
517
MoveLimit[board.side]++;
518
TimeLimit[board.side] += Game[GameCnt+1].et;
519
if (!(flags & XBOARD)) ShowBoard ();
526
" Usage: %s [OPTION]\n"
528
" -h, --help display this help and exit\n"
529
" -v, --version display version information and exit\n"
531
" -x, --xboard start in engine mode\n"
532
" -p, --post start up showing thinking\n"
533
" -e, --easy disable thinking in opponents time\n"
534
" -m, --manual enable manual mode\n"
535
" -s size, --hashsize=size specify hashtable size in slots\n"
537
" Options xboard and post are accepted without leading dashes\n"
538
" for backward compatibility\n"
540
"Report bugs to <bug-gnu-chess@gnu.org>.\n"
545
/* Play variant, we instruct interface in protover we play normal */
546
void cmd_variant(void) {}
548
void cmd_version(void)
550
if (!(flags & XBOARD))
551
printf ("%s %s\n", PROGRAM, VERSION);
559
* No longer used by Xboard but requested as a feature
562
NewPosition(); /* Reset some flags and settings */
563
CLEAR ( flags, THINK);
564
CLEAR ( flags, MANUAL);
565
CLEAR (flags, TIMEOUT);
567
if ( board.side == black ){
569
board.ep = -1; /* Hack to fixed appearing pawn bug */
573
void cmd_xboard(void)
575
if (tokeneq (token[1], "off"))
576
CLEAR (flags, XBOARD);
577
else if (tokeneq (token[1], "on"))
579
else if (!(flags & XBOARD)) { /* set if unset and only xboard called */
580
SET (flags, XBOARD); /* like in xboard/winboard usage */
585
* Command with subcommands, could write secondary method
590
/************************************************************************
592
* The show command driver section.
594
************************************************************************/
596
if (tokeneq (token[1], "board"))
598
else if (tokeneq (token[1], "rating"))
600
printf("My rating = %d\n",myrating);
601
printf("Opponent rating = %d\n",opprating);
603
else if (tokeneq (token[1], "time"))
605
else if (tokeneq (token[1], "moves")) {
607
TreePtr[2] = TreePtr[1];
610
printf ("No. of moves generated = %ld\n", GenCnt);
612
else if (tokeneq (token[1], "escape")) {
614
TreePtr[2] = TreePtr[1];
617
printf ("No. of moves generated = %ld\n", GenCnt);
619
else if (tokeneq (token[1], "noncapture"))
622
TreePtr[2] = TreePtr[1];
624
FilterIllegalMoves (1);
626
printf ("No. of moves generated = %ld\n", GenCnt);
628
else if (tokeneq (token[1], "capture"))
631
TreePtr[2] = TreePtr[1];
633
FilterIllegalMoves (1);
635
printf ("No. of moves generated = %ld\n", GenCnt);
637
else if (tokeneq (token[1], "eval") || tokeneq (token[1], "score"))
639
int s, wp, bp, wk, bk;
645
hunged[white] = EvalHung(white);
646
hunged[black] = EvalHung(black);
648
pieces[white] = b[knight] | b[bishop] | b[rook] | b[queen];
650
pieces[black] = b[knight] | b[bishop] | b[rook] | b[queen];
655
printf ("White: Mat:%4d/%4d P:%d N:%d B:%d R:%d Q:%d K:%d Dev:%d h:%d x:%d\n",
656
board.pmaterial[white], board.material[white], wp, ScoreN(white),
657
ScoreB(white), ScoreR(white), ScoreQ(white), wk,
658
ScoreDev(white), hunged[white], ExchCnt[white]);
659
printf ("Black: Mat:%4d/%4d P:%d N:%d B:%d R:%d Q:%d K:%d Dev:%d h:%d x:%d\n",
660
board.pmaterial[black], board.material[black], bp, ScoreN(black),
661
ScoreB(black), ScoreR(black), ScoreQ(black), bk,
662
ScoreDev(black), hunged[black], ExchCnt[black]);
663
printf ("Phase: %d\t", PHASE);
664
s = ( EvaluateDraw () ? DRAWSCORE : Evaluate (-INFINITY, INFINITY));
665
printf ("score = %d\n", s);
668
else if (tokeneq (token[1], "game"))
670
else if (tokeneq (token[1], "pin"))
680
/*************************************************************************
682
* The test command driver section.
684
*************************************************************************/
686
if (tokeneq (token[1], "movelist"))
688
else if (tokeneq (token[1], "capture"))
690
else if (tokeneq (token[1], "movegenspeed"))
692
else if (tokeneq (token[1], "capturespeed"))
693
TestCaptureGenSpeed ();
694
else if (tokeneq (token[1], "eval"))
696
else if (tokeneq (token[1], "evalspeed"))
701
* This is more or less copied from the readme, and the
702
* parser is not very clever, so the lines containing
703
* command names should not be indented, the lines with
704
* explanations following them should be indented. Do not
705
* use tabs for indentation, only spaces. CAPITALS are
706
* reserved for parameters in the command names. The
707
* array must be terminated by two NULLs.
709
* This one should be integrated in the method table.
710
* (Very much like docstrings in Lisp.)
713
static const char * const helpstr[] = {
715
" Typically the interrupt key stops a search in progress,",
716
" makes the move last considered best and returns to the",
719
" quit the program.",
721
" In analysis mode this stops analysis, otherwise it quits the program.",
723
" Produces a help blurb corresponding to this list of commands.",
725
" add - compiles book.dat from book.pgn",
726
" on - enables use of book",
727
" off - disables use of book",
728
" worst - play worst move from book",
729
" best - play best move from book",
730
" prefer - default, same as 'book on'",
731
" random - play any move from book",
733
" prints out the version of this program",
735
" saves the game so far to the file from memory",
737
" loads the game in the file into memory",
740
" Makes the program stop moving. You may now enter moves",
741
" to reach some position in the future.",
744
" Program plays white",
746
" Program plays black",
748
" Computer takes whichever side is on move and begins its",
749
" thinking immediately",
751
" Arranges for verbose thinking output showing variation, score,",
752
" time, depth, etc.",
754
" Turns off verbose thinking output",
756
" Lets you input your name. Also writes the log.nnn and a",
757
" corresponding game.nnn file. For details please see",
758
" auxillary file format sections.",
760
" Mostly used by Internet Chess server.",
762
" This command reactivates a game that has been terminated automatically",
763
" due to checkmate or no more time on the clock. However, it does not",
764
" alter those conditions. You would have to undo a move or two or",
765
" add time to the clock with level or time in that case.",
766
"rating COMPUTERRATING OPPONENTRATING",
767
" Inputs the estimated rating for computer and for its opponent",
769
" Sets up new game (i.e. positions in original positions)",
771
" Inputs time left in game for computer in hundredths of a second.",
772
" Mostly used by Internet Chess server.",
774
" on - enables using the memory hash table to speed search",
775
" off - disables the memory hash table",
777
" Sets the hash table to permit storage of N positions",
779
" on - enables using the null move heuristic to speed search",
780
" off - disables using the null move heuristic",
782
" on - enables use of xboard/winboard",
783
" off - disables use of xboard/winboard",
785
" Sets the program to look N ply (half-moves) deep for every",
786
" search it performs. If there is a checkmate or other condition",
787
" that does not allow that depth, then it will not be ",
788
"level MOVES MINUTES INCREMENT",
789
" Sets time control to be MOVES in MINUTES with each move giving",
790
" an INCREMENT (in seconds, i.e. Fischer-style clock).",
793
" Loads a position in EPD format from disk into memory.",
796
" Saves game position into EPD format from memory to disk.",
798
" Switches side to move",
801
" Solves the positions in FILENAME",
803
" Backs up two moves in game history",
805
" Backs up one move in game history",
807
" Display command line syntax",
809
" board - displays the current board",
810
" time - displays the time settings",
811
" moves - shows all moves using one call to routine",
812
" escape - shows moves that escape from check using one call to routine",
813
" noncapture - shows non-capture moves",
814
" capture - shows capture moves",
815
" eval [or score] - shows the evaluation per piece and overall",
816
" game - shows moves in game history",
817
" pin - shows pinned pieces",
819
" movelist - reads in an epd file and shows legal moves for its entries",
820
" capture - reads in an epd file and shows legal captures for its entries",
821
" movegenspeed - tests speed of move generator",
822
" capturespeed - tests speed of capture move generator",
823
" eval - reads in an epd file and shows evaluation for its entries",
824
" evalspeed tests speed of the evaluator",
826
" show moves from opening book.",
832
/**************************************************************************
834
* Display all the help commands.
836
**************************************************************************/
838
const char * const *p;
841
if (strlen(token[1])>0) {
842
for (p=helpstr, count=0; *p; p++) {
843
if (strncmp(*p, token[1], strlen(token[1])) == 0) {
845
while (*++p && **p != ' ') /* Skip aliases */ ;
846
for (; *p && **p == ' '; p++) {
852
printf("Help for command %s not found\n\n", token[1]);
854
printf("List of commands: (help COMMAND to get more help)\n");
855
for (p=helpstr, count=0; *p; p++) {
856
len = strcspn(*p, " ");
858
count += printf("%.*s ", len, *p);
869
* Try a method table, one could also include the documentation
875
void (*method) (void);
878
/* Last entry contains to NULL pointers */
880
/* List commands we don't implement to avoid illegal moving them */
882
const struct methodtable commands[] = {
883
{ "?", cmd_movenow },
884
{ "accepted", cmd_accepted },
885
{ "activate", cmd_activate },
886
{ "analyze", cmd_analyze },
888
{ "black", cmd_black },
889
{ "book", cmd_book },
890
{ "computer", cmd_computer },
891
{ "depth", cmd_depth },
892
{ "draw", cmd_draw },
893
{ "easy", cmd_easy },
894
{ "edit", cmd_edit },
896
{ "epdload", cmd_load },
897
{ "epdsave", cmd_save },
898
{ "exit", cmd_exit },
899
{ "force", cmd_force },
901
{ "hard", cmd_hard },
902
{ "hash", cmd_hash },
903
{ "hashsize", cmd_hashsize },
904
{ "help", cmd_help },
905
{ "hint", cmd_hint },
906
{ "level", cmd_level },
907
{ "list", cmd_list },
908
{ "load", cmd_load },
909
{ "manual", cmd_manual },
910
{ "name", cmd_name },
912
{ "nopost", cmd_nopost },
913
{ "null", cmd_null },
914
{ "otim", cmd_otim },
915
{ "pgnload", cmd_pgnload },
916
{ "pgnsave", cmd_pgnsave },
917
{ "ping", cmd_ping },
918
{ "post", cmd_post },
919
{ "protover", cmd_protover },
920
{ "quit", cmd_quit },
921
{ "random", cmd_random },
922
{ "rating", cmd_rating },
923
{ "rejected", cmd_rejected },
924
{ "remove", cmd_remove },
925
{ "result", cmd_result },
926
{ "save", cmd_save },
927
{ "setboard", cmd_setboard },
928
{ "show", cmd_show },
929
{ "solve", cmd_solve },
930
{ "solveepd", cmd_solve },
932
{ "switch", cmd_switch },
933
{ "test", cmd_test },
934
{ "time", cmd_time },
935
{ "undo", cmd_undo },
936
{ "usage", cmd_usage },
937
{ "variant", cmd_variant },
938
{ "version", cmd_version },
939
{ "white", cmd_white },
940
{ "xboard", cmd_xboard },
944
void parse_input(void)
945
/*************************************************************************
947
* This is the main user command interface driver.
949
*************************************************************************/
952
const struct methodtable * meth;
954
dbg_printf("parse_input() called, inputstr = *%s*\n", inputstr);
958
for (meth = commands; meth->name != NULL; meth++) {
959
if (tokeneq(token[0], meth->name)) {
965
/* OK, no known command, this should be a move */
966
ptr = ValidateMove (token[0]);
968
SANMove (ptr->move, 1);
969
MakeMove (board.side, &ptr->move);
970
strcpy (Game[GameCnt].SANmv, SANmv);
971
printf("%d. ",GameCnt/2+1);
972
printf("%s",token[0]);
974
fprintf(ofp,"%d. ",GameCnt/2+1);
975
fprintf(ofp,"%s",token[0]);
983
if (!(flags & XBOARD)) ShowBoard ();
988
* Must Output Illegal move to prevent Xboard accepting illegal
989
* en passant captures and other subtle mistakes
991
printf("Illegal move: %s\n",token[0]);