Scid  4.6.5
tkscid.cpp
Go to the documentation of this file.
1 //////////////////////////////////////////////////////////////////////
2 //
3 // FILE: tkscid.cpp
4 // Scid extensions to Tcl/Tk interpreter
5 //
6 // Part of: Scid (Shane's Chess Information Database)
7 //
8 // Notice: Copyright (c) 1999-2004 Shane Hudson. All rights reserved.
9 // Copyright (c) 2006-2007 Pascal Georges
10 // Copyright (c) 2013-2014 Benini Fulvio
11 //
12 // Scid is free software: you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation.
15 //
16 // Scid is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with Scid. If not, see <http://www.gnu.org/licenses/>.
23 //
24 //////////////////////////////////////////////////////////////////////
25 
26 
27 
28 #include "crosstab.h"
29 #include "dstring.h"
30 #include "engine.h"
31 #include "game.h"
32 #include "mfile.h"
33 #include "optable.h"
34 #include "pbook.h"
35 #include "pgnparse.h"
36 #include "polyglot.h"
37 #include "position.h"
38 #include "probe.h"
39 #include "scidbase.h"
40 #include "searchpos.h"
41 #include "spellchk.h"
42 #include "stored.h"
43 #include "timer.h"
44 #include "tree.h"
45 #include "dbasepool.h"
46 #include "ui.h"
47 #include <time.h>
48 #include <sys/stat.h>
49 #include <set>
50 #include <algorithm>
51 
52 //TODO: delete
53 #include "tkscid.h"
54 
55 
56 //TODO: delete
57 extern scidBaseT* db;
58 const int MAX_BASES = 9;
59 /////////////////
60 
61 
62 static Game * scratchGame = NULL; // "scratch" game for searches, etc.
63 static PBook * ecoBook = NULL; // eco classification pbook.
64 static SpellChecker* spellChk; // Name correction.
65 static OpTable * reports[2] = {NULL, NULL};
66 
67 void scid_Exit(void*) {
69  if (scratchGame != NULL) delete scratchGame;
70  if (ecoBook != NULL) delete ecoBook;
71  if (spellChk != NULL) delete spellChk;
72  for (size_t i = 0, n = sizeof(reports) / sizeof(reports[0]); i < n; i++) {
73  if (reports[i] != NULL) delete reports[i];
74  }
75 }
76 
77 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
78 // Main procedure
79 //
80 int
81 main(int argc, char * argv[])
82 {
83  srand(time(NULL));
84 
85  scratchGame = new Game;
87 
88  return UI_Main(argc, argv, scid_Exit);
89 }
90 
91 
92 
93 
94 
95 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
96 // Global variables:
97 static const char * reportTypeName[2] = { "opening", "player" };
98 static const uint REPORT_OPENING = 0;
99 static const uint REPORT_PLAYER = 1;
100 
101 static char decimalPointChar = '.';
102 static uint htmlDiagStyle = 0;
103 
104 // Tablebase probe modes:
105 #define PROBE_NONE 0
106 #define PROBE_RESULT 1
107 #define PROBE_SUMMARY 2
108 #define PROBE_REPORT 3
109 #define PROBE_OPTIMAL 4
110 
111 
112 
113 //////////////////////////////////////////////////////////////////////
114 //
115 // Inline routines for setting Tcl result strings:
116 //
117 
118 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
119 // setResult():
120 // Inline function to set the Tcl interpreter result to a
121 // constant string.
122 inline int
123 setResult (Tcl_Interp * ti, const char * str)
124 {
125  Tcl_SetResult (ti, (char *) str, TCL_STATIC);
126  return TCL_OK;
127 }
128 
129 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130 // setIntResult():
131 // Inline function to set the Tcl interpreter result to a
132 // signed integer value.
133 inline int
134 setIntResult (Tcl_Interp * ti, int i)
135 {
136  char temp [20];
137  sprintf (temp, "%d", i);
138  Tcl_SetResult (ti, temp, TCL_VOLATILE);
139  return TCL_OK;
140 }
141 
142 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
143 // setUintResult():
144 // Inline function to set the Tcl interpreter result to an
145 // unsigned integer value.
146 inline int
147 setUintResult (Tcl_Interp * ti, uint i)
148 {
149  char temp [20];
150  sprintf (temp, "%u", i);
151  Tcl_SetResult (ti, temp, TCL_VOLATILE);
152  return TCL_OK;
153 }
154 
155 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
156 // appendUintResult:
157 // Inline function to append the specified unsigned value to the
158 // Tcl interpreter result.
159 inline int
160 appendUintResult (Tcl_Interp * ti, uint i)
161 {
162  char temp [20];
163  sprintf (temp, "%u", i);
164  Tcl_AppendResult (ti, temp, NULL);
165  return TCL_OK;
166 }
167 
168 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
169 // appendUintElement:
170 // Inline function to append the specified unsigned value to the
171 // Tcl interpreter list result.
172 inline uint
173 appendUintElement (Tcl_Interp * ti, uint i)
174 {
175  char temp[20];
176  sprintf (temp, "%u", i);
177  Tcl_AppendElement (ti, temp);
178  return TCL_OK;
179 }
180 
181 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182 // setUintWidthResult():
183 // Inline function to set the Tcl interpreter result to an
184 // unsigned integer value, with zeroes to pad to the desired width.
185 inline int
186 setUintWidthResult (Tcl_Interp * ti, uint i, uint width)
187 {
188  char temp [20];
189  sprintf (temp, "%0*u", width, i);
190  Tcl_SetResult (ti, temp, TCL_VOLATILE);
191  return TCL_OK;
192 }
193 
194 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195 // appendCharResult:
196 // Inline function to append the specified character value to the
197 // Tcl interpreter result.
198 inline int
199 appendCharResult (Tcl_Interp * ti, char ch)
200 {
201  char tempStr [4];
202  tempStr[0] = ch;
203  tempStr[1] = 0;
204  Tcl_AppendResult (ti, tempStr, NULL);
205  return TCL_OK;
206 }
207 
208 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
209 // translate:
210 // Return the translation for a phrase.
211 //
212 inline const char *
213 translate (Tcl_Interp * ti, const char * name, const char * defaultText)
214 {
215  const char * str = Tcl_GetVar2 (ti, "tr", (char *) name, TCL_GLOBAL_ONLY);
216  if (str == NULL) { str = defaultText; }
217  return str;
218 }
219 
220 inline const char *
221 translate (Tcl_Interp * ti, const char * name)
222 {
223  return translate (ti, name, name);
224 }
225 
226 inline int errorResult (Tcl_Interp * ti, errorT err, const char* errorMsg = 0) {
227  if (errorMsg != 0) Tcl_SetResult (ti, (char*) errorMsg, TCL_STATIC);
228  ASSERT(err != OK);
229  Tcl_SetObjErrorCode(ti, Tcl_NewIntObj(err));
230  return TCL_ERROR;
231 }
232 inline int errorResult (Tcl_Interp * ti, const char* errorMsg) {
233  return errorResult(ti, ERROR_BadArg, errorMsg);
234 }
235 
236 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
237 // InvalidCommand():
238 // Given a Tcl Interpreter, a major command name (e.g. "sc_base") and
239 // a null-terminated array of minor commands, this function sets
240 // the interpreter's result to a useful error message listing the
241 // available subcommands.
242 // Returns TCL_ERROR, so caller can simply:
243 // return InvalidCommand (...);
244 // instead of:
245 // InvalidCommand (...);
246 // return TCL_ERROR;
247 int
248 InvalidCommand (Tcl_Interp * ti, const char * majorCmd,
249  const char ** minorCmds)
250 {
251  ASSERT (majorCmd != NULL);
252  Tcl_AppendResult (ti, "Invalid command: ", majorCmd,
253  " has the following minor commands:\n", NULL);
254  while (*minorCmds != NULL) {
255  Tcl_AppendResult (ti, " ", *minorCmds, "\n", NULL);
256  minorCmds++;
257  }
258  return TCL_ERROR;
259 }
260 
261 
262 /************ End of Tcl result routines ***********/
263 
264 
265 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266 // Standard error messages:
267 //
268 const char *
269 errMsgNotOpen (Tcl_Interp * ti)
270 {
271  return translate (ti, "ErrNotOpen", "This is not an open database.");
272 }
273 
274 const char *
275 errMsgSearchInterrupted (Tcl_Interp * ti)
276 {
277  return translate (ti, "ErrSearchInterrupted",
278  "[Interrupted search; results are incomplete]");
279 }
280 
281 
282 /////////////////////////////////////////////////////////////////////
283 // MISC functions
284 /////////////////////////////////////////////////////////////////////
285 
286 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
287 // str_is_prefix:
288 // Provides a fast Tcl command "strIsPrefix" for checking if the
289 // first string provided is a prefix of the second string, without
290 // needing the standard slower [string match] or [string range]
291 // routines.
292 int
293 str_is_prefix (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
294 {
295  if (argc != 3) {
296  return errorResult (ti, "Usage: strIsPrefix <shortStr> <longStr>");
297  }
298 
299  return UI_Result(ti, OK, strIsPrefix (argv[1], argv[2]));
300 }
301 
302 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
303 // str_prefix_len:
304 // Tcl command that returns the length of the common text at the start
305 // of two strings.
306 int
307 str_prefix_len (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
308 {
309  if (argc != 3) {
310  return errorResult (ti, "Usage: strPrefixLen <str> <str>");
311  }
312 
313  return setUintResult (ti, strPrefix (argv[1], argv[2]));
314 }
315 
316 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
317 // sc_base_inUse
318 // Returns 1 if the database slot is in use; 0 otherwise.
319 int
320 sc_base_inUse (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
321 {
322  const scidBaseT* basePtr = db;
323  if (argc > 2) {
324  basePtr = DBasePool::getBase(strGetUnsigned(argv[2]));
325  if (basePtr == 0) return UI_Result(ti, OK, false);
326  }
327 
328  return UI_Result(ti, OK, basePtr->inUse);
329 }
330 
331 
332 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
333 // exportGame:
334 // Called by sc_base_export() to export a single game.
335 void
336 exportGame (Game * g, FILE * exportFile, gameFormatT format, uint pgnStyle)
337 {
338  char old_language = language;
339 
340  g->ResetPgnStyle (pgnStyle);
341  g->SetPgnFormat (format);
342 
343  // Format-specific settings:
344  switch (format) {
345  case PGN_FORMAT_HTML:
346  case PGN_FORMAT_LaTeX:
348  break;
349  default:
350  language = 0;
351  break;
352  }
353 
354  g->SetHtmlStyle (htmlDiagStyle);
355  std::pair<const char*, unsigned> pgn = g->WriteToPGN(75, true, format != PGN_FORMAT_LaTeX);
356  //size_t nWrited =
357  fwrite(pgn.first, 1, pgn.second, exportFile);
358  //TODO:
359  //if (nWrited != db->tbuf->GetByteCount()) error
360  language = old_language;
361 }
362 
363 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
364 // sc_base_export:
365 // Exports the current game or all filter games in the database
366 // to a PGN, HTML or LaTeX file.
367 int
368 sc_base_export (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
369 {
370  FILE * exportFile = NULL;
371  bool exportFilter = false;
372  bool appendToFile = false;
373  gameFormatT outputFormat = PGN_FORMAT_Plain;
374  const char * startText = "";
375  const char * endText = "";
376  const char * usage = "Usage: sc_base export current|filter PGN|HTML|LaTeX <pgn_filename> options...";
377  uint pgnStyle = PGN_STYLE_TAGS;
378 
379  const char * options[] = {
380  "-append", "-starttext", "-endtext", "-comments", "-variations",
381  "-spaces", "-symbols", "-indentComments", "-indentVariations",
382  "-column", "-noMarkCodes", "-convertNullMoves", NULL
383  };
384  enum {
385  OPT_APPEND, OPT_STARTTEXT, OPT_ENDTEXT, OPT_COMMENTS, OPT_VARIATIONS,
386  OPT_SPACES, OPT_SYMBOLS, OPT_INDENTC, OPT_INDENTV,
387  OPT_COLUMN, OPT_NOMARKS, OPT_CONVERTNULL
388  };
389 
390  if (argc < 5) { return errorResult (ti, usage); }
391 
392  if (strIsPrefix (argv[2], "current")) {
393  exportFilter = false;
394  } else if (strIsPrefix (argv[2], "filter")) {
395  exportFilter = true;
396  } else {
397  return errorResult (ti, usage);
398  }
399 
400  if (! Game::PgnFormatFromString (argv[3], &outputFormat)) {
401  return errorResult (ti, usage);
402  }
403 
404  if (exportFilter && !db->inUse) {
405  return errorResult (ti, errMsgNotOpen(ti));
406  }
407 
408  const char * exportFileName = argv[4];
409 
410  // Check for an even number of optional parameters:
411  if ((argc % 2) != 1) { return errorResult (ti, usage); }
412 
413  // Parse all optional parameters:
414  for (int arg = 5; arg < argc; arg += 2) {
415  const char * value = argv[arg+1];
416  bool flag = strGetBoolean (value);
417  int option = strUniqueMatch (argv[arg], options);
418 
419  switch (option) {
420  case OPT_APPEND:
421  appendToFile = flag;
422  break;
423 
424  case OPT_STARTTEXT:
425  startText = value;
426  break;
427 
428  case OPT_ENDTEXT:
429  endText = value;
430  break;
431 
432  case OPT_COMMENTS:
433  if (flag) { pgnStyle |= PGN_STYLE_COMMENTS; }
434  break;
435 
436  case OPT_VARIATIONS:
437  if (flag) { pgnStyle |= PGN_STYLE_VARS; }
438  break;
439 
440  case OPT_SPACES:
441  if (flag) { pgnStyle |= PGN_STYLE_MOVENUM_SPACE; }
442  break;
443 
444  case OPT_SYMBOLS:
445  if (flag) { pgnStyle |= PGN_STYLE_SYMBOLS; }
446  break;
447 
448  case OPT_INDENTC:
449  if (flag) { pgnStyle |= PGN_STYLE_INDENT_COMMENTS; }
450  break;
451 
452  case OPT_INDENTV:
453  if (flag) { pgnStyle |= PGN_STYLE_INDENT_VARS; }
454  break;
455 
456  case OPT_COLUMN:
457  if (flag) { pgnStyle |= PGN_STYLE_COLUMN; }
458  break;
459 
460  case OPT_NOMARKS:
461  if (flag) { pgnStyle |= PGN_STYLE_STRIP_MARKS; }
462  break;
463 
464  case OPT_CONVERTNULL:
465  if (flag) { pgnStyle |= PGN_STYLE_NO_NULL_MOVES; }
466  break;
467 
468  default:
469  return InvalidCommand (ti, "sc_base export", options);
470  }
471  }
472  exportFile = fopen (exportFileName, (appendToFile ? "r+" : "w"));
473  if (exportFile == NULL) {
474  return errorResult (ti, "Error opening file for exporting games.");
475  }
476  // Write start text or find the place in the file to append games:
477  if (appendToFile) {
478  if (outputFormat == PGN_FORMAT_Plain) {
479  fseek (exportFile, 0, SEEK_END);
480  } else {
481  fseek (exportFile, 0, SEEK_SET);
482  const char * endMarker = "";
483  if (outputFormat == PGN_FORMAT_HTML) {
484  endMarker = "</body>";
485  } else if (outputFormat == PGN_FORMAT_LaTeX) {
486  endMarker = "\\end{document}";
487  }
488  char line [1024];
489  uint pos = 0;
490  while (1) {
491  char* err = fgets(line, 1024, exportFile);
492  if (err == 0 || feof(exportFile)) break;
493  const char * s = strTrimLeft (line, " ");
494  if (strIsCasePrefix (endMarker, s)) {
495  // We have seen the line to stop at, so break out
496  break;
497  }
498  pos = ftell (exportFile);
499  }
500  fseek (exportFile, pos, SEEK_SET);
501  }
502  } else {
503  fputs (startText, exportFile);
504  }
505 
506  if (!exportFilter) {
507  exportGame (db->game, exportFile, outputFormat, pgnStyle);
508  } else { //TODO: remove this (duplicate of sc_filter export)
509  Progress progress = UI_CreateProgress(ti);
510  uint numSeen = 0;
511  uint numToExport = db->dbFilter->Count();
512  Game * g = scratchGame;
513  for (gamenumT i=0, n=db->numGames(); i < n; i++) {
514  if (db->dbFilter->Get(i)) { // Export this game:
515  if (++numSeen % 1024 == 0) { // Update the percentage done bar:
516  if (!progress.report(numSeen, numToExport)) break;
517  }
518 
519  // Print the game, skipping any corrupt games:
520  const IndexEntry* ie = db->getIndexEntry(i);
521  if (ie->GetLength() == 0) { continue; }
522  if (db->getGame(ie, db->bbuf) != OK) {
523  continue;
524  }
525  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
526  continue;
527  }
528  g->LoadStandardTags (ie, db->getNameBase());
529  exportGame (g, exportFile, outputFormat, pgnStyle);
530  }
531  }
532  progress.report(1, 1);
533  }
534  fputs (endText, exportFile);
535  fclose (exportFile);
536  return TCL_OK;
537 }
538 
539 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
540 // sc_base_piecetrack:
541 // Examines games in the filter of the current database and
542 // returns a list of 64 integers indicating how frequently
543 // the specified piece moves to each square.
544 int
545 sc_base_piecetrack (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
546 {
547  const char * usage =
548  "Usage: sc_base piecetrack [-g|-t] <minMoves> <maxMoves> <startSquare ...>";
549 
550  if (argc < 5) {
551  return errorResult (ti, usage);
552  }
553 
554  // Check for optional mode parameter:
555  bool timeOnSquareMode = false;
556  int arg = 2;
557  if (argv[arg][0] == '-') {
558  if (argv[arg][1] == 'g' && strIsPrefix (argv[arg], "-games")) {
559  timeOnSquareMode = false;
560  arg++;
561  } else if (argv[arg][1] == 't' && strIsPrefix (argv[arg], "-time")) {
562  timeOnSquareMode = true;
563  arg++;
564  } else {
565  return errorResult (ti, usage);
566  }
567  }
568 
569  // Read the two move-number parameters:
570  uint minPly = strGetUnsigned(argv[arg]) * 2;
571  arg++;
572  uint maxPly = strGetUnsigned(argv[arg]) * 2;
573  arg++;
574 
575  // Convert moves to plycounts, e.g. "5-10" -> "9-20"
576  if (minPly < 2) { minPly=2; }
577  if (maxPly < minPly) { maxPly = minPly; }
578  minPly--;
579 
580  // Parse the variable number of tracked square arguments:
581  uint sqFreq[64] = {0};
582  bool trackSquare[64] = { false };
583  int nTrackSquares = 0;
584  for (int a=arg; a < argc; a++) {
585  squareT sq = strGetSquare (argv[a]);
586  if (sq == NULL_SQUARE) { return errorResult (ti, usage); }
587  if (!trackSquare[sq]) {
588  // Seen another starting square to track.
589  trackSquare[sq] = true;
590  nTrackSquares++;
591  }
592  }
593 
594  // If current base is unused, filter is empty, or no track
595  // squares specified, then just return a zero-filled list:
596 
597  if (! db->inUse || db->dbFilter->Count() == 0 || nTrackSquares == 0) {
598  for (uint i=0; i < 64; i++) { appendUintElement (ti, 0); }
599  return TCL_OK;
600  }
601 
602  // Examine every filter game and track the selected pieces:
603 
604  Progress progress = UI_CreateProgress(ti);
605  uint filterCount = db->dbFilter->Count();
606  uint filterSeen = 0;
607 
608  for (uint gnum = 0, n = db->numGames(); gnum < n; gnum++) {
609  // Skip over non-filter games:
610  if (!db->dbFilter->Get(gnum)) { continue; }
611 
612  // Update progress bar:
613  if ((filterSeen++ % 1000) == 0) {
614  if (!progress.report(filterSeen, filterCount)) {
615  return UI_Result(ti, ERROR_UserCancel);
616  }
617  }
618 
619  const IndexEntry* ie = db->getIndexEntry(gnum);
620 
621  // Skip games with non-standard start or no moves:
622  if (ie->GetStartFlag()) { continue; }
623  if (ie->GetLength() == 0) { continue; }
624 
625  // Skip games too short to be useful:
626  if (ie->GetNumHalfMoves() < minPly) { continue; }
627 
628  // Set up piece tracking for this game:
629  bool movedTo[64] = { false };
630  bool track[64];
631  int ntrack = nTrackSquares;
632  for (uint sq=0; sq < 64; sq++) { track[sq] = trackSquare[sq]; }
633 
634  Game * g = scratchGame;
635  if (db->getGame(ie, db->bbuf) != OK) {
636  continue;
637  }
638  db->bbuf->BackToStart();
639  g->Clear();
640  if (g->DecodeStart (db->bbuf) != OK) { continue; }
641 
642  uint plyCount = 0;
643  simpleMoveT sm;
644 
645  // Process each game move until the maximum ply or end of
646  // the game is reached:
647 
648  while (plyCount < maxPly) {
649  if (g->DecodeNextMove (db->bbuf, &sm) != OK) { break; }
650  plyCount++;
651  squareT toSquare = sm.to;
652  squareT fromSquare = sm.from;
653 
654  // Special hack for castling:
655  if (piece_Type(sm.movingPiece) == KING) {
656  if (fromSquare == E1) {
657  if (toSquare == G1 && track[H1]) {
658  fromSquare = H1; toSquare = F1;
659  }
660  if (toSquare == C1 && track[A1]) {
661  fromSquare = A1; toSquare = D1;
662  }
663  }
664  if (fromSquare == E8) {
665  if (toSquare == G8 && track[H8]) {
666  fromSquare = H8; toSquare = F8;
667  }
668  if (toSquare == C8 && track[A8]) {
669  fromSquare = A8; toSquare = D8;
670  }
671  }
672  }
673 
674  // TODO: Special hack for en-passant capture?
675 
676  if (track[toSquare]) {
677  // A tracked piece has been captured:
678  track[toSquare] = false;
679  ntrack--;
680  if (ntrack <= 0) { break; }
681 
682  } else if (track[fromSquare]) {
683  // A tracked piece is moving:
684  track[fromSquare] = false;
685  track[toSquare] = true;
686  if (plyCount >= minPly) {
687  // If not time-on-square mode, and this
688  // new target square has not been moved to
689  // already by a tracked piece in this game,
690  // increase its frequency now:
691  if (!timeOnSquareMode && !movedTo[toSquare]) {
692  sqFreq[toSquare]++;
693  }
694  movedTo[toSquare] = true;
695  }
696  }
697 
698  if (timeOnSquareMode && plyCount >= minPly) {
699  // Time-on-square mode: find all tracked squares
700  // (there are ntrack of them) and increment the
701  // frequency of each.
702  int nleft = ntrack;
703  for (uint i=0; i < 64; i++) {
704  if (track[i]) {
705  sqFreq[i]++;
706  nleft--;
707  // We can stop early when all tracked
708  // squares have been found:
709  if (nleft <= 0) { break; }
710  }
711  }
712  }
713  } // while (plyCount < maxPly)
714  } // foreach game
715 
716  progress.report(1, 1);
717 
718  // Now return the 64-integer list: if in time-on-square mode,
719  // the value for each square is the number of plies when a
720  // tracked piece was on it, so halve it to convert to moves:
721 
722  for (uint i=0; i < 64; i++) {
723  appendUintElement (ti, timeOnSquareMode ? sqFreq[i] / 2 : sqFreq[i]);
724  }
725  return TCL_OK;
726 }
727 
728 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
729 // sc_base_duplicates:
730 // Finds duplicate games and marks them deleted.
731 // A pair of games are considered duplicates if the Event, Site,
732 // White, Black, and Round values all match identically, and the
733 // Date matches to within 2 days (that is, the same year, the same
734 // month, and the days of month differ by 2 at most).
735 //
736 // Furthermore, the moves of one game should, after truncating, be the
737 // same as the moves of the other game, for them to be duplicates.
738 
739 struct gNumListT {
740  uint64_t hash;
742  bool operator<(const gNumListT& a) const { return hash < a.hash; }
743 };
744 
745 struct dupCriteriaT {
748  bool sameEvent;
749  bool sameSite;
750  bool sameRound;
752  bool sameYear;
753  bool sameMonth;
754  bool sameDay;
756  bool sameMoves;
757 };
758 
759 bool
761  const IndexEntry * ie1, const IndexEntry * ie2,
762  dupCriteriaT * cr)
763 {
764  if (ie1->GetDeleteFlag() || ie2->GetDeleteFlag()) { return false; }
765  if (cr->sameEvent) {
766  if (ie1->GetEvent() != ie2->GetEvent()) { return false; }
767  }
768  if (cr->sameSite) {
769  if (ie1->GetSite() != ie2->GetSite()) { return false; }
770  }
771  if (cr->sameRound) {
772  if (ie1->GetRound() != ie2->GetRound()) { return false; }
773  }
774  if (cr->sameYear) {
775  if (ie1->GetYear() != ie2->GetYear()) { return false; }
776  }
777  if (cr->sameMonth) {
778  if (ie1->GetMonth() != ie2->GetMonth()) { return false; }
779  }
780  if (cr->sameDay) {
781  if (ie1->GetDay() != ie2->GetDay()) { return false; }
782  }
783  if (cr->sameResult) {
784  if (ie1->GetResult() != ie2->GetResult()) { return false; }
785  }
786  if (cr->sameEcoCode) {
787  ecoStringT a;
788  ecoStringT b;
789  eco_ToBasicString (ie1->GetEcoCode(), a);
790  eco_ToBasicString (ie2->GetEcoCode(), b);
791  if (a[0] != b[0] || a[1] != b[1] || a[2] != b[2]) { return false; }
792  }
793 
794  // There are a lot of "place-holding" games in some database, that have
795  // just one (usually wrong) move and a result, that are then replaced by
796  // the full version of the game. Therefore, if we reach here and one
797  // of the games (or both) have only one move or no moves, return true
798  // as long as they have the same year, site and round:
799 
800  if (ie1->GetNumHalfMoves() <= 2 || ie2->GetNumHalfMoves() <= 2) {
801  if (ie1->GetYear() == ie2->GetYear() &&
802  ie1->GetSite() == ie2->GetSite() &&
803  ie1->GetRound() == ie2->GetRound()) {
804  return true;
805  }
806  }
807 
808  // Now check that the games contain the same moves, up to the length
809  // of the shorter game:
810 
811  if (cr->sameMoves) {
812  const byte * hpData1 = ie1->GetHomePawnData();
813  const byte * hpData2 = ie2->GetHomePawnData();
814  if (! hpSig_Prefix (hpData1, hpData2)) { return false; }
815  // Now we have to check the actual moves of the games:
816  uint length = std::min(ie1->GetNumHalfMoves(), ie2->GetNumHalfMoves());
817  std::string a = base->getGame(ie1).getMoveSAN(0, length);
818  std::string b = base->getGame(ie2).getMoveSAN(0, length);
819  return (a == b);
820  }
821  return true;
822 }
823 
824 uint
825 sc_base_duplicates (scidBaseT* dbase, UI_handle_t ti, int argc, const char ** argv)
826 {
827  dupCriteriaT criteria;
828  criteria.exactNames = false;
829  criteria.sameColors = true;
830  criteria.sameEvent = true;
831  criteria.sameSite = true;
832  criteria.sameRound = true;
833  criteria.sameYear = true;
834  criteria.sameMonth = true;
835  criteria.sameDay = false;
836  criteria.sameResult = false;
837  criteria.sameEcoCode = false;
838  criteria.sameMoves = true;
839 
840  bool skipShortGames = false;
841  bool keepAllCommentedGames = true;
842  bool keepAllGamesWithVars = true;
843  bool setFilterToDups = false;
844  bool onlyFilterGames = false;
845  bool copyRatings = false;
846 
847  // Deletion strategy: delete the shorter game, the game with the
848  // smaller game number, or the game with the larger game number.
849  enum deleteStrategyT { DELETE_SHORTER, DELETE_OLDER, DELETE_NEWER };
850  deleteStrategyT deleteStrategy = DELETE_SHORTER;
851 
852  // Parse command options in pairs of arguments:
853 
854  const char * options[] = {
855  "-players", "-colors", "-event", "-site", "-round", "-year",
856  "-month", "-day", "-result", "-eco", "-moves", "-skipshort",
857  "-comments", "-variations", "-setfilter", "-usefilter",
858  "-copyratings", "-delete",
859  NULL
860  };
861  enum {
862  OPT_PLAYERS, OPT_COLORS, OPT_EVENT, OPT_SITE, OPT_ROUND, OPT_YEAR,
863  OPT_MONTH, OPT_DAY, OPT_RESULT, OPT_ECO, OPT_MOVES, OPT_SKIPSHORT,
864  OPT_COMMENTS, OPT_VARIATIONS, OPT_SETFILTER, OPT_USEFILTER,
865  OPT_COPYRATINGS, OPT_DELETE
866  };
867 
868  for (int arg = 3; arg < argc; arg += 2) {
869  const char * optStr = argv[arg];
870  const char * valueStr = argv[arg + 1];
871  bool b = strGetBoolean (valueStr);
872  int index = strUniqueMatch (optStr, options);
873  switch (index) {
874  case OPT_PLAYERS: criteria.exactNames = b; break;
875  case OPT_COLORS: criteria.sameColors = b; break;
876  case OPT_EVENT: criteria.sameEvent = b; break;
877  case OPT_SITE: criteria.sameSite = b; break;
878  case OPT_ROUND: criteria.sameRound = b; break;
879  case OPT_YEAR: criteria.sameYear = b; break;
880  case OPT_MONTH: criteria.sameMonth = b; break;
881  case OPT_DAY: criteria.sameDay = b; break;
882  case OPT_RESULT: criteria.sameResult = b; break;
883  case OPT_ECO: criteria.sameEcoCode = b; break;
884  case OPT_MOVES: criteria.sameMoves = b; break;
885  case OPT_SKIPSHORT: skipShortGames = b; break;
886  case OPT_COMMENTS: keepAllCommentedGames = b; break;
887  case OPT_VARIATIONS: keepAllGamesWithVars = b; break;
888  case OPT_SETFILTER: setFilterToDups = b; break;
889  case OPT_USEFILTER: onlyFilterGames = b; break;
890  case OPT_COPYRATINGS: copyRatings = b; break;
891  case OPT_DELETE:
892  if (strIsCasePrefix (valueStr, "shorter")) {
893  deleteStrategy = DELETE_SHORTER;
894  } else if (strIsCasePrefix (valueStr, "older")) {
895  deleteStrategy = DELETE_OLDER;
896  } else if (strIsCasePrefix (valueStr, "newer")) {
897  deleteStrategy = DELETE_NEWER;
898  } else {
899  return errorResult (ti, "Invalid option.");
900  }
901  break;
902  default:
903  return InvalidCommand (ti, "sc_base duplicates", options);
904  }
905  }
906  uint deletedCount = 0;
907  const gamenumT numGames = dbase->numGames();
908 
909  // Setup duplicates array:
910  uint* duplicates = new uint [numGames];
911  std::fill(duplicates, duplicates + numGames, 0);
912 
913  // We use a hashtable to limit duplicate game comparisons; each game
914  // is only compared to others that hash to the same value.
915  std::vector<gNumListT> hash(numGames);
916  size_t n_hash = 0;
917  const std::vector<uint32_t>& hashMap = (criteria.exactNames)
918  ? std::vector<uint32_t>()
920  for (gamenumT i=0; i < numGames; i++) {
921  const IndexEntry* ie = dbase->getIndexEntry(i);
922  if (! ie->GetDeleteFlag() /* && !ie->GetStartFlag() */
923  && (!skipShortGames || ie->GetNumHalfMoves() >= 10)
924  && (!onlyFilterGames || dbase->dbFilter->Get(i) > 0)) {
925 
926  uint32_t wh = ie->GetWhite();
927  uint32_t bl = ie->GetBlack();
928  if (!criteria.exactNames) {
929  wh = hashMap[wh];
930  bl = hashMap[bl];
931  }
932  if (!criteria.sameColors && bl > wh) {
933  std::swap(wh, bl);
934  }
935  gNumListT* node = &(hash[n_hash++]);
936  node->hash = (uint64_t(wh) << 32) + bl;
937  node->gNumber = i;
938  }
939  }
940  hash.resize(n_hash);
941  std::sort(hash.begin(), hash.end());
942 
943  if (setFilterToDups) { dbase->dbFilter->Fill (0); }
944  Progress progress = UI_CreateProgress(ti);
945 
946  dbase->beginTransaction();
947 
948  // Now check same-hash games for duplicates:
949  for (size_t i=0; i < n_hash; i++) {
950  if ((i % 1024) == 0) {
951  if (!progress.report(i, numGames)) break;
952  }
953  gNumListT* head = &(hash[i]);
954  IndexEntry* ieHead = dbase->idx->FetchEntry (head->gNumber);
955 
956  for (size_t comp=i+1; comp < n_hash; comp++) {
957  gNumListT* compare = &(hash[comp]);
958  if (compare->hash != head->hash) break;
959 
960  IndexEntry * ieComp = dbase->idx->FetchEntry (compare->gNumber);
961 
962  if (checkDuplicate (dbase, ieHead, ieComp, &criteria)) {
963  duplicates[head->gNumber] = compare->gNumber + 1;
964  duplicates[compare->gNumber] = head->gNumber + 1;
965 
966  // Found a duplicate! Decide which one to delete:
967 
968  bool headImmune = false;
969  bool compImmune = false;
970  bool doDeletion = false;
971  bool copiedRatings = false;
972  gamenumT gnumKeep, gnumDelete;
973  IndexEntry * ieDelete, * ieKeep;
974 
975  if (keepAllCommentedGames) {
976  if (ieHead->GetCommentsFlag()) { headImmune = true; }
977  if (ieComp->GetCommentsFlag()) { compImmune = true; }
978  }
979  if (keepAllGamesWithVars) {
980  if (ieHead->GetVariationsFlag()) { headImmune = true; }
981  if (ieComp->GetVariationsFlag()) { compImmune = true; }
982  }
983 
984  // Decide which game should get deleted:
985  bool deleteHead = false;
986  if (deleteStrategy == DELETE_OLDER) {
987  deleteHead = (head->gNumber < compare->gNumber);
988  } else if (deleteStrategy == DELETE_NEWER) {
989  deleteHead = (head->gNumber > compare->gNumber);
990  } else {
991  ASSERT (deleteStrategy == DELETE_SHORTER);
992  uint a = ieHead->GetNumHalfMoves();
993  uint b = ieComp->GetNumHalfMoves();
994  deleteHead = (a <= b);
995  if (a == b && headImmune) deleteHead = false;
996  }
997 
998  if (deleteHead) {
999  ieDelete = ieHead;
1000  ieKeep = ieComp;
1001  gnumDelete = head->gNumber;
1002  gnumKeep = compare->gNumber;
1003  doDeletion = ! headImmune;
1004  } else {
1005  ieDelete = ieComp;
1006  ieKeep = ieHead;
1007  gnumDelete = compare->gNumber;
1008  gnumKeep = head->gNumber;
1009  doDeletion = ! compImmune;
1010  }
1011  // Delete whichever game is to be deleted:
1012  if (doDeletion) {
1013  deletedCount++;
1014  ieDelete->SetDeleteFlag (true);
1015  if (copyRatings && ieKeep->GetWhiteElo() == 0) {
1016  eloT elo = ieDelete->GetWhiteElo();
1017  byte rtype = ieDelete->GetWhiteRatingType();
1018  if (elo != 0) {
1019  ieKeep->SetWhiteElo (elo);
1020  ieKeep->SetWhiteRatingType (rtype);
1021  copiedRatings = true;
1022  }
1023  }
1024  if (copyRatings && ieKeep->GetBlackElo() == 0) {
1025  eloT elo = ieDelete->GetBlackElo();
1026  byte rtype = ieDelete->GetBlackRatingType();
1027  if (elo != 0) {
1028  ieKeep->SetBlackElo (elo);
1029  ieKeep->SetBlackRatingType (rtype);
1030  copiedRatings = true;
1031  }
1032  }
1033  dbase->idx->WriteEntry (ieDelete, gnumDelete);
1034  if (copiedRatings) {
1035  dbase->idx->WriteEntry (ieKeep, gnumKeep);
1036  }
1037  if (setFilterToDups) {
1038  dbase->dbFilter->Set (gnumDelete, 1);
1039  }
1040  }
1041  }
1042  }
1043  }
1044 
1045  dbase->endTransaction();
1046  dbase->setDuplicates(duplicates);
1047  progress.report(1,1);
1048 
1049  return deletedCount;
1050 }
1051 
1052 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1053 // sc_base_tag:
1054 // Produce a list of PGN tags used in the database,
1055 // or strip an unwanted non-essential tag from the
1056 // database. It cannot be used for in-index tags
1057 // such as ratings, ECO or EventDate, or the FEN
1058 // or Setup tags.
1059 // The command has three subcommands:
1060 // find <tag>: set the filter to contain all games
1061 // that have the specified tag.
1062 // list: return a even-sized list, where each pair
1063 // of elements is a tag name and its frequency,
1064 // for all non-standard tags stored as Extra
1065 // tags in the game file of the database.
1066 // strip <tag>: Remove all occurrences of the
1067 // specified tag from the database.
1068 int
1069 sc_base_tag (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1070 {
1071  const char * usage = "Usage: sc_base tag [filter <tagname> | list | strip <tagname>]";
1072  const char * options[] = {
1073  "find", "list", "strip", NULL
1074  };
1075  enum {
1076  TAG_FIND, TAG_LIST, TAG_STRIP
1077  };
1078 
1079  const char * tag = NULL; // For "find" or "strip" commands
1080  std::vector< std::pair <std::string, uint> > tag_freq; // For "list" command
1081 
1082  if (! db->inUse) {
1083  return errorResult (ti, errMsgNotOpen(ti));
1084  }
1085 
1086  int cmd = -1;
1087  if (argc >= 3) { cmd = strUniqueMatch (argv[2], options); }
1088 
1089  switch (cmd) {
1090  case TAG_LIST:
1091  if (argc != 3) { return errorResult (ti, usage); }
1092  break;
1093  case TAG_FIND: // Same extra parameter as TAG_STRIP
1094  case TAG_STRIP:
1095  if (argc != 4) { return errorResult (ti,usage); }
1096  tag = argv[3];
1097  break;
1098  default:
1099  return errorResult (ti, usage);
1100  };
1101 
1102  if (cmd == TAG_STRIP) {
1103  // If stripping a tag, make sure we have a writable database:
1104  if (db->isReadOnly())
1105  return errorResult(ti, ERROR_FileReadOnly);
1106  db->beginTransaction();
1107  }
1108 
1109  // If setting filter, clear it now:
1110  if (cmd == TAG_FIND) { db->dbFilter->Fill (0); }
1111 
1112  // Process each game in the database:
1113  Progress progress = UI_CreateProgress(ti);
1114  Game * g = scratchGame;
1115  uint nEditedGames = 0;
1116 
1117  for (gamenumT gnum = 0, n = db->numGames(); gnum < n; gnum++) {
1118  if ((gnum % 1000) == 0) {
1119  if (!progress.report(gnum, n)) break;
1120  }
1121 
1122  const IndexEntry* ie = db->getIndexEntry(gnum);
1123  if (ie->GetLength() == 0) { continue; }
1124  if (db->getGame(ie, db->bbuf) != OK) {
1125  continue;
1126  }
1127  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
1128  continue;
1129  }
1130  if (cmd == TAG_FIND) {
1131  if (g->FindExtraTag (tag) != NULL) {
1132  // Found the tag, so add this game to the filter:
1133  db->dbFilter->Set (gnum, 1);
1134  }
1135  } else if (cmd == TAG_STRIP) {
1136  if (g->RemoveExtraTag (tag)) {
1137  // The tag was found and stripped. Re-save the game,
1138  // remembering to load its standard tags first:
1139  g->LoadStandardTags (ie, db->getNameBase());
1140  errorT res = db->saveGameHelper(g, gnum);
1141  if (res != OK) return UI_Result(ti, res);
1142  nEditedGames++;
1143  }
1144  } else {
1145  ASSERT (cmd == TAG_LIST);
1146  uint numtags = g->GetNumExtraTags();
1147  tagT * taglist = g->GetExtraTags();
1148  // Increment frequency for each extra tag:
1149  while (numtags > 0) {
1150  bool found = false;
1151  for (uint i=0; i < tag_freq.size(); i++) {
1152  if (tag_freq[i].first == taglist->tag) {
1153  tag_freq[i].second++;
1154  found = true;
1155  break;
1156  }
1157  }
1158  if (!found) {
1159  tag_freq.push_back(std::make_pair(taglist->tag, 1));
1160  }
1161  numtags--;
1162  taglist++;
1163  }
1164  }
1165  }
1166  // Done searching through all games.
1167 
1168  // If necessary, update index and name files:
1169  if (cmd == TAG_STRIP) {
1170  db->endTransaction();
1171  setUintResult (ti, nEditedGames);
1172  }
1173 
1174  // If listing extra tags, generate the list now:
1175  if (cmd == TAG_LIST) {
1176  for (uint i=0; i < tag_freq.size(); i++) {
1177  uint freq = tag_freq[i].second;
1178  const char* name = tag_freq[i].first.c_str();
1179  if (freq > 0 && !strEqual (name, "SetUp")) {
1180  Tcl_AppendElement (ti, name);
1181  appendUintElement (ti, freq);
1182  }
1183  }
1184  }
1185  return TCL_OK;
1186 }
1187 
1188 
1189 //////////////////////////////////////////////////////////////////////
1190 /// CLIPBASE functions
1191 
1192 int sc_clipbase_paste (scidBaseT* clipbase, Tcl_Interp * ti, int argc, const char ** argv);
1193 
1194 
1195 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1196 // sc_clipbase:
1197 // Game clipbase functions.
1198 // Copies a game to, or pastes from, the clipbase database.
1199 int
1200 sc_clipbase (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1201 {
1203 
1204  static const char * options [] = {
1205  "clear", "paste", NULL
1206  };
1207  enum {
1208  CLIP_CLEAR, CLIP_PASTE
1209  };
1210  int index = -1;
1211 
1212  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
1213 
1214  switch (index) {
1215  case CLIP_CLEAR:
1216  clipbase->Close();
1217  clipbase->Open(ICodecDatabase::MEMORY, FMODE_Memory, "<clipbase>");
1218  clipbase->setExtraInfo("type", "2");
1219  return TCL_OK;
1220 
1221  case CLIP_PASTE:
1222  return sc_clipbase_paste (clipbase, ti, argc, argv);
1223 
1224  default:
1225  return InvalidCommand (ti, "sc_clipbase", options);
1226  }
1227 
1228  return TCL_OK;
1229 }
1230 
1231 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1232 // sc_clipbase_paste:
1233 // Paste the active clipbase game, replacing the current game state.
1234 int
1235 sc_clipbase_paste(scidBaseT* clipbase, Tcl_Interp * ti, int, const char**)
1236 {
1237  // Cannot paste the clipbase game when already in the clipbase:
1238  if (db == clipbase) { return TCL_OK; }
1239 
1240  uint location = clipbase->game->GetPgnOffset ();
1241  if (clipbase->game->Encode (db->bbuf, NULL) != OK) {
1242  return errorResult (ti, "Error encoding game.");
1243  }
1244  db->bbuf->BackToStart();
1245  db->game->Clear();
1246  db->gameNumber = -1;
1247  db->gameAltered = true;
1248 
1249  if (db->game->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
1250  return errorResult (ti, "Error decoding game.");
1251  }
1252 
1253  // Copy the standard tag values from the clipbase game:
1254  db->game->CopyStandardTags (clipbase->game);
1255 
1256  // Move to the current position in the clipbase game:
1257  db->game->MoveToLocationInPGN (location);
1258 
1259  return TCL_OK;
1260 }
1261 
1262 
1263 //////////////////////////////////////////////////////////////////////
1264 /// ECO Classification functions
1265 
1266 // ecoTranslateT:
1267 // Structure for a linked list of ECO opening name translations.
1268 //
1270  char language;
1271  char * from;
1272  char * to;
1274 };
1275 
1276 static ecoTranslateT * ecoTranslations = NULL;
1277 void translateECO (Tcl_Interp * ti, const char * strFrom, DString * dstrTo);
1278 
1279 int
1280 sc_eco (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
1281 {
1282  int index = -1;
1283  static const char * options [] = {
1284  "base", "game", "read", "reset", "size", "summary",
1285  "translate", NULL
1286  };
1287  enum {
1288  ECO_BASE, ECO_GAME, ECO_READ, ECO_RESET, ECO_SIZE, ECO_SUMMARY,
1289  ECO_TRANSLATE
1290  };
1291 
1292  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
1293 
1294  switch (index) {
1295  case ECO_BASE:
1296  return sc_eco_base (cd, ti, argc, argv);
1297 
1298  case ECO_GAME:
1299  return sc_eco_game (cd, ti, argc, argv);
1300 
1301  case ECO_READ:
1302  return sc_eco_read (cd, ti, argc, argv);
1303 
1304  case ECO_RESET:
1305  if (ecoBook) { delete ecoBook; ecoBook = NULL; }
1306  break;
1307 
1308  case ECO_SIZE:
1309  return setUintResult (ti, ecoBook == NULL ? 0 : ecoBook->Size());
1310 
1311  case ECO_SUMMARY:
1312  return sc_eco_summary (cd, ti, argc, argv);
1313 
1314  case ECO_TRANSLATE:
1315  return sc_eco_translate (cd, ti, argc, argv);
1316 
1317  default:
1318  return InvalidCommand (ti, "sc_eco", options);
1319  }
1320 
1321  return TCL_OK;
1322 }
1323 
1324 
1325 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1326 // sc_eco_base:
1327 // Reclassifies all games in the current base by ECO code.
1328 //
1329 // The first parameter indicates if all games (not only those
1330 // with no existing ECO code) should be classified.
1331 // "0" or "nocode": only games with no ECO code.
1332 // "1" or "all": classify all games.
1333 // "date:yyyy.mm.dd": only games since date.
1334 // The second boolean parameter indicates if Scid-specific ECO
1335 // extensions (e.g. "B12j" instead of just "B12") should be used.
1336 //
1337 // If the database is read-only, games can still be classified but
1338 // the results will not be stored to the database file.
1339 int
1340 sc_eco_base (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1341 {
1342  if (argc != 4) {
1343  return errorResult (ti, "Usage: sc_eco base <bool:all_games> <bool:extensions>");
1344  }
1345  if (!ecoBook) { return errorResult (ti, "No ECO Book is loaded."); }
1346  if (! db->inUse) return errorResult (ti, ERROR_FileNotOpen);
1347  if (db->isReadOnly()) return errorResult (ti, ERROR_FileReadOnly);
1348 
1349  int option = -1;
1350  enum {ECO_NOCODE, ECO_ALL, ECO_DATE, ECO_FILTER};
1351 
1352  switch (argv[2][0]) {
1353  case '0':
1354  case 'n':
1355  option = ECO_NOCODE; break;
1356  case 'd':
1357  option = ECO_DATE; break;
1358  case 'f':
1359  option = ECO_FILTER; break;
1360  default:
1361  option = ECO_ALL; break;
1362  }
1363 
1364  db->beginTransaction();
1365 
1366  bool extendedCodes = strGetBoolean(argv[3]);
1367  Game * g = scratchGame;
1368  IndexEntry * ie;
1369  errorT err = OK;
1370  uint countClassified = 0; // Count of games classified.
1371  dateT startDate = ZERO_DATE;
1372  if (option == ECO_DATE) {
1373  startDate = date_EncodeFromString (&(argv[2][5]));
1374  }
1375  Progress progress = UI_CreateProgress(ti);
1376  Timer timer; // Time the classification operation.
1377 
1378  // Read each game:
1379  for (uint i=0, n = db->numGames(); i < n; i++) {
1380  if ((i % 1000) == 0) { // Update the percentage done bar:
1381  if (!progress.report(i, n)) break;
1382  }
1383  ie = db->idx->FetchEntry (i);
1384  if (ie->GetLength() == 0) { continue; }
1385 
1386  // Ignore games not in current filter if directed:
1387  if (option == ECO_FILTER && db->dbFilter->Get(i) == 0) { continue; }
1388 
1389  // Ignore games with existing ECO code if directed:
1390  if (option == ECO_NOCODE && ie->GetEcoCode() != 0) { continue; }
1391 
1392  // Ignore games before starting date if directed:
1393  if (option == ECO_DATE && ie->GetDate() < startDate) { continue; }
1394 
1395  if (db->getGame(ie, db->bbuf) != OK) { continue; }
1396  db->bbuf->BackToStart();
1397  g->Clear();
1398  if (g->DecodeStart (db->bbuf) != OK) { continue; }
1399 
1400  // First, read in the game -- with a limit of 30 moves per
1401  // side, since an ECO match after move 31 is very unlikely and
1402  // we can save time by setting a limit. Also, stop when the
1403  // material left in on the board is less than that of the
1404  // book position with the least material, since no further
1405  // positions in the game could possibly match.
1406 
1407  uint maxPly = 60;
1408  uint leastMaterial = ecoBook->FewestPieces();
1409  uint material;
1410 
1411  do {
1412  err = g->DecodeNextMove (db->bbuf, NULL);
1413  maxPly--;
1414  material = g->GetCurrentPos()->TotalMaterial();
1415  } while (err == OK && maxPly > 0 && material >= leastMaterial);
1416 
1417  // Now, move back through the game to the start searching for a
1418  // match in the ECO book. Stop at the first match found since it
1419  // is the deepest.
1420 
1421  DString commentStr;
1422  ecoT ecoCode = ECO_None;
1423 
1424  do {
1425  if (ecoBook->FindOpcode (g->GetCurrentPos(), "eco",
1426  &commentStr) == OK) {
1427  ecoCode = eco_FromString (commentStr.Data());
1428  if (! extendedCodes) {
1429  ecoCode = eco_BasicCode (ecoCode);
1430  }
1431  break;
1432  }
1433  err = g->MoveBackup();
1434  } while (err == OK);
1435 
1436  if (ie->GetEcoCode() != ecoCode) {
1437  ie->SetEcoCode (ecoCode);
1438  err = db->idx->WriteEntry (ie, i);
1439  if (err != OK) return errorResult(ti, err);
1440  countClassified++;
1441  }
1442  }
1443  progress.report(1,1);
1444 
1445  // Update the index file header:
1446  err = db->endTransaction();
1447  if (err != OK) return errorResult(ti, err);
1448 
1449  int centisecs = timer.CentiSecs();
1450  char tempStr [100];
1451  sprintf (tempStr, "Classified %d game%s in %d%c%02d seconds",
1452  countClassified, strPlural (countClassified),
1453  centisecs / 100, decimalPointChar, centisecs % 100);
1454  return UI_Result(ti, OK, tempStr);
1455 }
1456 
1457 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1458 // sc_eco_game:
1459 // Returns ECO code for the current game. If the optional
1460 // parameter <ply> is passed, it returns the ply depth of the
1461 // deepest match instead of the ECO code.
1462 int
1463 sc_eco_game (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1464 {
1465  int found = 0;
1466  uint ply = 0;
1467  uint returnPly = 0;
1468  if (argc > 2) {
1469  if (argc == 3 && strIsPrefix (argv[2], "ply")) {
1470  returnPly = 1;
1471  } else {
1472  return errorResult (ti, "Usage: sc_game eco [ply]");
1473  }
1474  }
1475  if (!ecoBook) { return TCL_OK; }
1476 
1477  db->game->SaveState();
1478  db->game->MoveToPly (0);
1479  DString ecoStr;
1480 
1481  do {} while (db->game->MoveForward() == OK);
1482  do {
1483  if (ecoBook->FindOpcode (db->game->GetCurrentPos(), "eco",
1484  &ecoStr) == OK) {
1485  found = 1;
1486  ply = db->game->GetCurrentPly();
1487  break;
1488  }
1489  } while (db->game->MoveBackup() == OK);
1490 
1491  if (found) {
1492  if (returnPly) {
1493  setUintResult (ti, ply);
1494  } else {
1495  ecoT ecoCode = eco_FromString (ecoStr.Data());
1496  ecoStringT extEco;
1497  eco_ToExtendedString(ecoCode, extEco);
1498  Tcl_AppendResult(ti, extEco, NULL);
1499  }
1500  }
1501  db->game->RestoreState ();
1502  return TCL_OK;
1503 }
1504 
1505 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1506 // sc_eco_read:
1507 // Reads a book file for ECO classification.
1508 // Returns the book size (number of positions).
1509 int
1510 sc_eco_read (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1511 {
1512  if (argc < 3) { return TCL_OK; }
1513  if (ecoBook) { delete ecoBook; }
1514  ecoBook = new PBook;
1515  ecoBook->SetFileName (argv[2]);
1516  errorT err = ecoBook->ReadEcoFile();
1517  if (err != OK) {
1518  if (err == ERROR_FileOpen) {
1519  Tcl_AppendResult (ti, "Unable to open the ECO file:\n",
1520  argv[2], NULL);
1521  } else {
1522  Tcl_AppendResult (ti, "Unable to load the ECO file:\n",
1523  argv[2], NULL);
1524  Tcl_AppendResult (ti, "\n\nError at line ", NULL);
1525  appendUintResult (ti, ecoBook->GetLineNumber());
1526  }
1527  delete ecoBook;
1528  ecoBook = NULL;
1529  return TCL_ERROR;
1530  }
1531  return setUintResult (ti, ecoBook->Size());
1532 }
1533 
1534 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1535 // sc_eco_summary:
1536 // Returns a listing of positions for the specified ECO prefix,
1537 // in plain text or color (Scid hypertext) format.
1538 int
1539 sc_eco_summary (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1540 {
1541  bool color = true;
1542  if (argc != 3 && argc != 4) {
1543  return errorResult (ti, "Usage: sc_eco summary <ECO-prefix> [<bool:color>]");
1544  }
1545  if (argc == 4) { color = strGetBoolean (argv[3]); }
1546  if (!ecoBook) { return TCL_OK; }
1547  DString * dstr = new DString;
1548  DString * temp = new DString;
1549  bool inMoveList = false;
1550  ecoBook->EcoSummary (argv[2], temp);
1551  translateECO (ti, temp->Data(), dstr);
1552  temp->Clear();
1553  if (color) {
1554  DString * oldstr = dstr;
1555  dstr = new DString;
1556  const char * s = oldstr->Data();
1557  while (*s) {
1558  char ch = *s;
1559  switch (ch) {
1560  case '[':
1561  dstr->Append ("<tab>");
1562  dstr->AddChar (ch);
1563  break;
1564  case ']':
1565  dstr->AddChar (ch);
1566  dstr->Append ("<blue><run importMoveListTrans {");
1567  inMoveList = true;
1568  temp->Clear();
1569  break;
1570  case '\n':
1571  if (inMoveList) {
1572  dstr->Append ("}>", temp->Data());
1573  inMoveList = false;
1574  }
1575  dstr->Append ("</run></blue></tab><br>");
1576  break;
1577  default:
1578  dstr->AddChar (ch);
1579  if (inMoveList) { temp->AddChar (transPiecesChar(ch)); }//{ temp->AddChar (ch); }
1580  }
1581  s++;
1582  }
1583  delete oldstr;
1584  }
1585  Tcl_AppendResult (ti, dstr->Data(), NULL);
1586  delete temp;
1587  delete dstr;
1588  return TCL_OK;
1589 }
1590 
1591 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1592 // sc_eco_translate:
1593 // Adds a new ECO openings phrase translation.
1594 int
1595 sc_eco_translate (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1596 {
1597  if (argc != 5) {
1598  return errorResult (ti, "Usage: sc_eco translate <lang> <from> <to>");
1599  }
1600 
1601 #ifdef WINCE
1602  ecoTranslateT * trans = (ecoTranslateT * )my_Tcl_Alloc( sizeof(ecoTranslateT));
1603 #else
1604  ecoTranslateT * trans = new ecoTranslateT;
1605 #endif
1606  trans->next = ecoTranslations;
1607  trans->language = argv[2][0];
1608  trans->from = strDuplicate (argv[3]);
1609  trans->to = strDuplicate (argv[4]);
1610  ecoTranslations = trans;
1611  return TCL_OK;
1612 }
1613 
1614 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1615 // translateECO:
1616 // Translates an ECO opening name into the current language.
1617 //
1618 void
1619 translateECO (Tcl_Interp * ti, const char * strFrom, DString * dstrTo)
1620 {
1621  ecoTranslateT * trans = ecoTranslations;
1622  dstrTo->Clear();
1623  dstrTo->Append (strFrom);
1624  const char * language = Tcl_GetVar (ti, "language", TCL_GLOBAL_ONLY);
1625  if (language == NULL) { return; }
1626  char lang = language[0];
1627  while (trans != NULL) {
1628  if (trans->language == lang
1629  && strContains (dstrTo->Data(), trans->from)) {
1630  // Translate this phrase in the string:
1631  char * temp = strDuplicate (dstrTo->Data());
1632  dstrTo->Clear();
1633  char * in = temp;
1634  while (*in != 0) {
1635  if (strIsPrefix (trans->from, in)) {
1636  dstrTo->Append (trans->to);
1637  in += strLength (trans->from);
1638  } else {
1639  dstrTo->AddChar (*in);
1640  in++;
1641  }
1642  }
1643 #ifdef WINCE
1644  my_Tcl_Free((char*) temp);
1645 #else
1646  delete[] temp;
1647 #endif
1648  }
1649  trans = trans->next;
1650  }
1651 }
1652 
1653 //////////////////////////////////////////////////////////////////////
1654 /// FILTER functions
1655 
1656 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1657 // sc_filter: filter commands. Valid minor commands:
1658 // count: returns the number of games in the filter.
1659 // reset: resets the filter so all games are included.
1660 // remove: removes game number <x> from the filter.
1661 // stats: prints filter statistics.
1662 int
1663 sc_filter_old(ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
1664 {
1665  int index = -1;
1666  static const char * options [] = {
1667  "count", "first", "frequency",
1668  "last", "negate", "next",
1669  "previous", "stats",
1670  "search", "release",
1671  "treestats", "export", "copy", "and", "or", "new", NULL
1672  };
1673  enum {
1674  FILTER_COUNT, FILTER_FIRST, FILTER_FREQ,
1675  FILTER_LAST, FILTER_NEGATE, FILTER_NEXT,
1676  FILTER_PREV, FILTER_STATS,
1677  FILTER_SEARCH, FILTER_RELEASE,
1678  FILTER_TREESTATS, FILTER_EXPORT, FILTER_COPY, FILTER_AND, FILTER_OR, FILTER_NEW
1679  };
1680 
1681  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
1682 
1683  switch (index) {
1684  case FILTER_COUNT:
1685  if (argc == 2) {
1686  size_t res = db->getFilter("dbfilter")->size();
1687  return UI_Result(ti, OK, res);
1688  }
1689  break;
1690 
1691  case FILTER_NEW:
1692  if (argc == 3 || argc == 4) {
1693  scidBaseT* dbase = DBasePool::getBase(strGetUnsigned(argv[2]));
1694  if (dbase == NULL) return UI_Result(ti, ERROR_BadArg, "sc_filter: invalid baseId");
1695  if (argc == 4) {
1696  //TODO: Use argv[4] (FEN) instead of current Position
1697  SearchPos fp(db->game->GetCurrentPos());
1698  //TODO: use a dedicated filter instead of treeFilter
1699  HFilter maskfilter = HFilter(dbase->treeFilter);
1700  std::string val;
1701  if (fp.setFilter(dbase, maskfilter, UI_CreateProgressPosMask(ti))) {
1702  val = "tree";
1703  }
1704  return UI_Result(ti, OK, val);
1705  }
1706  return UI_Result(ti, OK, dbase->newFilter());
1707  }
1708  return UI_Result(ti, ERROR_BadArg, "Usage: sc_filter new baseId [FEN]");
1709 
1710  case FILTER_FIRST:
1711  return sc_filter_first (cd, ti, argc, argv);
1712 
1713  case FILTER_LAST:
1714  return sc_filter_last (cd, ti, argc, argv);
1715 
1716  case FILTER_NEXT:
1717  return sc_filter_next (cd, ti, argc, argv);
1718 
1719  case FILTER_PREV:
1720  return sc_filter_prev (cd, ti, argc, argv);
1721 
1722  case FILTER_STATS:
1723  return sc_filter_stats (cd, ti, argc, argv);
1724 
1725  }
1726 
1727  if (argc < 4) return errorResult (ti, "Usage: sc_filter <cmd> baseId filterName");
1728  scidBaseT* dbase = DBasePool::getBase(strGetUnsigned(argv[2]));
1729  if (dbase == NULL) return errorResult (ti, "sc_filter: invalid baseId");
1730  HFilter filter = dbase->getFilter(argv[3]);
1731  if (filter == 0) return errorResult (ti, "sc_filter: invalid filterName");
1732  switch (index) {
1733  case FILTER_AND:
1734  if (argc == 5) {
1735  const HFilter f = dbase->getFilter(argv[4]);
1736  if (f != 0) {
1737  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1738  if (filter.get(i) != 0 && f.get(i) == 0) filter.set(i, 0);
1739  }
1740  return UI_Result(ti, OK);
1741  }
1742  }
1743  return errorResult (ti, "Usage: sc_filter and baseId filterName filterAnd");
1744 
1745  case FILTER_OR:
1746  if (argc == 5) {
1747  const HFilter f = dbase->getFilter(argv[4]);
1748  if (f != 0) {
1749  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1750  if (filter.get(i) == 0) filter.set(i, f.get(i));
1751  }
1752  return UI_Result(ti, OK);
1753  }
1754  }
1755  return errorResult (ti, "Usage: sc_filter or baseId filterName filterOr");
1756 
1757  case FILTER_COPY:
1758  if (argc == 5) {
1759  const HFilter f = dbase->getFilter(argv[4]);
1760  if (f != 0) {
1761  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1762  filter.set(i, f.get(i));
1763  }
1764  return UI_Result(ti, OK);
1765  }
1766  }
1767  return errorResult (ti, "Usage: sc_filter copy baseId filterTo filterFrom");
1768 
1769  case FILTER_FREQ:
1770  return sc_filter_freq (dbase, filter, ti, argc, argv);
1771 
1772  case FILTER_NEGATE:
1773  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1774  filter.set(i, ! filter.get(i) );
1775  }
1776  return UI_Result(ti, OK);
1777 
1778  case FILTER_COUNT:
1779  return setUintResult (ti, filter->size());
1780 
1781  case FILTER_RELEASE:
1782  dbase->deleteFilter(argv[3]);
1783  return TCL_OK;
1784 
1785  case FILTER_SEARCH:
1786  if (argc > 5) {
1787  if (strCompare("header", argv[4]) == 0)
1788  return sc_search_header (cd, ti, dbase, filter, argc -3, argv +3);
1789  }
1790  return errorResult (ti, "Usage: sc_filter search baseId filterName <header> [args]");
1791 
1792  case FILTER_TREESTATS: {
1793  std::vector<scidBaseT::TreeStat> stats = dbase->getTreeStat(filter);
1794  UI_List res (stats.size());
1795  UI_List ginfo(8);
1796  for (uint i=0; i < stats.size(); i++) {
1797  ginfo.clear();
1798  ginfo.push_back(stats[i].SAN);
1799  ginfo.push_back(stats[i].ngames);
1800  ginfo.push_back(stats[i].resultW);
1801  ginfo.push_back(stats[i].resultD);
1802  ginfo.push_back(stats[i].resultB);
1803  ginfo.push_back(stats[i].exp);
1804  ginfo.push_back(stats[i].nexp);
1805  if (stats[i].toMove == WHITE) ginfo.push_back("W");
1806  else ginfo.push_back(stats[i].toMove == BLACK ? "B" : " ");
1807 
1808  res.push_back(ginfo);
1809  }
1810  return UI_Result(ti, OK, res);
1811  }
1812 
1813  case FILTER_EXPORT:
1814  if (argc >= 7 && argc <=9) {
1815  FILE* exportFile = fopen (argv[5], "w");
1816  if (exportFile == NULL) return errorResult (ti, "Error opening file for exporting games.");
1817  Game g;
1818  if (strCompare("LaTeX", argv[6]) == 0) {
1821  } else { //Default to PGN
1824  }
1825  if (argc > 7) fprintf(exportFile, "%s", argv[7]);
1826  Progress progress = UI_CreateProgress(ti);
1827  const NameBase* nb = dbase->getNameBase();
1828  size_t count = filter->size();
1829  gamenumT* idxList = new gamenumT[count];
1830  count = dbase->listGames(argv[4], 0, count, filter, idxList);
1831  errorT err = OK;
1832  for (size_t i = 0; i < count; ++i) {
1833  const IndexEntry* ie = dbase->getIndexEntry(idxList[i]);
1834  // Skip any corrupt games:
1835  if (dbase->getGame(ie, dbase->bbuf) != OK) continue;
1836  if (g.Decode (dbase->bbuf, GAME_DECODE_ALL) != OK) continue;
1837  g.LoadStandardTags (ie, nb);
1838  std::pair<const char*, unsigned> pgn = g.WriteToPGN(75, true);
1839  if (pgn.second != fwrite(pgn.first, 1, pgn.second, exportFile)) {
1840  err = ERROR_FileWrite;
1841  break;
1842  }
1843  if ((i % 1024 == 0) && !progress.report(i, count)) {
1844  err = ERROR_UserCancel;
1845  break;
1846  }
1847  }
1848  if (err == OK && argc > 8)
1849  fprintf(exportFile, "%s", argv[8]);
1850  fclose (exportFile);
1851  delete[] idxList;
1852  return UI_Result(ti, err);
1853  }
1854  return errorResult (ti, "Usage: sc_filter export baseId filterName sortCrit filename <PGN|LaTeX> [header] [footer]");
1855 
1856  }
1857  return InvalidCommand (ti, "sc_filter", options);
1858 }
1859 
1860 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1861 // sc_filter_freq:
1862 // Returns a two-integer list showing how many filter games,
1863 // and how many total database games, meet the specified
1864 // date or mean rating range criteria.
1865 // Usage:
1866 // sc_filter freq baseId filterName date <startdate> [<endDate>]
1867 // or sc_filter freq baseId filterName elo <lowerMeanElo> [<upperMeanElo>]
1868 //Klimmek: or sc_filter freq baseId filterName moves <lowerhalfMove> <higherhalfMove>
1869 // add mode to count games with specified movenumber
1870 // where the final parameter defaults to the maximum allowed
1871 // date or Elo rating.
1872 // Note for rating queries: only the rating values in the game
1873 // are used; estimates from other games or the spelling file
1874 // will be ignored. Also, if one player has an Elo rating but
1875 // the other does not, the other rating will be assumed to be
1876 // same as the nonzero rating, up to a maximum of 2200.
1877 int
1878 sc_filter_freq (scidBaseT* dbase, const HFilter& filter, Tcl_Interp * ti, int argc, const char ** argv)
1879 {
1880  const char * usage =
1881  "Usage: sc_filter freq baseId filterName date|elo|move <startDate|minElo|lowerhalfMove> [<endDate|maxElo|higherhalfMove>] [GuessElo]";
1882 
1883  bool eloMode = false;
1884  bool moveMode = false;
1885  bool guessElo = true;
1886  const char * options[] = { "date", "elo", "move", NULL };
1887  enum { OPT_DATE, OPT_ELO, OPT_MOVE };
1888  int option = -1;
1889 
1890  if (argc >= 6 && argc <= 8) {
1891  option = strUniqueMatch (argv[4], options);
1892  }
1893  switch (option) {
1894  case OPT_DATE: eloMode = false; break;
1895  case OPT_ELO: eloMode = true; break;
1896  case OPT_MOVE: moveMode = true; break;
1897  default: return errorResult (ti, usage);
1898  }
1899 
1900  dateT startDate = date_EncodeFromString (argv[5]);
1901  dateT endDate = DATE_MAKE (YEAR_MAX, 12, 31);
1902  uint minElo = strGetUnsigned (argv[5]);
1903  uint maxElo = MAX_ELO;
1904  uint maxMove, minMove;
1905 
1906  minMove = minElo;
1907  maxMove = minMove + 1;
1908  if (argc >= 7) {
1909  endDate = date_EncodeFromString (argv[6]);
1910  maxMove = maxElo = strGetUnsigned (argv[6]);
1911  }
1912  if (argc == 8) {
1913  guessElo = strGetUnsigned (argv[7]);
1914  }
1915  //Klimmek: define _NoEloGuess_: Do not guess Elo, else old behavior
1916  if ( guessElo ) {
1917  // Double min/max Elos to avoid halving every mean Elo:
1918  minElo = minElo + minElo;
1919  maxElo = maxElo + maxElo + 1;
1920  }
1921  // Calculate frequencies in the specified date or rating range:
1922  uint filteredCount = 0;
1923  uint allCount = 0;
1924 
1925  if (eloMode) {
1926  for (uint gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
1927  const IndexEntry* ie = dbase->getIndexEntry(gnum);
1928  if ( guessElo ) {
1929  uint wElo = ie->GetWhiteElo();
1930  uint bElo = ie->GetBlackElo();
1931  uint bothElo = wElo + bElo;
1932  if (wElo == 0 && bElo != 0) {
1933  bothElo += (bElo > 2200 ? 2200 : bElo);
1934  } else if (bElo == 0 && wElo != 0) {
1935  bothElo += (wElo > 2200 ? 2200 : wElo);
1936  }
1937  if (bothElo >= minElo && bothElo <= maxElo) {
1938  allCount++;
1939  if (filter.get(gnum) != 0) {
1940  filteredCount++;
1941  }
1942  }
1943  } else {
1944  //Klimmek: if lowest Elo in the Range: count them
1945  uint mini = ie->GetWhiteElo();
1946  if ( mini > ie->GetBlackElo() ) mini = ie->GetBlackElo();
1947  if (mini < minElo || mini >= maxElo)
1948  continue;
1949  allCount++;
1950  if (filter.get(gnum) != 0) {
1951  filteredCount++;
1952  }
1953  }
1954  }
1955  } else if ( moveMode ) {
1956  //Klimmek: count games with x Moves minMove=NumberHalfmove and maxMove Numberhalfmove+1
1957  for (uint gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
1958  const IndexEntry* ie = dbase->getIndexEntry(gnum);
1959  uint move = ie->GetNumHalfMoves();
1960  if (move >= minMove && move <= maxMove) {
1961  allCount++;
1962  if (filter.get(gnum) != 0) {
1963  filteredCount++;
1964  }
1965  }
1966  }
1967  }
1968  else { // datemode
1969  for (uint gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
1970  const IndexEntry* ie = dbase->getIndexEntry(gnum);
1971  dateT date = ie->GetDate();
1972  if (date >= startDate && date <= endDate) {
1973  allCount++;
1974  if (filter.get(gnum) != 0) {
1975  filteredCount++;
1976  }
1977  }
1978  }
1979  }
1980  appendUintElement (ti, filteredCount);
1981  appendUintElement (ti, allCount);
1982  return TCL_OK;
1983 }
1984 
1985 //TODO:
1986 //This functions do not works because they do not specify the base, the filter and the sort criteria
1987 //So for the moment we assume base=db, filter=dbFilter and sort=N+
1988 
1989 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1990 // sc_filter_first:
1991 // Returns the game number of the first game in the filter,
1992 // or 0 if the filter is empty.
1993 int
1994 sc_filter_first(ClientData, Tcl_Interp * ti, int, const char**)
1995 {
1996  for (uint gnum=0; gnum < db->numGames(); gnum++) {
1997  if (db->dbFilter->Get(gnum) == 0) continue;
1998  return setUintResult (ti, gnum +1);
1999  }
2000  return setUintResult (ti, 0);
2001 }
2002 
2003 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2004 // sc_filter_last:
2005 // Returns the game number of the last game in the filter,
2006 // or 0 if the filter is empty.
2007 int
2008 sc_filter_last(ClientData, Tcl_Interp * ti, int, const char**)
2009 {
2010  long gnum = db->numGames();
2011  for (gnum--; gnum >= 0; gnum--) {
2012  if (db->dbFilter->Get(gnum) == 0) continue;
2013  return setUintResult (ti, gnum +1);
2014  }
2015  return setUintResult (ti, 0);
2016 }
2017 
2018 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2019 // sc_filter_next:
2020 // Returns number of next game in the filter.
2021 int
2022 sc_filter_next(ClientData, Tcl_Interp * ti, int, const char**)
2023 {
2024  if (db->inUse) {
2025  uint nextNumber = db->gameNumber + 1;
2026  while (nextNumber < db->numGames()) {
2027  if (db->dbFilter->Get(nextNumber) > 0) {
2028  return setUintResult (ti, nextNumber + 1);
2029  }
2030  nextNumber++;
2031  }
2032  }
2033  return setUintResult (ti, 0);
2034 }
2035 
2036 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2037 // sc_filter_prev:
2038 // Returns number of previous game in the filter.
2039 int
2040 sc_filter_prev(ClientData, Tcl_Interp * ti, int, const char**)
2041 {
2042  if (db->inUse) {
2043  int prevNumber = db->gameNumber - 1;
2044  while (prevNumber >= 0) {
2045  if (db->dbFilter->Get(prevNumber) > 0) {
2046  return setIntResult (ti, prevNumber + 1);
2047  }
2048  prevNumber--;
2049  }
2050  }
2051  return setUintResult (ti, 0);
2052 }
2053 
2054 //END TODO
2055 
2056 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2057 // sc_filter_stats:
2058 // Returns statistics about the filter.
2059 int
2060 sc_filter_stats (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2061 {
2062  enum {STATS_ALL, STATS_ELO, STATS_YEAR};
2063 
2064  if (argc < 2 || argc > 5) {
2065  return errorResult (ti, "Usage: sc_filter stats [all | elo <xx> | year <xx>]");
2066  }
2067  int statType = STATS_ALL;
2068  uint min = 0;
2069  uint max = 0;
2070  uint inv_max = 0;
2071  if (argc > 2) {
2072  if (argv[2][0] == 'e') { statType = STATS_ELO; }
2073  if (argv[2][0] == 'y') { statType = STATS_YEAR; }
2074  }
2075  if (statType == STATS_ELO || statType == STATS_YEAR) {
2076  if (argc < 4) {
2077  return errorResult (ti, "Incorrect number of parameters.");
2078  }
2079  min = strGetUnsigned (argv[3]);
2080  max = strGetUnsigned (argv[4]);
2081  //Klimmek: +10000 workaround to trigger max elo in filter function
2082  if ( max > 10000 ) {
2083  max -= 10000;
2084  inv_max = 1;
2085  }
2086  }
2087  uint results[4] = {0, 0, 0, 0};
2088  uint total = 0;
2089  const HFilter filter = db->getFilter("dbfilter");
2090  for (uint i=0, n = db->numGames(); i < n; i++) {
2091  const IndexEntry* ie = db->getIndexEntry(i);
2092  if (filter.get(i)) {
2093  if ( max == 0 ) { //Old Statistic :
2094  if (statType == STATS_ELO &&
2095  (ie->GetWhiteElo() < min || ie->GetBlackElo() < min)) {
2096  continue;
2097  }
2098  if (statType == STATS_YEAR
2099  && date_GetYear(ie->GetDate()) < min) {
2100  continue;
2101  }
2102  } else { //Klimmek: new statistic: evaluation in intervals
2103  //count all games where player with highest Elo is in the specific range
2104  if (statType == STATS_ELO ) {
2105  if (inv_max) {
2106  uint maxi = ie->GetWhiteElo();
2107  if ( maxi < ie->GetBlackElo() ) maxi = ie->GetBlackElo();
2108  if (maxi < min || maxi >= max)
2109  continue;
2110  }
2111  else {
2112  //count all games where player with lowest Elo is in the specific range
2113  uint mini = ie->GetWhiteElo();
2114  if ( mini > ie->GetBlackElo() ) mini = ie->GetBlackElo();
2115  if (mini < min || mini >= max)
2116  continue;
2117  }
2118  }
2119  if (statType == STATS_YEAR
2120  && ( date_GetYear(ie->GetDate()) < min || date_GetYear(ie->GetDate()) >= max) ) {
2121  continue;
2122  }
2123  }
2124  results[ie->GetResult()]++;
2125  total++;
2126  }
2127  }
2128  char temp[80];
2129  uint percentScore = results[RESULT_White] * 2 + results[RESULT_Draw] +
2130  results[RESULT_None];
2131  percentScore = total ? percentScore * 500 / total : 0;
2132  sprintf (temp, "%7u %7u %7u %7u %3u%c%u%%",
2133  total,
2134  results[RESULT_White],
2135  results[RESULT_Draw],
2136  results[RESULT_Black],
2137  percentScore / 10, decimalPointChar, percentScore % 10);
2138  Tcl_AppendResult (ti, temp, NULL);
2139  return TCL_OK;
2140 }
2141 
2142 //////////////////////////////////////////////////////////////////////
2143 /// GAME functions
2144 
2145 int
2146 sc_game (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
2147 {
2148  static const char * options [] = {
2149  "altered", "setaltered", "crosstable", "eco",
2150  "find", "firstMoves", "import",
2151  "info", "load", "merge", "moves",
2152  "new", "novelty", "number", "pgn",
2153  "pop", "push", "SANtoUCI", "save",
2154  "scores", "startBoard", "strip", "summary",
2155  "tags", "truncate", "truncatefree",
2156  "undo", "undoAll", "undoPoint", "redo", NULL
2157  };
2158  enum {
2159  GAME_ALTERED, GAME_SET_ALTERED, GAME_CROSSTABLE, GAME_ECO,
2160  GAME_FIND, GAME_FIRSTMOVES, GAME_IMPORT,
2161  GAME_INFO, GAME_LOAD, GAME_MERGE, GAME_MOVES,
2162  GAME_NEW, GAME_NOVELTY, GAME_NUMBER, GAME_PGN,
2163  GAME_POP, GAME_PUSH, GAME_SANTOUCI, GAME_SAVE,
2164  GAME_SCORES, GAME_STARTBOARD, GAME_STRIP, GAME_SUMMARY,
2165  GAME_TAGS, GAME_TRUNCATE, GAME_TRUNCATEANDFREE,
2166  GAME_UNDO, GAME_UNDO_ALL, GAME_UNDO_POINT, GAME_REDO
2167  };
2168  int index = -1;
2169  char old_language = 0;
2170 
2171  if (argc > 1) { index = strUniqueMatch (argv[1], options);}
2172 
2173  switch (index) {
2174  case GAME_ALTERED:
2175  return UI_Result(ti, OK, db->gameAltered);
2176 
2177  case GAME_SET_ALTERED:
2178  if (argc != 3 ) {
2179  return errorResult (ti, "Usage: sc_game setaltered [0|1]");
2180  }
2181  db->gameAltered = strGetUnsigned (argv[2]);
2182  break;
2183  case GAME_CROSSTABLE:
2184  return sc_game_crosstable (cd, ti, argc, argv);
2185 
2186  case GAME_ECO: // "sc_game eco" is equivalent to "sc_eco game"
2187  return sc_eco_game (cd, ti, argc, argv);
2188 
2189  case GAME_FIND:
2190  return sc_game_find (cd, ti, argc, argv);
2191 
2192  case GAME_FIRSTMOVES:
2193  return sc_game_firstMoves (cd, ti, argc, argv);
2194 
2195  case GAME_IMPORT:
2196  return sc_game_import (cd, ti, argc, argv);
2197 
2198  case GAME_INFO:
2199  return sc_game_info (cd, ti, argc, argv);
2200 
2201  case GAME_LOAD:
2202  return sc_game_load (cd, ti, argc, argv);
2203 
2204  case GAME_MERGE:
2205  return sc_game_merge (cd, ti, argc, argv);
2206 
2207  case GAME_MOVES:
2208  return sc_game_moves (cd, ti, argc, argv);
2209 
2210  case GAME_NEW:
2211  db->gameAlterations.clear();
2212  return sc_game_new (cd, ti, argc, argv);
2213 
2214  case GAME_NOVELTY:
2215  return sc_game_novelty (cd, ti, argc, argv);
2216 
2217  case GAME_NUMBER:
2218  return setUintResult (ti, db->gameNumber + 1);
2219 
2220  case GAME_PGN:
2221  return sc_game_pgn (cd, ti, argc, argv);
2222 
2223  case GAME_POP:
2224  return sc_game_pop (cd, ti, argc, argv);
2225 
2226  case GAME_PUSH:
2227  return sc_game_push (cd, ti, argc, argv);
2228 
2229  case GAME_SANTOUCI:
2230  if (argc == 3) {
2231  Game* g = db->game->clone();
2232  PgnParser parser;
2233  parser.Reset (argv[2]);
2234  parser.SetEndOfInputWarnings (false);
2235  parser.SetResultWarnings (false);
2236  char buf [1000];
2237  errorT err = parser.ParseMoves (g, buf, 1000);
2238  if (parser.ErrorCount() > 0) err = ERROR_InvalidMove;
2239  if (err != OK) {
2240  delete g;
2241  return errorResult(ti, err);
2242  }
2243  buf[0] = 0;
2244  g->GetPrevMoveUCI(buf);
2245  delete g;
2246  return UI_Result(ti, OK, std::string(buf));
2247  }
2248  return errorResult(ti, "usage sc_game SANtoUCI move");
2249 
2250  case GAME_SAVE:
2251  return sc_game_save (cd, ti, argc, argv);
2252 
2253  case GAME_SCORES:
2254  return sc_game_scores (cd, ti, argc, argv);
2255 
2256  case GAME_STARTBOARD:
2257  return sc_game_startBoard (cd, ti, argc, argv);
2258 
2259  case GAME_STRIP:
2260  return sc_game_strip (cd, ti, argc, argv);
2261 
2262  case GAME_SUMMARY:
2263  return sc_game_summary (cd, ti, argc, argv);
2264 
2265  case GAME_TAGS:
2266  return sc_game_tags (cd, ti, argc, argv);
2267 
2268  case GAME_TRUNCATE:
2269  old_language = language;
2270  language = 0;
2271  if (argc > 2 && strIsPrefix (argv[2], "-start")) {
2272  // "sc_game truncate -start" truncates the moves up to the
2273  // current position:
2274  db->game->TruncateStart();
2275  } else {
2276  // Truncate from the current position to the end of the game
2277  db->game->Truncate();
2278  }
2279  db->gameAltered = true;
2280  language = old_language;
2281  break;
2282  case GAME_TRUNCATEANDFREE:
2283  old_language = language;
2284  language = 0;
2285  // Truncate from the current position to the end of the game
2286  // and free moves memory (to FreeList
2287  db->game->TruncateAndFree();
2288  language = old_language;
2289  break;
2290  case GAME_UNDO:
2291  if (argc > 2 && strCompare("size", argv[2]) == 0) {
2292  return UI_Result(ti, OK, (uint) db->gameAlterations.undoSize());
2293  }
2294  db->gameAlterations.undo(db->game);
2295  break;
2296 
2297  case GAME_UNDO_ALL:
2298  if (! db->gameAlterations.undoAll(db->game)) {
2299  db->gameAltered = false;
2300  db->gameAlterations.clear();
2301  if (db->gameNumber < 0) {
2302  db->game->Clear();
2303  return UI_Result(ti, OK);
2304  }
2305  const IndexEntry* ie = db->getIndexEntry(db->gameNumber);
2306  errorT err = db->getGame(ie, db->bbuf);
2307  if (err != OK) return UI_Result(ti, err);
2308  err = db->game->Decode (db->bbuf, GAME_DECODE_ALL);
2309  if (err != OK) return UI_Result(ti, err);
2310  db->game->LoadStandardTags (ie, db->getNameBase());
2311  db->game->MoveToPly(0);
2312  return UI_Result(ti, OK);
2313  }
2314  break;
2315 
2316  case GAME_UNDO_POINT:
2317  db->gameAlterations.store(db->game);
2318  break;
2319 
2320  case GAME_REDO:
2321  if (argc > 2 && strCompare("size", argv[2]) == 0) {
2322  return UI_Result(ti, OK, (uint) db->gameAlterations.redoSize());
2323  }
2324  db->gameAlterations.redo(db->game);
2325  break;
2326 
2327  default:
2328  return InvalidCommand (ti, "sc_game", options);
2329  }
2330 
2331  return TCL_OK;
2332 }
2333 
2334 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2335 // isCrosstableGame:
2336 // Returns true if the game with the specified index entry
2337 // is considered a crosstable game. It must have the specified
2338 // Event and Site, and a Date within the specified range or
2339 // have the specified non-zero EventDate.
2340 static inline bool
2341 isCrosstableGame (const IndexEntry* ie, idNumberT siteID, idNumberT eventID,
2342  dateT eventDate)
2343 {
2344  if (ie->GetSite() != siteID || ie->GetEvent() != eventID) {
2345  return false;
2346  }
2347  dateT EventDate = ie->GetEventDate();
2348  if (eventDate != 0 && EventDate != 0 && EventDate != eventDate) {
2349  return false;
2350  }
2351  return true;
2352 }
2353 
2354 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2355 // sc_game_crosstable:
2356 // Returns the crosstable for the current game.
2357 int
2358 sc_game_crosstable (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2359 {
2360 #ifndef WINCE
2361  static const char * options [] = {
2362  "plain", "html", "hypertext", "latex", "filter", "count", NULL
2363  };
2364  enum {
2365  OPT_PLAIN, OPT_HTML, OPT_HYPERTEXT, OPT_LATEX, OPT_FILTER, OPT_COUNT
2366  };
2367  int option = -1;
2368 
2369  const char * usageMsg =
2370  "Usage: sc_game crosstable plain|html|hypertext|filter|count [name|rating|score|country] [allplay|swiss] [(+|-)(colors|countries|tallies|ratings|titles|groups|breaks|numcolumns)]";
2371 
2372  static const char * extraOptions [] = {
2373  "allplay", "knockout", "swiss", "auto",
2374  "name", "rating", "score", "country",
2375  "-ages", "+ages", // Show player ages
2376  "-breaks", "+breaks", // Show tiebreak scores
2377  "-colors", "+colors", // Show game colors in Swiss table
2378  "-countries", "+countries", // Show current countries
2379  "-tallies", "+tallies",
2380  "-ratings", "+ratings", // Show Elo ratings
2381  "-titles", "+titles", // Show FIDE titles
2382  "-groups", "+groups", // Separate players into score groups
2383  "-deleted", "+deleted", // Include deleted games in table
2384  "-numcolumns", "+numcolumns", // All-play-all numbered columns
2385  "-gameNumber",
2386  "-threewin", "+threewin", // Give 3 points for win, 1 for draw
2387  NULL
2388  };
2389  enum {
2390  EOPT_ALLPLAY, EOPT_KNOCKOUT, EOPT_SWISS, EOPT_AUTO,
2391  EOPT_SORT_NAME, EOPT_SORT_RATING, EOPT_SORT_SCORE, EOPT_SORT_COUNTRY,
2392  EOPT_AGES_OFF, EOPT_AGES_ON,
2393  EOPT_BREAKS_OFF, EOPT_BREAKS_ON,
2394  EOPT_COLORS_OFF, EOPT_COLORS_ON,
2395  EOPT_COUNTRIES_OFF, EOPT_COUNTRIES_ON,
2396  EOPT_TALLIES_OFF, EOPT_TALLIES_ON,
2397  EOPT_RATINGS_OFF, EOPT_RATINGS_ON,
2398  EOPT_TITLES_OFF, EOPT_TITLES_ON,
2399  EOPT_GROUPS_OFF, EOPT_GROUPS_ON,
2400  EOPT_DELETED_OFF, EOPT_DELETED_ON,
2401  EOPT_NUMCOLUMNS_OFF, EOPT_NUMCOLUMNS_ON,
2402  EOPT_GNUMBER,
2403  EOPT_THREEWIN_OFF, EOPT_THREEWIN_ON
2404  };
2405 
2406  int sort = EOPT_SORT_SCORE;
2408  bool showAges = true;
2409  bool showColors = true;
2410  bool showCountries = true;
2411  bool showTallies = true;
2412  bool showRatings = true;
2413  bool showTitles = true;
2414  bool showBreaks = false;
2415  bool scoreGroups = false;
2416  bool useDeletedGames = false;
2417  bool numColumns = false; // Numbers for columns in all-play-all table
2418  uint numTableGames = 0;
2419  uint gameNumber = 0;
2420  bool threewin = false;
2421 
2422  if (argc >= 3) { option = strUniqueMatch (argv[2], options); }
2423  if (option < 0) { return errorResult (ti, usageMsg); }
2424 
2425  for (int arg=3; arg < argc; arg++) {
2426  int extraOption = strUniqueMatch (argv[arg], extraOptions);
2427  switch (extraOption) {
2428  case EOPT_ALLPLAY: mode = CROSSTABLE_AllPlayAll; break;
2429  case EOPT_KNOCKOUT: mode = CROSSTABLE_Knockout; break;
2430  case EOPT_SWISS: mode = CROSSTABLE_Swiss; break;
2431  case EOPT_AUTO: mode = CROSSTABLE_Auto; break;
2432  case EOPT_SORT_NAME: sort = EOPT_SORT_NAME; break;
2433  case EOPT_SORT_RATING: sort = EOPT_SORT_RATING; break;
2434  case EOPT_SORT_SCORE: sort = EOPT_SORT_SCORE; break;
2435  case EOPT_SORT_COUNTRY: sort = EOPT_SORT_COUNTRY; break;
2436  case EOPT_AGES_OFF: showAges = false; break;
2437  case EOPT_AGES_ON: showAges = true; break;
2438  case EOPT_BREAKS_OFF: showBreaks = false; break;
2439  case EOPT_BREAKS_ON: showBreaks = true; break;
2440  case EOPT_COLORS_OFF: showColors = false; break;
2441  case EOPT_COLORS_ON: showColors = true; break;
2442  case EOPT_COUNTRIES_OFF: showCountries = false; break;
2443  case EOPT_COUNTRIES_ON: showCountries = true; break;
2444  case EOPT_TALLIES_OFF: showTallies = false; break;
2445  case EOPT_TALLIES_ON: showTallies = true; break;
2446  case EOPT_RATINGS_OFF: showRatings = false; break;
2447  case EOPT_RATINGS_ON: showRatings = true; break;
2448  case EOPT_TITLES_OFF: showTitles = false; break;
2449  case EOPT_TITLES_ON: showTitles = true; break;
2450  case EOPT_GROUPS_OFF: scoreGroups = false; break;
2451  case EOPT_GROUPS_ON: scoreGroups = true; break;
2452  case EOPT_DELETED_OFF: useDeletedGames = false; break;
2453  case EOPT_DELETED_ON: useDeletedGames = true; break;
2454  case EOPT_NUMCOLUMNS_OFF: numColumns = false; break;
2455  case EOPT_NUMCOLUMNS_ON: numColumns = true; break;
2456  case EOPT_GNUMBER:
2457  // Game number to print the crosstable for is
2458  // given in the next argument:
2459  if (arg+1 >= argc) { return errorResult (ti, usageMsg); }
2460  gameNumber = strGetUnsigned (argv[arg+1]);
2461  arg++;
2462  break;
2463  case EOPT_THREEWIN_OFF: threewin = false ; break;
2464  case EOPT_THREEWIN_ON: threewin = true ; break;
2465  default: return errorResult (ti, usageMsg);
2466  }
2467  }
2468  if (!db->inUse) { return TCL_OK; }
2469 
2470  const char * newlineStr = "";
2471  switch (option) {
2472  case OPT_PLAIN: newlineStr = "\n"; break;
2473  case OPT_HTML: newlineStr = "<br>\n"; break;
2474  case OPT_HYPERTEXT: newlineStr = "<br>"; break;
2475  case OPT_LATEX: newlineStr = "\\\\\n"; break;
2476  }
2477 
2478  // Load crosstable game if necessary:
2479  Game * g = db->game;
2480  if (gameNumber > 0) {
2481  g = scratchGame;
2482  g->Clear();
2483  if (gameNumber > db->numGames()) {
2484  return setResult (ti, "Invalid game number");
2485  }
2486  const IndexEntry* ie = db->getIndexEntry(gameNumber - 1);
2487  if (ie->GetLength() == 0) {
2488  return errorResult (ti, "Error: empty game file record.");
2489  }
2490  if (db->getGame(ie, db->bbuf) != OK) {
2491  return errorResult (ti, "Error reading game file.");
2492  }
2493  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
2494  return errorResult (ti, "Error decoding game.");
2495  }
2496  g->LoadStandardTags (ie, db->getNameBase());
2497  }
2498 
2499  idNumberT eventId = 0, siteId = 0;
2500  if (db->getNameBase()->FindExactName (NAME_EVENT, g->GetEventStr(), &eventId) != OK) {
2501  return TCL_OK;
2502  }
2503  if (db->getNameBase()->FindExactName (NAME_SITE, g->GetSiteStr(), &siteId) != OK) {
2504  return TCL_OK;
2505  }
2506 
2507  dateT eventDate = g->GetEventDate();
2508  dateT firstSeenDate = g->GetDate();
2509  dateT lastSeenDate = g->GetDate();
2510 
2511  Crosstable * ctable = new Crosstable;
2512  if (sort == EOPT_SORT_NAME) { ctable->SortByName(); }
2513  if (sort == EOPT_SORT_RATING) { ctable->SortByElo(); }
2514  if (sort == EOPT_SORT_COUNTRY) { ctable->SortByCountry(); }
2515 
2516  ctable->SetThreeWin(threewin);
2517  ctable->SetSwissColors (showColors);
2518  ctable->SetAges (showAges);
2519  ctable->SetCountries (showCountries);
2520  ctable->SetTallies (showTallies);
2521  ctable->SetElos (showRatings);
2522  ctable->SetTitles (showTitles);
2523  ctable->SetTiebreaks (showBreaks);
2524  ctable->SetSeparateScoreGroups (scoreGroups);
2525  ctable->SetDecimalPointChar (decimalPointChar);
2526  ctable->SetNumberedColumns (numColumns);
2527 
2528  switch (option) {
2529  case OPT_PLAIN: ctable->SetPlainOutput(); break;
2530  case OPT_HTML: ctable->SetHtmlOutput(); break;
2531  case OPT_HYPERTEXT: ctable->SetHypertextOutput(); break;
2532  case OPT_LATEX: ctable->SetLaTeXOutput(); break;
2533  }
2534 
2535  // Find all games that should be listed in the crosstable:
2536  const SpellChecker* spell = spellChk;
2537  bool tableFullMessage = false;
2538  for (uint i=0, n = db->numGames(); i < n; i++) {
2539  const IndexEntry* ie = db->getIndexEntry(i);
2540  if (ie->GetDeleteFlag() && !useDeletedGames) { continue; }
2541  if (! isCrosstableGame (ie, siteId, eventId, eventDate)) {
2542  continue;
2543  }
2544  idNumberT whiteId = ie->GetWhite();
2545  const char * whiteName = db->getNameBase()->GetName (NAME_PLAYER, whiteId);
2546  idNumberT blackId = ie->GetBlack();
2547  const char * blackName = db->getNameBase()->GetName (NAME_PLAYER, blackId);
2548 
2549  // Ensure we have two different players:
2550  if (whiteId == blackId) { continue; }
2551 
2552  // If option is OPT_FILTER, adjust the filter and continue &&&
2553  if (option == OPT_FILTER) {
2554  db->dbFilter->Set (i, 1);
2555  continue;
2556  }
2557 
2558  // If option is OPT_COUNT, increment game count and continue:
2559  if (option == OPT_COUNT) {
2560  numTableGames++;
2561  continue;
2562  }
2563 
2564  // Add the two players to the crosstable:
2565  if (ctable->AddPlayer (whiteId, whiteName, ie->GetWhiteElo(), spell) != OK ||
2566  ctable->AddPlayer (blackId, blackName, ie->GetBlackElo(), spell) != OK)
2567  {
2568  if (! tableFullMessage) {
2569  tableFullMessage = true;
2570  Tcl_AppendResult (ti, "Warning: Player limit reached; table is incomplete\n\n", NULL);
2571  }
2572  continue;
2573  }
2574 
2576  dateT date = ie->GetDate();
2577  resultT result = ie->GetResult();
2578  ctable->AddResult (i+1, whiteId, blackId, result, round, date);
2579  if (date < firstSeenDate) { firstSeenDate = date; }
2580  if (date > lastSeenDate) { lastSeenDate = date; }
2581  }
2582 
2583  if (option == OPT_COUNT) {
2584  // Just return a count of the number of tournament games:
2585  delete ctable;
2586  return setUintResult (ti, numTableGames);
2587  }
2588  if (option == OPT_FILTER) {
2589  delete ctable;
2590  return TCL_OK;
2591  }
2592  if (ctable->NumPlayers() < 2) {
2593  delete ctable;
2594  return setResult (ti, "No crosstable for this game.");
2595  }
2596 
2597  if (option == OPT_LATEX) {
2598  Tcl_AppendResult (ti, "\\documentclass[10pt,a4paper]{article}\n\n",
2599  "\\usepackage{a4wide}\n\n",
2600  "\\begin{document}\n\n",
2601  "\\setlength{\\parindent}{0cm}\n",
2602  "\\setlength{\\parskip}{0.5ex}\n",
2603  "\\small\n", NULL);
2604  }
2605 
2606  if (mode == CROSSTABLE_Auto) { mode = ctable->BestMode(); }
2607 
2608  // Limit all-play-all tables to 300 players:
2609  uint apaLimit = 300;
2610  if (mode == CROSSTABLE_AllPlayAll &&
2611  ctable->NumPlayers() > apaLimit &&
2612  !tableFullMessage) {
2613  Tcl_AppendResult (ti, "Warning: Too many players for all-play-all; try displaying as a swiss tournament.\n\n", NULL);
2614  }
2615 
2616  char stemp[1000];
2617  sprintf (stemp, "%s%s%s, ", g->GetEventStr(), newlineStr, g->GetSiteStr());
2618  Tcl_AppendResult (ti, stemp, NULL);
2619  date_DecodeToString (firstSeenDate, stemp);
2620  strTrimDate (stemp);
2621  Tcl_AppendResult (ti, stemp, NULL);
2622  if (lastSeenDate != firstSeenDate) {
2623  date_DecodeToString (lastSeenDate, stemp);
2624  strTrimDate (stemp);
2625  Tcl_AppendResult (ti, " - ", stemp, NULL);
2626  }
2627  Tcl_AppendResult (ti, newlineStr, NULL);
2628 
2629  eloT avgElo = ctable->AvgRating();
2630  if (avgElo > 0 && showRatings) {
2631  Tcl_AppendResult (ti, translate (ti, "AverageRating", "Average Rating"),
2632  ": ", NULL);
2633  appendUintResult (ti, avgElo);
2634  uint category = ctable->FideCategory (avgElo);
2635  if (category > 0 && mode == CROSSTABLE_AllPlayAll) {
2636  sprintf (stemp, " (%s %u)",
2637  translate (ti, "Category", "Category"), category);
2638  Tcl_AppendResult (ti, stemp, NULL);
2639  }
2640  Tcl_AppendResult (ti, newlineStr, NULL);
2641  }
2642 
2643  DString * dstr = new DString;
2644  if (mode != CROSSTABLE_AllPlayAll) { apaLimit = 0; }
2645  ctable->PrintTable (dstr, mode, apaLimit, db->gameNumber+1);
2646 
2647  Tcl_AppendResult (ti, dstr->Data(), NULL);
2648  if (option == OPT_LATEX) {
2649  Tcl_AppendResult (ti, "\n\\end{document}\n", NULL);
2650  }
2651  delete ctable;
2652  delete dstr;
2653 #endif
2654  return TCL_OK;
2655 }
2656 
2657 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2658 // sc_game_find:
2659 // Returns the game number of the game in that current database
2660 // that best matches the specified number, player names, site,
2661 // round, year and result.
2662 // This command is used primarily to locate a bookmarked game in
2663 // a database where the number may be inaccurate due to database
2664 // sorting or compaction.
2665 int
2666 sc_game_find (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2667 {
2668  if (argc != 9) {
2669  return errorResult (ti, "sc_game_find: Incorrect parameters");
2670  }
2671 
2672  uint gnum = strGetUnsigned (argv[2]);
2673  if (gnum == 0) { return setUintResult (ti, 0); }
2674  gnum--;
2675  const char * whiteStr = argv[3];
2676  const char * blackStr = argv[4];
2677  const char * siteStr = argv[5];
2678  const char * roundStr = argv[6];
2679  uint year = strGetUnsigned(argv[7]);
2680  resultT result = strGetResult (argv[8]);
2681 
2682  idNumberT white, black, site, round;
2683  white = black = site = round = 0;
2684  db->getNameBase()->FindExactName (NAME_PLAYER, whiteStr, &white);
2685  db->getNameBase()->FindExactName (NAME_PLAYER, blackStr, &black);
2686  db->getNameBase()->FindExactName (NAME_SITE, siteStr, &site);
2687  db->getNameBase()->FindExactName (NAME_ROUND, roundStr, &round);
2688 
2689  // We give each game a "score" which is 1 for each matching field.
2690  // So the best possible score is 6.
2691 
2692  // First, check if the specified game number matches all fields:
2693  if (db->numGames() > gnum) {
2694  uint score = 0;
2695  const IndexEntry* ie = db->getIndexEntry(gnum);
2696  if (ie->GetWhite() == white) { score++; }
2697  if (ie->GetBlack() == black) { score++; }
2698  if (ie->GetSite() == site) { score++; }
2699  if (ie->GetRound() == round) { score++; }
2700  if (ie->GetYear() == year) { score++; }
2701  if (ie->GetResult() == result) { score++; }
2702  if (score == 6) { return setUintResult (ti, gnum+1); }
2703  }
2704 
2705  // Now look for the best matching game:
2706  uint bestNum = 0;
2707  uint bestScore = 0;
2708 
2709  for (uint i=0, n = db->numGames(); i < n; i++) {
2710  uint score = 0;
2711  const IndexEntry* ie = db->getIndexEntry(i);
2712  if (ie->GetWhite() == white) { score++; }
2713  if (ie->GetBlack() == black) { score++; }
2714  if (ie->GetSite() == site) { score++; }
2715  if (ie->GetRound() == round) { score++; }
2716  if (ie->GetYear() == year) { score++; }
2717  if (ie->GetResult() == result) { score++; }
2718  // Update if the best score, favouring the specified game number
2719  // in the case of a tie:
2720  if (score > bestScore || (score == bestScore && gnum == i)) {
2721  bestScore = score;
2722  bestNum = i;
2723  }
2724  // Stop now if the best possible match is found:
2725  if (score == 6) { break; }
2726  }
2727  return setUintResult (ti, bestNum + 1);
2728 }
2729 
2730 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2731 // sc_game_firstMoves:
2732 // get the first few moves of the specified game as a text line.
2733 // E.g., "sc_game firstMoves 4" might return "1.e4 e5 2.Nf3 Nf6"
2734 int
2735 sc_game_firstMoves (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2736 {
2737  if (argc != 3) {
2738  return errorResult (ti, "Usage: sc_game firstMoves <numMoves>");
2739  }
2740  if (!db->inUse) {
2741  return errorResult (ti, errMsgNotOpen(ti));
2742  }
2743 
2744  int plyCount = strGetInteger (argv[2]);
2745  // Check plyCount is a reasonable value, or set it to current plycount.
2746  if (plyCount < 0) plyCount = db->game->GetCurrentPly();
2747  if (plyCount == 0) plyCount = 1;
2748 
2749  DString dstr;
2750  db->game->GetPartialMoveList (&dstr, plyCount);
2751  return UI_Result(ti, OK, std::string(dstr.Data()));
2752 }
2753 
2754 int
2755 sc_game_import (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2756 {
2757  if (argc != 3) {
2758  return errorResult (ti, "Usage: sc_game import <pgn-text>");
2759  }
2760  PgnParser parser (argv[2]);
2761  errorT err = parser.ParseGame (db->game);
2762  if (err == ERROR_NotFound) {
2763  // No PGN header tags were found, so try just parsing moves:
2764  db->game->Clear();
2765  parser.Reset (argv[2]);
2766  parser.SetEndOfInputWarnings (false);
2767  parser.SetResultWarnings (false);
2768  err = parser.ParseMoves (db->game);
2769  }
2770  db->gameAltered = true;
2771  if (err == OK && parser.ErrorCount() == 0) {
2772  return setResult (ti, "PGN text imported with no errors or warnings.");
2773  }
2774  Tcl_AppendResult (ti, "Errors/warnings importing PGN text:\n\n",
2775  parser.ErrorMessages(), NULL);
2776  if (err == ERROR_NotFound) {
2777  Tcl_AppendResult (ti, "ERROR: No PGN header tag (e.g. ",
2778  "[Result \"1-0\"]) found.", NULL);
2779  }
2780  return (err == OK ? TCL_OK : TCL_ERROR);
2781 }
2782 
2783 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2784 // probe_tablebase:
2785 // Probes the tablebases for the current position, and returns
2786 // the score, a descriptive score with optimal moves, or just a
2787 // (random) optimal move.
2788 bool
2789 probe_tablebase (Tcl_Interp * ti, int mode, DString * dstr)
2790 {
2791  int score = 0;
2792  bool showResult = false;
2793  bool showSummary = false;
2794  bool fullReport = false;
2795  bool optimalMoves = false;
2796  colorT toMove = db->game->GetCurrentPos()->GetToMove();
2797 
2798  switch (mode) {
2799  case PROBE_RESULT:
2800  showResult = true;
2801  break;
2802  case PROBE_SUMMARY:
2803  showResult = true;
2804  showSummary = true;
2805  break;
2806  case PROBE_REPORT:
2807  fullReport = true;
2808  break;
2809  case PROBE_OPTIMAL:
2810  optimalMoves = true;
2811  break;
2812  default:
2813  return false;
2814  }
2815 
2816  if (scid_TB_Probe (db->game->GetCurrentPos(), &score) != OK) {
2817  if (! fullReport) { return false; }
2818  }
2819 
2820  Position * gamePos = NULL;
2821  bool moveFound [MAX_LEGAL_MOVES] = {0};
2822  int moveScore [MAX_LEGAL_MOVES] = {0};
2823  bool movePrinted [MAX_LEGAL_MOVES] = {0};
2824  uint winCount = 0;
2825  uint drawCount = 0;
2826  uint lossCount = 0;
2827  uint unknownCount = 0;
2828 
2829  MoveList moveList;
2830  sanListT sanList;
2831  gamePos = db->game->GetCurrentPos();
2832  gamePos->GenerateMoves (&moveList);
2833  gamePos->CalcSANStrings (&sanList, SAN_CHECKTEST);
2834 
2835  if (showSummary || fullReport || optimalMoves) {
2836  Position scratchPos = *gamePos;
2837 
2838  for (uint i=0; i < moveList.Size(); i++) {
2839  simpleMoveT * smPtr = moveList.Get(i);
2840  scratchPos.DoSimpleMove (smPtr);
2841  moveFound[i] = false;
2842  movePrinted[i] = false;
2843  int newScore = 0;
2844  if (scid_TB_Probe (&scratchPos, &newScore) == OK) {
2845  moveFound[i] = true;
2846  moveScore[i] = newScore;
2847  if (newScore < 0) {
2848  winCount++;
2849  } else if (newScore == 0) {
2850  drawCount++;
2851  } else {
2852  lossCount++;
2853  }
2854  } else {
2855  unknownCount++;
2856  }
2857  scratchPos.UndoSimpleMove (smPtr);
2858  }
2859  }
2860 
2861  // Optimal moves mode: return only the optimal moves, nothing else.
2862  if (optimalMoves) {
2863  uint count = 0;
2864  for (uint i=0; i < moveList.Size(); i++) {
2865  if ((score >= 0 && moveScore[i] == -score) ||
2866  (score < 0 && moveScore[i] == -score - 1)) {
2867  if (count > 0) { dstr->Append (" "); }
2868  dstr->Append (sanList.list[i]);
2869  count++;
2870  }
2871  }
2872  return true;
2873  }
2874 
2875  if (fullReport) {
2876  char tempStr [80];
2877  sprintf (tempStr, "+:%u =:%u -:%u ?:%u",
2878  winCount, drawCount, lossCount, unknownCount);
2879  dstr->Append (tempStr);
2880  int prevScore = -9999999; // Lower than any possible TB score
2881  bool first = true;
2882 
2883  while (1) {
2884  bool found = false;
2885  uint index = 0;
2886  int bestScore = 0;
2887  const char * bestMove = "";
2888  for (uint i=0; i < moveList.Size(); i++) {
2889  if (movePrinted[i]) { continue; }
2890  if (! moveFound[i]) { continue; }
2891  int newScore = - moveScore[i];
2892  if (!found ||
2893  (newScore > 0 && bestScore <= 0) ||
2894  (newScore > 0 && newScore < bestScore) ||
2895  (newScore == 0 && bestScore < 0) ||
2896  (newScore < 0 && bestScore < 0 && newScore < bestScore) ||
2897  (newScore == bestScore &&
2898  strCompare (bestMove, sanList.list[i]) > 0) ) {
2899  found = true;
2900  index = i;
2901  bestScore = newScore;
2902  bestMove = sanList.list[i];
2903  }
2904  }
2905  if (!found) { break; }
2906  movePrinted[index] = true;
2907  if (first ||
2908  (bestScore > 0 && prevScore < 0) ||
2909  (bestScore == 0 && prevScore != 0) ||
2910  (bestScore < 0 && prevScore >= 0)) {
2911  dstr->Append ("\n");
2912  first = false;
2913  const char * tag = NULL;
2914  const char * msg = NULL;
2915  if (bestScore > 0) {
2916  tag = "WinningMoves"; msg = "Winning moves";
2917  } else if (bestScore < 0) {
2918  tag = "LosingMoves"; msg = "Losing moves";
2919  } else {
2920  tag = "DrawingMoves"; msg = "Drawing moves";
2921  }
2922  dstr->Append ("\n", translate(ti, tag, msg), ":");
2923  }
2924  if (bestScore != prevScore) {
2925  if (bestScore > 0) {
2926  sprintf (tempStr, " +%3d ", bestScore);
2927  } else if (bestScore == 0) {
2928  strCopy (tempStr, " = ");
2929  } else {
2930  sprintf (tempStr, " -%3d ", -bestScore);
2931  }
2932  dstr->Append ("\n", tempStr);
2933  } else {
2934  dstr->Append (", ");
2935  }
2936  prevScore = bestScore;
2937  dstr->Append (bestMove);
2938  }
2939  if (unknownCount > 0) {
2940  dstr->Append ("\n\n");
2941  dstr->Append (translate (ti, "UnknownMoves", "Unknown-result moves"));
2942  dstr->Append (":\n ? ");
2943  bool firstUnknown = true;
2944  while (1) {
2945  bool found = false;
2946  const char * bestMove = "";
2947  uint index = 0;
2948  for (uint i=0; i < moveList.Size(); i++) {
2949  if (!moveFound[i] && !movePrinted[i]) {
2950  if (!found ||
2951  strCompare (bestMove, sanList.list[i]) > 0) {
2952  found = true;
2953  bestMove = sanList.list[i];
2954  index = i;
2955  }
2956  }
2957  }
2958  if (!found) { break; }
2959  movePrinted[index] = true;
2960  if (!firstUnknown) {
2961  dstr->Append (", ");
2962  }
2963  firstUnknown = false;
2964  dstr->Append (bestMove);
2965  }
2966  }
2967  dstr->Append ("\n");
2968  return true;
2969  }
2970 
2971  if (score == 0) {
2972  // Print drawn tablebase position info:
2973  if (showResult) {
2974  dstr->Append ("= [", translate (ti, "Draw"));
2975  }
2976  if (showSummary) {
2977  uint drawcount = 0;
2978  uint losscount = 0;
2979  const char * drawlist [MAX_LEGAL_MOVES];
2980  const char * losslist [MAX_LEGAL_MOVES];
2981 
2982  for (uint i=0; i < moveList.Size(); i++) {
2983  if (moveFound[i]) {
2984  if (moveScore[i] == 0) {
2985  drawlist[drawcount] = sanList.list[i];
2986  drawcount++;
2987  } else {
2988  losslist[losscount] = sanList.list[i];
2989  losscount++;
2990  }
2991  }
2992  }
2993  if (moveList.Size() == 0) {
2994  dstr->Append (" (", translate (ti, "stalemate"), ")");
2995  } else if (drawcount == moveList.Size()) {
2996  dstr->Append (" ", translate (ti, "withAllMoves"));
2997  } else if (drawcount == 1) {
2998  dstr->Append (" ", translate (ti, "with"));
2999  dstr->Append (" ", drawlist[0]);
3000  } else if (drawcount+1 == moveList.Size() && losscount==1) {
3001  dstr->Append (" ", translate (ti, "withAllButOneMove"));
3002  } else if (drawcount > 0) {
3003  dstr->Append (" ", translate (ti, "with"), " ");
3004  dstr->Append (drawcount);
3005  dstr->Append (" ");
3006  if (drawcount == 1) {
3007  dstr->Append (translate (ti, "move"));
3008  } else {
3009  dstr->Append (translate (ti, "moves"));
3010  }
3011  dstr->Append (": ");
3012  for (uint m=0; m < drawcount; m++) {
3013  if (m < 3) {
3014  if (m > 0) { dstr->Append (", "); }
3015  dstr->Append (drawlist[m]);
3016  }
3017  }
3018  if (drawcount > 3) { dstr->Append (", ..."); }
3019  }
3020  if (losscount > 0) {
3021  dstr->Append (" (");
3022  if (losscount == 1) {
3023  if (losscount+drawcount == moveList.Size()) {
3024  dstr->Append (translate (ti, "only"), " ");
3025  }
3026  dstr->Append (losslist[0], " ", translate (ti, "loses"));
3027  } else if (drawcount < 4 &&
3028  drawcount+losscount == moveList.Size()) {
3029  dstr->Append (translate (ti, "allOthersLose"));
3030  } else {
3031  dstr->Append (losscount);
3032  dstr->Append (" ", translate (ti, "lose"), ": ");
3033  for (uint m=0; m < losscount; m++) {
3034  if (m < 3) {
3035  if (m > 0) { dstr->Append (", "); }
3036  dstr->Append (losslist[m]);
3037  }
3038  }
3039  if (losscount > 3) { dstr->Append (", ..."); }
3040  }
3041  dstr->Append (")");
3042  }
3043  }
3044  if (showResult) { dstr->Append ("]"); }
3045 
3046  } else if (score > 0) {
3047  // Print side-to-move-mates tablebase info:
3048  if (showResult) {
3049  char temp[200];
3050  sprintf (temp, "%s:%d [%s %s %d",
3051  toMove == WHITE ? "+-" : "-+", score,
3052  translate (ti, toMove == WHITE ? "White" : "Black"),
3053  translate (ti, "matesIn"), score);
3054  dstr->Append (temp);
3055  }
3056 
3057  // Now show all moves that mate optimally.
3058  // This requires generating all legal moves, and trying each
3059  // to find its tablebase score; optimal moves will have
3060  // the condition (new_score == -old_score).
3061 
3062  if (showSummary) {
3063  uint count = 0;
3064 
3065  for (uint i=0; i < moveList.Size(); i++) {
3066  if (moveFound[i] && moveScore[i] == -score) {
3067  count++;
3068  if (count == 1) {
3069  dstr->Append (" ", translate (ti, "with"), ": ");
3070  } else {
3071  dstr->Append (", ");
3072  }
3073  dstr->Append (sanList.list[i]);
3074  }
3075  }
3076  }
3077  if (showResult) { dstr->Append ("]"); }
3078 
3079  } else {
3080  // Score is negative so side to move is LOST:
3081  if (showResult) {
3082  char tempStr [80];
3083  if (score == -1) {
3084  sprintf (tempStr, "# [%s %s %s",
3085  translate (ti, toMove == WHITE ? "Black" : "White"),
3086  translate (ti, "hasCheckmated"),
3087  translate (ti, toMove == WHITE ? "White" : "Black"));
3088  } else {
3089  sprintf (tempStr, "%s:%d [%s %s %d",
3090  toMove == WHITE ? "-+" : "+-", -1 - score,
3091  translate (ti, toMove == WHITE ? "Black" : "White"),
3092  translate (ti, "matesIn"),
3093  -1 - score);
3094  }
3095  dstr->Append (tempStr);
3096  }
3097 
3098  // Now show all moves that last optimally.
3099  // This requires generating all legal moves, and trying
3100  // each to find its tablebase score; optimal moves will
3101  // have the condition (new_score == (-old_score - 1)).
3102 
3103  if (showSummary) {
3104  uint count = 0;
3105  for (uint i=0; i < moveList.Size(); i++) {
3106  if (moveFound[i] && moveScore[i] == (-score - 1)) {
3107  count++;
3108  dstr->Append (", ");
3109  if (count == 1) {
3110  dstr->Append (translate (ti, "longest"), ": ");
3111  }
3112  dstr->Append (sanList.list[i]);
3113  }
3114  }
3115  }
3116  if (showResult) { dstr->Append ("]"); }
3117  }
3118 
3119  return true;
3120 }
3121 
3122 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3123 // sc_game_info:
3124 // Return the Game Info string for the active game.
3125 // The returned text includes color codes.
3126 int
3127 sc_game_info (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3128 {
3129  bool hideNextMove = false;
3130  bool showMaterialValue = false;
3131  bool showFEN = false;
3132  uint commentWidth = 50;
3133  uint commentHeight = 1;
3134  bool fullComment = false;
3135  uint showTB = 2; // 0 = no TB output, 1 = score only, 2 = best moves.
3136  char temp[1024];
3137  char tempTrans[10];
3138 
3139  int arg = 2;
3140  while (arg < argc) {
3141  if (strIsPrefix (argv[arg], "-hideNextMove")) {
3142  if (arg+1 < argc) {
3143  arg++;
3144  hideNextMove = strGetBoolean(argv[arg]);
3145  }
3146  } else if (strIsPrefix (argv[arg], "-materialValue")) {
3147  if (arg+1 < argc) {
3148  arg++;
3149  showMaterialValue = strGetBoolean(argv[arg]);
3150  }
3151  } else if (strIsPrefix (argv[arg], "-tb")) {
3152  if (arg+1 < argc) {
3153  arg++;
3154  showTB = strGetUnsigned(argv[arg]);
3155  }
3156  } else if (strIsPrefix (argv[arg], "-fen")) {
3157  if (arg+1 < argc) {
3158  arg++;
3159  showFEN = strGetBoolean(argv[arg]);
3160  }
3161  } else if (strIsPrefix (argv[arg], "-cfull")) {
3162  // Show full comment:
3163  if (arg+1 < argc) {
3164  arg++;
3165  fullComment = strGetBoolean(argv[arg]);
3166  if (fullComment) {
3167  commentWidth = 99999;
3168  commentHeight = 99999;
3169  }
3170  }
3171  } else if (strIsPrefix (argv[arg], "-cwidth")) {
3172  if (arg+1 < argc) {
3173  arg++;
3174  commentWidth = strGetBoolean(argv[arg]);
3175  }
3176  } else if (strIsPrefix (argv[arg], "-cheight")) {
3177  if (arg+1 < argc) {
3178  arg++;
3179  commentHeight = strGetBoolean(argv[arg]);
3180  }
3181  } else if (strIsPrefix (argv[arg], "white")) {
3182  Tcl_AppendResult (ti, db->game->GetWhiteStr(), NULL);
3183  return TCL_OK;
3184  } else if (strIsPrefix (argv[arg], "welo")) {
3185  return setIntResult (ti, db->game->GetWhiteElo() );
3186  } else if (strIsPrefix (argv[arg], "black")) {
3187  Tcl_AppendResult (ti, db->game->GetBlackStr(), NULL);
3188  return TCL_OK;
3189  } else if (strIsPrefix (argv[arg], "belo")) {
3190  return setIntResult (ti, db->game->GetBlackElo() );
3191  } else if (strIsPrefix (argv[arg], "event")) {
3192  Tcl_AppendResult (ti, db->game->GetEventStr(), NULL);
3193  return TCL_OK;
3194  } else if (strIsPrefix (argv[arg], "site")) {
3195  Tcl_AppendResult (ti, db->game->GetSiteStr(), NULL);
3196  return TCL_OK;
3197  } else if (strIsPrefix (argv[arg], "round")) {
3198  Tcl_AppendResult (ti, db->game->GetRoundStr(), NULL);
3199  return TCL_OK;
3200  } else if (strIsPrefix (argv[arg], "date")) {
3201  char dateStr [12];
3202  date_DecodeToString (db->game->GetDate(), dateStr);
3203  Tcl_AppendResult (ti, dateStr, NULL);
3204  return TCL_OK;
3205  } else if (strIsPrefix (argv[arg], "year")) {
3206  return setUintResult (ti, date_GetYear (db->game->GetDate()));
3207  } else if (strIsPrefix (argv[arg], "result")) {
3208  return setResult (ti, RESULT_STR[db->game->GetResult()]);
3209  } else if (strIsPrefix (argv[arg], "nextMove")) {
3210  db->game->GetSAN (temp);
3211  strcpy(tempTrans, temp);
3212  transPieces(tempTrans);
3213  Tcl_AppendResult (ti, tempTrans, NULL);
3214  return TCL_OK;
3215 // nextMoveNT is the same as nextMove, except that the move is not translated
3216  } else if (strIsPrefix (argv[arg], "nextMoveNT")) {
3217  db->game->GetSAN (temp);
3218  Tcl_AppendResult (ti, temp, NULL);
3219  return TCL_OK;
3220 // returns next move played in UCI format
3221  } else if (strIsPrefix (argv[arg], "nextMoveUCI")) {
3222  db->game->GetNextMoveUCI (temp);
3223  Tcl_AppendResult (ti, temp, NULL);
3224  return TCL_OK;
3225  } else if (strIsPrefix (argv[arg], "previousMove")) {
3226  db->game->GetPrevSAN (temp);
3227  strcpy(tempTrans, temp);
3228  transPieces(tempTrans);
3229  Tcl_AppendResult (ti, tempTrans, NULL);
3230  return TCL_OK;
3231 // previousMoveNT is the same as previousMove, except that the move is not translated
3232  } else if (strIsPrefix (argv[arg], "previousMoveNT")) {
3233  db->game->GetPrevSAN (temp);
3234  Tcl_AppendResult (ti, temp, NULL);
3235  return TCL_OK;
3236 // returns previous move played in UCI format
3237  } else if (strIsPrefix (argv[arg], "previousMoveUCI")) {
3238  db->game->GetPrevMoveUCI (temp);
3239  Tcl_AppendResult (ti, temp, NULL);
3240  return TCL_OK;
3241  } else if (strIsPrefix (argv[arg], "duplicate")) {
3242  uint dupGameNum = db->getDuplicates(db->gameNumber);
3243  return setUintResult (ti, dupGameNum);
3244  }
3245  arg++;
3246  }
3247 
3248  const char * gameStr = translate (ti, "game");
3249  sprintf (temp, "%c%s %u: <pi %s>%s</pi>", toupper(gameStr[0]),
3250  gameStr + 1, db->gameNumber + 1,
3251  db->game->GetWhiteStr(), db->game->GetWhiteStr());
3252  if (db->game->FindExtraTag("WhiteCountry") != NULL) {
3253  sprintf (temp, "%s (%s)", temp, db->game->FindExtraTag("WhiteCountry"));
3254 
3255  //--- using img code causes scid to segfault in tcl (?!)
3256  // sprintf (temp, "%s <img flag_%c%c%c>", temp,
3257  // tolower(db->game->FindExtraTag("WhiteCountry")[0]),
3258  // tolower(db->game->FindExtraTag("WhiteCountry")[1]),
3259  // tolower(db->game->FindExtraTag("WhiteCountry")[2])
3260  // );
3261  }
3262 
3263  Tcl_AppendResult (ti, temp, NULL);
3264  eloT elo = db->game->GetWhiteElo();
3265  bool eloEstimated = false;
3266  if (elo == 0) {
3267  elo = db->game->GetWhiteEstimateElo();
3268  eloEstimated = true;
3269  }
3270  if (elo != 0) {
3271  sprintf (temp, " <red>%u%s</red>", elo, eloEstimated ? "*" : "");
3272  Tcl_AppendResult (ti, temp, NULL);
3273  }
3274  sprintf (temp, " -- <pi %s>%s</pi>",
3275  db->game->GetBlackStr(), db->game->GetBlackStr());
3276  if (db->game->FindExtraTag("BlackCountry") != NULL) {
3277  sprintf (temp, "%s (%s)", temp, db->game->FindExtraTag("BlackCountry"));
3278 
3279  //--- using img code causes scid to segfault in tcl (?!)
3280  // sprintf (temp, "%s <img flag_%c%c%c>", temp,
3281  // tolower(db->game->FindExtraTag("BlackCountry")[0]),
3282  // tolower(db->game->FindExtraTag("BlackCountry")[1]),
3283  // tolower(db->game->FindExtraTag("BlackCountry")[2])
3284  // );
3285  }
3286  Tcl_AppendResult (ti, temp, NULL);
3287  elo = db->game->GetBlackElo();
3288  eloEstimated = false;
3289  if (elo == 0) {
3290  elo = db->game->GetBlackEstimateElo();
3291  eloEstimated = true;
3292  }
3293  if (elo != 0) {
3294  sprintf (temp, " <red>%u%s</red>", elo, eloEstimated ? "*" : "");
3295  Tcl_AppendResult (ti, temp, NULL);
3296  }
3297 
3298  if (hideNextMove) {
3299  sprintf (temp, "<br>(%s: %s)",
3300  translate (ti, "Result"), translate (ti, "hidden"));
3301  } else {
3302  sprintf (temp, "<br>%s <red>(%u)</red>",
3303  RESULT_LONGSTR[db->game->GetResult()],
3304  (db->game->GetNumHalfMoves() + 1) / 2);
3305  }
3306  Tcl_AppendResult (ti, temp, NULL);
3307 
3308  if (db->game->GetEco() != 0) {
3309  ecoStringT fullEcoStr;
3310  eco_ToExtendedString (db->game->GetEco(), fullEcoStr);
3311  ecoStringT basicEcoStr;
3312  strCopy (basicEcoStr, fullEcoStr);
3313  if (strLength(basicEcoStr) >= 4) { basicEcoStr[3] = 0; }
3314  Tcl_AppendResult (ti, " <blue><run ::windows::eco::Refresh ",
3315  basicEcoStr, ">", fullEcoStr,
3316  "</run></blue>", NULL);
3317  }
3318  char dateStr[20];
3319  date_DecodeToString (db->game->GetDate(), dateStr);
3320  strTrimDate (dateStr);
3321  Tcl_AppendResult (ti, " <red>", dateStr, "</red>", NULL);
3322 
3323  if (db->gameNumber >= 0) {
3324  // Check if this game is deleted or has other user-settable flags:
3325  const IndexEntry* ie = db->getIndexEntry(db->gameNumber);
3326  if (ie->GetDeleteFlag()) {
3327  Tcl_AppendResult (ti, " <gray>(",
3328  translate (ti, "deleted"), ")</gray>", NULL);
3329  }
3330  char userFlags[16];
3331  if (ie->GetFlagStr (userFlags, NULL) != 0) {
3332  // Print other flags set for this game:
3333  const char * flagStr = userFlags;
3334  // Skip over "D" for Deleted, as it is indicated above:
3335  if (*flagStr == 'D') { flagStr++; }
3336  if (*flagStr != 0) {
3337  Tcl_AppendResult (ti, " <gray>(",
3338  translate (ti, "flags", "flags"),
3339  ": ", flagStr, NULL);
3340  int flagCount = 0;
3341  while (*flagStr != 0) {
3342  const char * flagName = NULL;
3343  switch (*flagStr) {
3344  case 'W': flagName = "WhiteOpFlag"; break;
3345  case 'B': flagName = "BlackOpFlag"; break;
3346  case 'M': flagName = "MiddlegameFlag"; break;
3347  case 'E': flagName = "EndgameFlag"; break;
3348  case 'N': flagName = "NoveltyFlag"; break;
3349  case 'P': flagName = "PawnFlag"; break;
3350  case 'T': flagName = "TacticsFlag"; break;
3351  case 'Q': flagName = "QsideFlag"; break;
3352  case 'K': flagName = "KsideFlag"; break;
3353  case '!': flagName = "BrilliancyFlag"; break;
3354  case '?': flagName = "BlunderFlag"; break;
3355  case 'U': flagName = "UserFlag"; break;
3356  }
3357  if (flagName != NULL) {
3358  Tcl_AppendResult (ti, (flagCount > 0 ? ", " : " - "),
3359  translate (ti, flagName), NULL);
3360  }
3361  flagCount++;
3362  flagStr++;
3363  }
3364  Tcl_AppendResult (ti, ")</gray>", NULL);
3365  }
3366  }
3367 
3368  if (db->game->FindExtraTag("Bib") != NULL) {
3369  Tcl_AppendResult (ti, " <red><run ::Bibliography::ShowRef>Bib</run></red>", NULL);
3370  }
3371 
3372  // Check if this game has a twin (duplicate):
3373  if (db->getDuplicates(db->gameNumber) != 0) {
3374  Tcl_AppendResult (ti, " <blue><run updateTwinChecker>(",
3375  translate (ti, "twin"), ")</run></blue>", NULL);
3376  }
3377  }
3378  sprintf (temp, "<br><gray><run ::crosstab::Open>%s: %s</run> (%s)</gray><br>",
3379  db->game->GetSiteStr(),
3380  db->game->GetEventStr(),
3381  db->game->GetRoundStr());
3382  Tcl_AppendResult (ti, temp, NULL);
3383 
3384  char san [20];
3385  byte * nags;
3386  colorT toMove = db->game->GetCurrentPos()->GetToMove();
3387  uint moveCount = db->game->GetCurrentPos()->GetFullMoveCount();
3388  uint prevMoveCount = moveCount;
3389  if (toMove == WHITE) { prevMoveCount--; }
3390 
3391  db->game->GetPrevSAN (san);
3392  strcpy(tempTrans, san);
3393  transPieces(tempTrans);
3394  bool printNags = true;
3395  if (san[0] == 0) {
3396  strCopy (temp, "(");
3397  strAppend (temp, db->game->GetVarLevel() == 0 ?
3398  translate (ti, "GameStart", "Start of game") :
3399  translate (ti, "LineStart", "Start of line"));
3400  strAppend (temp, ")");
3401  printNags = false;
3402  } else {
3403  sprintf (temp, "<run ::move::Back>%u.%s%s</run>",
3404  prevMoveCount, toMove==WHITE ? ".." : "", tempTrans);//san);
3405  printNags = true;
3406  }
3407  Tcl_AppendResult (ti, translate (ti, "LastMove", "Last move"), NULL);
3408  Tcl_AppendResult (ti, ": <darkblue>", temp, "</darkblue>", NULL);
3409  nags = db->game->GetNags();
3410  if (printNags && *nags != 0 && !hideNextMove) {
3411  Tcl_AppendResult (ti, "<red>", NULL);
3412  for (uint nagCount = 0 ; nags[nagCount] != 0; nagCount++) {
3413  char nagstr[20];
3414  game_printNag (nags[nagCount], nagstr, true, PGN_FORMAT_Plain);
3415  if (nagCount > 0 || (nagstr[0] != '!' && nagstr[0] != '?')) {
3416  Tcl_AppendResult (ti, " ", NULL);
3417  }
3418  Tcl_AppendResult (ti, nagstr, NULL);
3419  }
3420  Tcl_AppendResult (ti, "</red>", NULL);
3421  }
3422 
3423  // Now print next move:
3424 
3425  db->game->GetSAN (san);
3426  strcpy(tempTrans, san);
3427  transPieces(tempTrans);
3428  if (san[0] == 0) {
3429  strCopy (temp, "(");
3430  strAppend (temp, db->game->GetVarLevel() == 0 ?
3431  translate (ti, "GameEnd", "End of game") :
3432  translate (ti, "LineEnd", "End of line"));
3433  strAppend (temp, ")");
3434  printNags = false;
3435  } else if (hideNextMove) {
3436  sprintf (temp, "%u.%s(", moveCount, toMove==WHITE ? "" : "..");
3437  strAppend (temp, translate (ti, "hidden"));
3438  strAppend (temp, ")");
3439  printNags = false;
3440  } else {
3441  sprintf (temp, "<run ::move::Forward>%u.%s%s</run>",
3442  moveCount, toMove==WHITE ? "" : "..", tempTrans);//san);
3443  printNags = true;
3444  }
3445  Tcl_AppendResult (ti, " ", translate (ti, "NextMove", "Next"), NULL);
3446  Tcl_AppendResult (ti, ": <darkblue>", temp, "</darkblue>", NULL);
3447  nags = db->game->GetNextNags();
3448  if (printNags && !hideNextMove && *nags != 0) {
3449  Tcl_AppendResult (ti, "<red>", NULL);
3450  for (uint nagCount = 0 ; nags[nagCount] != 0; nagCount++) {
3451  char nagstr[20];
3452  game_printNag (nags[nagCount], nagstr, true, PGN_FORMAT_Plain);
3453  if (nagCount > 0 || (nagstr[0] != '!' && nagstr[0] != '?')) {
3454  Tcl_AppendResult (ti, " ", NULL);
3455  }
3456  Tcl_AppendResult (ti, nagstr, NULL);
3457  }
3458  Tcl_AppendResult (ti, "</red>", NULL);
3459  }
3460 
3461  if (db->game->GetVarLevel() > 0) {
3462  Tcl_AppendResult (ti, " <green><run sc_var exit; updateBoard -animate>",
3463  "(<lt>-Var)", "</run></green>", NULL);
3464  }
3465 
3466  if (showMaterialValue) {
3467  uint mWhite = db->game->GetCurrentPos()->MaterialValue (WHITE);
3468  uint mBlack = db->game->GetCurrentPos()->MaterialValue (BLACK);
3469  sprintf (temp, " <gray>(%u-%u", mWhite, mBlack);
3470  Tcl_AppendResult (ti, temp, NULL);
3471  if (mWhite > mBlack) {
3472  sprintf (temp, ":+%u", mWhite - mBlack);
3473  Tcl_AppendResult (ti, temp, NULL);
3474  } else if (mBlack > mWhite) {
3475  sprintf (temp, ":-%u", mBlack - mWhite);
3476  Tcl_AppendResult (ti, temp, NULL);
3477  }
3478  Tcl_AppendResult (ti, ")</gray>", NULL);
3479  }
3480 
3481  // Print first few variations if there are any:
3482 
3483  uint varCount = db->game->GetNumVariations();
3484  if (!hideNextMove && varCount > 0) {
3485  Tcl_AppendResult (ti, "<br>", translate (ti, "Variations"), ":", NULL);
3486  for (uint vnum = 0; vnum < varCount && vnum < 5; vnum++) {
3487  char s[20];
3488  db->game->MoveIntoVariation (vnum);
3489  db->game->GetSAN (s);
3490  strcpy(tempTrans, s);
3491  transPieces(tempTrans);
3492  sprintf (temp, " <run sc_var enter %u; updateBoard -animate>v%u",
3493  vnum, vnum+1);
3494  Tcl_AppendResult (ti, "<green>", temp, "</green>: ", NULL);
3495  if (s[0] == 0) {
3496  sprintf (temp, "<darkblue>(empty)</darkblue>");
3497  } else {
3498  sprintf (temp, "<darkblue>%u.%s%s</darkblue>",
3499  moveCount, toMove == WHITE ? "" : "..", tempTrans);//s);
3500  }
3501  Tcl_AppendResult (ti, temp, NULL);
3502  byte * firstNag = db->game->GetNextNags();
3503  if (*firstNag >= NAG_GoodMove && *firstNag <= NAG_DubiousMove) {
3504  game_printNag (*firstNag, s, true, PGN_FORMAT_Plain);
3505  Tcl_AppendResult (ti, "<red>", s, "</red>", NULL);
3506  }
3507  Tcl_AppendResult (ti, "</run>", NULL);
3508  db->game->MoveExitVariation ();
3509  }
3510  }
3511 
3512  // Check if this move has a comment:
3513 
3514  if (db->game->GetMoveComment() != NULL) {
3515  Tcl_AppendResult (ti, "<br>", translate(ti, "Comment"),
3516  " <green><run makeCommentWin>", NULL);
3517  char * str = strDuplicate(db->game->GetMoveComment());
3518  strTrimMarkCodes (str);
3519  const char * s = str;
3520  uint len;
3521  uint lines = 0;
3522  // Add the first commentWidth characters of the comment, up to
3523  // the first commentHeight lines:
3524  for (len = 0; len < commentWidth; len++, s++) {
3525  char ch = *s;
3526  if (ch == 0) { break; }
3527  if (ch == '\n') {
3528  lines++;
3529  if (lines >= commentHeight) { break; }
3530  Tcl_AppendResult (ti, "<br>", NULL);
3531  } else if (ch == '<') {
3532  Tcl_AppendResult (ti, "<lt>", NULL);
3533  } else if (ch == '>') {
3534  Tcl_AppendResult (ti, "<gt>", NULL);
3535  } else {
3536  appendCharResult (ti, ch);
3537  }
3538  }
3539  // Complete the current comment word and add "..." if necessary:
3540  if (len == commentWidth) {
3541  char ch = *s;
3542  while (ch != ' ' && ch != '\n' && ch != 0) {
3543  appendCharResult (ti, ch);
3544  s++;
3545  ch = *s;
3546  }
3547  if (ch != 0) {
3548  Tcl_AppendResult (ti, "...", NULL);
3549  }
3550  }
3551  Tcl_AppendResult (ti, "</run></green>", NULL);
3552 #ifdef WINCE
3553  my_Tcl_Free((char*) str);
3554 #else
3555  delete[] str;
3556 #endif
3557  }
3558 
3559  // Probe tablebases:
3560 
3561  if (!hideNextMove) {
3562  DString * tbStr = new DString;
3563  if (probe_tablebase (ti, showTB, tbStr)) {
3564  Tcl_AppendResult (ti, "<br>TB: <blue><run ::tb::open>",
3565  tbStr->Data(), "</run></blue>", NULL);
3566  }
3567  delete tbStr;
3568  }
3569 
3570  // Now check ECO book for the current position:
3571  if (ecoBook) {
3572  DString ecoComment;
3573  if (ecoBook->FindOpcode (db->game->GetCurrentPos(), "eco",
3574  &ecoComment) == OK) {
3575  ecoT eco = eco_FromString (ecoComment.Data());
3576  ecoStringT estr;
3577  eco_ToExtendedString (eco, estr);
3578  uint len = strLength (estr);
3579  if (len >= 4) { estr[3] = 0; }
3580  DString * tempDStr = new DString;
3581  translateECO (ti, ecoComment.Data(), tempDStr);
3582  Tcl_AppendResult (ti, "<br>ECO: <blue><run ::windows::eco::Refresh ",
3583  estr, ">", tempDStr->Data(),
3584  "</run></blue>", NULL);
3585  delete tempDStr;
3586  }
3587  }
3588  if (showFEN) {
3589  char boardStr [200];
3590  db->game->GetCurrentPos()->PrintFEN (boardStr, FEN_ALL_FIELDS);
3591  Tcl_AppendResult (ti, "<br><gray>", boardStr, "</gray>", NULL);
3592  }
3593  return TCL_OK;
3594 }
3595 
3596 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3597 // sc_game_load:
3598 // Takes a game number and loads the game
3599 int
3600 sc_game_load (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3601 {
3602  if (!db->inUse) {
3603  return errorResult (ti, errMsgNotOpen(ti));
3604  }
3605  if (argc != 3) {
3606  return errorResult (ti, "Usage: sc_game load <gameNumber>");
3607  }
3608 
3609  db->gameAlterations.clear();
3610 
3611  uint gnum = strGetUnsigned (argv[2]);
3612 
3613  // Check the game number is valid::
3614  if (gnum < 1 || gnum > db->numGames()) {
3615  return errorResult (ti, "Invalid game number.");
3616  }
3617 
3618  // We number games from 0 internally, so subtract one:
3619  gnum--;
3620  const char * corruptMsg = "Sorry, this game appears to be corrupt.";
3621 
3622  const IndexEntry* ie = db->getIndexEntry(gnum);
3623 
3624  if (db->getGame(ie, db->bbuf) != OK) {
3625  return errorResult (ti, corruptMsg);
3626  }
3627  if (db->game->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
3628  return errorResult (ti, corruptMsg);
3629  }
3630 
3631  if (db->dbFilter->Get(gnum) > 0) {
3632  db->game->MoveToPly(db->dbFilter->Get(gnum) - 1);
3633  } else {
3634  db->game->MoveToPly(0);
3635  }
3636 
3637  db->game->LoadStandardTags (ie, db->getNameBase());
3638  db->gameNumber = gnum;
3639  db->gameAltered = false;
3640  return OK;
3641 }
3642 
3643 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3644 // sc_game_merge:
3645 // Merge the specified game into a variation from the current
3646 // game position.
3647 int
3648 sc_game_merge (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3649 {
3650  const char * usage = "Usage: sc_game merge <baseNum> <gameNum> [<endPly>]";
3651  if (argc < 4 || argc > 5) { return errorResult (ti, usage); }
3652 
3653  const scidBaseT* base = DBasePool::getBase(strGetUnsigned(argv[2]));
3654  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
3655 
3656  uint gnum = strGetUnsigned (argv[3]);
3657  uint endPly = 9999; // Defaults to huge number for all moves.
3658  if (argc == 5) { endPly = strGetUnsigned (argv[4]); }
3659 
3660  if (gnum < 1 || gnum > base->numGames()) {
3661  return errorResult (ti, "Invalid game number.");
3662  }
3663  // Number games from 0 internally:
3664  gnum--;
3665 
3666  // Check that the specified game can be merged:
3667  if (base == db && (int)gnum == db->gameNumber) {
3668  return errorResult (ti, "This game cannot be merged into itself.");
3669  }
3670  if (db->game->AtStart() && db->game->AtEnd()) {
3671  return errorResult (ti, "The current game has no moves.");
3672  }
3673  if (db->game->HasNonStandardStart()) {
3674  return errorResult (ti, "The current game has a non-standard start position.");
3675  }
3676 
3677  // Load the merge game:
3678 
3679  const IndexEntry* ie = base->getIndexEntry(gnum);
3680  if (base->getGame(ie, base->bbuf) != OK) {
3681  return errorResult (ti, "Error loading game.");
3682  }
3683  Game * merge = scratchGame;
3684  merge->Clear();
3685  if (merge->Decode (base->bbuf, GAME_DECODE_NONE) != OK) {
3686  return errorResult (ti, "Error decoding game.");
3687  }
3688  merge->LoadStandardTags (ie, base->getNameBase());
3689  if (merge->HasNonStandardStart()) {
3690  return errorResult (ti, "The merge game has a non-standard start position.");
3691  }
3692 
3693  // Set up an array of all the game positions in the merge game:
3694  uint nMergePos = merge->GetNumHalfMoves() + 1;
3695  typedef char compactBoardStr [36];
3696  compactBoardStr * mergeBoards = new compactBoardStr [nMergePos];
3697  merge->MoveToPly (0);
3698  for (uint i=0; i < nMergePos; i++) {
3699  merge->GetCurrentPos()->PrintCompactStr (mergeBoards[i]);
3700  merge->MoveForward();
3701  }
3702 
3703  // Now find the deepest position in the current game that occurs
3704  // in the merge game:
3705  db->game->MoveToPly (0);
3706  uint matchPly = 0;
3707  uint mergePly = 0;
3708  uint ply = 0;
3709  bool done = false;
3710  while (!done) {
3711  if (db->game->MoveForward() != OK) { done = true; }
3712  ply++;
3713  compactBoardStr currentBoard;
3714  db->game->GetCurrentPos()->PrintCompactStr (currentBoard);
3715  for (uint n=0; n < nMergePos; n++) {
3716  if (strEqual (currentBoard, mergeBoards[n])) {
3717  matchPly = ply;
3718  mergePly = n;
3719  }
3720  }
3721  }
3722 
3723  delete [] mergeBoards;
3724 
3725  // Now the games match at the locations matchPly in the current
3726  // game and mergePly in the merge game.
3727  // Create a new variation and add merge-game moves to it:
3728  db->game->MoveToPly (matchPly);
3729  bool atLastMove = db->game->AtEnd();
3730  simpleMoveT * sm = NULL;
3731  if (atLastMove) {
3732  // At end of game, so remember final game move for replicating
3733  // at the start of the variation:
3734  db->game->MoveBackup();
3735  sm = db->game->GetCurrentMove();
3736  db->game->MoveForward();
3737  }
3738  db->game->MoveForward();
3739  db->game->AddVariation();
3740  db->gameAltered = true;
3741  if (atLastMove) {
3742  // We need to replicate the last move of the current game.
3743  db->game->AddMove (sm, NULL);
3744  }
3745  merge->MoveToPly (mergePly);
3746  ply = mergePly;
3747  while (ply < endPly) {
3748  simpleMoveT * mergeMove = merge->GetCurrentMove();
3749  if (merge->MoveForward() != OK) { break; }
3750  if (mergeMove == NULL) { break; }
3751  if (db->game->AddMove (mergeMove, NULL) != OK) { break; }
3752  ply++;
3753  }
3754 
3755  // Finally, add a comment describing the merge-game details:
3756  DString * dstr = new DString;
3757  dstr->Append (RESULT_LONGSTR[ie->GetResult()]);
3758  if (ply < merge->GetNumHalfMoves()) {
3759  dstr->Append ("(", (merge->GetNumHalfMoves()+1) / 2, ")");
3760  }
3761  dstr->Append (" ", ie->GetWhiteName (base->getNameBase()));
3762  eloT elo = ie->GetWhiteElo();
3763  if (elo > 0) { dstr->Append (" (", elo, ")"); }
3764  dstr->Append (" - ");
3765  dstr->Append (ie->GetBlackName (base->getNameBase()));
3766  elo = ie->GetBlackElo();
3767  if (elo > 0) { dstr->Append (" (", elo, ")"); }
3768  dstr->Append (" / ", ie->GetEventName (base->getNameBase()));
3769  dstr->Append (" (", ie->GetRoundName (base->getNameBase()), ")");
3770  dstr->Append (", ", ie->GetSiteName (base->getNameBase()));
3771  dstr->Append (" ", ie->GetYear());
3772  db->game->SetMoveComment ((char *) dstr->Data());
3773  delete dstr;
3774 
3775  // And exit the new variation:
3776  db->game->MoveExitVariation();
3777  return TCL_OK;
3778 }
3779 
3780 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3781 // sc_game_moves:
3782 // Return a string of the moves reaching the current game position.
3783 // Optional arguments: "coord" for coordinate notation (1 move per line);
3784 // "nomoves" for standard algebraic without move numbers.
3785 // Default output is standard algebraic with move numbers.
3786 int
3787 sc_game_moves (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3788 {
3789  bool sanFormat = true;
3790  bool printMoves = true;
3791  bool listFormat = false;
3792  const uint MAXMOVES = 500;
3793 #ifdef WINCE
3794  sanStringT * moveStrings = (sanStringT * ) my_Tcl_Alloc(sizeof( sanStringT [MAXMOVES]));
3795 #else
3796  sanStringT * moveStrings = new sanStringT [MAXMOVES];
3797 #endif
3798  uint plyCount = 0;
3799  Game * g = db->game;
3800  for (int arg = 2; arg < argc; arg++) {
3801  if (argv[arg][0] == 'c') { sanFormat = false; }
3802  if (argv[arg][0] == 'n') { printMoves = false; }
3803  if (argv[arg][0] == 'l') { printMoves = false; }
3804  }
3805 
3806  g->SaveState();
3807  while (! g->AtStart()) {
3808  if (g->AtVarStart()) {
3809  g->MoveExitVariation();
3810  continue;
3811  }
3812  g->MoveBackup();
3813  simpleMoveT * sm = g->GetCurrentMove();
3814  if (sm == NULL) { break; }
3815  char * s = moveStrings[plyCount];
3816  if (sanFormat) {
3817  g->GetSAN (s);
3818  } else {
3819  *s++ = square_FyleChar(sm->from);
3820  *s++ = square_RankChar(sm->from);
3821  *s++ = square_FyleChar(sm->to);
3822  *s++ = square_RankChar(sm->to);
3823  if (sm->promote != EMPTY) {
3824  *s++ = piece_Char (piece_Type (sm->promote));
3825  }
3826  *s = 0;
3827  }
3828  plyCount++;
3829  if (plyCount == MAXMOVES) {
3830  // Too many moves, just give up:
3831  g->RestoreState();
3832 #ifdef WINCE
3833  my_Tcl_Free((char*) moveStrings);
3834 #else
3835  delete[] moveStrings;
3836 #endif
3837 
3838  return TCL_OK;
3839  }
3840  }
3841  g->RestoreState();
3842  uint count = 0;
3843  for (uint i = plyCount; i > 0; i--, count++) {
3844  char move [20];
3845  if (sanFormat) {
3846  move[0] = 0;
3847  if (printMoves && (count % 2 == 0)) {
3848  sprintf (move, "%u.", (count / 2) + 1);
3849  }
3850  strAppend (move, moveStrings[i - 1]);
3851  } else {
3852  strCopy (move, moveStrings [i - 1]);
3853  }
3854  if (listFormat) {
3855  Tcl_AppendElement (ti, move);
3856  } else {
3857  Tcl_AppendResult (ti, (count == 0 ? "" : " "), move, NULL);
3858  }
3859  }
3860 #ifdef WINCE
3861  my_Tcl_Free((char*) moveStrings);
3862 #else
3863  delete[] moveStrings;
3864 #endif
3865  return TCL_OK;
3866 }
3867 
3868 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3869 // sc_game_new:
3870 // Clears the current game.
3871 int
3872 sc_game_new(ClientData, Tcl_Interp*, int, const char**)
3873 {
3874  db->game->Clear();
3875  db->gameNumber = -1;
3876  db->gameAltered = false;
3877  return TCL_OK;
3878 }
3879 
3880 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3881 // sc_game_novelty:
3882 // Finds the first move in the current game (after the deepest
3883 // position found in the ECO book) that reaches a position not
3884 // found in the selected database. It then moves to that point
3885 // in the game and returns a text string of the move.
3886 int
3887 sc_game_novelty (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3888 {
3889  const char * usage =
3890  "Usage: sc_game novelty [-older] base";
3891 
3892  bool olderGamesOnly = false;
3893 
3894  int baseArg = 2;
3895  if (argc >= baseArg
3896  && argv[baseArg][0] == '-' && argv[baseArg][1] == 'o'
3897  && strIsPrefix (argv[baseArg], "-older")) {
3898  olderGamesOnly = true;
3899  baseArg++;
3900  }
3901  if (argc < baseArg || argc > baseArg+1) return errorResult(ti, usage);
3902  scidBaseT* base = DBasePool::getBase(strGetInteger (argv[baseArg]));
3903  if (base == 0) return UI_Result(ti, ERROR_BadArg);
3904 
3905  // First, move to the deepest ECO position in the game.
3906  // This code is adapted from sc_eco_game().
3907  Game* g = base->game;
3908  if (ecoBook) {
3909  while (g->MoveForward() == OK) {}
3910  DString ecoStr;
3911  while (ecoBook->FindOpcode (g->GetCurrentPos(), "eco", &ecoStr) != OK) {
3912  if (g->MoveBackup() != OK) break;
3913  }
3914  }
3915 
3916  // Now keep doing an exact position search (ignoring the current
3917  // game) and skipping to the next game position whenever a match
3918  // is found, until a position not in any database game is reached:
3919  Progress progress = UI_CreateProgress(ti);
3920  std::string filtername = base->newFilter();
3921  HFilter filter = base->getFilter(filtername);
3922  dateT currentDate = g->GetDate();
3923  while (g->MoveForward() == OK) {
3924  SearchPos(g->GetCurrentPos()).setFilter(base, filter, Progress());
3925  int count = 0;
3926  for (uint i=0, n = base->numGames(); i < n; i++) {
3927  if (filter.get(i) == 0) continue;
3928 
3929  // Ignore newer games if requested:
3930  if (olderGamesOnly) {
3931  if (base->getIndexEntry(i)->GetDate() >= currentDate) continue;
3932  }
3933  if (count++ != 0) break;
3934  }
3935 
3936  if (count <= 1) { // Novelty found
3937  base->deleteFilter(filtername.c_str());
3938  return UI_Result(ti, OK, g->GetCurrentPly());
3939  }
3940 
3941  if (!progress.report(g->GetCurrentPly() +1, g->GetNumHalfMoves())) {
3942  base->deleteFilter(filtername.c_str());
3943  return UI_Result(ti, ERROR_UserCancel);
3944  }
3945  }
3946 
3947  base->deleteFilter(filtername.c_str());
3948  return UI_Result(ti, OK, -1);
3949 }
3950 
3951 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3952 // sc_game_pgn:
3953 // Returns the PGN representation of the game.
3954 // Optional args:
3955 // -format (plain|html|latex): output format. Default=plain.
3956 // -shortHeader (0|1): short, 3-line (non-PGN) header. Default=0.
3957 // -space (0|1): printing a space after move numbers. Default=0.
3958 // -tags (0|1): printing (nonstandard) tags. Default=1.
3959 // -comments (0|1): printing nags/comments. Default=1.
3960 // -variations (0|1): printing variations. Default=1.
3961 // -indentVars (0|1): indenting variations. Default=0.
3962 // -indentComments (0|1): indenting comments. Default=0.
3963 // -width (number): line length for wordwrap. Default=huge (99999),
3964 // to let a Tk text widget do its own line-breaking.
3965 // -base (number): Print the game from the numbered base.
3966 // -gameNumber (number): Print the numbered game instead of the
3967 // active game.
3968 // -unicode (0|1): use unicocde characters (e.g. U+2654 for king). Default=0.
3969 int
3970 sc_game_pgn (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3971 {
3972  static const char * options [] = {
3973  "-column", "-comments", "-base", "-gameNumber", "-format",
3974  "-shortHeader", "-indentComments", "-indentVariations",
3975  "-symbols", "-tags", "-variations", "-width", "-space",
3976  "-markCodes", "-unicode",
3977  NULL
3978  };
3979  enum {
3980  OPT_COLUMN, OPT_COMMENTS, OPT_BASE, OPT_GAME_NUMBER, OPT_FORMAT,
3981  OPT_SHORT_HDR, OPT_INDENT_COMMENTS, OPT_INDENT_VARS,
3982  OPT_SYMBOLS, OPT_TAGS, OPT_VARS, OPT_WIDTH, OPT_SPACE,
3983  OPT_NOMARKS, OPT_UNICODE,
3984  };
3985 
3986  const scidBaseT* base = db;
3987  Game * g = db->game;
3988  uint lineWidth = 99999;
3989  g->ResetPgnStyle();
3992 
3993  // Parse all the command options:
3994  // Note that every option takes a value so options/values always occur
3995  // in pairs, which simplifies the code.
3996 
3997  int thisArg = 2;
3998  while (thisArg < argc) {
3999  int index = strUniqueMatch (argv[thisArg], options);
4000  if (index == -1) {
4001  Tcl_AppendResult (ti, "Invalid option to sc_game pgn: ",
4002  argv[thisArg], "; valid options are: ", NULL);
4003  for (const char ** s = options; *s != NULL; s++) {
4004  Tcl_AppendResult (ti, *s, " ", NULL);
4005  }
4006  return TCL_ERROR;
4007  }
4008 
4009  // Check that our option has a value:
4010  if (thisArg+1 == argc) {
4011  Tcl_AppendResult (ti, "Invalid option value: sc_game pgn ",
4012  options[index], " requires a value.", NULL);
4013  return TCL_ERROR;
4014  }
4015 
4016  uint value = strGetUnsigned (argv[thisArg+1]);
4017 
4018  if (index == OPT_WIDTH) {
4019  lineWidth = value;
4020 
4021  } else if (index == OPT_BASE) {
4022  base = DBasePool::getBase(value);
4023  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
4024  g = base->game;
4025 
4026  } else if (index == OPT_GAME_NUMBER) {
4027  // Print the numbered game instead of the active game:
4028 
4029  g = scratchGame;
4030  g->Clear();
4031  if (value < 1 || value > base->numGames()) {
4032  return setResult (ti, "Invalid game number");
4033  }
4034  const IndexEntry* ie = base->getIndexEntry(value - 1);
4035  if (ie->GetLength() == 0) {
4036  return errorResult (ti, "Error: empty game file record.");
4037  }
4038  if (base->getGame(ie, base->bbuf) != OK) {
4039  return errorResult (ti, "Error reading game file.");
4040  }
4041  if (g->Decode (base->bbuf, GAME_DECODE_ALL) != OK) {
4042  return errorResult (ti, "Error decoding game.");
4043  }
4044  g->LoadStandardTags (ie, base->getNameBase());
4045 
4046  } else if (index == OPT_FORMAT) {
4047  // The option value should be "plain", "html" or "latex".
4048  if (! g->SetPgnFormatFromString (argv[thisArg+1])) {
4049  return errorResult (ti, "Invalid -format option.");
4050  }
4051 
4052  } else {
4053  // The option is a boolean affecting pgn style:
4054  uint bitmask = 0;
4055  switch (index) {
4056  case OPT_COLUMN:
4057  bitmask = PGN_STYLE_COLUMN; break;
4058  case OPT_COMMENTS:
4059  bitmask = PGN_STYLE_COMMENTS; break;
4060  case OPT_SYMBOLS:
4061  bitmask = PGN_STYLE_SYMBOLS; break;
4062  case OPT_TAGS:
4063  bitmask = PGN_STYLE_TAGS; break;
4064  case OPT_VARS:
4065  bitmask = PGN_STYLE_VARS; break;
4066  case OPT_SHORT_HDR:
4067  bitmask = PGN_STYLE_SHORT_HEADER; break;
4068  case OPT_SPACE:
4069  bitmask = PGN_STYLE_MOVENUM_SPACE; break;
4070  case OPT_INDENT_VARS:
4071  bitmask = PGN_STYLE_INDENT_VARS; break;
4072  case OPT_INDENT_COMMENTS:
4073  bitmask = PGN_STYLE_INDENT_COMMENTS; break;
4074  case OPT_NOMARKS:
4075  bitmask = PGN_STYLE_STRIP_MARKS; break;
4076  case OPT_UNICODE:
4077  bitmask = PGN_STYLE_UNICODE; break;
4078  default: // unreachable!
4079  return errorResult (ti, "Invalid option.");
4080  };
4081  if (bitmask > 0) {
4082  if (value) {
4083  g->AddPgnStyle (bitmask);
4084  } else {
4085  g->RemovePgnStyle (bitmask);
4086  }
4087  }
4088  }
4089  thisArg += 2;
4090  }
4091 
4092  std::pair<const char*, unsigned> pgnBuf = g->WriteToPGN(lineWidth);
4093  Tcl_AppendResult (ti, pgnBuf.first, NULL);
4094  return TCL_OK;
4095 }
4096 
4097 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4098 // sc_game_pop:
4099 // Restores the last game saved with sc_game_push.
4100 int
4101 sc_game_pop(ClientData, Tcl_Interp*, int, const char**)
4102 {
4103  if (db->game->GetNextGame() != NULL) {
4104  Game * g = db->game->GetNextGame();
4105  delete db->game;
4106  db->gameAltered = g->GetAltered();
4107  db->game = g;
4108  }
4109  return TCL_OK;
4110 }
4111 
4112 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4113 // sc_game_push:
4114 // Saves the current game and pushes a new empty game onto
4115 // the game state stack.
4116 // If the optional argument "copy" is present, the new game will be
4117 // a copy of the current game.
4118 int
4119 sc_game_push (ClientData, Tcl_Interp*, int argc, const char ** argv)
4120 {
4121  bool copy = false;
4122 
4123  if ( argc > 2 && !strcmp( argv[2], "copy" ) ) {
4124  copy = true;
4125  }
4126  else if ( argc > 2 && !strcmp( argv[2], "copyfast" ) ) {
4127  copy = true;
4128  }
4129 
4130  Game* g = (copy) ? db->game->clone() : new Game;
4131  g->SetNextGame (db->game);
4132  db->game->SetAltered (db->gameAltered);
4133  db->game = g;
4134  db->gameAltered = false;
4135 
4136  return TCL_OK;
4137 }
4138 
4139 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4140 // sc_game_save:
4141 // Saves the current game. If the parameter is 0, a NEW
4142 // game is added; otherwise, that game number is REPLACED.
4143 int
4144 sc_game_save (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4145 {
4146  scidBaseT * dbase = db;
4147  Game* currGame = db->game;
4148  if (argc == 4) {
4149  dbase = DBasePool::getBase(strGetUnsigned(argv[3]));
4150  if (dbase == 0) return errorResult (ti, "Invalid database number.");
4151  } else if (argc != 3) {
4152  return errorResult (ti, "Usage: sc_game save <gameNumber> [targetbaseId]");
4153  }
4154 
4155  gamenumT gnum = strGetUnsigned(argv[2]);
4156  if (gnum == 0) {
4157  gnum = INVALID_GAMEID;
4158  } else {
4159  gnum -= 1;
4160  const IndexEntry* ieOld = dbase->getIndexEntry_bounds(gnum);
4161  if (ieOld == 0) return ERROR_BadArg;
4162  // User-settable flags were stored in currGame when the game
4163  // was loaded, but the user may have changed them.
4164  char buf[IndexEntry::IDX_NUM_FLAGS + 1];
4165  ieOld->GetFlagStr(buf, "WBMENPTKQ!?U123456");
4166  currGame->SetScidFlags(buf);
4167  }
4168  currGame->SaveState();
4169  errorT res = dbase->saveGame(currGame, gnum);
4170  currGame->RestoreState ();
4171  if (res == OK) {
4172  if (gnum == INVALID_GAMEID && db == dbase) {
4173  // Saved new game, so set gameNumber to the saved game number:
4174  db->gameNumber = db->numGames() - 1;
4175  }
4176  db->gameAltered = false;
4177  }
4178 
4179  return UI_Result(ti, res);;
4180 }
4181 
4182 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4183 // addScoreToList:
4184 // Called by sc_game_scores to check a comment for a numeric
4185 // evaluation (a score), and add it to the list result for the
4186 // specified Tcl interpreter if a score is found.
4187 //
4188 static bool
4189 addScoreToList (Tcl_Interp * ti, int moveCounter, const char * comment,
4190  bool negate, float min, float max)
4191 {
4192  char buffer[1024];
4193  if (comment == NULL) { return false; }
4194  const char* avoid_overflow = comment;
4195  while (*comment != 0 && *comment != '+' && *comment != '-') {
4196  comment++;
4197  }
4198  if (*comment == 0 ||
4199  ! isdigit(static_cast<unsigned char>(*(comment+1)))) {
4200  return false;
4201  }
4202  //Klimmek: ignore game results like 1-0 or 0-1 in a comment
4203  if (*comment == '-' && comment != avoid_overflow &&
4204  isdigit(static_cast<unsigned char>(*(comment-1)))) {
4205  return false;
4206  }
4207  // OK, now we have found "+[digit]" or "-[digit]" in the comment,
4208  // so extract its evaluation and add it to our list:
4209  sprintf (buffer, "%.1f", (float)moveCounter * 0.5);
4210  Tcl_AppendElement (ti, buffer);
4211  float f;
4212  sscanf (comment, "%f", &f);
4213  if (negate) { f = -f; }
4214  if (f < min) { f = min; }
4215  if (f > max) { f = max; }
4216  sprintf (buffer, "%.2f", f);
4217  Tcl_AppendElement (ti, buffer);
4218  return true;
4219 }
4220 
4221 
4222 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4223 // sc_game_scores:
4224 // Returns a Tcl list of the numeric scores of each move, as found
4225 // in the comment for each move.
4226 // A score is a number with the format
4227 // "+digits.digits" or
4228 // "-digits.digits"
4229 // found somewhere in the comment of the move, OR the comment of the
4230 // first variation of the move.
4231 //
4232 // In this way, both Scid annotations which have the form
4233 // 1.e4 {"+0.13: ...."} e5 ...
4234 // and those produced by crafty's annotate command which have the form
4235 // 1.e4 ({7:+0.12} ...) e5 ...
4236 // are recognised. The latter form (comments in variations) had the score
4237 // from the perspective of the side to move in Crafty versions 17 and
4238 // older, but now have the score always from White's perspective, since
4239 // version 18.
4240 //
4241 // The list returned should be read in pairs of values: the first is the
4242 // move (0.0 = start, 0.5 after White's first move, 1.0 after Black's
4243 // first move, etc) and the second is the value found.
4244 //
4245 int
4246 sc_game_scores (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4247 {
4248  int moveCounter = 0;
4249  float max = 10.0;
4250  float min = -max;
4251  bool inv_w = false;
4252  bool inv_b = false;
4253 
4254  if (argc == 3) {
4255  max = atof (argv[2]);
4256  min = -max;
4257  }
4258  // Klimmek: check Invertflags
4259  else if (argc == 4) {
4260  inv_w = atoi (argv[2]);
4261  inv_b = atoi (argv[3]);
4262  }
4263 
4264  Game * g = db->game;
4265  g->SaveState ();
4266  g->MoveToPly (0);
4267  while (g->MoveForward() == OK) {
4268  moveCounter++;
4269  const char * comment = g->GetMoveComment();
4270  // Klimmek: use invertflags
4271  if (addScoreToList (ti, moveCounter, comment, moveCounter % 2 ? inv_b : inv_w, min, max)) {
4272  continue;
4273  }
4274  // Now try finding a score in the comment at the start of the
4275  // first variation:
4276  if (g->GetNumVariations() > 0) {
4277  g->MoveIntoVariation (0);
4278  comment = g->GetMoveComment();
4279  addScoreToList (ti, moveCounter, comment,
4280  //false,
4281  // For the annotate format of crafty before v18,
4282  // replace "false" above with:
4283  moveCounter % 2 ? inv_b : inv_w,
4284  min, max);
4285  g->MoveExitVariation();
4286  }
4287  }
4288  db->game->RestoreState ();
4289  return TCL_OK;
4290 }
4291 
4292 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4293 // sc_game_startBoard:
4294 // Sets the starting position from a FEN string.
4295 // If there is no FEN string argument, a boolean value is
4296 // returned indicating whether the current game starts with
4297 // a setup position.
4298 int
4299 sc_game_startBoard (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4300 {
4301  if (argc == 2) {
4302  return UI_Result(ti, OK, db->game->HasNonStandardStart());
4303  } else if (argc != 3) {
4304  return errorResult (ti, "Usage: sc_game startBoard <fenString>");
4305  }
4306  const char * str = argv[2];
4307  Position scratchPos;
4308  if (strIsPrefix ("random:", str)) {
4309  // A "FEN" string that starts with "random:" is interpreted as a
4310  // material configuration, and a random position with this
4311  // set of material is generated. For example, "random:krpkr"
4312  // generates a random legal Rook+Pawn-vs-Rook position.
4313  if (scratchPos.Random (str+7) != OK) {
4314  return errorResult (ti, "Invalid material string.");
4315  }
4316  } else {
4317  if (scratchPos.ReadFromFEN (str) != OK) {
4318  if (scratchPos.ReadFromLongStr (str) != OK) {
4319  return errorResult (ti, "Invalid FEN string.");
4320  }
4321  }
4322  // ReadFromFEN checks that there is one king of each side, but it
4323  // does not check that the position is actually legal:
4324  if (! scratchPos.IsLegal()) {
4325  // Illegal position! Find out why to return a useful error:
4326  squareT wk = scratchPos.GetKingSquare (WHITE);
4327  squareT bk = scratchPos.GetKingSquare (BLACK);
4328  if (square_Adjacent (wk, bk)) {
4329  return errorResult (ti, "Illegal position: adjacent kings.");
4330  }
4331  // No adjacent kings, so enemy king must be in check.
4332  return errorResult (ti, "Illegal position: enemy king in check.");
4333  }
4334  }
4335  db->game->SetStartPos(&scratchPos);
4336  db->gameAltered = true;
4337  return TCL_OK;
4338 }
4339 
4340 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4341 // sc_game_strip:
4342 // Strips all comments, variations or annotations from a game.
4343 int
4344 sc_game_strip (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4345 {
4346  const char * usage =
4347  "Usage: sc_game strip [comments|variations]";
4348 
4349  const char * options[] = { "comments", "variations", NULL };
4350  enum { OPT_COMS, OPT_VARS };
4351 
4352  // we need to switch off short header style or PGN parsing will not work
4353  uint old_style = db->game->GetPgnStyle ();
4354  if (old_style & PGN_STYLE_SHORT_HEADER)
4355  db->game->SetPgnStyle (PGN_STYLE_SHORT_HEADER, false);
4356 
4361 
4362  int index = -1;
4363  if (argc == 3) { index = strUniqueMatch (argv[2], options); }
4364 
4365  switch (index) {
4366  case OPT_COMS: db->game->RemovePgnStyle (PGN_STYLE_COMMENTS); break;
4367  case OPT_VARS: db->game->RemovePgnStyle (PGN_STYLE_VARS); break;
4368  default: return errorResult (ti, usage);
4369  }
4370 
4371  int old_lang = language;
4372  language = 0;
4373  std::pair<const char*, unsigned> pgnBuf = db->game->WriteToPGN();
4374  PgnParser parser;
4375  parser.Reset (pgnBuf.first);
4376  scratchGame->Clear();
4377  if (parser.ParseGame (scratchGame)) {
4378  return errorResult (ti, "Error: unable to strip this game.");
4379  }
4380  parser.Reset (pgnBuf.first);
4381  db->game->Clear();
4382  parser.ParseGame (db->game);
4383 
4384  // Restore PGN style (Short header)
4385  if (old_style & PGN_STYLE_SHORT_HEADER)
4386  db->game->SetPgnStyle (PGN_STYLE_SHORT_HEADER, true);
4387 
4388  db->gameAltered = true;
4389  language = old_lang;
4390  return TCL_OK;
4391 }
4392 
4393 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4394 // sc_game_summary:
4395 // Returns summary information of the specified game:
4396 // its players, site, etc; or its moves; or all its boards
4397 // positions.
4398 int
4399 sc_game_summary (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4400 {
4401  const char * usage = "Usage: sc_game summary [-base <baseNum>] [-gameNumber <gameNum>] header|boards|moves";
4402 
4403  const char * options[] = {
4404  "-base", "-gameNumber", NULL
4405  };
4406  enum { OPT_BASE, OPT_GNUM };
4407 
4408  const scidBaseT* base = db;
4409  uint gnum = 0;
4410 
4411  int arg = 2;
4412  while (arg+1 < argc) {
4413  const char * value = argv[arg+1];
4414  int index = strUniqueMatch (argv[arg], options);
4415  arg += 2;
4416 
4417  if (index == OPT_BASE) {
4418  base = DBasePool::getBase(strGetUnsigned(value));
4419  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
4420  } else if (index == OPT_GNUM) {
4421  gnum = strGetUnsigned (value);
4422  } else {
4423  return errorResult (ti, usage);
4424  }
4425  }
4426  if (arg+1 != argc) { return errorResult (ti, usage); }
4427 
4428  enum modeT { MODE_HEADER, MODE_BOARDS, MODE_MOVES };
4429  modeT mode = MODE_HEADER;
4430  switch (tolower(argv[arg][0])) {
4431  case 'h': mode = MODE_HEADER; break;
4432  case 'b': mode = MODE_BOARDS; break;
4433  case 'm': mode = MODE_MOVES; break;
4434  default: return errorResult (ti, usage);
4435  }
4436 
4437  Game * g = scratchGame;
4438  if (gnum == 0) {
4439  g = base->game;
4440  } else {
4441  // Load the specified game number:
4442  if (! base->inUse) {
4443  return errorResult (ti, "This database is not in use.");
4444  }
4445  if (gnum > base->numGames()) {
4446  return errorResult (ti, "Invalid game number.");
4447  }
4448  gnum--;
4449  const IndexEntry* ie = base->getIndexEntry(gnum);
4450  if (base->getGame(ie, base->bbuf) != OK) {
4451  return errorResult (ti, "Error loading game.");
4452  }
4453  g->Clear();
4454  if (g->Decode (base->bbuf, GAME_DECODE_NONE) != OK) {
4455  return errorResult (ti, "Error decoding game.");
4456  }
4457  g->LoadStandardTags (ie, base->getNameBase());
4458  }
4459 
4460  // Return header summary if requested:
4461  if (mode == MODE_HEADER) {
4462  DString * dstr = new DString;
4463  dstr->Append (g->GetWhiteStr());
4464  eloT elo = g->GetWhiteElo();
4465  if (elo > 0) { dstr->Append (" (", elo, ")"); }
4466  dstr->Append (" -- ", g->GetBlackStr());
4467  elo = g->GetBlackElo();
4468  if (elo > 0) { dstr->Append (" (", elo, ")"); }
4469  dstr->Append ("\n", g->GetEventStr());
4470  const char * round = g->GetRoundStr();
4471  if (! strIsUnknownName(round)) {
4472  dstr->Append (" (", round, ")");
4473  }
4474  dstr->Append (" ", g->GetSiteStr(), "\n");
4475  char dateStr [20];
4476  date_DecodeToString (g->GetDate(), dateStr);
4477  // Remove ".??" or ".??.??" from end of date:
4478  if (dateStr[4] == '.' && dateStr[5] == '?') { dateStr[4] = 0; }
4479  if (dateStr[7] == '.' && dateStr[8] == '?') { dateStr[7] = 0; }
4480  dstr->Append (dateStr, " ");
4481  dstr->Append (RESULT_LONGSTR[g->GetResult()]);
4482  ecoT eco = g->GetEco();
4483  if (eco != 0) {
4484  ecoStringT ecoStr;
4485  eco_ToExtendedString (eco, ecoStr);
4486  dstr->Append (" ", ecoStr);
4487  }
4488  Tcl_AppendResult (ti, dstr->Data(), NULL);
4489  delete dstr;
4490  return TCL_OK;
4491  }
4492 
4493  // Here, a list of the boards or moves is requested:
4494  g->SaveState();
4495  g->MoveToPly (0);
4496  while (1) {
4497  if (mode == MODE_BOARDS) {
4498  char boardStr[100];
4499  g->GetCurrentPos()->MakeLongStr (boardStr);
4500  Tcl_AppendElement (ti, boardStr);
4501  } else {
4502  colorT toMove = g->GetCurrentPos()->GetToMove();
4503  uint moveCount = g->GetCurrentPos()->GetFullMoveCount();
4504  char san [20];
4505  g->GetSAN (san);
4506  if (san[0] != 0) {
4507  char temp[40];
4508  if (toMove == WHITE) {
4509  sprintf (temp, "%u.%s", moveCount, san);
4510  } else {
4511  strCopy (temp, san);
4512  }
4513  byte * nags = g->GetNextNags();
4514  if (*nags != 0) {
4515  for (uint nagCount = 0 ; nags[nagCount] != 0; nagCount++) {
4516  char nagstr[20];
4517  game_printNag (nags[nagCount], nagstr, true,
4519  if (nagCount > 0 ||
4520  (nagstr[0] != '!' && nagstr[0] != '?')) {
4521  strAppend (temp, " ");
4522  }
4523  strAppend (temp, nagstr);
4524  }
4525  }
4526  Tcl_AppendElement (ti, temp);
4527  } else {
4528  Tcl_AppendElement (ti, (char *)RESULT_LONGSTR[g->GetResult()]);
4529  }
4530  }
4531  if (g->MoveForward() != OK) { break; }
4532  }
4533 
4534  g->RestoreState();
4535  return TCL_OK;
4536 }
4537 
4538 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4539 // sc_game_tags:
4540 // Get, set or reload the current game tags, or share them
4541 // with another game.
4542 int
4543 sc_game_tags (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
4544 {
4545  const char * options[] = {
4546  "get", "set", "reload", "share", NULL
4547  };
4548  enum { OPT_GET, OPT_SET, OPT_RELOAD, OPT_SHARE };
4549 
4550  int index = -1;
4551  if (argc >= 3) { index = strUniqueMatch (argv[2], options); }
4552 
4553  switch (index) {
4554  case OPT_GET: return sc_game_tags_get (cd, ti, argc, argv);
4555  case OPT_SET:
4556  return sc_game_tags_set (cd, ti, argc, argv);
4557  case OPT_RELOAD: return sc_game_tags_reload (cd, ti, argc, argv);
4558  case OPT_SHARE: return sc_game_tags_share (cd, ti, argc, argv);
4559  default: return InvalidCommand (ti, "sc_game tags", options);
4560  }
4561  return TCL_OK;
4562 }
4563 
4564 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4565 // sc_game_tags_get:
4566 // Gets a tag for the active game given its name.
4567 // Valid names are: Event, Site, Date, Round, White, Black,
4568 // WhiteElo, BlackElo, ECO, Extra.
4569 // All except the last (Extra) return the tag value as a string.
4570 // For "Extra", the function returns all the extra tags as one long
4571 // string, in PGN format, one tag per line.
4572 int
4573 sc_game_tags_get (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4574 {
4575 
4576  static const char * options [] = {
4577  "Event", "Site", "Date", "Year", "Month", "Day",
4578  "Round", "White", "Black", "Result", "WhiteElo",
4579  "BlackElo", "WhiteRType", "BlackRType", "ECO",
4580  "EDate", "EYear", "EMonth", "EDay", "Extra",
4581  NULL
4582  };
4583  enum {
4584  T_Event, T_Site, T_Date, T_Year, T_Month, T_Day,
4585  T_Round, T_White, T_Black, T_Result, T_WhiteElo,
4586  T_BlackElo, T_WhiteRType, T_BlackRType, T_ECO,
4587  T_EDate, T_EYear, T_EMonth, T_EDay, T_Extra
4588  };
4589 
4590  const char * usage = "Usage: sc_game tags get [-last] <tagName>";
4591  const char * tagName;
4592  Game * g = db->game;
4593 
4594  if (argc < 4 || argc > 5) {
4595  return errorResult (ti, usage);
4596  }
4597  tagName = argv[3];
4598  if (argc == 5) {
4599  if (!strEqual (argv[3], "-last")) { return errorResult (ti, usage); }
4600  tagName = argv[4];
4601  if (db->numGames() > 0) {
4602  g = scratchGame;
4603  const IndexEntry* ie = db->getIndexEntry(db->numGames() - 1);
4604  if (db->getGame(ie, db->bbuf) != OK) {
4605  return errorResult (ti, "Error reading game file.");
4606  }
4607  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
4608  return errorResult (ti, "Error decoding game.");
4609  }
4610  g->LoadStandardTags (ie, db->getNameBase());
4611  }
4612  }
4613  const char * s;
4614  int index = strExactMatch (tagName, options);
4615 
4616  switch (index) {
4617  case T_Event:
4618  s = g->GetEventStr(); if (!s) { s = "?"; }
4619  Tcl_AppendResult (ti, s, NULL);
4620  break;
4621 
4622  case T_Site:
4623  s = g->GetSiteStr(); if (!s) { s = "?"; }
4624  Tcl_AppendResult (ti, s, NULL);
4625  break;
4626 
4627  case T_Date:
4628  {
4629  char dateStr[20];
4630  date_DecodeToString (g->GetDate(), dateStr);
4631  Tcl_AppendResult (ti, dateStr, NULL);
4632  }
4633  break;
4634 
4635  case T_Year:
4636  return setUintResult (ti, date_GetYear (g->GetDate()));
4637 
4638  case T_Month:
4639  return setUintWidthResult (ti, date_GetMonth (g->GetDate()), 2);
4640 
4641  case T_Day:
4642  return setUintWidthResult (ti, date_GetDay (g->GetDate()), 2);
4643 
4644  case T_Round:
4645  s = g->GetRoundStr(); if (!s) { s = "?"; }
4646  Tcl_AppendResult (ti, s, NULL);
4647  break;
4648 
4649  case T_White:
4650  s = g->GetWhiteStr(); if (!s) { s = "?"; }
4651  Tcl_AppendResult (ti, s, NULL);
4652  break;
4653 
4654  case T_Black:
4655  s = g->GetBlackStr(); if (!s) { s = "?"; }
4656  Tcl_AppendResult (ti, s, NULL);
4657  break;
4658 
4659  case T_Result:
4660  return UI_Result(ti, OK, std::string(1, RESULT_CHAR[g->GetResult()]));
4661 
4662  case T_WhiteElo:
4663  return setUintResult (ti, g->GetWhiteElo());
4664 
4665  case T_BlackElo:
4666  return setUintResult (ti, g->GetBlackElo());
4667 
4668  case T_WhiteRType:
4669  return setResult (ti, ratingTypeNames[g->GetWhiteRatingType()]);
4670 
4671  case T_BlackRType:
4672  return setResult (ti, ratingTypeNames[g->GetBlackRatingType()]);
4673 
4674  case T_ECO:
4675  {
4676  ecoStringT ecoStr;
4677  eco_ToExtendedString (g->GetEco(), ecoStr);
4678  Tcl_AppendResult (ti, ecoStr, NULL);
4679  break;
4680  }
4681 
4682  case T_EDate:
4683  {
4684  char dateStr[20];
4685  date_DecodeToString (g->GetEventDate(), dateStr);
4686  Tcl_AppendResult (ti, dateStr, NULL);
4687  }
4688  break;
4689 
4690  case T_EYear:
4691  return setUintResult (ti, date_GetYear (g->GetEventDate()));
4692 
4693  case T_EMonth:
4694  return setUintWidthResult (ti, date_GetMonth (g->GetEventDate()), 2);
4695 
4696  case T_EDay:
4697  return setUintWidthResult (ti, date_GetDay (g->GetEventDate()), 2);
4698 
4699  case T_Extra:
4700  {
4701  uint numTags = g->GetNumExtraTags();
4702  tagT * ptagList = g->GetExtraTags();
4703  while (numTags > 0) {
4704  Tcl_AppendResult (ti, ptagList->tag, " \"", ptagList->value,
4705  "\"\n", NULL);
4706  numTags--;
4707  ptagList++;
4708  }
4709  }
4710  break;
4711 
4712  default: // Not a valid tag name.
4713  return InvalidCommand (ti, "sc_game tags get", options);
4714  }
4715 
4716  return TCL_OK;
4717 }
4718 
4719 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4720 // sc_game_tags_set:
4721 // Set the standard tags for this game.
4722 // Args are: event, site, date, round, white, black, result,
4723 // whiteElo, whiteRatingType, blackElo, blackRatingType, Eco,
4724 // eventdate.
4725 // Last arg is the non-standard tags, a string of lines in the format:
4726 // [TagName "TagValue"]
4727 int
4728 sc_game_tags_set (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4729 {
4730  const char * options[] = {
4731  "-event", "-site", "-date", "-round", "-white", "-black", "-result",
4732  "-whiteElo", "-whiteRatingType", "-blackElo", "-blackRatingType",
4733  "-eco", "-eventdate", "-extra",
4734  NULL
4735  };
4736  enum {
4737  T_EVENT, T_SITE, T_DATE, T_ROUND, T_WHITE, T_BLACK, T_RESULT,
4738  T_WHITE_ELO, T_WHITE_RTYPE, T_BLACK_ELO, T_BLACK_RTYPE,
4739  T_ECO, T_EVENTDATE, T_EXTRA
4740  };
4741 
4742  int arg = 3;
4743  if (((argc-arg) % 2) != 0) {
4744  return errorResult (ti, "Odd number of parameters.");
4745  }
4746 
4747  // Process each pair of parameters:
4748  while (arg+1 < argc) {
4749  int index = strUniqueMatch (argv[arg], options);
4750  const char * value = argv[arg+1];
4751  arg += 2;
4752 
4753  switch (index) {
4754  case T_EVENT: db->game->SetEventStr (value); break;
4755  case T_SITE: db->game->SetSiteStr (value); break;
4756  case T_DATE:
4757  db->game->SetDate (date_EncodeFromString(value));
4758  break;
4759  case T_ROUND: db->game->SetRoundStr (value); break;
4760  case T_WHITE: db->game->SetWhiteStr (value); break;
4761  case T_BLACK: db->game->SetBlackStr (value); break;
4762  case T_RESULT: db->game->SetResult (strGetResult(value)); break;
4763  case T_WHITE_ELO:
4764  db->game->SetWhiteElo (strGetUnsigned(value)); break;
4765  case T_WHITE_RTYPE:
4766  db->game->SetWhiteRatingType (strGetRatingType (value)); break;
4767  case T_BLACK_ELO:
4768  db->game->SetBlackElo (strGetUnsigned(value)); break;
4769  case T_BLACK_RTYPE:
4770  db->game->SetBlackRatingType (strGetRatingType (value)); break;
4771  case T_ECO:
4772  db->game->SetEco (eco_FromString (value)); break;
4773  case T_EVENTDATE:
4774  db->game->SetEventDate (date_EncodeFromString(value));
4775  break;
4776  case T_EXTRA:
4777  {
4778  // Add all the nonstandard tags:
4779  db->game->ClearExtraTags ();
4780  int largc;
4781  const char ** largv;
4782  if (Tcl_SplitList (ti, value, &largc,
4783  (CONST84 char ***) &largv) != TCL_OK) {
4784  // Error from Tcl_SplitList!
4785  return errorResult (ti, "Error parsing extra tags.");
4786  }
4787 
4788  // Extract each tag-value pair and add it to the game:
4789  for (int i=0; i < largc; i++) {
4790  char tagStr [1024];
4791  char valueStr [1024];
4792  //if ( sscanf (largv[i], "%s", tagStr ) == 1 &&
4793  // sscanf (largv[i+1], "%s", valueStr) == 1) {
4794  // Usage :: sc_game tags set -extra [ list "Annotator \"boob [sc_pos moveNumber]\"\n" ]
4795  if (sscanf (largv[i], "%s \"%[^\"]\"\n", tagStr, valueStr) == 2) {
4796  db->game->AddPgnTag (tagStr, valueStr);
4797  } else {
4798  // Invalid line in the list; just ignore it.
4799  }
4800  }
4801  Tcl_Free ((char *) largv);
4802  }
4803  break;
4804  default:
4805  return InvalidCommand (ti, "sc_game tags set", options);
4806  }
4807  }
4808 
4809  return TCL_OK;
4810 }
4811 
4812 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4813 // sc_game_tags_reload:
4814 // Reloads the tags (White, Black, Event,Site, etc) for a game.
4815 // Useful when a name that may occur in the current game has been
4816 // edited.
4817 int
4818 sc_game_tags_reload(ClientData, Tcl_Interp*, int, const char**)
4819 {
4820  if (!db->inUse || db->gameNumber < 0) { return TCL_OK; }
4821  const IndexEntry* ie = db->getIndexEntry(db->gameNumber);
4822  db->game->LoadStandardTags (ie, db->getNameBase());
4823  return TCL_OK;
4824 }
4825 
4826 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4827 // sc_game_tags_share:
4828 // Shares tags between two games, updating one where the other
4829 // has more complete or better information.
4830 //
4831 // This is mainly useful for combining the header information
4832 // of a pair of twins before deleting one of them. For example,
4833 // one may have a less complete date while the other may have
4834 // no ratings or an unknown ("?") round value.
4835 //
4836 // If the subcommand parameter is "check", a list is returned
4837 // with a multiple of four elements, each set of four indicating
4838 // a game number, the tag that will be changed, the old value,
4839 // and the new value. If the parameter is "update", the changes
4840 // will be made and the empty string is returned.
4841 int
4842 sc_game_tags_share (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4843 {
4844  const char * usage =
4845  "Usage: sc_game tags share [check|update] <gameNumber1> <gameNumber2>";
4846  if (argc != 6) { return errorResult (ti, usage); }
4847  bool updateMode = false;
4848  if (strIsPrefix (argv[3], "check")) {
4849  updateMode = false;
4850  } else if (strIsPrefix (argv[3], "update")) {
4851  updateMode = true;
4852  } else {
4853  return errorResult (ti, usage);
4854  }
4855  // Get the two game numbers, which should be different and non-zero.
4856  uint gn1 = strGetUnsigned (argv[4]);
4857  uint gn2 = strGetUnsigned (argv[5]);
4858  if (gn1 == 0) { return TCL_OK; }
4859  if (gn2 == 0) { return TCL_OK; }
4860  if (gn1 == gn2) { return TCL_OK; }
4861  if (gn1 > db->numGames()) { return TCL_OK; }
4862  if (gn2 > db->numGames()) { return TCL_OK; }
4863 
4864  // Do nothing if the base is not writable:
4865  if (!db->inUse || db->isReadOnly()) { return TCL_OK; }
4866 
4867  // Make a local copy of each index entry:
4868  IndexEntry ie1 = *(db->getIndexEntry(gn1 - 1));
4869  IndexEntry ie2 = *(db->getIndexEntry(gn2 - 1));
4870  bool updated1 = false;
4871  bool updated2 = false;
4872 
4873  // Share dates if appropriate:
4874  char dateStr1 [16];
4875  char dateStr2 [16];
4876  dateT date1 = ie1.GetDate();
4877  dateT date2 = ie2.GetDate();
4878  date_DecodeToString (date1, dateStr1);
4879  date_DecodeToString (date2, dateStr2);
4880  strTrimDate (dateStr1);
4881  strTrimDate (dateStr2);
4882  if (date1 == 0) { *dateStr1 = 0; }
4883  if (date2 == 0) { *dateStr2 = 0; }
4884  // Check if one date is a prefix of the other:
4885  if (!strEqual (dateStr1, dateStr2) && strIsPrefix (dateStr1, dateStr2)) {
4886  // Copy date grom game 2 to game 1:
4887  if (updateMode) {
4888  ie1.SetDate (date2);
4889  updated1 = true;
4890  } else {
4891  appendUintElement (ti, gn1);
4892  Tcl_AppendElement (ti, "Date");
4893  Tcl_AppendElement (ti, dateStr1);
4894  Tcl_AppendElement (ti, dateStr2);
4895  }
4896  }
4897  if (!strEqual (dateStr1, dateStr2) && strIsPrefix (dateStr2, dateStr1)) {
4898  // Copy date grom game 1 to game 2:
4899  if (updateMode) {
4900  ie2.SetDate (date1);
4901  updated2 = true;
4902  } else {
4903  appendUintElement (ti, gn2);
4904  Tcl_AppendElement (ti, "Date");
4905  Tcl_AppendElement (ti, dateStr2);
4906  Tcl_AppendElement (ti, dateStr1);
4907  }
4908  }
4909 
4910  // Check if an event name can be updated:
4911  idNumberT event1 = ie1.GetEvent();
4912  idNumberT event2 = ie2.GetEvent();
4913  const char * eventStr1 = ie1.GetEventName (db->getNameBase());
4914  const char * eventStr2 = ie2.GetEventName (db->getNameBase());
4915  bool event1empty = strEqual (eventStr1, "") || strEqual (eventStr1, "?");
4916  bool event2empty = strEqual (eventStr2, "") || strEqual (eventStr2, "?");
4917  if (event1empty && !event2empty) {
4918  // Copy event from event 2 to game 1:
4919  if (updateMode) {
4920  ie1.SetEvent (event2);
4921  updated1 = true;
4922  } else {
4923  appendUintElement (ti, gn1);
4924  Tcl_AppendElement (ti, "Event");
4925  Tcl_AppendElement (ti, eventStr1);
4926  Tcl_AppendElement (ti, eventStr2);
4927  }
4928  }
4929  if (event2empty && !event1empty) {
4930  // Copy event from game 1 to game 2:
4931  if (updateMode) {
4932  ie2.SetEvent (event1);
4933  updated2 = true;
4934  } else {
4935  appendUintElement (ti, gn2);
4936  Tcl_AppendElement (ti, "Event");
4937  Tcl_AppendElement (ti, eventStr2);
4938  Tcl_AppendElement (ti, eventStr1);
4939  }
4940  }
4941 
4942  // Check if a round name can be updated:
4943  idNumberT round1 = ie1.GetRound();
4944  idNumberT round2 = ie2.GetRound();
4945  const char * roundStr1 = ie1.GetRoundName (db->getNameBase());
4946  const char * roundStr2 = ie2.GetRoundName (db->getNameBase());
4947  bool round1empty = strEqual (roundStr1, "") || strEqual (roundStr1, "?");
4948  bool round2empty = strEqual (roundStr2, "") || strEqual (roundStr2, "?");
4949  if (round1empty && !round2empty) {
4950  // Copy round from game 2 to game 1:
4951  if (updateMode) {
4952  ie1.SetRound (round2);
4953  updated1 = true;
4954  } else {
4955  appendUintElement (ti, gn1);
4956  Tcl_AppendElement (ti, "Round");
4957  Tcl_AppendElement (ti, roundStr1);
4958  Tcl_AppendElement (ti, roundStr2);
4959  }
4960  }
4961  if (round2empty && !round1empty) {
4962  // Copy round from game 1 to game 2:
4963  if (updateMode) {
4964  ie2.SetRound (round1);
4965  updated2 = true;
4966  } else {
4967  appendUintElement (ti, gn2);
4968  Tcl_AppendElement (ti, "Round");
4969  Tcl_AppendElement (ti, roundStr2);
4970  Tcl_AppendElement (ti, roundStr1);
4971  }
4972  }
4973 
4974  // Check if Elo ratings can be shared:
4975  eloT welo1 = ie1.GetWhiteElo();
4976  eloT belo1 = ie1.GetBlackElo();
4977  eloT welo2 = ie2.GetWhiteElo();
4978  eloT belo2 = ie2.GetBlackElo();
4979  if (welo1 == 0 && welo2 != 0) {
4980  // Copy White rating from game 2 to game 1:
4981  if (updateMode) {
4982  ie1.SetWhiteElo (welo2);
4983  updated1 = true;
4984  } else {
4985  appendUintElement (ti, gn1);
4986  Tcl_AppendElement (ti, "WhiteElo");
4987  appendUintElement (ti, welo1);
4988  appendUintElement (ti, welo2);
4989  }
4990  }
4991  if (welo2 == 0 && welo1 != 0) {
4992  // Copy White rating from game 1 to game 2:
4993  if (updateMode) {
4994  ie2.SetWhiteElo (welo1);
4995  updated2 = true;
4996  } else {
4997  appendUintElement (ti, gn2);
4998  Tcl_AppendElement (ti, "WhiteElo");
4999  appendUintElement (ti, welo2);
5000  appendUintElement (ti, welo1);
5001  }
5002  }
5003  if (belo1 == 0 && belo2 != 0) {
5004  // Copy Black rating from game 2 to game 1:
5005  if (updateMode) {
5006  ie1.SetBlackElo (belo2);
5007  updated1 = true;
5008  } else {
5009  appendUintElement (ti, gn1);
5010  Tcl_AppendElement (ti, "BlackElo");
5011  appendUintElement (ti, belo1);
5012  appendUintElement (ti, belo2);
5013  }
5014  }
5015  if (belo2 == 0 && belo1 != 0) {
5016  // Copy Black rating from game 1 to game 2:
5017  if (updateMode) {
5018  ie2.SetBlackElo (belo1);
5019  updated2 = true;
5020  } else {
5021  appendUintElement (ti, gn2);
5022  Tcl_AppendElement (ti, "BlackElo");
5023  appendUintElement (ti, belo2);
5024  appendUintElement (ti, belo1);
5025  }
5026  }
5027 
5028  // Write changes to the index file:
5029  if (updateMode && (updated1 || updated2)) {
5030  db->beginTransaction();
5031  if (updated1) {
5032  db->idx->WriteEntry (&ie1, gn1 - 1);
5033  }
5034  if (updated2) {
5035  db->idx->WriteEntry (&ie2, gn2 - 1);
5036  }
5037  db->endTransaction();
5038  }
5039  return TCL_OK;
5040 }
5041 
5042 //////////////////////////////////////////////////////////////////////
5043 /// INFO functions
5044 
5045 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5046 // sc_info:
5047 // General Scid Information commands.
5048 int
5049 sc_info (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
5050 {
5051  static const char * options [] = {
5052  "clipbase", "decimal", "fsize", "gzip",
5053  "html", "limit", "ratings",
5054  "suffix", "tb", "validDate", "version", "language", NULL
5055  };
5056  enum {
5057  INFO_CLIPBASE, INFO_DECIMAL, INFO_FSIZE, INFO_GZIP,
5058  INFO_HTML, INFO_LIMIT, INFO_RATINGS,
5059  INFO_SUFFIX, INFO_TB, INFO_VALIDDATE, INFO_VERSION, INFO_LANGUAGE
5060  };
5061  int index = -1;
5062 
5063  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
5064 
5065  switch (index) {
5066  case INFO_CLIPBASE:
5067  return UI_Result(ti, OK, DBasePool::getClipBase());
5068 
5069  case INFO_DECIMAL:
5070  if (argc >= 3) {
5071  decimalPointChar = argv[2][0];
5072  } else {
5073  return UI_Result(ti, OK, std::string(1, decimalPointChar));
5074  }
5075  break;
5076 
5077  case INFO_FSIZE:
5078  return sc_info_fsize (cd, ti, argc, argv);
5079 
5080  case INFO_GZIP:
5081  // Return true if gzip files can be decoded by Scid.
5082  return UI_Result(ti, OK, gzable());
5083 
5084  case INFO_HTML:
5085  if (argc >= 3) {
5086  htmlDiagStyle = strGetUnsigned (argv[2]);
5087  } else {
5088  return setUintResult (ti, htmlDiagStyle);
5089  }
5090  break;
5091 
5092  case INFO_LIMIT:
5093  return sc_info_limit (cd, ti, argc, argv);
5094 
5095  case INFO_RATINGS: // List of all recognised rating types.
5096  {
5097  uint i = 0;
5098  while (ratingTypeNames[i] != NULL) {
5099  Tcl_AppendElement (ti, (char *) ratingTypeNames[i]);
5100  i++;
5101  }
5102  }
5103  break;
5104 
5105  case INFO_SUFFIX:
5106  return sc_info_suffix (cd, ti, argc, argv);
5107 
5108  case INFO_TB:
5109  return sc_info_tb (cd, ti, argc, argv);
5110 
5111  case INFO_VALIDDATE:
5112  if (argc != 3) {
5113  return errorResult (ti, "Usage: sc_info validDate <datestring>");
5114  }
5115  return UI_Result(ti, OK, date_ValidString (argv[2]));
5116 
5117  case INFO_VERSION:
5118  if (argc >= 3 && strIsPrefix (argv[2], "date")) {
5119  setResult (ti, __DATE__);
5120  } else {
5122  }
5123  break;
5124  case INFO_LANGUAGE:
5125  if (argc != 3) {
5126  return errorResult (ti, "Usage: sc_info language <lang>");
5127  }
5128  if ( strcmp(argv[2], "en") == 0) {language = 0;}
5129  if ( strcmp(argv[2], "fr") == 0) {language = 1;}
5130  if ( strcmp(argv[2], "es") == 0) {language = 2;}
5131  if ( strcmp(argv[2], "de") == 0) {language = 3;}
5132  if ( strcmp(argv[2], "it") == 0) {language = 4;}
5133  if ( strcmp(argv[2], "ne") == 0) {language = 5;}
5134  if ( strcmp(argv[2], "cz") == 0) {language = 6;}
5135  if ( strcmp(argv[2], "hu") == 0) {language = 7;}
5136  if ( strcmp(argv[2], "no") == 0) {language = 8;}
5137  if ( strcmp(argv[2], "sw") == 0) {language = 9;}
5138  if ( strcmp(argv[2], "ca") == 0) {language = 10;}
5139  if ( strcmp(argv[2], "fi") == 0) {language = 11;}
5140  if ( strcmp(argv[2], "gr") == 0) {language = 12;}
5141 
5142  break;
5143  default:
5144  return InvalidCommand (ti, "sc_info", options);
5145  };
5146 
5147  return TCL_OK;
5148 }
5149 
5150 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5151 // sc_info_fsize:
5152 // Given the name of a .si3, .si, .pgn or .pgn.gz file, this command
5153 // returns the number of games in that file. For large PGN files,
5154 // the value returned is only an estimate.
5155 // To distinguish estimates from correct sizes, an estimate is
5156 // returned as a negative number.
5157 int
5158 sc_info_fsize (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5159 {
5160  if (argc != 3) {
5161  return errorResult (ti, "Usage: sc_info fsize <filename>");
5162  }
5163  const char * fname = argv[2];
5164  const char * lastSuffix = strFileSuffix (fname);
5165  uint fsize = 0;
5166  bool isGzipFile = false;
5167  bool isEpdFile = false;
5168  bool isRepFile = false;
5169 
5170  if (strAlphaContains (fname, ".epd")) { isEpdFile = true; }
5171  if (strAlphaContains (fname, ".sor")) { isRepFile = true; }
5172  if (lastSuffix != NULL && strEqual (lastSuffix, GZIP_SUFFIX)) {
5173  isGzipFile = true;
5174  }
5175 
5176  if (lastSuffix != NULL && strEqual (lastSuffix, OLD_INDEX_SUFFIX)) {
5177  fsize = rawFileSize (fname);
5178  fsize -= OLD_INDEX_HEADER_SIZE;
5179  fsize = fsize / OLD_INDEX_ENTRY_SIZE;
5180  return setUintResult (ti, fsize);
5181  }
5182  if (lastSuffix != NULL && strEqual (lastSuffix, INDEX_SUFFIX)) {
5183  fsize = rawFileSize (fname);
5184  fsize -= INDEX_HEADER_SIZE;
5185  fsize = fsize / INDEX_ENTRY_SIZE;
5186  return setUintResult (ti, fsize);
5187  }
5188 
5189  // Estimate size for PGN files, by reading the first 64 kb
5190  // of the file and counting the number of games seen:
5191 
5192  if (isGzipFile) {
5193  fsize = gzipFileSize (fname);
5194  } else {
5195  fsize = rawFileSize (fname);
5196  }
5197 
5198  MFile pgnFile;
5199  if (pgnFile.Open (fname, FMODE_ReadOnly) != OK) {
5200  return errorResult (ti, "Error opening file");
5201  }
5202 
5203  const uint maxBytes = 65536;
5204  char * buffer = new char [maxBytes];
5205  uint bytes = maxBytes - 1;
5206  if (bytes > fsize) { bytes = fsize; }
5207  if (pgnFile.ReadNBytes (buffer, bytes) != OK) {
5208  delete[] buffer;
5209  return errorResult (ti, "Error reading file");
5210  }
5211 
5212  buffer [bytes] = 0;
5213  const char * s = buffer;
5214  int ngames = 0;
5215 
5216  for (uint i=0; i < bytes; i++) {
5217  if (isEpdFile) {
5218  // EPD file: count positions, one per line.
5219  if (*s == '\n') { ngames++; }
5220  } else if (isRepFile) {
5221  // Repertoire file: count include (+) and exclude (-) lines.
5222  if (*s == ' ' || *s == '\n') {
5223  if (s[1] == '+' && s[2] == ' ') { ngames++; }
5224  if (s[1] == '-' && s[2] == ' ') { ngames++; }
5225  }
5226  } else {
5227  // PGN file: count Result tags.
5228  if (*s == '[' && strIsPrefix ("Result ", s+1)) { ngames++; }
5229  }
5230  s++;
5231  }
5232 
5233  // If the file is larger than maxBytes, this was only a sample
5234  // so return an estimate to the nearest 10 or 100 or 1000 games:
5235  if (fsize > bytes) {
5236  ngames = (uint) ((double)ngames * (double)fsize / (double)bytes);
5237  if (ngames > 10000) {
5238  ngames = ((ngames + 500) / 1000) * 1000;
5239  } else if (ngames > 1000) {
5240  ngames = ((ngames + 50) / 100) * 100;
5241  } else {
5242  ngames = ((ngames + 5) / 10) * 10;
5243  }
5244  ngames = -ngames;
5245  }
5246 #ifdef WINCE
5247  my_Tcl_Free((char*) buffer);
5248 #else
5249  delete[] buffer;
5250 #endif
5251  return setIntResult (ti, ngames);
5252 }
5253 
5254 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5255 // sc_info limit:
5256 // Limits that Scid imposes.
5257 int
5258 sc_info_limit (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5259 {
5260  static const char * options [] = {
5261  "elo", "games", "nags", "year", "bases", NULL
5262  };
5263  enum {
5264  LIM_ELO, LIM_GAMES, LIM_NAGS, LIM_YEAR, LIM_BASES
5265  };
5266  int index = -1;
5267  int result = 0;
5268 
5269  if (argc == 3) { index = strUniqueMatch (argv[2], options); }
5270 
5271  switch (index) {
5272  case LIM_ELO:
5273  result = MAX_ELO;
5274  break;
5275 
5276  case LIM_GAMES:
5277  result = MAX_GAMES;
5278  break;
5279 
5280  case LIM_NAGS:
5281  result = MAX_NAGS;
5282  break;
5283 
5284  case LIM_YEAR:
5285  result = YEAR_MAX;
5286  break;
5287 
5288  case LIM_BASES:
5289  result = MAX_BASES;
5290  break;
5291 
5292  default:
5293  return UI_Result(ti, ERROR_BadArg, "Usage: sc_info limit <elo|games|nags|year|bases>");
5294  }
5295 
5296  return UI_Result(ti, OK, result);
5297 }
5298 
5299 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5300 // sc_info suffix:
5301 // Returns a Scid file suffix for a database file type.
5302 // The suffix is returned with the leading dot.
5303 int
5304 sc_info_suffix (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5305 {
5306  static const char * options [] = {
5307  "index", NULL
5308  };
5309  enum {
5310  SUFFIX_OPT_INDEX
5311  };
5312  int index = -1;
5313 
5314  if (argc == 3) { index = strUniqueMatch (argv[2], options); }
5315 
5316  const char * suffix = "";
5317 
5318  switch (index) {
5319  case SUFFIX_OPT_INDEX: suffix = INDEX_SUFFIX; break;
5320  default: return InvalidCommand (ti, "sc_info suffix", options);
5321  }
5322 
5323  return setResult (ti, suffix);
5324 }
5325 
5326 
5327 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5328 // sc_info_tb:
5329 // Set up a tablebase directory, or check if a certain
5330 // tablebase is available.
5331 int
5332 sc_info_tb (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5333 {
5334  const char * usage =
5335  "Usage: sc_info tb [<directory>|available <material>|cache <size-kb>]";
5336 
5337  if (argc == 2) {
5338  // Command: sc_info tb
5339  // Returns whether tablebase support is complied.
5340  return UI_Result(ti, OK, scid_TB_compiled());
5341 
5342  } else if (argc == 3) {
5343  // Command: sc_info_tb <directories>
5344  // Clears tablebases and registers all tablebases in the
5345  // specified directories string, which can have more than
5346  // one directory separated by commas or semicolons.
5347  return setUintResult (ti, scid_TB_Init (argv[2]));
5348 
5349  } else if (argc == 4 && argv[2][0] == 'a') {
5350  // Command: sc_probe available <material>
5351  // The material is specified as "KRKN", "kr-kn", etc.
5352  // Set up the required material:
5353  matSigT ms = MATSIG_Empty;
5354  const char * material = argv[3];
5355  if (toupper(*material) != 'K') { return UI_Result(ti, OK, false); }
5356  material++;
5357  colorT side = WHITE;
5358  while (1) {
5359  char ch = toupper(*material);
5360  material++;
5361  if (ch == 0) { break; }
5362  if (ch == 'K') { side = BLACK; continue; }
5363  pieceT p = piece_Make (side, piece_FromChar (ch));
5364  if (ch == 'P') { p = piece_Make (side, PAWN); }
5365  if (p == EMPTY) { continue; }
5366  ms = matsig_setCount (ms, p, matsig_getCount (ms, p) + 1);
5367  }
5368  // Check if a tablebase for this material is available:
5369  return UI_Result(ti, OK, scid_TB_Available (ms));
5370  } else if (argc == 4 && argv[2][0] == 'c') {
5371  // Set the preferred tablebase cache size, to take effect
5372  // at the next tablebase initialisation.
5373  uint cachesize = strGetUnsigned (argv[3]);
5374  scid_TB_SetCacheSize (cachesize * 1024);
5375  return TCL_OK;
5376  } else {
5377  return errorResult (ti, usage);
5378  }
5379 }
5380 
5381 //////////////////////////////////////////////////////////////////////
5382 // MOVE functions
5383 
5384 int
5385 sc_move (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
5386 {
5387  static const char * options [] = {
5388  "add", "addSan", "addUCI", "back", "end", "forward",
5389  "pgn", "ply", "start", NULL
5390  };
5391  enum {
5392  MOVE_ADD, MOVE_ADDSAN, MOVE_ADDUCI, MOVE_BACK, MOVE_END, MOVE_FORWARD,
5393  MOVE_PGN, MOVE_PLY, MOVE_START
5394  };
5395  int index = -1;
5396 
5397  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
5398 
5399  switch (index) {
5400  case MOVE_ADD:
5401  return sc_move_add (cd, ti, argc, argv);
5402 
5403  case MOVE_ADDSAN:
5404  return sc_move_addSan (cd, ti, argc, argv);
5405 
5406  case MOVE_ADDUCI:
5407  return sc_move_addUCI (cd, ti, argc, argv);
5408 
5409  case MOVE_BACK:
5410  return sc_move_back (cd, ti, argc, argv);
5411 
5412  case MOVE_END:
5413  db->game->MoveToPly(0);
5414  {
5415  errorT err = OK;
5416  do {
5417  err = db->game->MoveForward();
5418  } while (err == OK);
5419  }
5420  break;
5421 
5422  case MOVE_FORWARD:
5423  return sc_move_forward (cd, ti, argc, argv);
5424 
5425  case MOVE_PGN:
5426  return sc_move_pgn (cd, ti, argc, argv);
5427 
5428  case MOVE_PLY:
5429  if (argc >= 3) {
5430  std::vector<int> v;
5431  for(int i=2; i < argc; i++) {
5432  v.push_back(strGetInteger(argv[i]));
5433  }
5434  db->game->MoveTo(v);
5435  return TCL_OK;
5436  }
5437  return errorResult (ti, "Usage: sc_move ply <plynumber>");
5438 
5439  case MOVE_START:
5440  db->game->MoveToPly (0);
5441  break;
5442 
5443  default:
5444  return InvalidCommand (ti, "sc_move", options);
5445  }
5446 
5447  return TCL_OK;
5448 }
5449 
5450 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5451 // sc_move_add: takes a move specified by three parameters
5452 // (square square promo) and adds it to the game.
5453 int
5454 sc_move_add (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5455 {
5456 
5457  if (argc != 5) {
5458  return errorResult (ti, "Usage: sc_move add <sq> <sq> <promo>");
5459  }
5460 
5461  uint sq1 = strGetUnsigned (argv[2]);
5462  uint sq2 = strGetUnsigned (argv[3]);
5463  uint promo = strGetUnsigned (argv[4]);
5464  if (promo == 0) { promo = EMPTY; }
5465 
5466  char s[8];
5467  s[0] = square_FyleChar (sq1);
5468  s[1] = square_RankChar (sq1);
5469  s[2] = square_FyleChar (sq2);
5470  s[3] = square_RankChar (sq2);
5471  if (promo == EMPTY) {
5472  s[4] = 0;
5473  } else {
5474  s[4] = piece_Char(promo);
5475  s[5] = 0;
5476  }
5477  simpleMoveT sm;
5478  Position * pos = db->game->GetCurrentPos();
5479  errorT err = pos->ReadCoordMove (&sm, s, true);
5480  if (err == OK) {
5481  err = db->game->AddMove (&sm, NULL);
5482  if (err == OK) {
5483  db->gameAltered = true;
5484  return TCL_OK;
5485  }
5486  }
5487  return errorResult (ti, "Error adding move.");
5488 }
5489 
5490 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5491 // sc_move_addSan:
5492 // Takes moves in regular SAN (e.g. "e4" or "Nbd2") and adds them
5493 // to the game. The moves can be in one large string, separate
5494 // list elements, or a mixture of both. Move numbers are ignored
5495 // but variations/comments/annotations are parsed and added.
5496 int
5497 sc_move_addSan (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5498 {
5499  const char ** argPtr = &(argv[2]);
5500  int argsLeft = argc - 2;
5501 
5502  if (argc < 3) { return TCL_OK; }
5503 
5504  PgnParser parser;
5505  char buf [1000];
5506  while (argsLeft > 0) {
5507  parser.Reset (*argPtr);
5508  parser.SetEndOfInputWarnings (false);
5509  parser.SetResultWarnings (false);
5510  errorT err = parser.ParseMoves (db->game, buf, 1000);
5511  if (err != OK || parser.ErrorCount() > 0) {
5512  Tcl_AppendResult (ti, "Error reading move(s): ", *argPtr, NULL);
5513  return TCL_ERROR;
5514  }
5515  db->gameAltered = true;
5516  argPtr++;
5517  argsLeft--;
5518  }
5519 
5520  // If we reach here, all moves were successfully added:
5521  return TCL_OK;
5522 }
5523 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5524 // sc_move_addUCI:
5525 // Takes moves in engine UCI format (e.g. "g1f3") and adds them
5526 // to the game. The result is translated.
5527 // In case of an error, return the moves that could be converted.
5528 int
5529 sc_move_addUCI (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5530 {
5531  char s[8], tmp[10];
5532  if (argc < 3) { return TCL_OK; }
5533  char * ptr = (char *) argv[2];
5534 
5535  while (*ptr != 0) {
5536  s[0] = ptr[0];
5537  s[1] = ptr[1];
5538  s[2] = ptr[2];
5539  s[3] = ptr[3];
5540  if (ptr[4] == ' ') {
5541  s[4] = 0;
5542  ptr += 5;
5543  } else if (ptr[4] == 0) {
5544  s[4] = 0;
5545  ptr += 4;
5546  } else {
5547  s[4] = ptr[4];
5548  s[5] = 0;
5549  ptr += 6;
5550  }
5551  simpleMoveT sm;
5552  Position * pos = db->game->GetCurrentPos();
5553  errorT err = pos->ReadCoordMove (&sm, s, true);
5554  if (err == OK) {
5555  err = db->game->AddMove (&sm, NULL);
5556  if (err == OK) {
5557  db->gameAltered = true;
5558  db->game->GetPrevSAN (tmp);
5559  transPieces(tmp);
5560  Tcl_AppendResult (ti, tmp, " ", NULL);
5561  } else {
5562  //Tcl_AppendResult (ti, "Error reading move(s): ", ptr, NULL);
5563  break;
5564  }
5565  } else {
5566  //Tcl_AppendResult (ti, "Error reading move(s): ", ptr, NULL);
5567  break;
5568  }
5569  }
5570 
5571  return TCL_OK;
5572 }
5573 
5574 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5575 // sc_move_back:
5576 // Moves back a specified number of moves (default = 1 move).
5577 int
5578 sc_move_back (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5579 {
5580  int numMovesTakenBack = 0;
5581  int count = 1;
5582  if (argc > 2) {
5583  count = strGetInteger (argv[2]);
5584  // if (count < 1) { count = 1; }
5585  }
5586 
5587  for (int i = 0; i < count; i++) {
5588  if (db->game->MoveBackup() != OK) { break; }
5589  numMovesTakenBack++;
5590  }
5591 
5592  setUintResult (ti, numMovesTakenBack);
5593  return TCL_OK;
5594 }
5595 
5596 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5597 // sc_move_forward:
5598 // Moves forward a specified number of moves (default = 1 move).
5599 int
5600 sc_move_forward (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5601 {
5602  int numMovesMade = 0;
5603  int count = 1;
5604  if (argc > 2) {
5605  count = strGetInteger (argv[2]);
5606  // Do we want to allow moving forward 0 moves? Yes, I think so.
5607  // if (count < 1) { count = 1; }
5608  }
5609 
5610  for (int i = 0; i < count; i++) {
5611  if (db->game->MoveForward() != OK) { break; }
5612  numMovesMade++;
5613  }
5614 
5615  setUintResult (ti, numMovesMade);
5616  return TCL_OK;
5617 }
5618 
5619 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5620 // sc_move_pgn:
5621 // Set the current board to the position closest to
5622 // the specified place in the PGN output (given as a byte count
5623 // from the start of the output).
5624 int
5625 sc_move_pgn (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5626 {
5627  if (argc != 3) {
5628  return errorResult (ti, "Usage: sc_move pgn <offset>");