Scid  4.7.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
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 "optable.h"
33 #include "pbook.h"
34 #include "pgnparse.h"
35 #include "polyglot.h"
36 #include "position.h"
37 #include "probe.h"
38 #include "scidbase.h"
39 #include "searchpos.h"
40 #include "spellchk.h"
41 #include "stored.h"
42 #include "timer.h"
43 #include "tree.h"
44 #include "dbasepool.h"
45 #include "ui.h"
46 #include <algorithm>
47 #include <cstring>
48 #include <numeric>
49 #include <set>
50 
51 //TODO: delete
52 #include "tkscid.h"
53 
54 
55 //TODO: delete
56 extern scidBaseT* db;
57 const int MAX_BASES = 9;
58 /////////////////
59 
60 
61 static Game * scratchGame = NULL; // "scratch" game for searches, etc.
62 static std::unique_ptr<PBook> ecoBook; // eco classification pbook.
63 static SpellChecker* spellChk; // Name correction.
64 static OpTable * reports[2] = {NULL, NULL};
65 
66 void scid_Exit(void*) {
68  if (scratchGame != NULL) delete scratchGame;
69  if (spellChk != NULL) delete spellChk;
70  for (size_t i = 0, n = sizeof(reports) / sizeof(reports[0]); i < n; i++) {
71  if (reports[i] != NULL) delete reports[i];
72  }
73 }
74 
75 /*! \mainpage
76  * Scid is an open source software released under the GPL licence.
77  * The core database library is written in c++ and the GUI uses
78  * <a href="http://core.tcl.tk"> the tcl/tk framework</a>.
79  *
80  * \section Tests
81  * Link to <a href="../gcov/index.html">code coverage</a>
82  */
83 int main(int argc, char* argv[]) {
84  srand(time(NULL));
85 
86  scratchGame = new Game;
88 
89  return UI_Main(argc, argv, scid_Exit);
90 }
91 
92 
93 
94 
95 
96 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
97 // Global variables:
98 static const char * reportTypeName[2] = { "opening", "player" };
99 static const uint REPORT_OPENING = 0;
100 static const uint REPORT_PLAYER = 1;
101 
102 static char decimalPointChar = '.';
103 static uint htmlDiagStyle = 0;
104 
105 // Tablebase probe modes:
106 #define PROBE_NONE 0
107 #define PROBE_RESULT 1
108 #define PROBE_SUMMARY 2
109 #define PROBE_REPORT 3
110 #define PROBE_OPTIMAL 4
111 
112 
113 
114 //////////////////////////////////////////////////////////////////////
115 //
116 // Inline routines for setting Tcl result strings:
117 //
118 
119 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
120 // setResult():
121 // Inline function to set the Tcl interpreter result to a
122 // constant string.
123 inline int
124 setResult (Tcl_Interp * ti, const char * str)
125 {
126  Tcl_SetResult (ti, (char *) str, TCL_STATIC);
127  return TCL_OK;
128 }
129 
130 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131 // setIntResult():
132 // Inline function to set the Tcl interpreter result to a
133 // signed integer value.
134 inline int
135 setIntResult (Tcl_Interp * ti, int i)
136 {
137  char temp [20];
138  sprintf (temp, "%d", i);
139  Tcl_SetResult (ti, temp, TCL_VOLATILE);
140  return TCL_OK;
141 }
142 
143 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144 // setUintResult():
145 // Inline function to set the Tcl interpreter result to an
146 // unsigned integer value.
147 inline int
148 setUintResult (Tcl_Interp * ti, uint i)
149 {
150  char temp [20];
151  sprintf (temp, "%u", i);
152  Tcl_SetResult (ti, temp, TCL_VOLATILE);
153  return TCL_OK;
154 }
155 
156 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157 // appendUintResult:
158 // Inline function to append the specified unsigned value to the
159 // Tcl interpreter result.
160 inline int
161 appendUintResult (Tcl_Interp * ti, uint i)
162 {
163  char temp [20];
164  sprintf (temp, "%u", i);
165  Tcl_AppendResult (ti, temp, NULL);
166  return TCL_OK;
167 }
168 
169 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
170 // appendUintElement:
171 // Inline function to append the specified unsigned value to the
172 // Tcl interpreter list result.
173 inline uint
174 appendUintElement (Tcl_Interp * ti, uint i)
175 {
176  char temp[20];
177  sprintf (temp, "%u", i);
178  Tcl_AppendElement (ti, temp);
179  return TCL_OK;
180 }
181 
182 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
183 // setUintWidthResult():
184 // Inline function to set the Tcl interpreter result to an
185 // unsigned integer value, with zeroes to pad to the desired width.
186 inline int
187 setUintWidthResult (Tcl_Interp * ti, uint i, uint width)
188 {
189  char temp [20];
190  sprintf (temp, "%0*u", width, i);
191  Tcl_SetResult (ti, temp, TCL_VOLATILE);
192  return TCL_OK;
193 }
194 
195 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
196 // appendCharResult:
197 // Inline function to append the specified character value to the
198 // Tcl interpreter result.
199 inline int
200 appendCharResult (Tcl_Interp * ti, char ch)
201 {
202  char tempStr [4];
203  tempStr[0] = ch;
204  tempStr[1] = 0;
205  Tcl_AppendResult (ti, tempStr, NULL);
206  return TCL_OK;
207 }
208 
209 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
210 // translate:
211 // Return the translation for a phrase.
212 //
213 inline const char *
214 translate (Tcl_Interp * ti, const char * name, const char * defaultText)
215 {
216  const char * str = Tcl_GetVar2 (ti, "tr", (char *) name, TCL_GLOBAL_ONLY);
217  if (str == NULL) { str = defaultText; }
218  return str;
219 }
220 
221 inline const char *
222 translate (Tcl_Interp * ti, const char * name)
223 {
224  return translate (ti, name, name);
225 }
226 
227 inline int errorResult (Tcl_Interp * ti, errorT err, const char* errorMsg = 0) {
228  if (errorMsg != 0) Tcl_SetResult (ti, (char*) errorMsg, TCL_STATIC);
229  ASSERT(err != OK);
230  Tcl_SetObjErrorCode(ti, Tcl_NewIntObj(err));
231  return TCL_ERROR;
232 }
233 inline int errorResult (Tcl_Interp * ti, const char* errorMsg) {
234  return errorResult(ti, ERROR_BadArg, errorMsg);
235 }
236 
237 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
238 // InvalidCommand():
239 // Given a Tcl Interpreter, a major command name (e.g. "sc_base") and
240 // a null-terminated array of minor commands, this function sets
241 // the interpreter's result to a useful error message listing the
242 // available subcommands.
243 // Returns TCL_ERROR, so caller can simply:
244 // return InvalidCommand (...);
245 // instead of:
246 // InvalidCommand (...);
247 // return TCL_ERROR;
248 int
249 InvalidCommand (Tcl_Interp * ti, const char * majorCmd,
250  const char ** minorCmds)
251 {
252  ASSERT (majorCmd != NULL);
253  Tcl_AppendResult (ti, "Invalid command: ", majorCmd,
254  " has the following minor commands:\n", NULL);
255  while (*minorCmds != NULL) {
256  Tcl_AppendResult (ti, " ", *minorCmds, "\n", NULL);
257  minorCmds++;
258  }
259  return TCL_ERROR;
260 }
261 
262 
263 /************ End of Tcl result routines ***********/
264 
265 
266 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
267 // Standard error messages:
268 //
269 const char *
270 errMsgNotOpen (Tcl_Interp * ti)
271 {
272  return translate (ti, "ErrNotOpen", "This is not an open database.");
273 }
274 
275 const char *
276 errMsgSearchInterrupted (Tcl_Interp * ti)
277 {
278  return translate (ti, "ErrSearchInterrupted",
279  "[Interrupted search; results are incomplete]");
280 }
281 
282 
283 /////////////////////////////////////////////////////////////////////
284 // MISC functions
285 /////////////////////////////////////////////////////////////////////
286 
287 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288 // str_is_prefix:
289 // Provides a fast Tcl command "strIsPrefix" for checking if the
290 // first string provided is a prefix of the second string, without
291 // needing the standard slower [string match] or [string range]
292 // routines.
293 int
294 str_is_prefix (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
295 {
296  if (argc != 3) {
297  return errorResult (ti, "Usage: strIsPrefix <shortStr> <longStr>");
298  }
299 
300  return UI_Result(ti, OK, strIsPrefix (argv[1], argv[2]));
301 }
302 
303 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
304 // str_prefix_len:
305 // Tcl command that returns the length of the common text at the start
306 // of two strings.
307 int
308 str_prefix_len (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
309 {
310  if (argc != 3) {
311  return errorResult (ti, "Usage: strPrefixLen <str> <str>");
312  }
313 
314  return setUintResult (ti, strPrefix (argv[1], argv[2]));
315 }
316 
317 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
318 // sc_base_inUse
319 // Returns 1 if the database slot is in use; 0 otherwise.
320 int
321 sc_base_inUse (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
322 {
323  const scidBaseT* basePtr = db;
324  if (argc > 2) {
325  basePtr = DBasePool::getBase(strGetUnsigned(argv[2]));
326  if (basePtr == 0) return UI_Result(ti, OK, false);
327  }
328 
329  return UI_Result(ti, OK, basePtr->inUse);
330 }
331 
332 
333 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334 // exportGame:
335 // Called by sc_base_export() to export a single game.
336 void
337 exportGame (Game * g, FILE * exportFile, gameFormatT format, uint pgnStyle)
338 {
339  char old_language = language;
340 
341  g->ResetPgnStyle (pgnStyle);
342  g->SetPgnFormat (format);
343 
344  // Format-specific settings:
345  switch (format) {
346  case PGN_FORMAT_HTML:
347  case PGN_FORMAT_LaTeX:
349  break;
350  default:
351  language = 0;
352  break;
353  }
354 
355  g->SetHtmlStyle (htmlDiagStyle);
356  std::pair<const char*, unsigned> pgn = g->WriteToPGN(75, true, format != PGN_FORMAT_LaTeX);
357  //size_t nWrited =
358  fwrite(pgn.first, 1, pgn.second, exportFile);
359  //TODO:
360  //if (nWrited != db->tbuf->GetByteCount()) error
361  language = old_language;
362 }
363 
364 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
365 // sc_base_export:
366 // Exports the current game or all filter games in the database
367 // to a PGN, HTML or LaTeX file.
368 int
369 sc_base_export (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
370 {
371  FILE * exportFile = NULL;
372  bool exportFilter = false;
373  bool appendToFile = false;
374  gameFormatT outputFormat = PGN_FORMAT_Plain;
375  const char * startText = "";
376  const char * endText = "";
377  const char * usage = "Usage: sc_base export current|filter PGN|HTML|LaTeX <pgn_filename> options...";
378  uint pgnStyle = PGN_STYLE_TAGS;
379 
380  const char * options[] = {
381  "-append", "-starttext", "-endtext", "-comments", "-variations",
382  "-spaces", "-symbols", "-indentComments", "-indentVariations",
383  "-column", "-noMarkCodes", "-convertNullMoves", NULL
384  };
385  enum {
386  OPT_APPEND, OPT_STARTTEXT, OPT_ENDTEXT, OPT_COMMENTS, OPT_VARIATIONS,
387  OPT_SPACES, OPT_SYMBOLS, OPT_INDENTC, OPT_INDENTV,
388  OPT_COLUMN, OPT_NOMARKS, OPT_CONVERTNULL
389  };
390 
391  if (argc < 5) { return errorResult (ti, usage); }
392 
393  if (strIsPrefix (argv[2], "current")) {
394  exportFilter = false;
395  } else if (strIsPrefix (argv[2], "filter")) {
396  exportFilter = true;
397  } else {
398  return errorResult (ti, usage);
399  }
400 
401  if (! Game::PgnFormatFromString (argv[3], &outputFormat)) {
402  return errorResult (ti, usage);
403  }
404 
405  if (exportFilter && !db->inUse) {
406  return errorResult (ti, errMsgNotOpen(ti));
407  }
408 
409  const char * exportFileName = argv[4];
410 
411  // Check for an even number of optional parameters:
412  if ((argc % 2) != 1) { return errorResult (ti, usage); }
413 
414  // Parse all optional parameters:
415  for (int arg = 5; arg < argc; arg += 2) {
416  const char * value = argv[arg+1];
417  bool flag = strGetBoolean (value);
418  int option = strUniqueMatch (argv[arg], options);
419 
420  switch (option) {
421  case OPT_APPEND:
422  appendToFile = flag;
423  break;
424 
425  case OPT_STARTTEXT:
426  startText = value;
427  break;
428 
429  case OPT_ENDTEXT:
430  endText = value;
431  break;
432 
433  case OPT_COMMENTS:
434  if (flag) { pgnStyle |= PGN_STYLE_COMMENTS; }
435  break;
436 
437  case OPT_VARIATIONS:
438  if (flag) { pgnStyle |= PGN_STYLE_VARS; }
439  break;
440 
441  case OPT_SPACES:
442  if (flag) { pgnStyle |= PGN_STYLE_MOVENUM_SPACE; }
443  break;
444 
445  case OPT_SYMBOLS:
446  if (flag) { pgnStyle |= PGN_STYLE_SYMBOLS; }
447  break;
448 
449  case OPT_INDENTC:
450  if (flag) { pgnStyle |= PGN_STYLE_INDENT_COMMENTS; }
451  break;
452 
453  case OPT_INDENTV:
454  if (flag) { pgnStyle |= PGN_STYLE_INDENT_VARS; }
455  break;
456 
457  case OPT_COLUMN:
458  if (flag) { pgnStyle |= PGN_STYLE_COLUMN; }
459  break;
460 
461  case OPT_NOMARKS:
462  if (flag) { pgnStyle |= PGN_STYLE_STRIP_MARKS; }
463  break;
464 
465  case OPT_CONVERTNULL:
466  if (flag) { pgnStyle |= PGN_STYLE_NO_NULL_MOVES; }
467  break;
468 
469  default:
470  return InvalidCommand (ti, "sc_base export", options);
471  }
472  }
473  exportFile = fopen (exportFileName, (appendToFile ? "r+" : "w"));
474  if (exportFile == NULL) {
475  return errorResult (ti, "Error opening file for exporting games.");
476  }
477  // Write start text or find the place in the file to append games:
478  if (appendToFile) {
479  if (outputFormat == PGN_FORMAT_Plain) {
480  fseek (exportFile, 0, SEEK_END);
481  } else {
482  fseek (exportFile, 0, SEEK_SET);
483  const char * endMarker = "";
484  if (outputFormat == PGN_FORMAT_HTML) {
485  endMarker = "</body>";
486  } else if (outputFormat == PGN_FORMAT_LaTeX) {
487  endMarker = "\\end{document}";
488  }
489  char line [1024];
490  uint pos = 0;
491  while (1) {
492  char* err = fgets(line, 1024, exportFile);
493  if (err == 0 || feof(exportFile)) break;
494  const char * s = strTrimLeft (line, " ");
495  if (strIsCasePrefix (endMarker, s)) {
496  // We have seen the line to stop at, so break out
497  break;
498  }
499  pos = ftell (exportFile);
500  }
501  fseek (exportFile, pos, SEEK_SET);
502  }
503  } else {
504  fputs (startText, exportFile);
505  }
506 
507  if (!exportFilter) {
508  exportGame (db->game, exportFile, outputFormat, pgnStyle);
509  } else { //TODO: remove this (duplicate of sc_filter export)
510  Progress progress = UI_CreateProgress(ti);
511  uint numSeen = 0;
512  uint numToExport = db->dbFilter->Count();
513  Game * g = scratchGame;
514  for (gamenumT i=0, n=db->numGames(); i < n; i++) {
515  if (db->dbFilter->Get(i)) { // Export this game:
516  if (++numSeen % 1024 == 0) { // Update the percentage done bar:
517  if (!progress.report(numSeen, numToExport)) break;
518  }
519 
520  // Print the game, skipping any corrupt games:
521  const IndexEntry* ie = db->getIndexEntry(i);
522  if (ie->GetLength() == 0) { continue; }
523  if (db->getGame(ie, db->bbuf) != OK) {
524  continue;
525  }
526  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
527  continue;
528  }
529  g->LoadStandardTags (ie, db->getNameBase());
530  exportGame (g, exportFile, outputFormat, pgnStyle);
531  }
532  }
533  progress.report(1, 1);
534  }
535  fputs (endText, exportFile);
536  fclose (exportFile);
537  return TCL_OK;
538 }
539 
540 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
541 // sc_base_piecetrack:
542 // Examines games in the filter of the current database and
543 // returns a list of 64 integers indicating how frequently
544 // the specified piece moves to each square.
545 int
546 sc_base_piecetrack (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
547 {
548  const char * usage =
549  "Usage: sc_base piecetrack [-g|-t] <minMoves> <maxMoves> <startSquare ...>";
550 
551  if (argc < 5) {
552  return errorResult (ti, usage);
553  }
554 
555  // Check for optional mode parameter:
556  bool timeOnSquareMode = false;
557  int arg = 2;
558  if (argv[arg][0] == '-') {
559  if (argv[arg][1] == 'g' && strIsPrefix (argv[arg], "-games")) {
560  timeOnSquareMode = false;
561  arg++;
562  } else if (argv[arg][1] == 't' && strIsPrefix (argv[arg], "-time")) {
563  timeOnSquareMode = true;
564  arg++;
565  } else {
566  return errorResult (ti, usage);
567  }
568  }
569 
570  // Read the two move-number parameters:
571  uint minPly = strGetUnsigned(argv[arg]) * 2;
572  arg++;
573  uint maxPly = strGetUnsigned(argv[arg]) * 2;
574  arg++;
575 
576  // Convert moves to plycounts, e.g. "5-10" -> "9-20"
577  if (minPly < 2) { minPly=2; }
578  if (maxPly < minPly) { maxPly = minPly; }
579  minPly--;
580 
581  // Parse the variable number of tracked square arguments:
582  uint sqFreq[64] = {0};
583  bool trackSquare[64] = { false };
584  int nTrackSquares = 0;
585  for (int a=arg; a < argc; a++) {
586  squareT sq = strGetSquare (argv[a]);
587  if (sq == NULL_SQUARE) { return errorResult (ti, usage); }
588  if (!trackSquare[sq]) {
589  // Seen another starting square to track.
590  trackSquare[sq] = true;
591  nTrackSquares++;
592  }
593  }
594 
595  // If current base is unused, filter is empty, or no track
596  // squares specified, then just return a zero-filled list:
597 
598  if (! db->inUse || db->dbFilter->Count() == 0 || nTrackSquares == 0) {
599  for (uint i=0; i < 64; i++) { appendUintElement (ti, 0); }
600  return TCL_OK;
601  }
602 
603  // Examine every filter game and track the selected pieces:
604 
605  Progress progress = UI_CreateProgress(ti);
606  uint filterCount = db->dbFilter->Count();
607  uint filterSeen = 0;
608 
609  for (uint gnum = 0, n = db->numGames(); gnum < n; gnum++) {
610  // Skip over non-filter games:
611  if (!db->dbFilter->Get(gnum)) { continue; }
612 
613  // Update progress bar:
614  if ((filterSeen++ % 1000) == 0) {
615  if (!progress.report(filterSeen, filterCount)) {
616  return UI_Result(ti, ERROR_UserCancel);
617  }
618  }
619 
620  const IndexEntry* ie = db->getIndexEntry(gnum);
621 
622  // Skip games with non-standard start or no moves:
623  if (ie->GetStartFlag()) { continue; }
624  if (ie->GetLength() == 0) { continue; }
625 
626  // Skip games too short to be useful:
627  if (ie->GetNumHalfMoves() < minPly) { continue; }
628 
629  // Set up piece tracking for this game:
630  bool movedTo[64] = { false };
631  bool track[64];
632  int ntrack = nTrackSquares;
633  for (uint sq=0; sq < 64; sq++) { track[sq] = trackSquare[sq]; }
634 
635  Game * g = scratchGame;
636  if (db->getGame(ie, db->bbuf) != OK) {
637  continue;
638  }
639  db->bbuf->BackToStart();
640  g->Clear();
641  if (g->DecodeStart (db->bbuf) != OK) { continue; }
642 
643  uint plyCount = 0;
644  simpleMoveT sm;
645 
646  // Process each game move until the maximum ply or end of
647  // the game is reached:
648 
649  while (plyCount < maxPly) {
650  if (g->DecodeNextMove (db->bbuf, &sm) != OK) { break; }
651  plyCount++;
652  squareT toSquare = sm.to;
653  squareT fromSquare = sm.from;
654 
655  // Special hack for castling:
656  if (piece_Type(sm.movingPiece) == KING) {
657  if (fromSquare == E1) {
658  if (toSquare == G1 && track[H1]) {
659  fromSquare = H1; toSquare = F1;
660  }
661  if (toSquare == C1 && track[A1]) {
662  fromSquare = A1; toSquare = D1;
663  }
664  }
665  if (fromSquare == E8) {
666  if (toSquare == G8 && track[H8]) {
667  fromSquare = H8; toSquare = F8;
668  }
669  if (toSquare == C8 && track[A8]) {
670  fromSquare = A8; toSquare = D8;
671  }
672  }
673  }
674 
675  // TODO: Special hack for en-passant capture?
676 
677  if (track[toSquare]) {
678  // A tracked piece has been captured:
679  track[toSquare] = false;
680  ntrack--;
681  if (ntrack <= 0) { break; }
682 
683  } else if (track[fromSquare]) {
684  // A tracked piece is moving:
685  track[fromSquare] = false;
686  track[toSquare] = true;
687  if (plyCount >= minPly) {
688  // If not time-on-square mode, and this
689  // new target square has not been moved to
690  // already by a tracked piece in this game,
691  // increase its frequency now:
692  if (!timeOnSquareMode && !movedTo[toSquare]) {
693  sqFreq[toSquare]++;
694  }
695  movedTo[toSquare] = true;
696  }
697  }
698 
699  if (timeOnSquareMode && plyCount >= minPly) {
700  // Time-on-square mode: find all tracked squares
701  // (there are ntrack of them) and increment the
702  // frequency of each.
703  int nleft = ntrack;
704  for (uint i=0; i < 64; i++) {
705  if (track[i]) {
706  sqFreq[i]++;
707  nleft--;
708  // We can stop early when all tracked
709  // squares have been found:
710  if (nleft <= 0) { break; }
711  }
712  }
713  }
714  } // while (plyCount < maxPly)
715  } // foreach game
716 
717  progress.report(1, 1);
718 
719  // Now return the 64-integer list: if in time-on-square mode,
720  // the value for each square is the number of plies when a
721  // tracked piece was on it, so halve it to convert to moves:
722 
723  for (uint i=0; i < 64; i++) {
724  appendUintElement (ti, timeOnSquareMode ? sqFreq[i] / 2 : sqFreq[i]);
725  }
726  return TCL_OK;
727 }
728 
729 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
730 // sc_base_duplicates:
731 // Finds duplicate games and marks them deleted.
732 // A pair of games are considered duplicates if the Event, Site,
733 // White, Black, and Round values all match identically, and the
734 // Date matches to within 2 days (that is, the same year, the same
735 // month, and the days of month differ by 2 at most).
736 //
737 // Furthermore, the moves of one game should, after truncating, be the
738 // same as the moves of the other game, for them to be duplicates.
739 
740 struct gNumListT {
741  uint64_t hash;
743  bool operator<(const gNumListT& a) const { return hash < a.hash; }
744 };
745 
746 struct dupCriteriaT {
749  bool sameEvent;
750  bool sameSite;
751  bool sameRound;
753  bool sameYear;
754  bool sameMonth;
755  bool sameDay;
757  bool sameMoves;
758 };
759 
760 bool
762  const IndexEntry * ie1, const IndexEntry * ie2,
763  dupCriteriaT * cr)
764 {
765  if (ie1->GetDeleteFlag() || ie2->GetDeleteFlag()) { return false; }
766  if (cr->sameEvent) {
767  if (ie1->GetEvent() != ie2->GetEvent()) { return false; }
768  }
769  if (cr->sameSite) {
770  if (ie1->GetSite() != ie2->GetSite()) { return false; }
771  }
772  if (cr->sameRound) {
773  if (ie1->GetRound() != ie2->GetRound()) { return false; }
774  }
775  if (cr->sameYear) {
776  if (ie1->GetYear() != ie2->GetYear()) { return false; }
777  }
778  if (cr->sameMonth) {
779  if (ie1->GetMonth() != ie2->GetMonth()) { return false; }
780  }
781  if (cr->sameDay) {
782  if (ie1->GetDay() != ie2->GetDay()) { return false; }
783  }
784  if (cr->sameResult) {
785  if (ie1->GetResult() != ie2->GetResult()) { return false; }
786  }
787  if (cr->sameEcoCode) {
788  ecoStringT a;
789  ecoStringT b;
790  eco_ToBasicString (ie1->GetEcoCode(), a);
791  eco_ToBasicString (ie2->GetEcoCode(), b);
792  if (a[0] != b[0] || a[1] != b[1] || a[2] != b[2]) { return false; }
793  }
794 
795  // There are a lot of "place-holding" games in some database, that have
796  // just one (usually wrong) move and a result, that are then replaced by
797  // the full version of the game. Therefore, if we reach here and one
798  // of the games (or both) have only one move or no moves, return true
799  // as long as they have the same year, site and round:
800 
801  if (ie1->GetNumHalfMoves() <= 2 || ie2->GetNumHalfMoves() <= 2) {
802  if (ie1->GetYear() == ie2->GetYear() &&
803  ie1->GetSite() == ie2->GetSite() &&
804  ie1->GetRound() == ie2->GetRound()) {
805  return true;
806  }
807  }
808 
809  // Now check that the games contain the same moves, up to the length
810  // of the shorter game:
811 
812  if (cr->sameMoves) {
813  const byte * hpData1 = ie1->GetHomePawnData();
814  const byte * hpData2 = ie2->GetHomePawnData();
815  if (! hpSig_Prefix (hpData1, hpData2)) { return false; }
816  // Now we have to check the actual moves of the games:
817  uint length = std::min(ie1->GetNumHalfMoves(), ie2->GetNumHalfMoves());
818  std::string a = base->getGame(ie1).getMoveSAN(0, length);
819  std::string b = base->getGame(ie2).getMoveSAN(0, length);
820  return (a == b);
821  }
822  return true;
823 }
824 
825 uint
826 sc_base_duplicates (scidBaseT* dbase, UI_handle_t ti, int argc, const char ** argv)
827 {
828  dupCriteriaT criteria;
829  criteria.exactNames = false;
830  criteria.sameColors = true;
831  criteria.sameEvent = true;
832  criteria.sameSite = true;
833  criteria.sameRound = true;
834  criteria.sameYear = true;
835  criteria.sameMonth = true;
836  criteria.sameDay = false;
837  criteria.sameResult = false;
838  criteria.sameEcoCode = false;
839  criteria.sameMoves = true;
840 
841  bool skipShortGames = false;
842  bool keepAllCommentedGames = true;
843  bool keepAllGamesWithVars = true;
844  bool setFilterToDups = false;
845  bool onlyFilterGames = false;
846  bool copyRatings = false;
847 
848  // Deletion strategy: delete the shorter game, the game with the
849  // smaller game number, or the game with the larger game number.
850  enum deleteStrategyT { DELETE_SHORTER, DELETE_OLDER, DELETE_NEWER };
851  deleteStrategyT deleteStrategy = DELETE_SHORTER;
852 
853  // Parse command options in pairs of arguments:
854 
855  const char * options[] = {
856  "-players", "-colors", "-event", "-site", "-round", "-year",
857  "-month", "-day", "-result", "-eco", "-moves", "-skipshort",
858  "-comments", "-variations", "-setfilter", "-usefilter",
859  "-copyratings", "-delete",
860  NULL
861  };
862  enum {
863  OPT_PLAYERS, OPT_COLORS, OPT_EVENT, OPT_SITE, OPT_ROUND, OPT_YEAR,
864  OPT_MONTH, OPT_DAY, OPT_RESULT, OPT_ECO, OPT_MOVES, OPT_SKIPSHORT,
865  OPT_COMMENTS, OPT_VARIATIONS, OPT_SETFILTER, OPT_USEFILTER,
866  OPT_COPYRATINGS, OPT_DELETE
867  };
868 
869  for (int arg = 3; arg < argc; arg += 2) {
870  const char * optStr = argv[arg];
871  const char * valueStr = argv[arg + 1];
872  bool b = strGetBoolean (valueStr);
873  int index = strUniqueMatch (optStr, options);
874  switch (index) {
875  case OPT_PLAYERS: criteria.exactNames = b; break;
876  case OPT_COLORS: criteria.sameColors = b; break;
877  case OPT_EVENT: criteria.sameEvent = b; break;
878  case OPT_SITE: criteria.sameSite = b; break;
879  case OPT_ROUND: criteria.sameRound = b; break;
880  case OPT_YEAR: criteria.sameYear = b; break;
881  case OPT_MONTH: criteria.sameMonth = b; break;
882  case OPT_DAY: criteria.sameDay = b; break;
883  case OPT_RESULT: criteria.sameResult = b; break;
884  case OPT_ECO: criteria.sameEcoCode = b; break;
885  case OPT_MOVES: criteria.sameMoves = b; break;
886  case OPT_SKIPSHORT: skipShortGames = b; break;
887  case OPT_COMMENTS: keepAllCommentedGames = b; break;
888  case OPT_VARIATIONS: keepAllGamesWithVars = b; break;
889  case OPT_SETFILTER: setFilterToDups = b; break;
890  case OPT_USEFILTER: onlyFilterGames = b; break;
891  case OPT_COPYRATINGS: copyRatings = b; break;
892  case OPT_DELETE:
893  if (strIsCasePrefix (valueStr, "shorter")) {
894  deleteStrategy = DELETE_SHORTER;
895  } else if (strIsCasePrefix (valueStr, "older")) {
896  deleteStrategy = DELETE_OLDER;
897  } else if (strIsCasePrefix (valueStr, "newer")) {
898  deleteStrategy = DELETE_NEWER;
899  } else {
900  return errorResult (ti, "Invalid option.");
901  }
902  break;
903  default:
904  return InvalidCommand (ti, "sc_base duplicates", options);
905  }
906  }
907  uint deletedCount = 0;
908  const gamenumT numGames = dbase->numGames();
909 
910  // Setup duplicates array:
911  uint* duplicates = new uint [numGames];
912  std::fill(duplicates, duplicates + numGames, 0);
913 
914  // We use a hashtable to limit duplicate game comparisons; each game
915  // is only compared to others that hash to the same value.
916  std::vector<gNumListT> hash(numGames);
917  size_t n_hash = 0;
918  const std::vector<uint32_t>& hashMap = (criteria.exactNames)
919  ? std::vector<uint32_t>()
921  for (gamenumT i=0; i < numGames; i++) {
922  const IndexEntry* ie = dbase->getIndexEntry(i);
923  if (! ie->GetDeleteFlag() /* && !ie->GetStartFlag() */
924  && (!skipShortGames || ie->GetNumHalfMoves() >= 10)
925  && (!onlyFilterGames || dbase->dbFilter->Get(i) > 0)) {
926 
927  uint32_t wh = ie->GetWhite();
928  uint32_t bl = ie->GetBlack();
929  if (!criteria.exactNames) {
930  wh = hashMap[wh];
931  bl = hashMap[bl];
932  }
933  if (!criteria.sameColors && bl > wh) {
934  std::swap(wh, bl);
935  }
936  gNumListT* node = &(hash[n_hash++]);
937  node->hash = (uint64_t(wh) << 32) + bl;
938  node->gNumber = i;
939  }
940  }
941  hash.resize(n_hash);
942  std::sort(hash.begin(), hash.end());
943 
944  if (setFilterToDups) { dbase->dbFilter->Fill (0); }
945  Progress progress = UI_CreateProgress(ti);
946 
947  dbase->beginTransaction();
948 
949  // Now check same-hash games for duplicates:
950  for (size_t i=0; i < n_hash; i++) {
951  if ((i % 1024) == 0) {
952  if (!progress.report(i, numGames)) break;
953  }
954  gNumListT* head = &(hash[i]);
955  IndexEntry* ieHead = dbase->idx->FetchEntry (head->gNumber);
956 
957  for (size_t comp=i+1; comp < n_hash; comp++) {
958  gNumListT* compare = &(hash[comp]);
959  if (compare->hash != head->hash) break;
960 
961  IndexEntry * ieComp = dbase->idx->FetchEntry (compare->gNumber);
962 
963  if (checkDuplicate (dbase, ieHead, ieComp, &criteria)) {
964  duplicates[head->gNumber] = compare->gNumber + 1;
965  duplicates[compare->gNumber] = head->gNumber + 1;
966 
967  // Found a duplicate! Decide which one to delete:
968 
969  bool headImmune = false;
970  bool compImmune = false;
971  bool doDeletion = false;
972  bool copiedRatings = false;
973  gamenumT gnumKeep, gnumDelete;
974  IndexEntry * ieDelete, * ieKeep;
975 
976  if (keepAllCommentedGames) {
977  if (ieHead->GetCommentsFlag()) { headImmune = true; }
978  if (ieComp->GetCommentsFlag()) { compImmune = true; }
979  }
980  if (keepAllGamesWithVars) {
981  if (ieHead->GetVariationsFlag()) { headImmune = true; }
982  if (ieComp->GetVariationsFlag()) { compImmune = true; }
983  }
984 
985  // Decide which game should get deleted:
986  bool deleteHead = false;
987  if (deleteStrategy == DELETE_OLDER) {
988  deleteHead = (head->gNumber < compare->gNumber);
989  } else if (deleteStrategy == DELETE_NEWER) {
990  deleteHead = (head->gNumber > compare->gNumber);
991  } else {
992  ASSERT (deleteStrategy == DELETE_SHORTER);
993  uint a = ieHead->GetNumHalfMoves();
994  uint b = ieComp->GetNumHalfMoves();
995  deleteHead = (a <= b);
996  if (a == b && headImmune) deleteHead = false;
997  }
998 
999  if (deleteHead) {
1000  ieDelete = ieHead;
1001  ieKeep = ieComp;
1002  gnumDelete = head->gNumber;
1003  gnumKeep = compare->gNumber;
1004  doDeletion = ! headImmune;
1005  } else {
1006  ieDelete = ieComp;
1007  ieKeep = ieHead;
1008  gnumDelete = compare->gNumber;
1009  gnumKeep = head->gNumber;
1010  doDeletion = ! compImmune;
1011  }
1012  // Delete whichever game is to be deleted:
1013  if (doDeletion) {
1014  deletedCount++;
1015  ieDelete->SetDeleteFlag (true);
1016  if (copyRatings && ieKeep->GetWhiteElo() == 0) {
1017  eloT elo = ieDelete->GetWhiteElo();
1018  byte rtype = ieDelete->GetWhiteRatingType();
1019  if (elo != 0) {
1020  ieKeep->SetWhiteElo (elo);
1021  ieKeep->SetWhiteRatingType (rtype);
1022  copiedRatings = true;
1023  }
1024  }
1025  if (copyRatings && ieKeep->GetBlackElo() == 0) {
1026  eloT elo = ieDelete->GetBlackElo();
1027  byte rtype = ieDelete->GetBlackRatingType();
1028  if (elo != 0) {
1029  ieKeep->SetBlackElo (elo);
1030  ieKeep->SetBlackRatingType (rtype);
1031  copiedRatings = true;
1032  }
1033  }
1034  dbase->idx->WriteEntry (ieDelete, gnumDelete);
1035  if (copiedRatings) {
1036  dbase->idx->WriteEntry (ieKeep, gnumKeep);
1037  }
1038  if (setFilterToDups) {
1039  dbase->dbFilter->Set (gnumDelete, 1);
1040  }
1041  }
1042  }
1043  }
1044  }
1045 
1046  dbase->endTransaction();
1047  dbase->setDuplicates(duplicates);
1048  progress.report(1,1);
1049 
1050  return deletedCount;
1051 }
1052 
1053 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1054 // sc_base_tag:
1055 // Produce a list of PGN tags used in the database,
1056 // or strip an unwanted non-essential tag from the
1057 // database. It cannot be used for in-index tags
1058 // such as ratings, ECO or EventDate, or the FEN
1059 // or Setup tags.
1060 // The command has three subcommands:
1061 // find <tag>: set the filter to contain all games
1062 // that have the specified tag.
1063 // list: return a even-sized list, where each pair
1064 // of elements is a tag name and its frequency,
1065 // for all non-standard tags stored as Extra
1066 // tags in the game file of the database.
1067 // strip <tag>: Remove all occurrences of the
1068 // specified tag from the database.
1069 int
1070 sc_base_tag (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1071 {
1072  const char * usage = "Usage: sc_base tag [filter <tagname> | list | strip <tagname>]";
1073  const char * options[] = {
1074  "find", "list", "strip", NULL
1075  };
1076  enum {
1077  TAG_FIND, TAG_LIST, TAG_STRIP
1078  };
1079 
1080  const char * tag = NULL; // For "find" or "strip" commands
1081  std::vector< std::pair <std::string, uint> > tag_freq; // For "list" command
1082 
1083  if (! db->inUse) {
1084  return errorResult (ti, errMsgNotOpen(ti));
1085  }
1086 
1087  int cmd = -1;
1088  if (argc >= 3) { cmd = strUniqueMatch (argv[2], options); }
1089 
1090  switch (cmd) {
1091  case TAG_LIST:
1092  if (argc != 3) { return errorResult (ti, usage); }
1093  break;
1094  case TAG_FIND: // Same extra parameter as TAG_STRIP
1095  case TAG_STRIP:
1096  if (argc != 4) { return errorResult (ti,usage); }
1097  tag = argv[3];
1098  break;
1099  default:
1100  return errorResult (ti, usage);
1101  };
1102 
1103  if (cmd == TAG_STRIP) {
1104  // If stripping a tag, make sure we have a writable database:
1105  if (db->isReadOnly())
1106  return errorResult(ti, ERROR_FileReadOnly);
1107  db->beginTransaction();
1108  }
1109 
1110  // If setting filter, clear it now:
1111  if (cmd == TAG_FIND) { db->dbFilter->Fill (0); }
1112 
1113  // Process each game in the database:
1114  Progress progress = UI_CreateProgress(ti);
1115  Game * g = scratchGame;
1116  uint nEditedGames = 0;
1117 
1118  for (gamenumT gnum = 0, n = db->numGames(); gnum < n; gnum++) {
1119  if ((gnum % 1000) == 0) {
1120  if (!progress.report(gnum, n)) break;
1121  }
1122 
1123  const IndexEntry* ie = db->getIndexEntry(gnum);
1124  if (ie->GetLength() == 0) { continue; }
1125  if (db->getGame(ie, db->bbuf) != OK) {
1126  continue;
1127  }
1128  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
1129  continue;
1130  }
1131  if (cmd == TAG_FIND) {
1132  if (g->FindExtraTag (tag) != NULL) {
1133  // Found the tag, so add this game to the filter:
1134  db->dbFilter->Set (gnum, 1);
1135  }
1136  } else if (cmd == TAG_STRIP) {
1137  if (g->RemoveExtraTag (tag)) {
1138  // The tag was found and stripped. Re-save the game,
1139  // remembering to load its standard tags first:
1140  g->LoadStandardTags (ie, db->getNameBase());
1141  errorT res = db->saveGameHelper(g, gnum);
1142  if (res != OK) return UI_Result(ti, res);
1143  nEditedGames++;
1144  }
1145  } else {
1146  ASSERT (cmd == TAG_LIST);
1147  // Increment frequency for each extra tag:
1148  for (auto& tag : g->GetExtraTags()) {
1149  bool found = false;
1150  for (uint i=0; i < tag_freq.size(); i++) {
1151  if (tag_freq[i].first == tag.first) {
1152  tag_freq[i].second++;
1153  found = true;
1154  break;
1155  }
1156  }
1157  if (!found) {
1158  tag_freq.emplace_back(tag.first, 1);
1159  }
1160  }
1161  }
1162  }
1163  // Done searching through all games.
1164 
1165  // If necessary, update index and name files:
1166  if (cmd == TAG_STRIP) {
1167  db->endTransaction();
1168  setUintResult (ti, nEditedGames);
1169  }
1170 
1171  // If listing extra tags, generate the list now:
1172  if (cmd == TAG_LIST) {
1173  for (uint i=0; i < tag_freq.size(); i++) {
1174  uint freq = tag_freq[i].second;
1175  const char* name = tag_freq[i].first.c_str();
1176  if (freq > 0 && !strEqual (name, "SetUp")) {
1177  Tcl_AppendElement (ti, name);
1178  appendUintElement (ti, freq);
1179  }
1180  }
1181  }
1182  return TCL_OK;
1183 }
1184 
1185 
1186 //////////////////////////////////////////////////////////////////////
1187 /// CLIPBASE functions
1188 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1189 // sc_clipbase:
1190 // Game clipbase functions.
1191 // Copies a game to, or pastes from, the clipbase database.
1192 int
1193 sc_clipbase (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1194 {
1196  ASSERT(clipbase);
1197 
1198  static const char * options [] = {
1199  "clear", "paste", NULL
1200  };
1201  enum {
1202  CLIP_CLEAR, CLIP_PASTE
1203  };
1204  int index = -1;
1205 
1206  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
1207 
1208  switch (index) {
1209  case CLIP_CLEAR:
1210  clipbase->Close();
1211  clipbase->Open(ICodecDatabase::MEMORY, FMODE_Memory, "<clipbase>");
1212  clipbase->setExtraInfo("type", "2");
1213  break;
1214 
1215  case CLIP_PASTE: // Paste the active clipbase game
1216  if (db != clipbase) {
1217  delete db->game;
1218  db->game = clipbase->game->clone();
1219  db->gameNumber = -1;
1220  db->gameAltered = true;
1221  }
1222  break;
1223 
1224  default:
1225  return InvalidCommand(ti, "sc_clipbase", options);
1226  }
1227 
1228  return UI_Result(ti, OK);
1229 }
1230 
1231 //////////////////////////////////////////////////////////////////////
1232 /// ECO Classification functions
1233 
1234 // ecoTranslateT:
1235 // Structure for a linked list of ECO opening name translations.
1236 //
1238  char language;
1239  char * from;
1240  char * to;
1242 };
1243 
1244 static ecoTranslateT * ecoTranslations = NULL;
1245 void translateECO (Tcl_Interp * ti, const char * strFrom, DString * dstrTo);
1246 
1247 int
1248 sc_eco (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
1249 {
1250  int index = -1;
1251  static const char * options [] = {
1252  "base", "game", "read", "reset", "size", "summary",
1253  "translate", NULL
1254  };
1255  enum {
1256  ECO_BASE, ECO_GAME, ECO_READ, ECO_RESET, ECO_SIZE, ECO_SUMMARY,
1257  ECO_TRANSLATE
1258  };
1259 
1260  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
1261 
1262  switch (index) {
1263  case ECO_BASE:
1264  return sc_eco_base (cd, ti, argc, argv);
1265 
1266  case ECO_GAME:
1267  return sc_eco_game (cd, ti, argc, argv);
1268 
1269  case ECO_READ:
1270  return sc_eco_read (cd, ti, argc, argv);
1271 
1272  case ECO_RESET:
1273  if (ecoBook) { ecoBook = NULL; }
1274  break;
1275 
1276  case ECO_SIZE:
1277  return setUintResult (ti, ecoBook == NULL ? 0 : ecoBook->Size());
1278 
1279  case ECO_SUMMARY:
1280  return sc_eco_summary (cd, ti, argc, argv);
1281 
1282  case ECO_TRANSLATE:
1283  return sc_eco_translate (cd, ti, argc, argv);
1284 
1285  default:
1286  return InvalidCommand (ti, "sc_eco", options);
1287  }
1288 
1289  return TCL_OK;
1290 }
1291 
1292 
1293 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1294 // sc_eco_base:
1295 // Reclassifies all games in the current base by ECO code.
1296 //
1297 // The first parameter indicates if all games (not only those
1298 // with no existing ECO code) should be classified.
1299 // "0" or "nocode": only games with no ECO code.
1300 // "1" or "all": classify all games.
1301 // "date:yyyy.mm.dd": only games since date.
1302 // The second boolean parameter indicates if Scid-specific ECO
1303 // extensions (e.g. "B12j" instead of just "B12") should be used.
1304 //
1305 // If the database is read-only, games can still be classified but
1306 // the results will not be stored to the database file.
1307 int
1308 sc_eco_base (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1309 {
1310  if (argc != 4) {
1311  return errorResult (ti, "Usage: sc_eco base <bool:all_games> <bool:extensions>");
1312  }
1313  if (!ecoBook) { return errorResult (ti, "No ECO Book is loaded."); }
1314  if (! db->inUse) return errorResult (ti, ERROR_FileNotOpen);
1315  if (db->isReadOnly()) return errorResult (ti, ERROR_FileReadOnly);
1316 
1317  int option = -1;
1318  enum {ECO_NOCODE, ECO_ALL, ECO_DATE, ECO_FILTER};
1319 
1320  switch (argv[2][0]) {
1321  case '0':
1322  case 'n':
1323  option = ECO_NOCODE; break;
1324  case 'd':
1325  option = ECO_DATE; break;
1326  case 'f':
1327  option = ECO_FILTER; break;
1328  default:
1329  option = ECO_ALL; break;
1330  }
1331 
1332  bool extendedCodes = strGetBoolean(argv[3]);
1333  dateT startDate = ZERO_DATE;
1334  if (option == ECO_DATE) {
1335  startDate = date_EncodeFromString (&(argv[2][5]));
1336  }
1337 
1338  scidBaseT& dbase = *db;
1339  auto entry_op = [&](IndexEntry& ie) {
1340  if (ie.GetLength() == 0)
1341  return false;
1342 
1343  // Ignore games with existing ECO code if directed:
1344  if (option == ECO_NOCODE && ie.GetEcoCode() != 0)
1345  return false;
1346 
1347  // Ignore games before starting date if directed:
1348  if (option == ECO_DATE && ie.GetDate() < startDate)
1349  return false;
1350 
1351  if (dbase.getGame(&ie, dbase.bbuf) != OK)
1352  return false;
1353  db->bbuf->BackToStart();
1354  Game* g = scratchGame;
1355  g->Clear();
1356  if (g->DecodeStart(dbase.bbuf) != OK)
1357  return false;
1358 
1359  // First, read in the game -- with a limit of 30 moves per
1360  // side, since an ECO match after move 31 is very unlikely and
1361  // we can save time by setting a limit. Also, stop when the
1362  // material left in on the board is less than that of the
1363  // book position with the least material, since no further
1364  // positions in the game could possibly match.
1365 
1366  uint maxPly = 60;
1367  uint leastMaterial = ecoBook->FewestPieces();
1368  uint material;
1369 
1370  errorT err = OK;
1371  do {
1372  err = g->DecodeNextMove (db->bbuf, NULL);
1373  maxPly--;
1374  material = g->GetCurrentPos()->TotalMaterial();
1375  } while (err == OK && maxPly > 0 && material >= leastMaterial);
1376 
1377  // Now, move back through the game to the start searching for a
1378  // match in the ECO book. Stop at the first match found since it
1379  // is the deepest.
1380 
1381  ecoT ecoCode = ECO_None;
1382  do {
1383  ecoCode = ecoBook->findECO(g->GetCurrentPos());
1384  if (ecoCode != ECO_None) {
1385  if (! extendedCodes) {
1386  ecoCode = eco_BasicCode (ecoCode);
1387  }
1388  break;
1389  }
1390  err = g->MoveBackup();
1391  } while (err == OK);
1392 
1393  if (ie.GetEcoCode() != ecoCode) {
1394  ie.SetEcoCode(ecoCode);
1395  return true;
1396  }
1397  return false;
1398  };
1399 
1400  std::string filter =
1401  (option == ECO_FILTER) ? "dbfilter" : dbase.newFilter();
1402  auto hf = dbase.getFilter(filter);
1403  auto changes = dbase.transformIndex(hf, UI_CreateProgress(ti), entry_op);
1404  if (option == ECO_FILTER)
1405  dbase.deleteFilter(filter.c_str());
1406 
1407  return UI_Result(ti, changes.first, changes.second);
1408 }
1409 
1410 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1411 // sc_eco_game:
1412 // Returns ECO code for the current game. If the optional
1413 // parameter <ply> is passed, it returns the ply depth of the
1414 // deepest match instead of the ECO code.
1415 int
1416 sc_eco_game (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1417 {
1418  uint returnPly = 0;
1419  if (argc > 2) {
1420  if (argc == 3 && strIsPrefix (argv[2], "ply")) {
1421  returnPly = 1;
1422  } else {
1423  return errorResult (ti, "Usage: sc_game eco [ply]");
1424  }
1425  }
1426  if (!ecoBook) { return TCL_OK; }
1427 
1428  auto location = db->game->currentLocation();
1429  db->game->MoveToPly (0);
1430 
1431  do {} while (db->game->MoveForward() == OK);
1432  ecoT ecoCode = ECO_None;
1433  do {
1434  ecoCode = ecoBook->findECO(db->game->GetCurrentPos());
1435  } while (ecoCode == ECO_None && db->game->MoveBackup() == OK);
1436 
1437  auto ply = db->game->GetCurrentPly();
1438  db->game->restoreLocation(location);
1439 
1440  if (ecoCode == ECO_None)
1441  return UI_Result(ti, OK);
1442 
1443  if (returnPly)
1444  return UI_Result(ti, OK, ply);
1445 
1446  ecoStringT extEco;
1447  eco_ToExtendedString(ecoCode, extEco);
1448  return UI_Result(ti, OK, extEco);
1449 }
1450 
1451 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1452 // sc_eco_read:
1453 // Reads a book file for ECO classification.
1454 // Returns the book size (number of positions).
1455 int
1456 sc_eco_read (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1457 {
1458  if (argc < 3) { return TCL_OK; }
1459  ecoBook = nullptr;
1460  auto book = PBook::ReadEcoFile(argv[2]);
1461  if (book.first != OK) {
1462  if (book.first == ERROR_FileOpen) {
1463  Tcl_AppendResult (ti, "Unable to open the ECO file:\n",
1464  argv[2], NULL);
1465  } else {
1466  Tcl_AppendResult (ti, "Unable to load the ECO file:\n",
1467  argv[2], NULL);
1468  }
1469  return book.first;
1470  }
1471  ecoBook = std::move(book.second);
1472  return setUintResult (ti, ecoBook->Size());
1473 }
1474 
1475 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1476 // sc_eco_summary:
1477 // Returns a listing of positions for the specified ECO prefix,
1478 // in plain text or color (Scid hypertext) format.
1479 int
1480 sc_eco_summary (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1481 {
1482  bool color = true;
1483  if (argc != 3 && argc != 4) {
1484  return errorResult (ti, "Usage: sc_eco summary <ECO-prefix> [<bool:color>]");
1485  }
1486  if (argc == 4) { color = strGetBoolean (argv[3]); }
1487  if (!ecoBook) { return TCL_OK; }
1488  DString * dstr = new DString;
1489  DString * temp = new DString;
1490  bool inMoveList = false;
1491  translateECO(ti, ecoBook->EcoSummary(argv[2]).c_str(), dstr);
1492  if (color) {
1493  DString * oldstr = dstr;
1494  dstr = new DString;
1495  const char * s = oldstr->Data();
1496  while (*s) {
1497  char ch = *s;
1498  switch (ch) {
1499  case '[':
1500  dstr->Append ("<tab>");
1501  dstr->AddChar (ch);
1502  break;
1503  case ']':
1504  dstr->AddChar (ch);
1505  dstr->Append ("<blue><run importMoveList {");
1506  inMoveList = true;
1507  temp->Clear();
1508  break;
1509  case '\n':
1510  if (inMoveList) {
1511  dstr->Append ("}>", temp->Data());
1512  inMoveList = false;
1513  }
1514  dstr->Append ("</run></blue></tab><br>");
1515  break;
1516  default:
1517  dstr->AddChar (ch);
1518  if (inMoveList) { temp->AddChar (transPiecesChar(ch)); }//{ temp->AddChar (ch); }
1519  }
1520  s++;
1521  }
1522  delete oldstr;
1523  }
1524  Tcl_AppendResult (ti, dstr->Data(), NULL);
1525  delete temp;
1526  delete dstr;
1527  return TCL_OK;
1528 }
1529 
1530 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1531 // sc_eco_translate:
1532 // Adds a new ECO openings phrase translation.
1533 int
1534 sc_eco_translate (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
1535 {
1536  if (argc != 5) {
1537  return errorResult (ti, "Usage: sc_eco translate <lang> <from> <to>");
1538  }
1539 
1540 #ifdef WINCE
1541  ecoTranslateT * trans = (ecoTranslateT * )my_Tcl_Alloc( sizeof(ecoTranslateT));
1542 #else
1543  ecoTranslateT * trans = new ecoTranslateT;
1544 #endif
1545  trans->next = ecoTranslations;
1546  trans->language = argv[2][0];
1547  trans->from = strDuplicate (argv[3]);
1548  trans->to = strDuplicate (argv[4]);
1549  ecoTranslations = trans;
1550  return TCL_OK;
1551 }
1552 
1553 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1554 // translateECO:
1555 // Translates an ECO opening name into the current language.
1556 //
1557 void
1558 translateECO (Tcl_Interp * ti, const char * strFrom, DString * dstrTo)
1559 {
1560  ecoTranslateT * trans = ecoTranslations;
1561  dstrTo->Clear();
1562  dstrTo->Append (strFrom);
1563  const char * language = Tcl_GetVar (ti, "language", TCL_GLOBAL_ONLY);
1564  if (language == NULL) { return; }
1565  char lang = language[0];
1566  while (trans != NULL) {
1567  if (trans->language == lang
1568  && strContains (dstrTo->Data(), trans->from)) {
1569  // Translate this phrase in the string:
1570  char * temp = strDuplicate (dstrTo->Data());
1571  dstrTo->Clear();
1572  char * in = temp;
1573  while (*in != 0) {
1574  if (strIsPrefix (trans->from, in)) {
1575  dstrTo->Append (trans->to);
1576  in += strLength (trans->from);
1577  } else {
1578  dstrTo->AddChar (*in);
1579  in++;
1580  }
1581  }
1582 #ifdef WINCE
1583  my_Tcl_Free((char*) temp);
1584 #else
1585  delete[] temp;
1586 #endif
1587  }
1588  trans = trans->next;
1589  }
1590 }
1591 
1592 //////////////////////////////////////////////////////////////////////
1593 /// FILTER functions
1594 
1595 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1596 // sc_filter: filter commands. Valid minor commands:
1597 // count: returns the number of games in the filter.
1598 // reset: resets the filter so all games are included.
1599 // remove: removes game number <x> from the filter.
1600 // stats: prints filter statistics.
1601 int
1602 sc_filter_old(ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
1603 {
1604  int index = -1;
1605  static const char * options [] = {
1606  "count", "first", "frequency",
1607  "last", "negate", "next",
1608  "previous", "stats",
1609  "search", "release",
1610  "treestats", "export", "copy", "and", "or", "new", NULL
1611  };
1612  enum {
1613  FILTER_COUNT, FILTER_FIRST, FILTER_FREQ,
1614  FILTER_LAST, FILTER_NEGATE, FILTER_NEXT,
1615  FILTER_PREV, FILTER_STATS,
1616  FILTER_SEARCH, FILTER_RELEASE,
1617  FILTER_TREESTATS, FILTER_EXPORT, FILTER_COPY, FILTER_AND, FILTER_OR, FILTER_NEW
1618  };
1619 
1620  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
1621 
1622  switch (index) {
1623  case FILTER_COUNT:
1624  if (argc == 2) {
1625  size_t res = db->getFilter("dbfilter")->size();
1626  return UI_Result(ti, OK, res);
1627  }
1628  break;
1629 
1630  case FILTER_NEW:
1631  if (argc == 3 || argc == 4) {
1632  scidBaseT* dbase = DBasePool::getBase(strGetUnsigned(argv[2]));
1633  if (dbase == NULL) return UI_Result(ti, ERROR_BadArg, "sc_filter: invalid baseId");
1634  if (argc == 4) {
1635  //TODO: Use argv[4] (FEN) instead of current Position
1636  SearchPos fp(db->game->GetCurrentPos());
1637  //TODO: use a dedicated filter instead of treeFilter
1638  HFilter maskfilter = HFilter(dbase->treeFilter);
1639  std::string val;
1640  if (fp.setFilter(dbase, maskfilter, UI_CreateProgressPosMask(ti))) {
1641  val = "tree";
1642  }
1643  return UI_Result(ti, OK, val);
1644  }
1645  return UI_Result(ti, OK, dbase->newFilter());
1646  }
1647  return UI_Result(ti, ERROR_BadArg, "Usage: sc_filter new baseId [FEN]");
1648 
1649  case FILTER_FIRST:
1650  return sc_filter_first (cd, ti, argc, argv);
1651 
1652  case FILTER_LAST:
1653  return sc_filter_last (cd, ti, argc, argv);
1654 
1655  case FILTER_NEXT:
1656  return sc_filter_next (cd, ti, argc, argv);
1657 
1658  case FILTER_PREV:
1659  return sc_filter_prev (cd, ti, argc, argv);
1660 
1661  case FILTER_STATS:
1662  return sc_filter_stats (cd, ti, argc, argv);
1663 
1664  }
1665 
1666  if (argc < 4) return errorResult (ti, "Usage: sc_filter <cmd> baseId filterName");
1667  scidBaseT* dbase = DBasePool::getBase(strGetUnsigned(argv[2]));
1668  if (dbase == NULL) return errorResult (ti, "sc_filter: invalid baseId");
1669  HFilter filter = dbase->getFilter(argv[3]);
1670  if (filter == 0) return errorResult (ti, "sc_filter: invalid filterName");
1671  switch (index) {
1672  case FILTER_AND:
1673  if (argc == 5) {
1674  const HFilter f = dbase->getFilter(argv[4]);
1675  if (f != 0) {
1676  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1677  if (filter.get(i) != 0 && f.get(i) == 0) filter.set(i, 0);
1678  }
1679  return UI_Result(ti, OK);
1680  }
1681  }
1682  return errorResult (ti, "Usage: sc_filter and baseId filterName filterAnd");
1683 
1684  case FILTER_OR:
1685  if (argc == 5) {
1686  const HFilter f = dbase->getFilter(argv[4]);
1687  if (f != 0) {
1688  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1689  if (filter.get(i) == 0) filter.set(i, f.get(i));
1690  }
1691  return UI_Result(ti, OK);
1692  }
1693  }
1694  return errorResult (ti, "Usage: sc_filter or baseId filterName filterOr");
1695 
1696  case FILTER_COPY:
1697  if (argc == 5) {
1698  const HFilter f = dbase->getFilter(argv[4]);
1699  if (f != 0) {
1700  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1701  filter.set(i, f.get(i));
1702  }
1703  return UI_Result(ti, OK);
1704  }
1705  }
1706  return errorResult (ti, "Usage: sc_filter copy baseId filterTo filterFrom");
1707 
1708  case FILTER_FREQ:
1709  return sc_filter_freq (dbase, filter, ti, argc, argv);
1710 
1711  case FILTER_NEGATE:
1712  for (uint i=0, n = dbase->numGames(); i < n; i++) {
1713  filter.set(i, ! filter.get(i) );
1714  }
1715  return UI_Result(ti, OK);
1716 
1717  case FILTER_COUNT:
1718  return setUintResult (ti, filter->size());
1719 
1720  case FILTER_RELEASE:
1721  dbase->deleteFilter(argv[3]);
1722  return TCL_OK;
1723 
1724  case FILTER_SEARCH:
1725  if (argc > 5) {
1726  if (strCompare("header", argv[4]) == 0)
1727  return sc_search_header (cd, ti, dbase, filter, argc -3, argv +3);
1728 
1729  if (strCompare("board", argv[4]) == 0 && argc == 10) {
1730  const char* args[6] = {argv[3], argv[4], argv[9], argv[5],
1731  argv[6], argv[7]};
1732  return sc_search_board(ti, dbase, filter, 6, args);
1733  }
1734  }
1735  return errorResult (ti, "Usage: sc_filter search baseId filterName <header> [args]");
1736 
1737  case FILTER_TREESTATS: {
1738  std::vector<scidBaseT::TreeStat> stats = dbase->getTreeStat(filter);
1739  UI_List res (stats.size());
1740  UI_List ginfo(8);
1741  for (uint i=0; i < stats.size(); i++) {
1742  ginfo.clear();
1743  ginfo.push_back(stats[i].SAN);
1744  ginfo.push_back(stats[i].ngames);
1745  ginfo.push_back(stats[i].resultW);
1746  ginfo.push_back(stats[i].resultD);
1747  ginfo.push_back(stats[i].resultB);
1748  ginfo.push_back(stats[i].exp);
1749  ginfo.push_back(stats[i].nexp);
1750  if (stats[i].toMove == WHITE) ginfo.push_back("W");
1751  else ginfo.push_back(stats[i].toMove == BLACK ? "B" : " ");
1752 
1753  res.push_back(ginfo);
1754  }
1755  return UI_Result(ti, OK, res);
1756  }
1757 
1758  case FILTER_EXPORT:
1759  if (argc >= 7 && argc <=9) {
1760  FILE* exportFile = fopen(argv[5], "wb");
1761  if (exportFile == NULL) return errorResult (ti, "Error opening file for exporting games.");
1762  Game g;
1763  if (strCompare("LaTeX", argv[6]) == 0) {
1766  } else { //Default to PGN
1769  }
1770  if (argc > 7) fprintf(exportFile, "%s", argv[7]);
1771  Progress progress = UI_CreateProgress(ti);
1772  const NameBase* nb = dbase->getNameBase();
1773  size_t count = filter->size();
1774  gamenumT* idxList = new gamenumT[count];
1775  count = dbase->listGames(argv[4], 0, count, filter, idxList);
1776  errorT err = OK;
1777  for (size_t i = 0; i < count; ++i) {
1778  const IndexEntry* ie = dbase->getIndexEntry(idxList[i]);
1779  // Skip any corrupt games:
1780  if (dbase->getGame(ie, dbase->bbuf) != OK) continue;
1781  if (g.Decode (dbase->bbuf, GAME_DECODE_ALL) != OK) continue;
1782  g.LoadStandardTags (ie, nb);
1783  std::pair<const char*, unsigned> pgn = g.WriteToPGN(75, true);
1784  if (pgn.second != fwrite(pgn.first, 1, pgn.second, exportFile)) {
1785  err = ERROR_FileWrite;
1786  break;
1787  }
1788  if ((i % 1024 == 0) && !progress.report(i, count)) {
1789  err = ERROR_UserCancel;
1790  break;
1791  }
1792  }
1793  if (err == OK && argc > 8)
1794  fprintf(exportFile, "%s", argv[8]);
1795  fclose (exportFile);
1796  delete[] idxList;
1797  return UI_Result(ti, err);
1798  }
1799  return errorResult (ti, "Usage: sc_filter export baseId filterName sortCrit filename <PGN|LaTeX> [header] [footer]");
1800 
1801  }
1802  return InvalidCommand (ti, "sc_filter", options);
1803 }
1804 
1805 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1806 // sc_filter_freq:
1807 // Returns a two-integer list showing how many filter games,
1808 // and how many total database games, meet the specified
1809 // date or mean rating range criteria.
1810 // Usage:
1811 // sc_filter freq baseId filterName date <startdate> [<endDate>]
1812 // or sc_filter freq baseId filterName elo <lowerMeanElo> [<upperMeanElo>]
1813 //Klimmek: or sc_filter freq baseId filterName moves <lowerhalfMove> <higherhalfMove>
1814 // add mode to count games with specified movenumber
1815 // where the final parameter defaults to the maximum allowed
1816 // date or Elo rating.
1817 // Note for rating queries: only the rating values in the game
1818 // are used; estimates from other games or the spelling file
1819 // will be ignored. Also, if one player has an Elo rating but
1820 // the other does not, the other rating will be assumed to be
1821 // same as the nonzero rating, up to a maximum of 2200.
1822 int
1823 sc_filter_freq (scidBaseT* dbase, const HFilter& filter, Tcl_Interp * ti, int argc, const char ** argv)
1824 {
1825  const char * usage =
1826  "Usage: sc_filter freq baseId filterName date|elo|move <startDate|minElo|lowerhalfMove> [<endDate|maxElo|higherhalfMove>] [GuessElo]";
1827 
1828  bool eloMode = false;
1829  bool moveMode = false;
1830  bool guessElo = true;
1831  const char * options[] = { "date", "elo", "move", NULL };
1832  enum { OPT_DATE, OPT_ELO, OPT_MOVE };
1833  int option = -1;
1834 
1835  if (argc >= 6 && argc <= 8) {
1836  option = strUniqueMatch (argv[4], options);
1837  }
1838  switch (option) {
1839  case OPT_DATE: eloMode = false; break;
1840  case OPT_ELO: eloMode = true; break;
1841  case OPT_MOVE: moveMode = true; break;
1842  default: return errorResult (ti, usage);
1843  }
1844 
1845  dateT startDate = date_EncodeFromString (argv[5]);
1846  dateT endDate = DATE_MAKE (YEAR_MAX, 12, 31);
1847  uint minElo = strGetUnsigned (argv[5]);
1848  uint maxElo = MAX_ELO;
1849  uint maxMove, minMove;
1850 
1851  minMove = minElo;
1852  maxMove = minMove + 1;
1853  if (argc >= 7) {
1854  endDate = date_EncodeFromString (argv[6]);
1855  maxMove = maxElo = strGetUnsigned (argv[6]);
1856  }
1857  if (argc == 8) {
1858  guessElo = strGetUnsigned (argv[7]);
1859  }
1860  //Klimmek: define _NoEloGuess_: Do not guess Elo, else old behavior
1861  if ( guessElo ) {
1862  // Double min/max Elos to avoid halving every mean Elo:
1863  minElo = minElo + minElo;
1864  maxElo = maxElo + maxElo + 1;
1865  }
1866  // Calculate frequencies in the specified date or rating range:
1867  uint filteredCount = 0;
1868  uint allCount = 0;
1869 
1870  if (eloMode) {
1871  for (uint gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
1872  const IndexEntry* ie = dbase->getIndexEntry(gnum);
1873  if ( guessElo ) {
1874  uint wElo = ie->GetWhiteElo();
1875  uint bElo = ie->GetBlackElo();
1876  uint bothElo = wElo + bElo;
1877  if (wElo == 0 && bElo != 0) {
1878  bothElo += (bElo > 2200 ? 2200 : bElo);
1879  } else if (bElo == 0 && wElo != 0) {
1880  bothElo += (wElo > 2200 ? 2200 : wElo);
1881  }
1882  if (bothElo >= minElo && bothElo <= maxElo) {
1883  allCount++;
1884  if (filter.get(gnum) != 0) {
1885  filteredCount++;
1886  }
1887  }
1888  } else {
1889  //Klimmek: if lowest Elo in the Range: count them
1890  uint mini = ie->GetWhiteElo();
1891  if ( mini > ie->GetBlackElo() ) mini = ie->GetBlackElo();
1892  if (mini < minElo || mini >= maxElo)
1893  continue;
1894  allCount++;
1895  if (filter.get(gnum) != 0) {
1896  filteredCount++;
1897  }
1898  }
1899  }
1900  } else if ( moveMode ) {
1901  //Klimmek: count games with x Moves minMove=NumberHalfmove and maxMove Numberhalfmove+1
1902  for (uint gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
1903  const IndexEntry* ie = dbase->getIndexEntry(gnum);
1904  uint move = ie->GetNumHalfMoves();
1905  if (move >= minMove && move <= maxMove) {
1906  allCount++;
1907  if (filter.get(gnum) != 0) {
1908  filteredCount++;
1909  }
1910  }
1911  }
1912  }
1913  else { // datemode
1914  for (uint gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
1915  const IndexEntry* ie = dbase->getIndexEntry(gnum);
1916  dateT date = ie->GetDate();
1917  if (date >= startDate && date <= endDate) {
1918  allCount++;
1919  if (filter.get(gnum) != 0) {
1920  filteredCount++;
1921  }
1922  }
1923  }
1924  }
1925  appendUintElement (ti, filteredCount);
1926  appendUintElement (ti, allCount);
1927  return TCL_OK;
1928 }
1929 
1930 //TODO:
1931 //This functions do not works because they do not specify the base, the filter and the sort criteria
1932 //So for the moment we assume base=db, filter=dbFilter and sort=N+
1933 
1934 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1935 // sc_filter_first:
1936 // Returns the game number of the first game in the filter,
1937 // or 0 if the filter is empty.
1938 int
1939 sc_filter_first(ClientData, Tcl_Interp * ti, int, const char**)
1940 {
1941  for (uint gnum=0; gnum < db->numGames(); gnum++) {
1942  if (db->dbFilter->Get(gnum) == 0) continue;
1943  return setUintResult (ti, gnum +1);
1944  }
1945  return setUintResult (ti, 0);
1946 }
1947 
1948 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1949 // sc_filter_last:
1950 // Returns the game number of the last game in the filter,
1951 // or 0 if the filter is empty.
1952 int
1953 sc_filter_last(ClientData, Tcl_Interp * ti, int, const char**)
1954 {
1955  long gnum = db->numGames();
1956  for (gnum--; gnum >= 0; gnum--) {
1957  if (db->dbFilter->Get(gnum) == 0) continue;
1958  return setUintResult (ti, gnum +1);
1959  }
1960  return setUintResult (ti, 0);
1961 }
1962 
1963 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1964 // sc_filter_next:
1965 // Returns number of next game in the filter.
1966 int
1967 sc_filter_next(ClientData, Tcl_Interp * ti, int, const char**)
1968 {
1969  if (db->inUse) {
1970  uint nextNumber = db->gameNumber + 1;
1971  while (nextNumber < db->numGames()) {
1972  if (db->dbFilter->Get(nextNumber) > 0) {
1973  return setUintResult (ti, nextNumber + 1);
1974  }
1975  nextNumber++;
1976  }
1977  }
1978  return setUintResult (ti, 0);
1979 }
1980 
1981 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1982 // sc_filter_prev:
1983 // Returns number of previous game in the filter.
1984 int
1985 sc_filter_prev(ClientData, Tcl_Interp * ti, int, const char**)
1986 {
1987  if (db->inUse) {
1988  int prevNumber = db->gameNumber - 1;
1989  while (prevNumber >= 0) {
1990  if (db->dbFilter->Get(prevNumber) > 0) {
1991  return setIntResult (ti, prevNumber + 1);
1992  }
1993  prevNumber--;
1994  }
1995  }
1996  return setUintResult (ti, 0);
1997 }
1998 
1999 //END TODO
2000 
2001 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2002 // sc_filter_stats:
2003 // Returns statistics about the filter.
2004 int
2005 sc_filter_stats (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2006 {
2007  enum {STATS_ALL, STATS_ELO, STATS_YEAR};
2008 
2009  if (argc < 2 || argc > 5) {
2010  return errorResult (ti, "Usage: sc_filter stats [all | elo <xx> | year <xx>]");
2011  }
2012  int statType = STATS_ALL;
2013  uint min = 0;
2014  uint max = 0;
2015  uint inv_max = 0;
2016  if (argc > 2) {
2017  if (argv[2][0] == 'e') { statType = STATS_ELO; }
2018  if (argv[2][0] == 'y') { statType = STATS_YEAR; }
2019  }
2020  if (statType == STATS_ELO || statType == STATS_YEAR) {
2021  if (argc < 4) {
2022  return errorResult (ti, "Incorrect number of parameters.");
2023  }
2024  min = strGetUnsigned (argv[3]);
2025  max = strGetUnsigned (argv[4]);
2026  //Klimmek: +10000 workaround to trigger max elo in filter function
2027  if ( max > 10000 ) {
2028  max -= 10000;
2029  inv_max = 1;
2030  }
2031  }
2032  uint results[4] = {0, 0, 0, 0};
2033  uint total = 0;
2034  const HFilter filter = db->getFilter("dbfilter");
2035  for (uint i=0, n = db->numGames(); i < n; i++) {
2036  const IndexEntry* ie = db->getIndexEntry(i);
2037  if (filter.get(i)) {
2038  if ( max == 0 ) { //Old Statistic :
2039  if (statType == STATS_ELO &&
2040  (ie->GetWhiteElo() < min || ie->GetBlackElo() < min)) {
2041  continue;
2042  }
2043  if (statType == STATS_YEAR
2044  && date_GetYear(ie->GetDate()) < min) {
2045  continue;
2046  }
2047  } else { //Klimmek: new statistic: evaluation in intervals
2048  //count all games where player with highest Elo is in the specific range
2049  if (statType == STATS_ELO ) {
2050  if (inv_max) {
2051  uint maxi = ie->GetWhiteElo();
2052  if ( maxi < ie->GetBlackElo() ) maxi = ie->GetBlackElo();
2053  if (maxi < min || maxi >= max)
2054  continue;
2055  }
2056  else {
2057  //count all games where player with lowest Elo is in the specific range
2058  uint mini = ie->GetWhiteElo();
2059  if ( mini > ie->GetBlackElo() ) mini = ie->GetBlackElo();
2060  if (mini < min || mini >= max)
2061  continue;
2062  }
2063  }
2064  if (statType == STATS_YEAR
2065  && ( date_GetYear(ie->GetDate()) < min || date_GetYear(ie->GetDate()) >= max) ) {
2066  continue;
2067  }
2068  }
2069  results[ie->GetResult()]++;
2070  total++;
2071  }
2072  }
2073  char temp[80];
2074  uint percentScore = results[RESULT_White] * 2 + results[RESULT_Draw] +
2075  results[RESULT_None];
2076  percentScore = total ? percentScore * 500 / total : 0;
2077  sprintf (temp, "%7u %7u %7u %7u %3u%c%u%%",
2078  total,
2079  results[RESULT_White],
2080  results[RESULT_Draw],
2081  results[RESULT_Black],
2082  percentScore / 10, decimalPointChar, percentScore % 10);
2083  Tcl_AppendResult (ti, temp, NULL);
2084  return TCL_OK;
2085 }
2086 
2087 //////////////////////////////////////////////////////////////////////
2088 /// GAME functions
2089 
2090 int
2091 sc_game (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
2092 {
2093  static const char * options [] = {
2094  "altered", "setaltered", "crosstable", "eco",
2095  "find", "firstMoves", "import",
2096  "info", "load", "merge", "moves",
2097  "new", "novelty", "number", "pgn",
2098  "pop", "push", "SANtoUCI", "save",
2099  "startBoard", "strip", "summary",
2100  "tags", "truncate",
2101  "undo", "undoAll", "undoPoint", "redo", NULL
2102  };
2103  enum {
2104  GAME_ALTERED, GAME_SET_ALTERED, GAME_CROSSTABLE, GAME_ECO,
2105  GAME_FIND, GAME_FIRSTMOVES, GAME_IMPORT,
2106  GAME_INFO, GAME_LOAD, GAME_MERGE, GAME_MOVES,
2107  GAME_NEW, GAME_NOVELTY, GAME_NUMBER, GAME_PGN,
2108  GAME_POP, GAME_PUSH, GAME_SANTOUCI, GAME_SAVE,
2109  GAME_STARTBOARD, GAME_STRIP, GAME_SUMMARY,
2110  GAME_TAGS, GAME_TRUNCATE,
2111  GAME_UNDO, GAME_UNDO_ALL, GAME_UNDO_POINT, GAME_REDO
2112  };
2113  int index = -1;
2114  char old_language = 0;
2115 
2116  if (argc > 1) { index = strUniqueMatch (argv[1], options);}
2117 
2118  switch (index) {
2119  case GAME_ALTERED:
2120  return UI_Result(ti, OK, db->gameAltered);
2121 
2122  case GAME_SET_ALTERED:
2123  if (argc != 3 ) {
2124  return errorResult (ti, "Usage: sc_game setaltered [0|1]");
2125  }
2126  db->gameAltered = strGetUnsigned (argv[2]);
2127  break;
2128  case GAME_CROSSTABLE:
2129  return sc_game_crosstable (cd, ti, argc, argv);
2130 
2131  case GAME_ECO: // "sc_game eco" is equivalent to "sc_eco game"
2132  return sc_eco_game (cd, ti, argc, argv);
2133 
2134  case GAME_FIND:
2135  return sc_game_find (cd, ti, argc, argv);
2136 
2137  case GAME_FIRSTMOVES:
2138  return sc_game_firstMoves (cd, ti, argc, argv);
2139 
2140  case GAME_IMPORT:
2141  return sc_game_import (cd, ti, argc, argv);
2142 
2143  case GAME_INFO:
2144  return sc_game_info (cd, ti, argc, argv);
2145 
2146  case GAME_LOAD:
2147  return sc_game_load (cd, ti, argc, argv);
2148 
2149  case GAME_MERGE:
2150  return sc_game_merge (cd, ti, argc, argv);
2151 
2152  case GAME_MOVES:
2153  return sc_game_moves (cd, ti, argc, argv);
2154 
2155  case GAME_NEW:
2156  db->gameAlterations.clear();
2157  return sc_game_new (cd, ti, argc, argv);
2158 
2159  case GAME_NOVELTY:
2160  return sc_game_novelty (cd, ti, argc, argv);
2161 
2162  case GAME_NUMBER:
2163  return setUintResult (ti, db->gameNumber + 1);
2164 
2165  case GAME_PGN:
2166  return sc_game_pgn (cd, ti, argc, argv);
2167 
2168  case GAME_POP:
2169  return sc_game_pop (cd, ti, argc, argv);
2170 
2171  case GAME_PUSH:
2172  return sc_game_push (cd, ti, argc, argv);
2173 
2174  case GAME_SANTOUCI:
2175  if (argc == 3) {
2176  auto pos = db->game->GetCurrentPos();
2177  simpleMoveT sm;
2178  errorT err = pos->ParseMove(&sm, argv[2]);
2179  if (err != OK)
2180  return UI_Result(ti, err);
2181 
2182  char buf[16];
2183  pos->MakeUCIString(&sm, buf);
2184  return UI_Result(ti, OK, buf);
2185  }
2186  return errorResult(ti, "usage sc_game SANtoUCI move");
2187 
2188  case GAME_SAVE:
2189  return sc_game_save (cd, ti, argc, argv);
2190 
2191  case GAME_STARTBOARD:
2192  return sc_game_startBoard (cd, ti, argc, argv);
2193 
2194  case GAME_STRIP:
2195  return sc_game_strip (cd, ti, argc, argv);
2196 
2197  case GAME_SUMMARY:
2198  return sc_game_summary (cd, ti, argc, argv);
2199 
2200  case GAME_TAGS:
2201  return sc_game_tags (cd, ti, argc, argv);
2202 
2203  case GAME_TRUNCATE:
2204  old_language = language;
2205  language = 0;
2206  if (argc > 2 && strIsPrefix (argv[2], "-start")) {
2207  // "sc_game truncate -start" truncates the moves up to the
2208  // current position:
2209  db->game->TruncateStart();
2210  } else {
2211  // Truncate from the current position to the end of the game
2212  db->game->Truncate();
2213  }
2214  db->gameAltered = true;
2215  language = old_language;
2216  break;
2217  case GAME_UNDO:
2218  if (argc > 2 && strCompare("size", argv[2]) == 0) {
2219  return UI_Result(ti, OK, (uint) db->gameAlterations.undoSize());
2220  }
2221  db->game = db->gameAlterations.undo(db->game);
2222  return UI_Result(ti, OK);
2223 
2224  case GAME_UNDO_ALL:
2225  db->gameAltered = false;
2226  db->gameAlterations.clear();
2227  if (db->gameNumber < 0) {
2228  db->game->Clear();
2229  } else {
2230  const IndexEntry* ie = db->getIndexEntry(db->gameNumber);
2231  errorT err = db->getGame(ie, db->bbuf);
2232  if (err != OK) return UI_Result(ti, err);
2233  err = db->game->Decode (db->bbuf, GAME_DECODE_ALL);
2234  if (err != OK) return UI_Result(ti, err);
2235  db->game->LoadStandardTags (ie, db->getNameBase());
2236  db->game->MoveToPly(0);
2237  }
2238  return UI_Result(ti, OK);
2239 
2240  case GAME_UNDO_POINT:
2241  db->gameAlterations.store(db->game);
2242  break;
2243 
2244  case GAME_REDO:
2245  if (argc > 2 && strCompare("size", argv[2]) == 0) {
2246  return UI_Result(ti, OK, (uint) db->gameAlterations.redoSize());
2247  }
2248  db->game = db->gameAlterations.redo(db->game);
2249  return UI_Result(ti, OK);
2250 
2251  default:
2252  return InvalidCommand (ti, "sc_game", options);
2253  }
2254 
2255  return TCL_OK;
2256 }
2257 
2258 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2259 // isCrosstableGame:
2260 // Returns true if the game with the specified index entry
2261 // is considered a crosstable game. It must have the specified
2262 // Event and Site, and a Date within the specified range or
2263 // have the specified non-zero EventDate.
2264 static inline bool
2265 isCrosstableGame (const IndexEntry* ie, idNumberT siteID, idNumberT eventID,
2266  dateT eventDate)
2267 {
2268  if (ie->GetSite() != siteID || ie->GetEvent() != eventID) {
2269  return false;
2270  }
2271  dateT EventDate = ie->GetEventDate();
2272  if (eventDate != 0 && EventDate != 0 && EventDate != eventDate) {
2273  return false;
2274  }
2275  return true;
2276 }
2277 
2278 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2279 // sc_game_crosstable:
2280 // Returns the crosstable for the current game.
2281 int
2282 sc_game_crosstable (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2283 {
2284 #ifndef WINCE
2285  static const char * options [] = {
2286  "plain", "html", "hypertext", "latex", "filter", "count", NULL
2287  };
2288  enum {
2289  OPT_PLAIN, OPT_HTML, OPT_HYPERTEXT, OPT_LATEX, OPT_FILTER, OPT_COUNT
2290  };
2291  int option = -1;
2292 
2293  const char * usageMsg =
2294  "Usage: sc_game crosstable plain|html|hypertext|filter|count [name|rating|score|country] [allplay|swiss] [(+|-)(colors|countries|tallies|ratings|titles|groups|breaks|numcolumns)]";
2295 
2296  static const char * extraOptions [] = {
2297  "allplay", "knockout", "swiss", "auto",
2298  "name", "rating", "score", "country",
2299  "-ages", "+ages", // Show player ages
2300  "-breaks", "+breaks", // Show tiebreak scores
2301  "-colors", "+colors", // Show game colors in Swiss table
2302  "-countries", "+countries", // Show current countries
2303  "-tallies", "+tallies",
2304  "-ratings", "+ratings", // Show Elo ratings
2305  "-titles", "+titles", // Show FIDE titles
2306  "-groups", "+groups", // Separate players into score groups
2307  "-deleted", "+deleted", // Include deleted games in table
2308  "-numcolumns", "+numcolumns", // All-play-all numbered columns
2309  "-gameNumber",
2310  "-threewin", "+threewin", // Give 3 points for win, 1 for draw
2311  NULL
2312  };
2313  enum {
2314  EOPT_ALLPLAY, EOPT_KNOCKOUT, EOPT_SWISS, EOPT_AUTO,
2315  EOPT_SORT_NAME, EOPT_SORT_RATING, EOPT_SORT_SCORE, EOPT_SORT_COUNTRY,
2316  EOPT_AGES_OFF, EOPT_AGES_ON,
2317  EOPT_BREAKS_OFF, EOPT_BREAKS_ON,
2318  EOPT_COLORS_OFF, EOPT_COLORS_ON,
2319  EOPT_COUNTRIES_OFF, EOPT_COUNTRIES_ON,
2320  EOPT_TALLIES_OFF, EOPT_TALLIES_ON,
2321  EOPT_RATINGS_OFF, EOPT_RATINGS_ON,
2322  EOPT_TITLES_OFF, EOPT_TITLES_ON,
2323  EOPT_GROUPS_OFF, EOPT_GROUPS_ON,
2324  EOPT_DELETED_OFF, EOPT_DELETED_ON,
2325  EOPT_NUMCOLUMNS_OFF, EOPT_NUMCOLUMNS_ON,
2326  EOPT_GNUMBER,
2327  EOPT_THREEWIN_OFF, EOPT_THREEWIN_ON
2328  };
2329 
2330  int sort = EOPT_SORT_SCORE;
2332  bool showAges = true;
2333  bool showColors = true;
2334  bool showCountries = true;
2335  bool showTallies = true;
2336  bool showRatings = true;
2337  bool showTitles = true;
2338  bool showBreaks = false;
2339  bool scoreGroups = false;
2340  bool useDeletedGames = false;
2341  bool numColumns = false; // Numbers for columns in all-play-all table
2342  uint numTableGames = 0;
2343  uint gameNumber = 0;
2344  bool threewin = false;
2345 
2346  if (argc >= 3) { option = strUniqueMatch (argv[2], options); }
2347  if (option < 0) { return errorResult (ti, usageMsg); }
2348 
2349  for (int arg=3; arg < argc; arg++) {
2350  int extraOption = strUniqueMatch (argv[arg], extraOptions);
2351  switch (extraOption) {
2352  case EOPT_ALLPLAY: mode = CROSSTABLE_AllPlayAll; break;
2353  case EOPT_KNOCKOUT: mode = CROSSTABLE_Knockout; break;
2354  case EOPT_SWISS: mode = CROSSTABLE_Swiss; break;
2355  case EOPT_AUTO: mode = CROSSTABLE_Auto; break;
2356  case EOPT_SORT_NAME: sort = EOPT_SORT_NAME; break;
2357  case EOPT_SORT_RATING: sort = EOPT_SORT_RATING; break;
2358  case EOPT_SORT_SCORE: sort = EOPT_SORT_SCORE; break;
2359  case EOPT_SORT_COUNTRY: sort = EOPT_SORT_COUNTRY; break;
2360  case EOPT_AGES_OFF: showAges = false; break;
2361  case EOPT_AGES_ON: showAges = true; break;
2362  case EOPT_BREAKS_OFF: showBreaks = false; break;
2363  case EOPT_BREAKS_ON: showBreaks = true; break;
2364  case EOPT_COLORS_OFF: showColors = false; break;
2365  case EOPT_COLORS_ON: showColors = true; break;
2366  case EOPT_COUNTRIES_OFF: showCountries = false; break;
2367  case EOPT_COUNTRIES_ON: showCountries = true; break;
2368  case EOPT_TALLIES_OFF: showTallies = false; break;
2369  case EOPT_TALLIES_ON: showTallies = true; break;
2370  case EOPT_RATINGS_OFF: showRatings = false; break;
2371  case EOPT_RATINGS_ON: showRatings = true; break;
2372  case EOPT_TITLES_OFF: showTitles = false; break;
2373  case EOPT_TITLES_ON: showTitles = true; break;
2374  case EOPT_GROUPS_OFF: scoreGroups = false; break;
2375  case EOPT_GROUPS_ON: scoreGroups = true; break;
2376  case EOPT_DELETED_OFF: useDeletedGames = false; break;
2377  case EOPT_DELETED_ON: useDeletedGames = true; break;
2378  case EOPT_NUMCOLUMNS_OFF: numColumns = false; break;
2379  case EOPT_NUMCOLUMNS_ON: numColumns = true; break;
2380  case EOPT_GNUMBER:
2381  // Game number to print the crosstable for is
2382  // given in the next argument:
2383  if (arg+1 >= argc) { return errorResult (ti, usageMsg); }
2384  gameNumber = strGetUnsigned (argv[arg+1]);
2385  arg++;
2386  break;
2387  case EOPT_THREEWIN_OFF: threewin = false ; break;
2388  case EOPT_THREEWIN_ON: threewin = true ; break;
2389  default: return errorResult (ti, usageMsg);
2390  }
2391  }
2392  if (!db->inUse) { return TCL_OK; }
2393 
2394  const char * newlineStr = "";
2395  switch (option) {
2396  case OPT_PLAIN: newlineStr = "\n"; break;
2397  case OPT_HTML: newlineStr = "<br>\n"; break;
2398  case OPT_HYPERTEXT: newlineStr = "<br>"; break;
2399  case OPT_LATEX: newlineStr = "\\\\\n"; break;
2400  }
2401 
2402  // Load crosstable game if necessary:
2403  Game * g = db->game;
2404  if (gameNumber > 0) {
2405  g = scratchGame;
2406  g->Clear();
2407  if (gameNumber > db->numGames()) {
2408  return setResult (ti, "Invalid game number");
2409  }
2410  const IndexEntry* ie = db->getIndexEntry(gameNumber - 1);
2411  if (ie->GetLength() == 0) {
2412  return errorResult (ti, "Error: empty game file record.");
2413  }
2414  if (db->getGame(ie, db->bbuf) != OK) {
2415  return errorResult (ti, "Error reading game file.");
2416  }
2417  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
2418  return errorResult (ti, "Error decoding game.");
2419  }
2420  g->LoadStandardTags (ie, db->getNameBase());
2421  }
2422 
2423  idNumberT eventId = 0, siteId = 0;
2424  if (db->getNameBase()->FindExactName (NAME_EVENT, g->GetEventStr(), &eventId) != OK) {
2425  return TCL_OK;
2426  }
2427  if (db->getNameBase()->FindExactName (NAME_SITE, g->GetSiteStr(), &siteId) != OK) {
2428  return TCL_OK;
2429  }
2430 
2431  dateT eventDate = g->GetEventDate();
2432  dateT firstSeenDate = g->GetDate();
2433  dateT lastSeenDate = g->GetDate();
2434 
2435  Crosstable * ctable = new Crosstable;
2436  if (sort == EOPT_SORT_NAME) { ctable->SortByName(); }
2437  if (sort == EOPT_SORT_RATING) { ctable->SortByElo(); }
2438  if (sort == EOPT_SORT_COUNTRY) { ctable->SortByCountry(); }
2439 
2440  ctable->SetThreeWin(threewin);
2441  ctable->SetSwissColors (showColors);
2442  ctable->SetAges (showAges);
2443  ctable->SetCountries (showCountries);
2444  ctable->SetTallies (showTallies);
2445  ctable->SetElos (showRatings);
2446  ctable->SetTitles (showTitles);
2447  ctable->SetTiebreaks (showBreaks);
2448  ctable->SetSeparateScoreGroups (scoreGroups);
2449  ctable->SetDecimalPointChar (decimalPointChar);
2450  ctable->SetNumberedColumns (numColumns);
2451 
2452  switch (option) {
2453  case OPT_PLAIN: ctable->SetPlainOutput(); break;
2454  case OPT_HTML: ctable->SetHtmlOutput(); break;
2455  case OPT_HYPERTEXT: ctable->SetHypertextOutput(); break;
2456  case OPT_LATEX: ctable->SetLaTeXOutput(); break;
2457  }
2458 
2459  // Find all games that should be listed in the crosstable:
2460  const SpellChecker* spell = spellChk;
2461  bool tableFullMessage = false;
2462  for (uint i=0, n = db->numGames(); i < n; i++) {
2463  const IndexEntry* ie = db->getIndexEntry(i);
2464  if (ie->GetDeleteFlag() && !useDeletedGames) { continue; }
2465  if (! isCrosstableGame (ie, siteId, eventId, eventDate)) {
2466  continue;
2467  }
2468  idNumberT whiteId = ie->GetWhite();
2469  const char * whiteName = db->getNameBase()->GetName (NAME_PLAYER, whiteId);
2470  idNumberT blackId = ie->GetBlack();
2471  const char * blackName = db->getNameBase()->GetName (NAME_PLAYER, blackId);
2472 
2473  // Ensure we have two different players:
2474  if (whiteId == blackId) { continue; }
2475 
2476  // If option is OPT_FILTER, adjust the filter and continue &&&
2477  if (option == OPT_FILTER) {
2478  db->dbFilter->Set (i, 1);
2479  continue;
2480  }
2481 
2482  // If option is OPT_COUNT, increment game count and continue:
2483  if (option == OPT_COUNT) {
2484  numTableGames++;
2485  continue;
2486  }
2487 
2488  // Add the two players to the crosstable:
2489  if (ctable->AddPlayer (whiteId, whiteName, ie->GetWhiteElo(), spell) != OK ||
2490  ctable->AddPlayer (blackId, blackName, ie->GetBlackElo(), spell) != OK)
2491  {
2492  if (! tableFullMessage) {
2493  tableFullMessage = true;
2494  Tcl_AppendResult (ti, "Warning: Player limit reached; table is incomplete\n\n", NULL);
2495  }
2496  continue;
2497  }
2498 
2500  dateT date = ie->GetDate();
2501  resultT result = ie->GetResult();
2502  ctable->AddResult (i+1, whiteId, blackId, result, round, date);
2503  if (date < firstSeenDate) { firstSeenDate = date; }
2504  if (date > lastSeenDate) { lastSeenDate = date; }
2505  }
2506 
2507  if (option == OPT_COUNT) {
2508  // Just return a count of the number of tournament games:
2509  delete ctable;
2510  return setUintResult (ti, numTableGames);
2511  }
2512  if (option == OPT_FILTER) {
2513  delete ctable;
2514  return TCL_OK;
2515  }
2516  if (ctable->NumPlayers() < 2) {
2517  delete ctable;
2518  return setResult (ti, "No crosstable for this game.");
2519  }
2520 
2521  if (option == OPT_LATEX) {
2522  Tcl_AppendResult (ti, "\\documentclass[10pt,a4paper]{article}\n\n",
2523  "\\usepackage{a4wide}\n\n",
2524  "\\begin{document}\n\n",
2525  "\\setlength{\\parindent}{0cm}\n",
2526  "\\setlength{\\parskip}{0.5ex}\n",
2527  "\\small\n", NULL);
2528  }
2529 
2530  if (mode == CROSSTABLE_Auto) { mode = ctable->BestMode(); }
2531 
2532  // Limit all-play-all tables to 300 players:
2533  uint apaLimit = 300;
2534  if (mode == CROSSTABLE_AllPlayAll &&
2535  ctable->NumPlayers() > apaLimit &&
2536  !tableFullMessage) {
2537  Tcl_AppendResult (ti, "Warning: Too many players for all-play-all; try displaying as a swiss tournament.\n\n", NULL);
2538  }
2539 
2540  char stemp[1000];
2541  sprintf (stemp, "%s%s%s, ", g->GetEventStr(), newlineStr, g->GetSiteStr());
2542  Tcl_AppendResult (ti, stemp, NULL);
2543  date_DecodeToString (firstSeenDate, stemp);
2544  strTrimDate (stemp);
2545  Tcl_AppendResult (ti, stemp, NULL);
2546  if (lastSeenDate != firstSeenDate) {
2547  date_DecodeToString (lastSeenDate, stemp);
2548  strTrimDate (stemp);
2549  Tcl_AppendResult (ti, " - ", stemp, NULL);
2550  }
2551  Tcl_AppendResult (ti, newlineStr, NULL);
2552 
2553  eloT avgElo = ctable->AvgRating();
2554  if (avgElo > 0 && showRatings) {
2555  Tcl_AppendResult (ti, translate (ti, "AverageRating", "Average Rating"),
2556  ": ", NULL);
2557  appendUintResult (ti, avgElo);
2558  uint category = ctable->FideCategory (avgElo);
2559  if (category > 0 && mode == CROSSTABLE_AllPlayAll) {
2560  sprintf (stemp, " (%s %u)",
2561  translate (ti, "Category", "Category"), category);
2562  Tcl_AppendResult (ti, stemp, NULL);
2563  }
2564  Tcl_AppendResult (ti, newlineStr, NULL);
2565  }
2566 
2567  DString * dstr = new DString;
2568  if (mode != CROSSTABLE_AllPlayAll) { apaLimit = 0; }
2569  ctable->PrintTable (dstr, mode, apaLimit, db->gameNumber+1);
2570 
2571  Tcl_AppendResult (ti, dstr->Data(), NULL);
2572  if (option == OPT_LATEX) {
2573  Tcl_AppendResult (ti, "\n\\end{document}\n", NULL);
2574  }
2575  delete ctable;
2576  delete dstr;
2577 #endif
2578  return TCL_OK;
2579 }
2580 
2581 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2582 // sc_game_find:
2583 // Returns the game number of the game in that current database
2584 // that best matches the specified number, player names, site,
2585 // round, year and result.
2586 // This command is used primarily to locate a bookmarked game in
2587 // a database where the number may be inaccurate due to database
2588 // sorting or compaction.
2589 int
2590 sc_game_find (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2591 {
2592  if (argc != 9) {
2593  return errorResult (ti, "sc_game_find: Incorrect parameters");
2594  }
2595 
2596  uint gnum = strGetUnsigned (argv[2]);
2597  if (gnum == 0) { return setUintResult (ti, 0); }
2598  gnum--;
2599  const char * whiteStr = argv[3];
2600  const char * blackStr = argv[4];
2601  const char * siteStr = argv[5];
2602  const char * roundStr = argv[6];
2603  uint year = strGetUnsigned(argv[7]);
2604  resultT result = strGetResult (argv[8]);
2605 
2606  idNumberT white, black, site, round;
2607  white = black = site = round = 0;
2608  db->getNameBase()->FindExactName (NAME_PLAYER, whiteStr, &white);
2609  db->getNameBase()->FindExactName (NAME_PLAYER, blackStr, &black);
2610  db->getNameBase()->FindExactName (NAME_SITE, siteStr, &site);
2611  db->getNameBase()->FindExactName (NAME_ROUND, roundStr, &round);
2612 
2613  // We give each game a "score" which is 1 for each matching field.
2614  // So the best possible score is 6.
2615 
2616  // First, check if the specified game number matches all fields:
2617  if (db->numGames() > gnum) {
2618  uint score = 0;
2619  const IndexEntry* ie = db->getIndexEntry(gnum);
2620  if (ie->GetWhite() == white) { score++; }
2621  if (ie->GetBlack() == black) { score++; }
2622  if (ie->GetSite() == site) { score++; }
2623  if (ie->GetRound() == round) { score++; }
2624  if (ie->GetYear() == year) { score++; }
2625  if (ie->GetResult() == result) { score++; }
2626  if (score == 6) { return setUintResult (ti, gnum+1); }
2627  }
2628 
2629  // Now look for the best matching game:
2630  uint bestNum = 0;
2631  uint bestScore = 0;
2632 
2633  for (uint i=0, n = db->numGames(); i < n; i++) {
2634  uint score = 0;
2635  const IndexEntry* ie = db->getIndexEntry(i);
2636  if (ie->GetWhite() == white) { score++; }
2637  if (ie->GetBlack() == black) { score++; }
2638  if (ie->GetSite() == site) { score++; }
2639  if (ie->GetRound() == round) { score++; }
2640  if (ie->GetYear() == year) { score++; }
2641  if (ie->GetResult() == result) { score++; }
2642  // Update if the best score, favouring the specified game number
2643  // in the case of a tie:
2644  if (score > bestScore || (score == bestScore && gnum == i)) {
2645  bestScore = score;
2646  bestNum = i;
2647  }
2648  // Stop now if the best possible match is found:
2649  if (score == 6) { break; }
2650  }
2651  return setUintResult (ti, bestNum + 1);
2652 }
2653 
2654 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2655 // sc_game_firstMoves:
2656 // get the first few moves of the specified game as a text line.
2657 // E.g., "sc_game firstMoves 4" might return "1.e4 e5 2.Nf3 Nf6"
2658 int
2659 sc_game_firstMoves (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
2660 {
2661  if (argc != 3) {
2662  return errorResult (ti, "Usage: sc_game firstMoves <numMoves>");
2663  }
2664  if (!db->inUse) {
2665  return errorResult (ti, errMsgNotOpen(ti));
2666  }
2667 
2668  int plyCount = strGetInteger (argv[2]);
2669  // Check plyCount is a reasonable value, or set it to current plycount.
2670  if (plyCount < 0) plyCount = db->game->GetCurrentPly();
2671  if (plyCount == 0) plyCount = 1;
2672 
2673  DString dstr;
2674  db->game->GetPartialMoveList (&dstr, plyCount);
2675  return UI_Result(ti, OK, std::string(dstr.Data()));
2676 }
2677 
2678 int sc_game_import(ClientData, Tcl_Interp* ti, int argc, const char** argv) {
2679  if (argc != 3)
2680  return errorResult(ti, "Usage: sc_game import <pgn-text>");
2681 
2682  db->game->Clear();
2683  db->gameAltered = true;
2684 
2685  PgnParseLog pgn;
2686  if (!pgnParseGame(argv[2], std::strlen(argv[2]), *db->game, pgn) &&
2687  pgn.log.empty())
2688  return UI_Result(ti, OK, "No PGN text found.");
2689 
2690  if (pgn.log.empty())
2691  return UI_Result(ti, OK,
2692  "PGN text imported with no errors or warnings.");
2693 
2694  return UI_Result(ti, OK,
2695  "Errors/warnings importing PGN text:\n\n" + pgn.log);
2696 }
2697 
2698 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2699 // probe_tablebase:
2700 // Probes the tablebases for the current position, and returns
2701 // the score, a descriptive score with optimal moves, or just a
2702 // (random) optimal move.
2703 bool
2704 probe_tablebase (Tcl_Interp * ti, int mode, DString * dstr)
2705 {
2706  int score = 0;
2707  bool showResult = false;
2708  bool showSummary = false;
2709  bool fullReport = false;
2710  bool optimalMoves = false;
2711  colorT toMove = db->game->GetCurrentPos()->GetToMove();
2712 
2713  switch (mode) {
2714  case PROBE_RESULT:
2715  showResult = true;
2716  break;
2717  case PROBE_SUMMARY:
2718  showResult = true;
2719  showSummary = true;
2720  break;
2721  case PROBE_REPORT:
2722  fullReport = true;
2723  break;
2724  case PROBE_OPTIMAL:
2725  optimalMoves = true;
2726  break;
2727  default:
2728  return false;
2729  }
2730 
2731  if (scid_TB_Probe (db->game->GetCurrentPos(), &score) != OK) {
2732  if (! fullReport) { return false; }
2733  }
2734 
2735  Position * gamePos = NULL;
2736  bool moveFound [MAX_LEGAL_MOVES] = {0};
2737  int moveScore [MAX_LEGAL_MOVES] = {0};
2738  bool movePrinted [MAX_LEGAL_MOVES] = {0};
2739  uint winCount = 0;
2740  uint drawCount = 0;
2741  uint lossCount = 0;
2742  uint unknownCount = 0;
2743 
2744  MoveList moveList;
2745  sanListT sanList;
2746  gamePos = db->game->GetCurrentPos();
2747  gamePos->GenerateMoves (&moveList);
2748  gamePos->CalcSANStrings (&sanList, SAN_CHECKTEST);
2749 
2750  if (showSummary || fullReport || optimalMoves) {
2751  Position scratchPos = *gamePos;
2752 
2753  for (uint i=0; i < moveList.Size(); i++) {
2754  simpleMoveT * smPtr = moveList.Get(i);
2755  scratchPos.DoSimpleMove (smPtr);
2756  moveFound[i] = false;
2757  movePrinted[i] = false;
2758  int newScore = 0;
2759  if (scid_TB_Probe (&scratchPos, &newScore) == OK) {
2760  moveFound[i] = true;
2761  moveScore[i] = newScore;
2762  if (newScore < 0) {
2763  winCount++;
2764  } else if (newScore == 0) {
2765  drawCount++;
2766  } else {
2767  lossCount++;
2768  }
2769  } else {
2770  unknownCount++;
2771  }
2772  scratchPos.UndoSimpleMove (smPtr);
2773  }
2774  }
2775 
2776  // Optimal moves mode: return only the optimal moves, nothing else.
2777  if (optimalMoves) {
2778  uint count = 0;
2779  for (uint i=0; i < moveList.Size(); i++) {
2780  if ((score >= 0 && moveScore[i] == -score) ||
2781  (score < 0 && moveScore[i] == -score - 1)) {
2782  if (count > 0) { dstr->Append (" "); }
2783  dstr->Append (sanList.list[i]);
2784  count++;
2785  }
2786  }
2787  return true;
2788  }
2789 
2790  if (fullReport) {
2791  char tempStr [80];
2792  sprintf (tempStr, "+:%u =:%u -:%u ?:%u",
2793  winCount, drawCount, lossCount, unknownCount);
2794  dstr->Append (tempStr);
2795  int prevScore = -9999999; // Lower than any possible TB score
2796  bool first = true;
2797 
2798  while (1) {
2799  bool found = false;
2800  uint index = 0;
2801  int bestScore = 0;
2802  const char * bestMove = "";
2803  for (uint i=0; i < moveList.Size(); i++) {
2804  if (movePrinted[i]) { continue; }
2805  if (! moveFound[i]) { continue; }
2806  int newScore = - moveScore[i];
2807  if (!found ||
2808  (newScore > 0 && bestScore <= 0) ||
2809  (newScore > 0 && newScore < bestScore) ||
2810  (newScore == 0 && bestScore < 0) ||
2811  (newScore < 0 && bestScore < 0 && newScore < bestScore) ||
2812  (newScore == bestScore &&
2813  strCompare (bestMove, sanList.list[i]) > 0) ) {
2814  found = true;
2815  index = i;
2816  bestScore = newScore;
2817  bestMove = sanList.list[i];
2818  }
2819  }
2820  if (!found) { break; }
2821  movePrinted[index] = true;
2822  if (first ||
2823  (bestScore > 0 && prevScore < 0) ||
2824  (bestScore == 0 && prevScore != 0) ||
2825  (bestScore < 0 && prevScore >= 0)) {
2826  dstr->Append ("\n");
2827  first = false;
2828  const char * tag = NULL;
2829  const char * msg = NULL;
2830  if (bestScore > 0) {
2831  tag = "WinningMoves"; msg = "Winning moves";
2832  } else if (bestScore < 0) {
2833  tag = "LosingMoves"; msg = "Losing moves";
2834  } else {
2835  tag = "DrawingMoves"; msg = "Drawing moves";
2836  }
2837  dstr->Append ("\n", translate(ti, tag, msg), ":");
2838  }
2839  if (bestScore != prevScore) {
2840  if (bestScore > 0) {
2841  sprintf (tempStr, " +%3d ", bestScore);
2842  } else if (bestScore == 0) {
2843  strCopy (tempStr, " = ");
2844  } else {
2845  sprintf (tempStr, " -%3d ", -bestScore);
2846  }
2847  dstr->Append ("\n", tempStr);
2848  } else {
2849  dstr->Append (", ");
2850  }
2851  prevScore = bestScore;
2852  dstr->Append (bestMove);
2853  }
2854  if (unknownCount > 0) {
2855  dstr->Append ("\n\n");
2856  dstr->Append (translate (ti, "UnknownMoves", "Unknown-result moves"));
2857  dstr->Append (":\n ? ");
2858  bool firstUnknown = true;
2859  while (1) {
2860  bool found = false;
2861  const char * bestMove = "";
2862  uint index = 0;
2863  for (uint i=0; i < moveList.Size(); i++) {
2864  if (!moveFound[i] && !movePrinted[i]) {
2865  if (!found ||
2866  strCompare (bestMove, sanList.list[i]) > 0) {
2867  found = true;
2868  bestMove = sanList.list[i];
2869  index = i;
2870  }
2871  }
2872  }
2873  if (!found) { break; }
2874  movePrinted[index] = true;
2875  if (!firstUnknown) {
2876  dstr->Append (", ");
2877  }
2878  firstUnknown = false;
2879  dstr->Append (bestMove);
2880  }
2881  }
2882  dstr->Append ("\n");
2883  return true;
2884  }
2885 
2886  if (score == 0) {
2887  // Print drawn tablebase position info:
2888  if (showResult) {
2889  dstr->Append ("= [", translate (ti, "Draw"));
2890  }
2891  if (showSummary) {
2892  uint drawcount = 0;
2893  uint losscount = 0;
2894  const char * drawlist [MAX_LEGAL_MOVES];
2895  const char * losslist [MAX_LEGAL_MOVES];
2896 
2897  for (uint i=0; i < moveList.Size(); i++) {
2898  if (moveFound[i]) {
2899  if (moveScore[i] == 0) {
2900  drawlist[drawcount] = sanList.list[i];
2901  drawcount++;
2902  } else {
2903  losslist[losscount] = sanList.list[i];
2904  losscount++;
2905  }
2906  }
2907  }
2908  if (moveList.Size() == 0) {
2909  dstr->Append (" (", translate (ti, "stalemate"), ")");
2910  } else if (drawcount == moveList.Size()) {
2911  dstr->Append (" ", translate (ti, "withAllMoves"));
2912  } else if (drawcount == 1) {
2913  dstr->Append (" ", translate (ti, "with"));
2914  dstr->Append (" ", drawlist[0]);
2915  } else if (drawcount+1 == moveList.Size() && losscount==1) {
2916  dstr->Append (" ", translate (ti, "withAllButOneMove"));
2917  } else if (drawcount > 0) {
2918  dstr->Append (" ", translate (ti, "with"), " ");
2919  dstr->Append (drawcount);
2920  dstr->Append (" ");
2921  if (drawcount == 1) {
2922  dstr->Append (translate (ti, "move"));
2923  } else {
2924  dstr->Append (translate (ti, "moves"));
2925  }
2926  dstr->Append (": ");
2927  for (uint m=0; m < drawcount; m++) {
2928  if (m < 3) {
2929  if (m > 0) { dstr->Append (", "); }
2930  dstr->Append (drawlist[m]);
2931  }
2932  }
2933  if (drawcount > 3) { dstr->Append (", ..."); }
2934  }
2935  if (losscount > 0) {
2936  dstr->Append (" (");
2937  if (losscount == 1) {
2938  if (losscount+drawcount == moveList.Size()) {
2939  dstr->Append (translate (ti, "only"), " ");
2940  }
2941  dstr->Append (losslist[0], " ", translate (ti, "loses"));
2942  } else if (drawcount < 4 &&
2943  drawcount+losscount == moveList.Size()) {
2944  dstr->Append (translate (ti, "allOthersLose"));
2945  } else {
2946  dstr->Append (losscount);
2947  dstr->Append (" ", translate (ti, "lose"), ": ");
2948  for (uint m=0; m < losscount; m++) {
2949  if (m < 3) {
2950  if (m > 0) { dstr->Append (", "); }
2951  dstr->Append (losslist[m]);
2952  }
2953  }
2954  if (losscount > 3) { dstr->Append (", ..."); }
2955  }
2956  dstr->Append (")");
2957  }
2958  }
2959  if (showResult) { dstr->Append ("]"); }
2960 
2961  } else if (score > 0) {
2962  // Print side-to-move-mates tablebase info:
2963  if (showResult) {
2964  char temp[200];
2965  sprintf (temp, "%s:%d [%s %s %d",
2966  toMove == WHITE ? "+-" : "-+", score,
2967  translate (ti, toMove == WHITE ? "White" : "Black"),
2968  translate (ti, "matesIn"), score);
2969  dstr->Append (temp);
2970  }
2971 
2972  // Now show all moves that mate optimally.
2973  // This requires generating all legal moves, and trying each
2974  // to find its tablebase score; optimal moves will have
2975  // the condition (new_score == -old_score).
2976 
2977  if (showSummary) {
2978  uint count = 0;
2979 
2980  for (uint i=0; i < moveList.Size(); i++) {
2981  if (moveFound[i] && moveScore[i] == -score) {
2982  count++;
2983  if (count == 1) {
2984  dstr->Append (" ", translate (ti, "with"), ": ");
2985  } else {
2986  dstr->Append (", ");
2987  }
2988  dstr->Append (sanList.list[i]);
2989  }
2990  }
2991  }
2992  if (showResult) { dstr->Append ("]"); }
2993 
2994  } else {
2995  // Score is negative so side to move is LOST:
2996  if (showResult) {
2997  char tempStr [80];
2998  if (score == -1) {
2999  sprintf (tempStr, "# [%s %s %s",
3000  translate (ti, toMove == WHITE ? "Black" : "White"),
3001  translate (ti, "hasCheckmated"),
3002  translate (ti, toMove == WHITE ? "White" : "Black"));
3003  } else {
3004  sprintf (tempStr, "%s:%d [%s %s %d",
3005  toMove == WHITE ? "-+" : "+-", -1 - score,
3006  translate (ti, toMove == WHITE ? "Black" : "White"),
3007  translate (ti, "matesIn"),
3008  -1 - score);
3009  }
3010  dstr->Append (tempStr);
3011  }
3012 
3013  // Now show all moves that last optimally.
3014  // This requires generating all legal moves, and trying
3015  // each to find its tablebase score; optimal moves will
3016  // have the condition (new_score == (-old_score - 1)).
3017 
3018  if (showSummary) {
3019  uint count = 0;
3020  for (uint i=0; i < moveList.Size(); i++) {
3021  if (moveFound[i] && moveScore[i] == (-score - 1)) {
3022  count++;
3023  dstr->Append (", ");
3024  if (count == 1) {
3025  dstr->Append (translate (ti, "longest"), ": ");
3026  }
3027  dstr->Append (sanList.list[i]);
3028  }
3029  }
3030  }
3031  if (showResult) { dstr->Append ("]"); }
3032  }
3033 
3034  return true;
3035 }
3036 
3037 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3038 // sc_game_info:
3039 // Return the Game Info string for the active game.
3040 // The returned text includes color codes.
3041 int
3042 sc_game_info (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3043 {
3044  bool hideNextMove = false;
3045  bool showMaterialValue = false;
3046  bool showFEN = false;
3047  uint commentWidth = 50;
3048  uint commentHeight = 1;
3049  bool fullComment = false;
3050  uint showTB = 2; // 0 = no TB output, 1 = score only, 2 = best moves.
3051  char temp[1024];
3052 
3053  int arg = 2;
3054  while (arg < argc) {
3055  if (strIsPrefix (argv[arg], "-hideNextMove")) {
3056  if (arg+1 < argc) {
3057  arg++;
3058  hideNextMove = strGetBoolean(argv[arg]);
3059  }
3060  } else if (strIsPrefix (argv[arg], "-materialValue")) {
3061  if (arg+1 < argc) {
3062  arg++;
3063  showMaterialValue = strGetBoolean(argv[arg]);
3064  }
3065  } else if (strIsPrefix (argv[arg], "-tb")) {
3066  if (arg+1 < argc) {
3067  arg++;
3068  showTB = strGetUnsigned(argv[arg]);
3069  }
3070  } else if (strIsPrefix (argv[arg], "-fen")) {
3071  if (arg+1 < argc) {
3072  arg++;
3073  showFEN = strGetBoolean(argv[arg]);
3074  }
3075  } else if (strIsPrefix (argv[arg], "-cfull")) {
3076  // Show full comment:
3077  if (arg+1 < argc) {
3078  arg++;
3079  fullComment = strGetBoolean(argv[arg]);
3080  if (fullComment) {
3081  commentWidth = 99999;
3082  commentHeight = 99999;
3083  }
3084  }
3085  } else if (strIsPrefix (argv[arg], "-cwidth")) {
3086  if (arg+1 < argc) {
3087  arg++;
3088  commentWidth = strGetBoolean(argv[arg]);
3089  }
3090  } else if (strIsPrefix (argv[arg], "-cheight")) {
3091  if (arg+1 < argc) {
3092  arg++;
3093  commentHeight = strGetBoolean(argv[arg]);
3094  }
3095  } else if (strIsPrefix (argv[arg], "white")) {
3096  Tcl_AppendResult (ti, db->game->GetWhiteStr(), NULL);
3097  return TCL_OK;
3098  } else if (strIsPrefix (argv[arg], "welo")) {
3099  return setIntResult (ti, db->game->GetWhiteElo() );
3100  } else if (strIsPrefix (argv[arg], "black")) {
3101  Tcl_AppendResult (ti, db->game->GetBlackStr(), NULL);
3102  return TCL_OK;
3103  } else if (strIsPrefix (argv[arg], "belo")) {
3104  return setIntResult (ti, db->game->GetBlackElo() );
3105  } else if (strIsPrefix (argv[arg], "event")) {
3106  Tcl_AppendResult (ti, db->game->GetEventStr(), NULL);
3107  return TCL_OK;
3108  } else if (strIsPrefix (argv[arg], "site")) {
3109  Tcl_AppendResult (ti, db->game->GetSiteStr(), NULL);
3110  return TCL_OK;
3111  } else if (strIsPrefix (argv[arg], "round")) {
3112  Tcl_AppendResult (ti, db->game->GetRoundStr(), NULL);
3113  return TCL_OK;
3114  } else if (strIsPrefix (argv[arg], "date")) {
3115  char dateStr [12];
3116  date_DecodeToString (db->game->GetDate(), dateStr);
3117  Tcl_AppendResult (ti, dateStr, NULL);
3118  return TCL_OK;
3119  } else if (strIsPrefix (argv[arg], "year")) {
3120  return setUintResult (ti, date_GetYear (db->game->GetDate()));
3121  } else if (strIsPrefix (argv[arg], "result")) {
3122  return setResult (ti, RESULT_STR[db->game->GetResult()]);
3123  } else if (strIsPrefix (argv[arg], "nextMove")) {
3124  db->game->GetSAN (temp);
3125  transPieces(temp);
3126  Tcl_AppendResult (ti, temp, NULL);
3127  return TCL_OK;
3128 // nextMoveNT is the same as nextMove, except that the move is not translated
3129  } else if (strIsPrefix (argv[arg], "nextMoveNT")) {
3130  db->game->GetSAN (temp);
3131  Tcl_AppendResult (ti, temp, NULL);
3132  return TCL_OK;
3133 // returns next move played in UCI format
3134  } else if (strIsPrefix (argv[arg], "nextMoveUCI")) {
3135  db->game->GetNextMoveUCI (temp);
3136  Tcl_AppendResult (ti, temp, NULL);
3137  return TCL_OK;
3138  } else if (strIsPrefix (argv[arg], "previousMove")) {
3139  db->game->GetPrevSAN (temp);
3140  transPieces(temp);
3141  Tcl_AppendResult (ti, temp, NULL);
3142  return TCL_OK;
3143 // previousMoveNT is the same as previousMove, except that the move is not translated
3144  } else if (strIsPrefix (argv[arg], "previousMoveNT")) {
3145  db->game->GetPrevSAN (temp);
3146  Tcl_AppendResult (ti, temp, NULL);
3147  return TCL_OK;
3148 // returns previous move played in UCI format
3149  } else if (strIsPrefix (argv[arg], "previousMoveUCI")) {
3150  db->game->GetPrevMoveUCI (temp);
3151  Tcl_AppendResult (ti, temp, NULL);
3152  return TCL_OK;
3153  } else if (strIsPrefix (argv[arg], "duplicate")) {
3154  uint dupGameNum = db->getDuplicates(db->gameNumber);
3155  return setUintResult (ti, dupGameNum);
3156  } else if (strIsPrefix(argv[arg], "ECO")) {
3157  std::string str;
3158  if (ecoBook) {
3159  auto ecoStr = ecoBook->findECOstr(db->game->GetCurrentPos());
3160  if (ecoStr.first)
3161  str.append(ecoStr.first, ecoStr.second);
3162  }
3163  return UI_Result(ti, OK, str);
3164  }
3165  arg++;
3166  }
3167 
3168  const char * gameStr = translate (ti, "game");
3169  sprintf (temp, "%c%s %u: <pi %s>%s</pi>", toupper(gameStr[0]),
3170  gameStr + 1, db->gameNumber + 1,
3171  db->game->GetWhiteStr(), db->game->GetWhiteStr());
3172  if (auto whCountry = db->game->FindExtraTag("WhiteCountry"))
3173  sprintf(temp + std::strlen(temp), " (%s)", whCountry);
3174 
3175  Tcl_AppendResult (ti, temp, NULL);
3176  eloT elo = db->game->GetWhiteElo();
3177  bool eloEstimated = false;
3178  if (elo == 0) {
3179  elo = db->game->GetWhiteEstimateElo();
3180  eloEstimated = true;
3181  }
3182  if (elo != 0) {
3183  sprintf (temp, " <red>%u%s</red>", elo, eloEstimated ? "*" : "");
3184  Tcl_AppendResult (ti, temp, NULL);
3185  }
3186  sprintf (temp, " -- <pi %s>%s</pi>",
3187  db->game->GetBlackStr(), db->game->GetBlackStr());
3188  if (auto blCountry = db->game->FindExtraTag("BlackCountry"))
3189  sprintf(temp + std::strlen(temp), " (%s)", blCountry);
3190 
3191  Tcl_AppendResult (ti, temp, NULL);
3192  elo = db->game->GetBlackElo();
3193  eloEstimated = false;
3194  if (elo == 0) {
3195  elo = db->game->GetBlackEstimateElo();
3196  eloEstimated = true;
3197  }
3198  if (elo != 0) {
3199  sprintf (temp, " <red>%u%s</red>", elo, eloEstimated ? "*" : "");
3200  Tcl_AppendResult (ti, temp, NULL);
3201  }
3202 
3203  if (hideNextMove) {
3204  sprintf (temp, "<br>(%s: %s)",
3205  translate (ti, "Result"), translate (ti, "hidden"));
3206  } else {
3207  sprintf (temp, "<br>%s <red>(%u)</red>",
3208  RESULT_LONGSTR[db->game->GetResult()],
3209  (db->game->GetNumHalfMoves() + 1) / 2);
3210  }
3211  Tcl_AppendResult (ti, temp, NULL);
3212 
3213  if (db->game->GetEco() != 0) {
3214  ecoStringT fullEcoStr;
3215  eco_ToExtendedString (db->game->GetEco(), fullEcoStr);
3216  ecoStringT basicEcoStr;
3217  strCopy (basicEcoStr, fullEcoStr);
3218  if (strLength(basicEcoStr) >= 4) { basicEcoStr[3] = 0; }
3219  Tcl_AppendResult (ti, " <blue><run ::windows::eco::Refresh ",
3220  basicEcoStr, ">", fullEcoStr,
3221  "</run></blue>", NULL);
3222  }
3223  char dateStr[20];
3224  date_DecodeToString (db->game->GetDate(), dateStr);
3225  strTrimDate (dateStr);
3226  Tcl_AppendResult (ti, " <red>", dateStr, "</red>", NULL);
3227 
3228  if (db->gameNumber >= 0) {
3229  // Check if this game is deleted or has other user-settable flags:
3230  const IndexEntry* ie = db->getIndexEntry(db->gameNumber);
3231  if (ie->GetDeleteFlag()) {
3232  Tcl_AppendResult (ti, " <gray>(",
3233  translate (ti, "deleted"), ")</gray>", NULL);
3234  }
3235  char userFlags[16];
3236  if (ie->GetFlagStr (userFlags, NULL) != 0) {
3237  // Print other flags set for this game:
3238  const char * flagStr = userFlags;
3239  // Skip over "D" for Deleted, as it is indicated above:
3240  if (*flagStr == 'D') { flagStr++; }
3241  if (*flagStr != 0) {
3242  Tcl_AppendResult (ti, " <gray>(",
3243  translate (ti, "flags", "flags"),
3244  ": ", flagStr, NULL);
3245  int flagCount = 0;
3246  while (*flagStr != 0) {
3247  const char * flagName = NULL;
3248  switch (*flagStr) {
3249  case 'W': flagName = "WhiteOpFlag"; break;
3250  case 'B': flagName = "BlackOpFlag"; break;
3251  case 'M': flagName = "MiddlegameFlag"; break;
3252  case 'E': flagName = "EndgameFlag"; break;
3253  case 'N': flagName = "NoveltyFlag"; break;
3254  case 'P': flagName = "PawnFlag"; break;
3255  case 'T': flagName = "TacticsFlag"; break;
3256  case 'Q': flagName = "QsideFlag"; break;
3257  case 'K': flagName = "KsideFlag"; break;
3258  case '!': flagName = "BrilliancyFlag"; break;
3259  case '?': flagName = "BlunderFlag"; break;
3260  case 'U': flagName = "UserFlag"; break;
3261  }
3262  if (flagName != NULL) {
3263  Tcl_AppendResult (ti, (flagCount > 0 ? ", " : " - "),
3264  translate (ti, flagName), NULL);
3265  }
3266  flagCount++;
3267  flagStr++;
3268  }
3269  Tcl_AppendResult (ti, ")</gray>", NULL);
3270  }
3271  }
3272 
3273  if (db->game->FindExtraTag("Bib") != NULL) {
3274  Tcl_AppendResult (ti, " <red><run ::Bibliography::ShowRef>Bib</run></red>", NULL);
3275  }
3276 
3277  // Check if this game has a twin (duplicate):
3278  if (db->getDuplicates(db->gameNumber) != 0) {
3279  Tcl_AppendResult (ti, " <blue><run updateTwinChecker>(",
3280  translate (ti, "twin"), ")</run></blue>", NULL);
3281  }
3282  }
3283  sprintf (temp, "<br><gray><run ::crosstab::Open>%s: %s</run> (%s)</gray><br>",
3284  db->game->GetSiteStr(),
3285  db->game->GetEventStr(),
3286  db->game->GetRoundStr());
3287  Tcl_AppendResult (ti, temp, NULL);
3288 
3289  char san [20];
3290  char tempTrans[20];
3291  byte * nags;
3292  colorT toMove = db->game->GetCurrentPos()->GetToMove();
3293  uint moveCount = db->game->GetCurrentPos()->GetFullMoveCount();
3294  uint prevMoveCount = moveCount;
3295  if (toMove == WHITE) { prevMoveCount--; }
3296 
3297  db->game->GetPrevSAN (san);
3298  strcpy(tempTrans, san);
3299  transPieces(tempTrans);
3300  bool printNags = true;
3301  if (san[0] == 0) {
3302  strCopy (temp, "(");
3303  strAppend (temp, db->game->GetVarLevel() == 0 ?
3304  translate (ti, "GameStart", "Start of game") :
3305  translate (ti, "LineStart", "Start of line"));
3306  strAppend (temp, ")");
3307  printNags = false;
3308  } else {
3309  sprintf (temp, "<run ::move::Back>%u.%s%s</run>",
3310  prevMoveCount, toMove==WHITE ? ".." : "", tempTrans);//san);
3311  printNags = true;
3312  }
3313  Tcl_AppendResult (ti, translate (ti, "LastMove", "Last move"), NULL);
3314  Tcl_AppendResult (ti, ": <darkblue>", temp, "</darkblue>", NULL);
3315  nags = db->game->GetNags();
3316  if (printNags && *nags != 0 && !hideNextMove) {
3317  Tcl_AppendResult (ti, "<red>", NULL);
3318  for (uint nagCount = 0 ; nags[nagCount] != 0; nagCount++) {
3319  char nagstr[20];
3320  game_printNag (nags[nagCount], nagstr, true, PGN_FORMAT_Plain);
3321  if (nagCount > 0 || (nagstr[0] != '!' && nagstr[0] != '?')) {
3322  Tcl_AppendResult (ti, " ", NULL);
3323  }
3324  Tcl_AppendResult (ti, nagstr, NULL);
3325  }
3326  Tcl_AppendResult (ti, "</red>", NULL);
3327  }
3328 
3329  // Now print next move:
3330 
3331  db->game->GetSAN (san);
3332  strcpy(tempTrans, san);
3333  transPieces(tempTrans);
3334  if (san[0] == 0) {
3335  strCopy (temp, "(");
3336  strAppend (temp, db->game->GetVarLevel() == 0 ?
3337  translate (ti, "GameEnd", "End of game") :
3338  translate (ti, "LineEnd", "End of line"));
3339  strAppend (temp, ")");
3340  printNags = false;
3341  } else if (hideNextMove) {
3342  sprintf (temp, "%u.%s(", moveCount, toMove==WHITE ? "" : "..");
3343  strAppend (temp, translate (ti, "hidden"));
3344  strAppend (temp, ")");
3345  printNags = false;
3346  } else {
3347  sprintf (temp, "<run ::move::Forward>%u.%s%s</run>",
3348  moveCount, toMove==WHITE ? "" : "..", tempTrans);//san);
3349  printNags = true;
3350  }
3351  Tcl_AppendResult (ti, " ", translate (ti, "NextMove", "Next"), NULL);
3352  Tcl_AppendResult (ti, ": <darkblue>", temp, "</darkblue>", NULL);
3353  nags = db->game->GetNextNags();
3354  if (printNags && !hideNextMove && *nags != 0) {
3355  Tcl_AppendResult (ti, "<red>", NULL);
3356  for (uint nagCount = 0 ; nags[nagCount] != 0; nagCount++) {
3357  char nagstr[20];
3358  game_printNag (nags[nagCount], nagstr, true, PGN_FORMAT_Plain);
3359  if (nagCount > 0 || (nagstr[0] != '!' && nagstr[0] != '?')) {
3360  Tcl_AppendResult (ti, " ", NULL);
3361  }
3362  Tcl_AppendResult (ti, nagstr, NULL);
3363  }
3364  Tcl_AppendResult (ti, "</red>", NULL);
3365  }
3366 
3367  if (db->game->GetVarLevel() > 0) {
3368  Tcl_AppendResult (ti, " <green><run sc_var exit; updateBoard -animate>",
3369  "(<lt>-Var)", "</run></green>", NULL);
3370  }
3371 
3372  if (showMaterialValue) {
3373  uint mWhite = db->game->GetCurrentPos()->MaterialValue (WHITE);
3374  uint mBlack = db->game->GetCurrentPos()->MaterialValue (BLACK);
3375  sprintf (temp, " <gray>(%u-%u", mWhite, mBlack);
3376  Tcl_AppendResult (ti, temp, NULL);
3377  if (mWhite > mBlack) {
3378  sprintf (temp, ":+%u", mWhite - mBlack);
3379  Tcl_AppendResult (ti, temp, NULL);
3380  } else if (mBlack > mWhite) {
3381  sprintf (temp, ":-%u", mBlack - mWhite);
3382  Tcl_AppendResult (ti, temp, NULL);
3383  }
3384  Tcl_AppendResult (ti, ")</gray>", NULL);
3385  }
3386 
3387  // Print first few variations if there are any:
3388 
3389  uint varCount = db->game->GetNumVariations();
3390  if (!hideNextMove && varCount > 0) {
3391  Tcl_AppendResult (ti, "<br>", translate (ti, "Variations"), ":", NULL);
3392  for (uint vnum = 0; vnum < varCount && vnum < 5; vnum++) {
3393  char s[20];
3394  db->game->MoveIntoVariation (vnum);
3395  db->game->GetSAN (s);
3396  strcpy(tempTrans, s);
3397  transPieces(tempTrans);
3398  sprintf (temp, " <run sc_var enter %u; updateBoard -animate>v%u",
3399  vnum, vnum+1);
3400  Tcl_AppendResult (ti, "<green>", temp, "</green>: ", NULL);
3401  if (s[0] == 0) {
3402  sprintf (temp, "<darkblue>(empty)</darkblue>");
3403  } else {
3404  sprintf (temp, "<darkblue>%u.%s%s</darkblue>",
3405  moveCount, toMove == WHITE ? "" : "..", tempTrans);//s);
3406  }
3407  Tcl_AppendResult (ti, temp, NULL);
3408  byte * firstNag = db->game->GetNextNags();
3409  if (*firstNag >= NAG_GoodMove && *firstNag <= NAG_DubiousMove) {
3410  game_printNag (*firstNag, s, true, PGN_FORMAT_Plain);
3411  Tcl_AppendResult (ti, "<red>", s, "</red>", NULL);
3412  }
3413  Tcl_AppendResult (ti, "</run>", NULL);
3414  db->game->MoveExitVariation ();
3415  }
3416  }
3417 
3418  // Check if this move has a comment:
3419 
3420  if (db->game->GetMoveComment() != NULL) {
3421  Tcl_AppendResult (ti, "<br>", translate(ti, "Comment"),
3422  " <green><run makeCommentWin>", NULL);
3423  char * str = strDuplicate(db->game->GetMoveComment());
3424  strTrimMarkCodes (str);
3425  const char * s = str;
3426  uint len;
3427  uint lines = 0;
3428  // Add the first commentWidth characters of the comment, up to
3429  // the first commentHeight lines:
3430  for (len = 0; len < commentWidth; len++, s++) {
3431  char ch = *s;
3432  if (ch == 0) { break; }
3433  if (ch == '\n') {
3434  lines++;
3435  if (lines >= commentHeight) { break; }
3436  Tcl_AppendResult (ti, "<br>", NULL);
3437  } else if (ch == '<') {
3438  Tcl_AppendResult (ti, "<lt>", NULL);
3439  } else if (ch == '>') {
3440  Tcl_AppendResult (ti, "<gt>", NULL);
3441  } else {
3442  appendCharResult (ti, ch);
3443  }
3444  }
3445  // Complete the current comment word and add "..." if necessary:
3446  if (len == commentWidth) {
3447  char ch = *s;
3448  while (ch != ' ' && ch != '\n' && ch != 0) {
3449  appendCharResult (ti, ch);
3450  s++;
3451  ch = *s;
3452  }
3453  if (ch != 0) {
3454  Tcl_AppendResult (ti, "...", NULL);
3455  }
3456  }
3457  Tcl_AppendResult (ti, "</run></green>", NULL);
3458 #ifdef WINCE
3459  my_Tcl_Free((char*) str);
3460 #else
3461  delete[] str;
3462 #endif
3463  }
3464 
3465  // Probe tablebases:
3466 
3467  if (!hideNextMove) {
3468  DString * tbStr = new DString;
3469  if (probe_tablebase (ti, showTB, tbStr)) {
3470  Tcl_AppendResult (ti, "<br>TB: <blue><run ::tb::open>",
3471  tbStr->Data(), "</run></blue>", NULL);
3472  }
3473  delete tbStr;
3474  }
3475 
3476  // Now check ECO book for the current position:
3477  if (ecoBook) {
3478  auto ecoStr = ecoBook->findECOstr(db->game->GetCurrentPos());
3479  if (ecoStr.first) {
3480  std::string ecoComment(ecoStr.first, ecoStr.second);
3481  ecoT eco = eco_FromString(ecoComment.c_str());
3482  ecoStringT estr;
3483  eco_ToExtendedString (eco, estr);
3484  uint len = strLength (estr);
3485  if (len >= 4) { estr[3] = 0; }
3486  DString tempDStr;
3487  translateECO (ti, ecoComment.c_str(), &tempDStr);
3488  Tcl_AppendResult (ti, "<br>ECO: <blue><run ::windows::eco::Refresh ",
3489  estr, ">", tempDStr.Data(),
3490  "</run></blue>", NULL);
3491  }
3492  }
3493  if (showFEN) {
3494  char boardStr [200];
3495  db->game->GetCurrentPos()->PrintFEN (boardStr, FEN_ALL_FIELDS);
3496  Tcl_AppendResult (ti, "<br><gray>", boardStr, "</gray>", NULL);
3497  }
3498  return TCL_OK;
3499 }
3500 
3501 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3502 // sc_game_load:
3503 // Takes a game number and loads the game
3504 int
3505 sc_game_load (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3506 {
3507  if (!db->inUse) {
3508  return errorResult (ti, errMsgNotOpen(ti));
3509  }
3510  if (argc != 3) {
3511  return errorResult (ti, "Usage: sc_game load <gameNumber>");
3512  }
3513 
3514  db->gameAlterations.clear();
3515 
3516  uint gnum = strGetUnsigned (argv[2]);
3517 
3518  // Check the game number is valid::
3519  if (gnum < 1 || gnum > db->numGames()) {
3520  return errorResult (ti, "Invalid game number.");
3521  }
3522 
3523  // We number games from 0 internally, so subtract one:
3524  gnum--;
3525  const char * corruptMsg = "Sorry, this game appears to be corrupt.";
3526 
3527  const IndexEntry* ie = db->getIndexEntry(gnum);
3528 
3529  if (db->getGame(ie, db->bbuf) != OK) {
3530  return errorResult (ti, corruptMsg);
3531  }
3532  if (db->game->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
3533  return errorResult (ti, corruptMsg);
3534  }
3535 
3536  if (db->dbFilter->Get(gnum) > 0) {
3537  db->game->MoveToPly(db->dbFilter->Get(gnum) - 1);
3538  } else {
3539  db->game->MoveToPly(0);
3540  }
3541 
3542  db->game->LoadStandardTags (ie, db->getNameBase());
3543  db->gameNumber = gnum;
3544  db->gameAltered = false;
3545  return OK;
3546 }
3547 
3548 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3549 // sc_game_merge:
3550 // Merge the specified game into a variation from the current
3551 // game position.
3552 int
3553 sc_game_merge (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3554 {
3555  const char * usage = "Usage: sc_game merge <baseNum> <gameNum> [<endPly>]";
3556  if (argc < 4 || argc > 5) { return errorResult (ti, usage); }
3557 
3558  const scidBaseT* base = DBasePool::getBase(strGetUnsigned(argv[2]));
3559  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
3560 
3561  uint gnum = strGetUnsigned (argv[3]);
3562  uint endPly = 9999; // Defaults to huge number for all moves.
3563  if (argc == 5) { endPly = strGetUnsigned (argv[4]); }
3564 
3565  if (gnum < 1 || gnum > base->numGames()) {
3566  return errorResult (ti, "Invalid game number.");
3567  }
3568  // Number games from 0 internally:
3569  gnum--;
3570 
3571  // Check that the specified game can be merged:
3572  if (base == db && (int)gnum == db->gameNumber) {
3573  return errorResult (ti, "This game cannot be merged into itself.");
3574  }
3575  if (db->game->AtStart() && db->game->AtEnd()) {
3576  return errorResult (ti, "The current game has no moves.");
3577  }
3578  if (db->game->HasNonStandardStart()) {
3579  return errorResult (ti, "The current game has a non-standard start position.");
3580  }
3581 
3582  // Load the merge game:
3583 
3584  const IndexEntry* ie = base->getIndexEntry(gnum);
3585  if (base->getGame(ie, base->bbuf) != OK) {
3586  return errorResult (ti, "Error loading game.");
3587  }
3588  Game * merge = scratchGame;
3589  merge->Clear();
3590  if (merge->Decode (base->bbuf, GAME_DECODE_NONE) != OK) {
3591  return errorResult (ti, "Error decoding game.");
3592  }
3593  merge->LoadStandardTags (ie, base->getNameBase());
3594  if (merge->HasNonStandardStart()) {
3595  return errorResult (ti, "The merge game has a non-standard start position.");
3596  }
3597 
3598  // Set up an array of all the game positions in the merge game:
3599  uint nMergePos = merge->GetNumHalfMoves() + 1;
3600  typedef char compactBoardStr [36];
3601  compactBoardStr * mergeBoards = new compactBoardStr [nMergePos];
3602  merge->MoveToPly (0);
3603  for (uint i=0; i < nMergePos; i++) {
3604  merge->GetCurrentPos()->PrintCompactStr (mergeBoards[i]);
3605  merge->MoveForward();
3606  }
3607 
3608  // Now find the deepest position in the current game that occurs
3609  // in the merge game:
3610  db->game->MoveToPly (0);
3611  uint matchPly = 0;
3612  uint mergePly = 0;
3613  uint ply = 0;
3614  bool done = false;
3615  while (!done) {
3616  if (db->game->MoveForward() != OK) { done = true; }
3617  ply++;
3618  compactBoardStr currentBoard;
3619  db->game->GetCurrentPos()->PrintCompactStr (currentBoard);
3620  for (uint n=0; n < nMergePos; n++) {
3621  if (strEqual (currentBoard, mergeBoards[n])) {
3622  matchPly = ply;
3623  mergePly = n;
3624  }
3625  }
3626  }
3627 
3628  delete [] mergeBoards;
3629 
3630  // Now the games match at the locations matchPly in the current
3631  // game and mergePly in the merge game.
3632  // Create a new variation and add merge-game moves to it:
3633  db->game->MoveToPly (matchPly);
3634  bool atLastMove = db->game->AtEnd();
3635  simpleMoveT * sm = NULL;
3636  if (atLastMove) {
3637  // At end of game, so remember final game move for replicating
3638  // at the start of the variation:
3639  db->game->MoveBackup();
3640  sm = db->game->GetCurrentMove();
3641  db->game->MoveForward();
3642  }
3643  db->game->MoveForward();
3644  db->game->AddVariation();
3645  db->gameAltered = true;
3646  if (atLastMove) {
3647  // We need to replicate the last move of the current game.
3648  db->game->AddMove(sm);
3649  }
3650  merge->MoveToPly (mergePly);
3651  ply = mergePly;
3652  while (ply < endPly) {
3653  simpleMoveT * mergeMove = merge->GetCurrentMove();
3654  if (merge->MoveForward() != OK) { break; }
3655  if (mergeMove == NULL) { break; }
3656  if (db->game->AddMove(mergeMove) != OK) { break; }
3657  ply++;
3658  }
3659 
3660  // Finally, add a comment describing the merge-game details:
3661  DString * dstr = new DString;
3662  dstr->Append (RESULT_LONGSTR[ie->GetResult()]);
3663  if (ply < merge->GetNumHalfMoves()) {
3664  dstr->Append ("(", (merge->GetNumHalfMoves()+1) / 2, ")");
3665  }
3666  dstr->Append (" ", ie->GetWhiteName (base->getNameBase()));
3667  eloT elo = ie->GetWhiteElo();
3668  if (elo > 0) { dstr->Append (" (", elo, ")"); }
3669  dstr->Append (" - ");
3670  dstr->Append (ie->GetBlackName (base->getNameBase()));
3671  elo = ie->GetBlackElo();
3672  if (elo > 0) { dstr->Append (" (", elo, ")"); }
3673  dstr->Append (" / ", ie->GetEventName (base->getNameBase()));
3674  dstr->Append (" (", ie->GetRoundName (base->getNameBase()), ")");
3675  dstr->Append (", ", ie->GetSiteName (base->getNameBase()));
3676  dstr->Append (" ", ie->GetYear());
3677  db->game->SetMoveComment ((char *) dstr->Data());
3678  delete dstr;
3679 
3680  // And exit the new variation:
3681  db->game->MoveExitVariation();
3682  return TCL_OK;
3683 }
3684 
3685 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3686 // sc_game_moves:
3687 // Return a string of the moves reaching the current game position.
3688 // Optional arguments: "coord" for coordinate notation (1 move per line);
3689 // "nomoves" for standard algebraic without move numbers.
3690 // Default output is standard algebraic with move numbers.
3691 int
3692 sc_game_moves (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3693 {
3694  bool sanFormat = true;
3695  bool printMoves = true;
3696  bool listFormat = false;
3697  const uint MAXMOVES = 500;
3698  sanStringT * moveStrings = new sanStringT [MAXMOVES];
3699  uint plyCount = 0;
3700  Game * g = db->game;
3701  for (int arg = 2; arg < argc; arg++) {
3702  if (argv[arg][0] == 'c') { sanFormat = false; }
3703  if (argv[arg][0] == 'n') { printMoves = false; }
3704  if (argv[arg][0] == 'l') { printMoves = false; }
3705  }
3706 
3707  auto location = g->currentLocation();
3708  while (! g->AtStart()) {
3709  if (g->AtVarStart()) {
3710  g->MoveExitVariation();
3711  continue;
3712  }
3713  g->MoveBackup();
3714  simpleMoveT * sm = g->GetCurrentMove();
3715  if (sm == NULL) { break; }
3716  char * s = moveStrings[plyCount];
3717  if (sanFormat) {
3718  g->GetSAN (s);
3719  } else {
3720  *s++ = square_FyleChar(sm->from);
3721  *s++ = square_RankChar(sm->from);
3722  *s++ = square_FyleChar(sm->to);
3723  *s++ = square_RankChar(sm->to);
3724  if (sm->promote != EMPTY) {
3725  *s++ = piece_Char (piece_Type (sm->promote));
3726  }
3727  *s = 0;
3728  }
3729  plyCount++;
3730  if (plyCount == MAXMOVES) {
3731  // Too many moves, just give up:
3732  g->restoreLocation(location);
3733  delete[] moveStrings;
3734  return TCL_OK;
3735  }
3736  }
3737  g->restoreLocation(location);
3738  uint count = 0;
3739  for (uint i = plyCount; i > 0; i--, count++) {
3740  char move [20];
3741  if (sanFormat) {
3742  move[0] = 0;
3743  if (printMoves && (count % 2 == 0)) {
3744  sprintf (move, "%u.", (count / 2) + 1);
3745  }
3746  strAppend (move, moveStrings[i - 1]);
3747  } else {
3748  strCopy (move, moveStrings [i - 1]);
3749  }
3750  if (listFormat) {
3751  Tcl_AppendElement (ti, move);
3752  } else {
3753  Tcl_AppendResult (ti, (count == 0 ? "" : " "), move, NULL);
3754  }
3755  }
3756  delete[] moveStrings;
3757  return TCL_OK;
3758 }
3759 
3760 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3761 // sc_game_new:
3762 // Clears the current game.
3763 int
3764 sc_game_new(ClientData, Tcl_Interp*, int, const char**)
3765 {
3766  db->game->Clear();
3767  db->gameNumber = -1;
3768  db->gameAltered = false;
3769  return TCL_OK;
3770 }
3771 
3772 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3773 // sc_game_novelty:
3774 // Finds the first move in the current game (after the deepest
3775 // position found in the ECO book) that reaches a position not
3776 // found in the selected database. It then moves to that point
3777 // in the game and returns a text string of the move.
3778 int
3779 sc_game_novelty (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3780 {
3781  const char * usage =
3782  "Usage: sc_game novelty [-older] base";
3783 
3784  bool olderGamesOnly = false;
3785 
3786  int baseArg = 2;
3787  if (argc >= baseArg
3788  && argv[baseArg][0] == '-' && argv[baseArg][1] == 'o'
3789  && strIsPrefix (argv[baseArg], "-older")) {
3790  olderGamesOnly = true;
3791  baseArg++;
3792  }
3793  if (argc < baseArg || argc > baseArg+1) return errorResult(ti, usage);
3794  scidBaseT* base = DBasePool::getBase(strGetInteger (argv[baseArg]));
3795  if (base == 0) return UI_Result(ti, ERROR_BadArg);
3796 
3797  // First, move to the deepest ECO position in the game.
3798  // This code is adapted from sc_eco_game().
3799  Game* g = base->game;
3800  if (ecoBook) {
3801  while (g->MoveForward() == OK) {}
3802  while (!ecoBook->findECOstr(g->GetCurrentPos()).first) {
3803  if (g->MoveBackup() != OK) break;
3804  }
3805  }
3806 
3807  // Now keep doing an exact position search (ignoring the current
3808  // game) and skipping to the next game position whenever a match
3809  // is found, until a position not in any database game is reached:
3810  Progress progress = UI_CreateProgress(ti);
3811  std::string filtername = base->newFilter();
3812  HFilter filter = base->getFilter(filtername);
3813  dateT currentDate = g->GetDate();
3814  while (g->MoveForward() == OK) {
3815  SearchPos(g->GetCurrentPos()).setFilter(base, filter, Progress());
3816  int count = 0;
3817  for (uint i=0, n = base->numGames(); i < n; i++) {
3818  if (filter.get(i) == 0) continue;
3819 
3820  // Ignore newer games if requested:
3821  if (olderGamesOnly) {
3822  if (base->getIndexEntry(i)->GetDate() >= currentDate) continue;
3823  }
3824  if (count++ != 0) break;
3825  }
3826 
3827  if (count <= 1) { // Novelty found
3828  base->deleteFilter(filtername.c_str());
3829  return UI_Result(ti, OK, g->GetCurrentPly());
3830  }
3831 
3832  if (!progress.report(g->GetCurrentPly() +1, g->GetNumHalfMoves())) {
3833  base->deleteFilter(filtername.c_str());
3834  return UI_Result(ti, ERROR_UserCancel);
3835  }
3836  }
3837 
3838  base->deleteFilter(filtername.c_str());
3839  return UI_Result(ti, OK, -1);
3840 }
3841 
3842 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3843 // sc_game_pgn:
3844 // Returns the PGN representation of the game.
3845 // Optional args:
3846 // -format (plain|html|latex): output format. Default=plain.
3847 // -shortHeader (0|1): short, 3-line (non-PGN) header. Default=0.
3848 // -space (0|1): printing a space after move numbers. Default=0.
3849 // -tags (0|1): printing (nonstandard) tags. Default=1.
3850 // -comments (0|1): printing nags/comments. Default=1.
3851 // -variations (0|1): printing variations. Default=1.
3852 // -indentVars (0|1): indenting variations. Default=0.
3853 // -indentComments (0|1): indenting comments. Default=0.
3854 // -width (number): line length for wordwrap. Default=huge (99999),
3855 // to let a Tk text widget do its own line-breaking.
3856 // -base (number): Print the game from the numbered base.
3857 // -gameNumber (number): Print the numbered game instead of the
3858 // active game.
3859 // -unicode (0|1): use unicocde characters (e.g. U+2654 for king). Default=0.
3860 int
3861 sc_game_pgn (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
3862 {
3863  static const char * options [] = {
3864  "-column", "-comments", "-base", "-gameNumber", "-format",
3865  "-shortHeader", "-indentComments", "-indentVariations",
3866  "-symbols", "-tags", "-variations", "-width", "-space",
3867  "-markCodes", "-unicode",
3868  NULL
3869  };
3870  enum {
3871  OPT_COLUMN, OPT_COMMENTS, OPT_BASE, OPT_GAME_NUMBER, OPT_FORMAT,
3872  OPT_SHORT_HDR, OPT_INDENT_COMMENTS, OPT_INDENT_VARS,
3873  OPT_SYMBOLS, OPT_TAGS, OPT_VARS, OPT_WIDTH, OPT_SPACE,
3874  OPT_NOMARKS, OPT_UNICODE,
3875  };
3876 
3877  const scidBaseT* base = db;
3878  Game * g = db->game;
3879  uint lineWidth = 99999;
3880  g->ResetPgnStyle();
3883 
3884  // Parse all the command options:
3885  // Note that every option takes a value so options/values always occur
3886  // in pairs, which simplifies the code.
3887 
3888  int thisArg = 2;
3889  while (thisArg < argc) {
3890  int index = strUniqueMatch (argv[thisArg], options);
3891  if (index == -1) {
3892  Tcl_AppendResult (ti, "Invalid option to sc_game pgn: ",
3893  argv[thisArg], "; valid options are: ", NULL);
3894  for (const char ** s = options; *s != NULL; s++) {
3895  Tcl_AppendResult (ti, *s, " ", NULL);
3896  }
3897  return TCL_ERROR;
3898  }
3899 
3900  // Check that our option has a value:
3901  if (thisArg+1 == argc) {
3902  Tcl_AppendResult (ti, "Invalid option value: sc_game pgn ",
3903  options[index], " requires a value.", NULL);
3904  return TCL_ERROR;
3905  }
3906 
3907  uint value = strGetUnsigned (argv[thisArg+1]);
3908 
3909  if (index == OPT_WIDTH) {
3910  lineWidth = value;
3911 
3912  } else if (index == OPT_BASE) {
3913  base = DBasePool::getBase(value);
3914  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
3915  g = base->game;
3916 
3917  } else if (index == OPT_GAME_NUMBER) {
3918  // Print the numbered game instead of the active game:
3919 
3920  g = scratchGame;
3921  g->Clear();
3922  if (value < 1 || value > base->numGames()) {
3923  return setResult (ti, "Invalid game number");
3924  }
3925  const IndexEntry* ie = base->getIndexEntry(value - 1);
3926  if (ie->GetLength() == 0) {
3927  return errorResult (ti, "Error: empty game file record.");
3928  }
3929  if (base->getGame(ie, base->bbuf) != OK) {
3930  return errorResult (ti, "Error reading game file.");
3931  }
3932  if (g->Decode (base->bbuf, GAME_DECODE_ALL) != OK) {
3933  return errorResult (ti, "Error decoding game.");
3934  }
3935  g->LoadStandardTags (ie, base->getNameBase());
3936 
3937  } else if (index == OPT_FORMAT) {
3938  // The option value should be "plain", "html" or "latex".
3939  if (! g->SetPgnFormatFromString (argv[thisArg+1])) {
3940  return errorResult (ti, "Invalid -format option.");
3941  }
3942 
3943  } else {
3944  // The option is a boolean affecting pgn style:
3945  uint bitmask = 0;
3946  switch (index) {
3947  case OPT_COLUMN:
3948  bitmask = PGN_STYLE_COLUMN; break;
3949  case OPT_COMMENTS:
3950  bitmask = PGN_STYLE_COMMENTS; break;
3951  case OPT_SYMBOLS:
3952  bitmask = PGN_STYLE_SYMBOLS; break;
3953  case OPT_TAGS:
3954  bitmask = PGN_STYLE_TAGS; break;
3955  case OPT_VARS:
3956  bitmask = PGN_STYLE_VARS; break;
3957  case OPT_SHORT_HDR:
3958  bitmask = PGN_STYLE_SHORT_HEADER; break;
3959  case OPT_SPACE:
3960  bitmask = PGN_STYLE_MOVENUM_SPACE; break;
3961  case OPT_INDENT_VARS:
3962  bitmask = PGN_STYLE_INDENT_VARS; break;
3963  case OPT_INDENT_COMMENTS:
3964  bitmask = PGN_STYLE_INDENT_COMMENTS; break;
3965  case OPT_NOMARKS:
3966  bitmask = PGN_STYLE_STRIP_MARKS; break;
3967  case OPT_UNICODE:
3968  bitmask = PGN_STYLE_UNICODE; break;
3969  default: // unreachable!
3970  return errorResult (ti, "Invalid option.");
3971  };
3972  if (bitmask > 0) {
3973  if (value) {
3974  g->AddPgnStyle (bitmask);
3975  } else {
3976  g->RemovePgnStyle (bitmask);
3977  }
3978  }
3979  }
3980  thisArg += 2;
3981  }
3982 
3983  std::pair<const char*, unsigned> pgnBuf = g->WriteToPGN(lineWidth);
3984  Tcl_AppendResult (ti, pgnBuf.first, NULL);
3985  return TCL_OK;
3986 }
3987 
3988 //~~~~~~~ DEPRECATED ~~~~~~
3989 // sc_game_pop:
3990 // Restores the last game saved with sc_game_push.
3991 int
3992 sc_game_pop(ClientData, Tcl_Interp*, int, const char**)
3993 {
3994  if (db->deprecated_push_pop.first) {
3995  delete db->game;
3996  db->game = db->deprecated_push_pop.first;
3997  db->gameAltered = db->deprecated_push_pop.second;
3998  db->deprecated_push_pop.first = nullptr;
3999  } else {
4000  ASSERT(0);
4001  }
4002  return TCL_OK;
4003 }
4004 
4005 //~~~~~~~ DEPRECATED ~~~~~~
4006 // sc_game_push:
4007 // Saves the current game and pushes a new empty game onto
4008 // the game state stack.
4009 // If the optional argument "copy" is present, the new game will be
4010 // a copy of the current game.
4011 int
4012 sc_game_push (ClientData, Tcl_Interp*, int argc, const char ** argv)
4013 {
4014  bool copy = false;
4015 
4016  if ( argc > 2 && !strcmp( argv[2], "copy" ) ) {
4017  copy = true;
4018  }
4019  else if ( argc > 2 && !strcmp( argv[2], "copyfast" ) ) {
4020  copy = true;
4021  }
4022 
4023  Game* g = (copy) ? db->game->clone() : new Game;
4024  if (db->deprecated_push_pop.first) {
4025  ASSERT(0);
4026  delete db->deprecated_push_pop.first;
4027  }
4028  db->deprecated_push_pop = {db->game, db->gameAltered};
4029  db->game = g;
4030  db->gameAltered = false;
4031 
4032  return TCL_OK;
4033 }
4034 
4035 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4036 // sc_game_save:
4037 // Saves the current game. If the parameter is 0, a NEW
4038 // game is added; otherwise, that game number is REPLACED.
4039 int
4040 sc_game_save (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4041 {
4042  scidBaseT * dbase = db;
4043  Game* currGame = db->game;
4044  if (argc == 4) {
4045  dbase = DBasePool::getBase(strGetUnsigned(argv[3]));
4046  if (dbase == 0) return errorResult (ti, "Invalid database number.");
4047  } else if (argc != 3) {
4048  return errorResult (ti, "Usage: sc_game save <gameNumber> [targetbaseId]");
4049  }
4050 
4051  gamenumT gnum = strGetUnsigned(argv[2]);
4052  if (gnum == 0) {
4053  gnum = INVALID_GAMEID;
4054  } else {
4055  gnum -= 1;
4056  const IndexEntry* ieOld = dbase->getIndexEntry_bounds(gnum);
4057  if (ieOld == 0) return ERROR_BadArg;
4058  // User-settable flags were stored in currGame when the game
4059  // was loaded, but the user may have changed them.
4060  char buf[IndexEntry::IDX_NUM_FLAGS + 1];
4061  ieOld->GetFlagStr(buf, "WBMENPTKQ!?U123456");
4062  currGame->SetScidFlags(buf);
4063  }
4064  auto location = currGame->currentLocation();
4065  errorT res = dbase->saveGame(currGame, gnum);
4066  currGame->restoreLocation(location);
4067  if (res == OK) {
4068  if (gnum == INVALID_GAMEID && db == dbase) {
4069  // Saved new game, so set gameNumber to the saved game number:
4070  db->gameNumber = db->numGames() - 1;
4071  }
4072  db->gameAltered = false;
4073  }
4074 
4075  return UI_Result(ti, res);;
4076 }
4077 
4078 
4079 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4080 // sc_game_startBoard:
4081 // Sets the starting position from a FEN string.
4082 // If there is no FEN string argument, a boolean value is
4083 // returned indicating whether the current game starts with
4084 // a setup position.
4085 int
4086 sc_game_startBoard (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4087 {
4088  if (argc == 2) {
4089  return UI_Result(ti, OK, db->game->HasNonStandardStart());
4090  } else if (argc != 3) {
4091  return errorResult (ti, "Usage: sc_game startBoard <fenString>");
4092  }
4093  const char * str = argv[2];
4094  char buf[256];
4095  if (strIsPrefix ("random:", str)) {
4096  // A "FEN" string that starts with "random:" is interpreted as a
4097  // material configuration, and a random position with this
4098  // set of material is generated. For example, "random:krpkr"
4099  // generates a random legal Rook+Pawn-vs-Rook position.
4100  Position scratchPos;
4101  if (scratchPos.Random (str+7) != OK) {
4102  return errorResult (ti, "Invalid material string.");
4103  }
4104  scratchPos.PrintFEN(buf, FEN_ALL_FIELDS);
4105  str = buf;
4106  }
4107  auto err = db->game->SetStartFen(str);
4108  if (err != OK)
4109  return errorResult(ti, "Invalid FEN string.");
4110 
4111  db->gameAltered = true;
4112  return UI_Result(ti, OK);
4113 }
4114 
4115 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4116 // sc_game_strip:
4117 // Strips all comments, variations or annotations from a game.
4118 int sc_game_strip(ClientData, Tcl_Interp* ti, int argc, const char** argv) {
4119  if (argc == 3 && !strcmp("variations", argv[2])) {
4120  db->game->strip(true, false, false);
4121  } else if (argc == 3 && !strcmp("comments", argv[2])) {
4122  db->game->strip(false, true, true);
4123  } else {
4124  return errorResult(ti, "Usage: sc_game strip [comments|variations]");
4125  }
4126  db->gameAltered = true;
4127  return UI_Result(ti, OK);
4128 }
4129 
4130 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4131 // sc_game_summary:
4132 // Returns summary information of the specified game:
4133 // its players, site, etc; or its moves; or all its boards
4134 // positions.
4135 int
4136 sc_game_summary (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4137 {
4138  const char * usage = "Usage: sc_game summary [-base <baseNum>] [-gameNumber <gameNum>] header|boards|moves";
4139 
4140  const char * options[] = {
4141  "-base", "-gameNumber", NULL
4142  };
4143  enum { OPT_BASE, OPT_GNUM };
4144 
4145  const scidBaseT* base = db;
4146  uint gnum = 0;
4147 
4148  int arg = 2;
4149  while (arg+1 < argc) {
4150  const char * value = argv[arg+1];
4151  int index = strUniqueMatch (argv[arg], options);
4152  arg += 2;
4153 
4154  if (index == OPT_BASE) {
4155  base = DBasePool::getBase(strGetUnsigned(value));
4156  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
4157  } else if (index == OPT_GNUM) {
4158  gnum = strGetUnsigned (value);
4159  } else {
4160  return errorResult (ti, usage);
4161  }
4162  }
4163  if (arg+1 != argc) { return errorResult (ti, usage); }
4164 
4165  enum modeT { MODE_HEADER, MODE_BOARDS, MODE_MOVES };
4166  modeT mode = MODE_HEADER;
4167  switch (tolower(argv[arg][0])) {
4168  case 'h': mode = MODE_HEADER; break;
4169  case 'b': mode = MODE_BOARDS; break;
4170  case 'm': mode = MODE_MOVES; break;
4171  default: return errorResult (ti, usage);
4172  }
4173 
4174  Game * g = scratchGame;
4175  if (gnum == 0) {
4176  g = base->game;
4177  } else {
4178  // Load the specified game number:
4179  if (! base->inUse) {
4180  return errorResult (ti, "This database is not in use.");
4181  }
4182  if (gnum > base->numGames()) {
4183  return errorResult (ti, "Invalid game number.");
4184  }
4185  gnum--;
4186  const IndexEntry* ie = base->getIndexEntry(gnum);
4187  if (base->getGame(ie, base->bbuf) != OK) {
4188  return errorResult (ti, "Error loading game.");
4189  }
4190  g->Clear();
4191  if (g->Decode (base->bbuf, GAME_DECODE_NONE) != OK) {
4192  return errorResult (ti, "Error decoding game.");
4193  }
4194  g->LoadStandardTags (ie, base->getNameBase());
4195  }
4196 
4197  // Return header summary if requested:
4198  if (mode == MODE_HEADER) {
4199  DString * dstr = new DString;
4200  dstr->Append (g->GetWhiteStr());
4201  eloT elo = g->GetWhiteElo();
4202  if (elo > 0) { dstr->Append (" (", elo, ")"); }
4203  dstr->Append (" -- ", g->GetBlackStr());
4204  elo = g->GetBlackElo();
4205  if (elo > 0) { dstr->Append (" (", elo, ")"); }
4206  dstr->Append ("\n", g->GetEventStr());
4207  const char * round = g->GetRoundStr();
4208  if (! strIsUnknownName(round)) {
4209  dstr->Append (" (", round, ")");
4210  }
4211  dstr->Append (" ", g->GetSiteStr(), "\n");
4212  char dateStr [20];
4213  date_DecodeToString (g->GetDate(), dateStr);
4214  // Remove ".??" or ".??.??" from end of date:
4215  if (dateStr[4] == '.' && dateStr[5] == '?') { dateStr[4] = 0; }
4216  if (dateStr[7] == '.' && dateStr[8] == '?') { dateStr[7] = 0; }
4217  dstr->Append (dateStr, " ");
4218  dstr->Append (RESULT_LONGSTR[g->GetResult()]);
4219  ecoT eco = g->GetEco();
4220  if (eco != 0) {
4221  ecoStringT ecoStr;
4222  eco_ToExtendedString (eco, ecoStr);
4223  dstr->Append (" ", ecoStr);
4224  }
4225  Tcl_AppendResult (ti, dstr->Data(), NULL);
4226  delete dstr;
4227  return TCL_OK;
4228  }
4229 
4230  // Here, a list of the boards or moves is requested:
4231  auto location = g->currentLocation();
4232  g->MoveToPly (0);
4233  while (1) {
4234  if (mode == MODE_BOARDS) {
4235  char boardStr[100];
4236  g->GetCurrentPos()->MakeLongStr (boardStr);
4237  Tcl_AppendElement (ti, boardStr);
4238  } else {
4239  colorT toMove = g->GetCurrentPos()->GetToMove();
4240  uint moveCount = g->GetCurrentPos()->GetFullMoveCount();
4241  char san [20];
4242  g->GetSAN (san);
4243  if (san[0] != 0) {
4244  char temp[40];
4245  if (toMove == WHITE) {
4246  sprintf (temp, "%u.%s", moveCount, san);
4247  } else {
4248  strCopy (temp, san);
4249  }
4250  byte * nags = g->GetNextNags();
4251  if (*nags != 0) {
4252  for (uint nagCount = 0 ; nags[nagCount] != 0; nagCount++) {
4253  char nagstr[20];
4254  game_printNag (nags[nagCount], nagstr, true,
4256  if (nagCount > 0 ||
4257  (nagstr[0] != '!' && nagstr[0] != '?')) {
4258  strAppend (temp, " ");
4259  }
4260  strAppend (temp, nagstr);
4261  }
4262  }
4263  Tcl_AppendElement (ti, temp);
4264  } else {
4265  Tcl_AppendElement (ti, (char *)RESULT_LONGSTR[g->GetResult()]);
4266  }
4267  }
4268  if (g->MoveForward() != OK) { break; }
4269  }
4270 
4271  g->restoreLocation(location);
4272  return TCL_OK;
4273 }
4274 
4275 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4276 // sc_game_tags:
4277 // Get, set or reload the current game tags, or share them
4278 // with another game.
4279 int
4280 sc_game_tags (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
4281 {
4282  const char * options[] = {
4283  "get", "set", "reload", "share", NULL
4284  };
4285  enum { OPT_GET, OPT_SET, OPT_RELOAD, OPT_SHARE };
4286 
4287  int index = -1;
4288  if (argc >= 3) { index = strUniqueMatch (argv[2], options); }
4289 
4290  switch (index) {
4291  case OPT_GET: return sc_game_tags_get (cd, ti, argc, argv);
4292  case OPT_SET:
4293  return sc_game_tags_set (cd, ti, argc, argv);
4294  case OPT_RELOAD: return sc_game_tags_reload (cd, ti, argc, argv);
4295  case OPT_SHARE: return sc_game_tags_share (cd, ti, argc, argv);
4296  default: return InvalidCommand (ti, "sc_game tags", options);
4297  }
4298  return TCL_OK;
4299 }
4300 
4301 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4302 // sc_game_tags_get:
4303 // Gets a tag for the active game given its name.
4304 // Valid names are: Event, Site, Date, Round, White, Black,
4305 // WhiteElo, BlackElo, ECO, Extra.
4306 // All except the last (Extra) return the tag value as a string.
4307 // For "Extra", the function returns all the extra tags as one long
4308 // string, in PGN format, one tag per line.
4309 int
4310 sc_game_tags_get (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4311 {
4312 
4313  static const char * options [] = {
4314  "Event", "Site", "Date", "Year", "Month", "Day",
4315  "Round", "White", "Black", "Result", "WhiteElo",
4316  "BlackElo", "WhiteRType", "BlackRType", "ECO",
4317  "EDate", "EYear", "EMonth", "EDay", "Extra",
4318  NULL
4319  };
4320  enum {
4321  T_Event, T_Site, T_Date, T_Year, T_Month, T_Day,
4322  T_Round, T_White, T_Black, T_Result, T_WhiteElo,
4323  T_BlackElo, T_WhiteRType, T_BlackRType, T_ECO,
4324  T_EDate, T_EYear, T_EMonth, T_EDay, T_Extra
4325  };
4326 
4327  const char * usage = "Usage: sc_game tags get [-last] <tagName>";
4328  const char * tagName;
4329  Game * g = db->game;
4330 
4331  if (argc < 4 || argc > 5) {
4332  return errorResult (ti, usage);
4333  }
4334  tagName = argv[3];
4335  if (argc == 5) {
4336  if (!strEqual (argv[3], "-last")) { return errorResult (ti, usage); }
4337  tagName = argv[4];
4338  if (db->numGames() > 0) {
4339  g = scratchGame;
4340  const IndexEntry* ie = db->getIndexEntry(db->numGames() - 1);
4341  if (db->getGame(ie, db->bbuf) != OK) {
4342  return errorResult (ti, "Error reading game file.");
4343  }
4344  if (g->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
4345  return errorResult (ti, "Error decoding game.");
4346  }
4347  g->LoadStandardTags (ie, db->getNameBase());
4348  }
4349  }
4350  const char * s;
4351  int index = strExactMatch (tagName, options);
4352 
4353  switch (index) {
4354  case T_Event:
4355  s = g->GetEventStr(); if (!s) { s = "?"; }
4356  Tcl_AppendResult (ti, s, NULL);
4357  break;
4358 
4359  case T_Site:
4360  s = g->GetSiteStr(); if (!s) { s = "?"; }
4361  Tcl_AppendResult (ti, s, NULL);
4362  break;
4363 
4364  case T_Date:
4365  {
4366  char dateStr[20];
4367  date_DecodeToString (g->GetDate(), dateStr);
4368  Tcl_AppendResult (ti, dateStr, NULL);
4369  }
4370  break;
4371 
4372  case T_Year:
4373  return setUintResult (ti, date_GetYear (g->GetDate()));
4374 
4375  case T_Month:
4376  return setUintWidthResult (ti, date_GetMonth (g->GetDate()), 2);
4377 
4378  case T_Day:
4379  return setUintWidthResult (ti, date_GetDay (g->GetDate()), 2);
4380 
4381  case T_Round:
4382  s = g->GetRoundStr(); if (!s) { s = "?"; }
4383  Tcl_AppendResult (ti, s, NULL);
4384  break;
4385 
4386  case T_White:
4387  s = g->GetWhiteStr(); if (!s) { s = "?"; }
4388  Tcl_AppendResult (ti, s, NULL);
4389  break;
4390 
4391  case T_Black:
4392  s = g->GetBlackStr(); if (!s) { s = "?"; }
4393  Tcl_AppendResult (ti, s, NULL);
4394  break;
4395 
4396  case T_Result:
4397  return UI_Result(ti, OK, std::string(1, RESULT_CHAR[g->GetResult()]));
4398 
4399  case T_WhiteElo:
4400  return setUintResult (ti, g->GetWhiteElo());
4401 
4402  case T_BlackElo:
4403  return setUintResult (ti, g->GetBlackElo());
4404 
4405  case T_WhiteRType:
4406  return setResult (ti, ratingTypeNames[g->GetWhiteRatingType()]);
4407 
4408  case T_BlackRType:
4409  return setResult (ti, ratingTypeNames[g->GetBlackRatingType()]);
4410 
4411  case T_ECO:
4412  {
4413  ecoStringT ecoStr;
4414  eco_ToExtendedString (g->GetEco(), ecoStr);
4415  Tcl_AppendResult (ti, ecoStr, NULL);
4416  break;
4417  }
4418 
4419  case T_EDate:
4420  {
4421  char dateStr[20];
4422  date_DecodeToString (g->GetEventDate(), dateStr);
4423  Tcl_AppendResult (ti, dateStr, NULL);
4424  }
4425  break;
4426 
4427  case T_EYear:
4428  return setUintResult (ti, date_GetYear (g->GetEventDate()));
4429 
4430  case T_EMonth:
4431  return setUintWidthResult (ti, date_GetMonth (g->GetEventDate()), 2);
4432 
4433  case T_EDay:
4434  return setUintWidthResult (ti, date_GetDay (g->GetEventDate()), 2);
4435 
4436  case T_Extra:
4437  for (auto& tag : g->GetExtraTags()) {
4438  Tcl_AppendResult(ti, tag.first.c_str(), " \"", tag.second.c_str(), "\"\n", NULL);
4439  }
4440  break;
4441 
4442  default: // Not a valid tag name.
4443  return InvalidCommand (ti, "sc_game tags get", options);
4444  }
4445 
4446  return TCL_OK;
4447 }
4448 
4449 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4450 // sc_game_tags_set:
4451 // Set the standard tags for this game.
4452 // Args are: event, site, date, round, white, black, result,
4453 // whiteElo, whiteRatingType, blackElo, blackRatingType, Eco,
4454 // eventdate.
4455 // Last arg is the non-standard tags, a string of lines in the format:
4456 // [TagName "TagValue"]
4457 int
4458 sc_game_tags_set (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4459 {
4460  const char * options[] = {
4461  "-event", "-site", "-date", "-round", "-white", "-black", "-result",
4462  "-whiteElo", "-whiteRatingType", "-blackElo", "-blackRatingType",
4463  "-eco", "-eventdate", "-extra",
4464  NULL
4465  };
4466  enum {
4467  T_EVENT, T_SITE, T_DATE, T_ROUND, T_WHITE, T_BLACK, T_RESULT,
4468  T_WHITE_ELO, T_WHITE_RTYPE, T_BLACK_ELO, T_BLACK_RTYPE,
4469  T_ECO, T_EVENTDATE, T_EXTRA
4470  };
4471 
4472  int arg = 3;
4473  if (((argc-arg) % 2) != 0) {
4474  return errorResult (ti, "Odd number of parameters.");
4475  }
4476 
4477  // Process each pair of parameters:
4478  while (arg+1 < argc) {
4479  int index = strUniqueMatch (argv[arg], options);
4480  const char * value = argv[arg+1];
4481  arg += 2;
4482 
4483  switch (index) {
4484  case T_EVENT: db->game->SetEventStr (value); break;
4485  case T_SITE: db->game->SetSiteStr (value); break;
4486  case T_DATE:
4487  db->game->SetDate (date_EncodeFromString(value));
4488  break;
4489  case T_ROUND: db->game->SetRoundStr (value); break;
4490  case T_WHITE: db->game->SetWhiteStr (value); break;
4491  case T_BLACK: db->game->SetBlackStr (value); break;
4492  case T_RESULT: db->game->SetResult (strGetResult(value)); break;
4493  case T_WHITE_ELO:
4494  db->game->SetWhiteElo (strGetUnsigned(value)); break;
4495  case T_WHITE_RTYPE:
4496  db->game->SetWhiteRatingType (strGetRatingType (value)); break;
4497  case T_BLACK_ELO:
4498  db->game->SetBlackElo (strGetUnsigned(value)); break;
4499  case T_BLACK_RTYPE:
4500  db->game->SetBlackRatingType (strGetRatingType (value)); break;
4501  case T_ECO:
4502  db->game->SetEco (eco_FromString (value)); break;
4503  case T_EVENTDATE:
4504  db->game->SetEventDate (date_EncodeFromString(value));
4505  break;
4506  case T_EXTRA:
4507  {
4508  // Add all the nonstandard tags:
4509  db->game->ClearExtraTags ();
4510  int largc;
4511  const char ** largv;
4512  if (Tcl_SplitList (ti, value, &largc,
4513  (CONST84 char ***) &largv) != TCL_OK) {
4514  // Error from Tcl_SplitList!
4515  return errorResult (ti, "Error parsing extra tags.");
4516  }
4517 
4518  // Extract each tag-value pair and add it to the game:
4519  for (int i=0; i < largc; i++) {
4520  char tagStr [1024];
4521  char valueStr [1024];
4522  //if ( sscanf (largv[i], "%s", tagStr ) == 1 &&
4523  // sscanf (largv[i+1], "%s", valueStr) == 1) {
4524  // Usage :: sc_game tags set -extra [ list "Annotator \"boob [sc_pos moveNumber]\"\n" ]
4525  if (sscanf (largv[i], "%s \"%[^\"]\"\n", tagStr, valueStr) == 2) {
4526  db->game->AddPgnTag (tagStr, valueStr);
4527  } else {
4528  // Invalid line in the list; just ignore it.
4529  }
4530  }
4531  Tcl_Free ((char *) largv);
4532  }
4533  break;
4534  default:
4535  return InvalidCommand (ti, "sc_game tags set", options);
4536  }
4537  }
4538 
4539  return TCL_OK;
4540 }
4541 
4542 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4543 // sc_game_tags_reload:
4544 // Reloads the tags (White, Black, Event,Site, etc) for a game.
4545 // Useful when a name that may occur in the current game has been
4546 // edited.
4547 int
4548 sc_game_tags_reload(ClientData, Tcl_Interp*, int, const char**)
4549 {
4550  if (!db->inUse || db->gameNumber < 0) { return TCL_OK; }
4551  const IndexEntry* ie = db->getIndexEntry(db->gameNumber);
4552  db->game->LoadStandardTags (ie, db->getNameBase());
4553  return TCL_OK;
4554 }
4555 
4556 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4557 // sc_game_tags_share:
4558 // Shares tags between two games, updating one where the other
4559 // has more complete or better information.
4560 //
4561 // This is mainly useful for combining the header information
4562 // of a pair of twins before deleting one of them. For example,
4563 // one may have a less complete date while the other may have
4564 // no ratings or an unknown ("?") round value.
4565 //
4566 // If the subcommand parameter is "check", a list is returned
4567 // with a multiple of four elements, each set of four indicating
4568 // a game number, the tag that will be changed, the old value,
4569 // and the new value. If the parameter is "update", the changes
4570 // will be made and the empty string is returned.
4571 int
4572 sc_game_tags_share (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4573 {
4574  const char * usage =
4575  "Usage: sc_game tags share [check|update] <gameNumber1> <gameNumber2>";
4576  if (argc != 6) { return errorResult (ti, usage); }
4577  bool updateMode = false;
4578  if (strIsPrefix (argv[3], "check")) {
4579  updateMode = false;
4580  } else if (strIsPrefix (argv[3], "update")) {
4581  updateMode = true;
4582  } else {
4583  return errorResult (ti, usage);
4584  }
4585  // Get the two game numbers, which should be different and non-zero.
4586  uint gn1 = strGetUnsigned (argv[4]);
4587  uint gn2 = strGetUnsigned (argv[5]);
4588  if (gn1 == 0) { return TCL_OK; }
4589  if (gn2 == 0) { return TCL_OK; }
4590  if (gn1 == gn2) { return TCL_OK; }
4591  if (gn1 > db->numGames()) { return TCL_OK; }
4592  if (gn2 > db->numGames()) { return TCL_OK; }
4593 
4594  // Do nothing if the base is not writable:
4595  if (!db->inUse || db->isReadOnly()) { return TCL_OK; }
4596 
4597  // Make a local copy of each index entry:
4598  IndexEntry ie1 = *(db->getIndexEntry(gn1 - 1));
4599  IndexEntry ie2 = *(db->getIndexEntry(gn2 - 1));
4600  bool updated1 = false;
4601  bool updated2 = false;
4602 
4603  // Share dates if appropriate:
4604  char dateStr1 [16];
4605  char dateStr2 [16];
4606  dateT date1 = ie1.GetDate();
4607  dateT date2 = ie2.GetDate();
4608  date_DecodeToString (date1, dateStr1);
4609  date_DecodeToString (date2, dateStr2);
4610  strTrimDate (dateStr1);
4611  strTrimDate (dateStr2);
4612  if (date1 == 0) { *dateStr1 = 0; }
4613  if (date2 == 0) { *dateStr2 = 0; }
4614  // Check if one date is a prefix of the other:
4615  if (!strEqual (dateStr1, dateStr2) && strIsPrefix (dateStr1, dateStr2)) {
4616  // Copy date grom game 2 to game 1:
4617  if (updateMode) {
4618  ie1.SetDate (date2);
4619  updated1 = true;
4620  } else {
4621  appendUintElement (ti, gn1);
4622  Tcl_AppendElement (ti, "Date");
4623  Tcl_AppendElement (ti, dateStr1);
4624  Tcl_AppendElement (ti, dateStr2);
4625  }
4626  }
4627  if (!strEqual (dateStr1, dateStr2) && strIsPrefix (dateStr2, dateStr1)) {
4628  // Copy date grom game 1 to game 2:
4629  if (updateMode) {
4630  ie2.SetDate (date1);
4631  updated2 = true;
4632  } else {
4633  appendUintElement (ti, gn2);
4634  Tcl_AppendElement (ti, "Date");
4635  Tcl_AppendElement (ti, dateStr2);
4636  Tcl_AppendElement (ti, dateStr1);
4637  }
4638  }
4639 
4640  // Check if an event name can be updated:
4641  idNumberT event1 = ie1.GetEvent();
4642  idNumberT event2 = ie2.GetEvent();
4643  const char * eventStr1 = ie1.GetEventName (db->getNameBase());
4644  const char * eventStr2 = ie2.GetEventName (db->getNameBase());
4645  bool event1empty = strEqual (eventStr1, "") || strEqual (eventStr1, "?");
4646  bool event2empty = strEqual (eventStr2, "") || strEqual (eventStr2, "?");
4647  if (event1empty && !event2empty) {
4648  // Copy event from event 2 to game 1:
4649  if (updateMode) {
4650  ie1.SetEvent (event2);
4651  updated1 = true;
4652  } else {
4653  appendUintElement (ti, gn1);
4654  Tcl_AppendElement (ti, "Event");
4655  Tcl_AppendElement (ti, eventStr1);
4656  Tcl_AppendElement (ti, eventStr2);
4657  }
4658  }
4659  if (event2empty && !event1empty) {
4660  // Copy event from game 1 to game 2:
4661  if (updateMode) {
4662  ie2.SetEvent (event1);
4663  updated2 = true;
4664  } else {
4665  appendUintElement (ti, gn2);
4666  Tcl_AppendElement (ti, "Event");
4667  Tcl_AppendElement (ti, eventStr2);
4668  Tcl_AppendElement (ti, eventStr1);
4669  }
4670  }
4671 
4672  // Check if a round name can be updated:
4673  idNumberT round1 = ie1.GetRound();
4674  idNumberT round2 = ie2.GetRound();
4675  const char * roundStr1 = ie1.GetRoundName (db->getNameBase());
4676  const char * roundStr2 = ie2.GetRoundName (db->getNameBase());
4677  bool round1empty = strEqual (roundStr1, "") || strEqual (roundStr1, "?");
4678  bool round2empty = strEqual (roundStr2, "") || strEqual (roundStr2, "?");
4679  if (round1empty && !round2empty) {
4680  // Copy round from game 2 to game 1:
4681  if (updateMode) {
4682  ie1.SetRound (round2);
4683  updated1 = true;
4684  } else {
4685  appendUintElement (ti, gn1);
4686  Tcl_AppendElement (ti, "Round");
4687  Tcl_AppendElement (ti, roundStr1);
4688  Tcl_AppendElement (ti, roundStr2);
4689  }
4690  }
4691  if (round2empty && !round1empty) {
4692  // Copy round from game 1 to game 2:
4693  if (updateMode) {
4694  ie2.SetRound (round1);
4695  updated2 = true;
4696  } else {
4697  appendUintElement (ti, gn2);
4698  Tcl_AppendElement (ti, "Round");
4699  Tcl_AppendElement (ti, roundStr2);
4700  Tcl_AppendElement (ti, roundStr1);
4701  }
4702  }
4703 
4704  // Check if Elo ratings can be shared:
4705  eloT welo1 = ie1.GetWhiteElo();
4706  eloT belo1 = ie1.GetBlackElo();
4707  eloT welo2 = ie2.GetWhiteElo();
4708  eloT belo2 = ie2.GetBlackElo();
4709  if (welo1 == 0 && welo2 != 0) {
4710  // Copy White rating from game 2 to game 1:
4711  if (updateMode) {
4712  ie1.SetWhiteElo (welo2);
4713  updated1 = true;
4714  } else {
4715  appendUintElement (ti, gn1);
4716  Tcl_AppendElement (ti, "WhiteElo");
4717  appendUintElement (ti, welo1);
4718  appendUintElement (ti, welo2);
4719  }
4720  }
4721  if (welo2 == 0 && welo1 != 0) {
4722  // Copy White rating from game 1 to game 2:
4723  if (updateMode) {
4724  ie2.SetWhiteElo (welo1);
4725  updated2 = true;
4726  } else {
4727  appendUintElement (ti, gn2);
4728  Tcl_AppendElement (ti, "WhiteElo");
4729  appendUintElement (ti, welo2);
4730  appendUintElement (ti, welo1);
4731  }
4732  }
4733  if (belo1 == 0 && belo2 != 0) {
4734  // Copy Black rating from game 2 to game 1:
4735  if (updateMode) {
4736  ie1.SetBlackElo (belo2);
4737  updated1 = true;
4738  } else {
4739  appendUintElement (ti, gn1);
4740  Tcl_AppendElement (ti, "BlackElo");
4741  appendUintElement (ti, belo1);
4742  appendUintElement (ti, belo2);
4743  }
4744  }
4745  if (belo2 == 0 && belo1 != 0) {
4746  // Copy Black rating from game 1 to game 2:
4747  if (updateMode) {
4748  ie2.SetBlackElo (belo1);
4749  updated2 = true;
4750  } else {
4751  appendUintElement (ti, gn2);
4752  Tcl_AppendElement (ti, "BlackElo");
4753  appendUintElement (ti, belo2);
4754  appendUintElement (ti, belo1);
4755  }
4756  }
4757 
4758  // Write changes to the index file:
4759  if (updateMode && (updated1 || updated2)) {
4760  db->beginTransaction();
4761  if (updated1) {
4762  db->idx->WriteEntry (&ie1, gn1 - 1);
4763  }
4764  if (updated2) {
4765  db->idx->WriteEntry (&ie2, gn2 - 1);
4766  }
4767  db->endTransaction();
4768  }
4769  return TCL_OK;
4770 }
4771 
4772 //////////////////////////////////////////////////////////////////////
4773 /// INFO functions
4774 static bool
4775 date_ValidString (const char * str)
4776 {
4777  uint maxValues[3] = { YEAR_MAX, 12, 31 };
4778 
4779  // Check year, then month, then day:
4780  for (uint i=0; i < 3; i++) {
4781  uint maxValue = maxValues[i];
4782  bool seenQuestion, seenDigit, seenOther;
4783  seenQuestion = seenDigit = seenOther = false;
4784  const char * start = str;
4785  while (*str != 0 && *str != '.') {
4786  char ch = *str;
4787  if (ch >= '0' && ch <= '9') {
4788  seenDigit = true;
4789  } else if (ch == '?') {
4790  seenQuestion = true;
4791  } else {
4792  seenOther = true;
4793  }
4794  str++;
4795  }
4796  // Here, we should have seen question marks or digits, not both:
4797  if (seenOther) { return false; }
4798  if (seenQuestion && seenDigit) { return false; }
4799  if (seenDigit) {
4800  // Check that the value is not too large:
4801  uint value = strGetUnsigned (start);
4802  if (value > maxValue) { return false; }
4803  }
4804  if (*str == 0) { return true; } else { str++; }
4805  }
4806  return false;
4807 }
4808 
4809 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4810 // sc_info:
4811 // General Scid Information commands.
4812 int
4813 sc_info (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
4814 {
4815  static const char * options [] = {
4816  "clipbase", "decimal", "priority",
4817  "html", "limit", "ratings",
4818  "suffix", "tb", "validDate", "version", "language", NULL
4819  };
4820  enum {
4821  INFO_CLIPBASE, INFO_DECIMAL, INFO_PRIORITY,
4822  INFO_HTML, INFO_LIMIT, INFO_RATINGS,
4823  INFO_SUFFIX, INFO_TB, INFO_VALIDDATE, INFO_VERSION, INFO_LANGUAGE
4824  };
4825  int index = -1;
4826 
4827  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
4828 
4829  switch (index) {
4830  case INFO_CLIPBASE:
4831  return UI_Result(ti, OK, DBasePool::getClipBase());
4832 
4833  case INFO_DECIMAL:
4834  if (argc >= 3) {
4835  decimalPointChar = argv[2][0];
4836  } else {
4837  return UI_Result(ti, OK, std::string(1, decimalPointChar));
4838  }
4839  break;
4840 
4841  case INFO_PRIORITY:
4842  return sc_info_priority(cd, ti, argc, argv);
4843 
4844  case INFO_HTML:
4845  if (argc >= 3) {
4846  htmlDiagStyle = strGetUnsigned (argv[2]);
4847  } else {
4848  return setUintResult (ti, htmlDiagStyle);
4849  }
4850  break;
4851 
4852  case INFO_LIMIT:
4853  return sc_info_limit (cd, ti, argc, argv);
4854 
4855  case INFO_RATINGS: // List of all recognised rating types.
4856  {
4857  uint i = 0;
4858  while (ratingTypeNames[i] != NULL) {
4859  Tcl_AppendElement (ti, (char *) ratingTypeNames[i]);
4860  i++;
4861  }
4862  }
4863  break;
4864 
4865  case INFO_SUFFIX:
4866  return sc_info_suffix (cd, ti, argc, argv);
4867 
4868  case INFO_TB:
4869  return sc_info_tb (cd, ti, argc, argv);
4870 
4871  case INFO_VALIDDATE:
4872  if (argc != 3) {
4873  return errorResult (ti, "Usage: sc_info validDate <datestring>");
4874  }
4875  return UI_Result(ti, OK, date_ValidString (argv[2]));
4876 
4877  case INFO_VERSION:
4878  if (argc >= 3 && strIsPrefix (argv[2], "date")) {
4879  setResult (ti, __DATE__);
4880  } else {
4882  }
4883  break;
4884  case INFO_LANGUAGE:
4885  if (argc != 3) {
4886  return errorResult (ti, "Usage: sc_info language <lang>");
4887  }
4888  if ( strcmp(argv[2], "en") == 0) {language = 0;}
4889  if ( strcmp(argv[2], "fr") == 0) {language = 1;}
4890  if ( strcmp(argv[2], "es") == 0) {language = 2;}
4891  if ( strcmp(argv[2], "de") == 0) {language = 3;}
4892  if ( strcmp(argv[2], "it") == 0) {language = 4;}
4893  if ( strcmp(argv[2], "ne") == 0) {language = 5;}
4894  if ( strcmp(argv[2], "cz") == 0) {language = 6;}
4895  if ( strcmp(argv[2], "hu") == 0) {language = 7;}
4896  if ( strcmp(argv[2], "no") == 0) {language = 8;}
4897  if ( strcmp(argv[2], "sw") == 0) {language = 9;}
4898  if ( strcmp(argv[2], "ca") == 0) {language = 10;}
4899  if ( strcmp(argv[2], "fi") == 0) {language = 11;}
4900  if ( strcmp(argv[2], "gr") == 0) {language = 12;}
4901 
4902  break;
4903  default:
4904  return InvalidCommand (ti, "sc_info", options);
4905  };
4906 
4907  return TCL_OK;
4908 }
4909 
4910 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4911 // sc_info limit:
4912 // Limits that Scid imposes.
4913 int
4914 sc_info_limit (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4915 {
4916  static const char * options [] = {
4917  "elo", "year", "bases", NULL
4918  };
4919  enum {
4920  LIM_ELO, LIM_YEAR, LIM_BASES
4921  };
4922  int index = -1;
4923  int result = 0;
4924 
4925  if (argc == 3) { index = strUniqueMatch (argv[2], options); }
4926 
4927  switch (index) {
4928  case LIM_ELO:
4929  result = MAX_ELO;
4930  break;
4931 
4932  case LIM_YEAR:
4933  result = YEAR_MAX;
4934  break;
4935 
4936  case LIM_BASES:
4937  result = MAX_BASES;
4938  break;
4939 
4940  default:
4941  return UI_Result(ti, ERROR_BadArg, "Usage: sc_info limit <elo|year|bases>");
4942  }
4943 
4944  return UI_Result(ti, OK, result);
4945 }
4946 
4947 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4948 // sc_info suffix:
4949 // Returns a Scid file suffix for a database file type.
4950 // The suffix is returned with the leading dot.
4951 int
4952 sc_info_suffix (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4953 {
4954  static const char * options [] = {
4955  "index", NULL
4956  };
4957  enum {
4958  SUFFIX_OPT_INDEX
4959  };
4960  int index = -1;
4961 
4962  if (argc == 3) { index = strUniqueMatch (argv[2], options); }
4963 
4964  const char * suffix = "";
4965 
4966  switch (index) {
4967  case SUFFIX_OPT_INDEX: suffix = INDEX_SUFFIX; break;
4968  default: return InvalidCommand (ti, "sc_info suffix", options);
4969  }
4970 
4971  return setResult (ti, suffix);
4972 }
4973 
4974 
4975 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4976 // sc_info_tb:
4977 // Set up a tablebase directory, or check if a certain
4978 // tablebase is available.
4979 int
4980 sc_info_tb (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
4981 {
4982  const char * usage =
4983  "Usage: sc_info tb [<directory>|available <material>|cache <size-kb>]";
4984 
4985  if (argc == 2) {
4986  // Command: sc_info tb
4987  // Returns whether tablebase support is complied.
4988  return UI_Result(ti, OK, scid_TB_compiled());
4989 
4990  } else if (argc == 3) {
4991  // Command: sc_info_tb <directories>
4992  // Clears tablebases and registers all tablebases in the
4993  // specified directories string, which can have more than
4994  // one directory separated by commas or semicolons.
4995  return setUintResult (ti, scid_TB_Init (argv[2]));
4996 
4997  } else if (argc == 4 && argv[2][0] == 'a') {
4998  // Command: sc_probe available <material>
4999  // The material is specified as "KRKN", "kr-kn", etc.
5000  // Set up the required material:
5001  matSigT ms = MATSIG_Empty;
5002  const char * material = argv[3];
5003  if (toupper(*material) != 'K') { return UI_Result(ti, OK, false); }
5004  material++;
5005  colorT side = WHITE;
5006  while (1) {
5007  char ch = toupper(*material);
5008  material++;
5009  if (ch == 0) { break; }
5010  if (ch == 'K') { side = BLACK; continue; }
5011  pieceT p = piece_Make (side, piece_FromChar (ch));
5012  if (ch == 'P') { p = piece_Make (side, PAWN); }
5013  if (p == EMPTY) { continue; }
5014  ms = matsig_setCount (ms, p, matsig_getCount (ms, p) + 1);
5015  }
5016  // Check if a tablebase for this material is available:
5017  return UI_Result(ti, OK, scid_TB_Available (ms));
5018  } else if (argc == 4 && argv[2][0] == 'c') {
5019  // Set the preferred tablebase cache size, to take effect
5020  // at the next tablebase initialisation.
5021  uint cachesize = strGetUnsigned (argv[3]);
5022  scid_TB_SetCacheSize (cachesize * 1024);
5023  return TCL_OK;
5024  } else {
5025  return errorResult (ti, usage);
5026  }
5027 }
5028 
5029 //////////////////////////////////////////////////////////////////////
5030 // MOVE functions
5031 
5032 int
5033 sc_move (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
5034 {
5035  static const char * options [] = {
5036  "add", "addSan", "addUCI", "back", "end", "forward",
5037  "pgn", "ply", "start", NULL
5038  };
5039  enum {
5040  MOVE_ADD, MOVE_ADDSAN, MOVE_ADDUCI, MOVE_BACK, MOVE_END, MOVE_FORWARD,
5041  MOVE_PGN, MOVE_PLY, MOVE_START
5042  };
5043  int index = -1;
5044 
5045  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
5046 
5047  switch (index) {
5048  case MOVE_ADD:
5049  return sc_move_add (cd, ti, argc, argv);
5050 
5051  case MOVE_ADDSAN:
5052  return sc_move_addSan (cd, ti, argc, argv);
5053 
5054  case MOVE_ADDUCI:
5055  return sc_move_addUCI (cd, ti, argc, argv);
5056 
5057  case MOVE_BACK:
5058  return sc_move_back (cd, ti, argc, argv);
5059 
5060  case MOVE_END:
5061  db->game->MoveToPly(0);
5062  {
5063  errorT err = OK;
5064  do {
5065  err = db->game->MoveForward();
5066  } while (err == OK);
5067  }
5068  break;
5069 
5070  case MOVE_FORWARD:
5071  return sc_move_forward (cd, ti, argc, argv);
5072 
5073  case MOVE_PGN:
5074  return sc_move_pgn (cd, ti, argc, argv);
5075 
5076  case MOVE_PLY:
5077  if (argc == 3) {
5078  db->game->MoveToPly(strGetUnsigned(argv[2]));
5079  return UI_Result(ti, OK);
5080  }
5081  return errorResult (ti, "Usage: sc_move ply <plynumber>");
5082 
5083  case MOVE_START:
5084  db->game->MoveToPly (0);
5085  break;
5086 
5087  default:
5088  return InvalidCommand (ti, "sc_move", options);
5089  }
5090 
5091  return TCL_OK;
5092 }
5093 
5094 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5095 // sc_move_add: takes a move specified by three parameters
5096 // (square square promo) and adds it to the game.
5097 int
5098 sc_move_add (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5099 {
5100 
5101  if (argc != 5) {
5102  return errorResult (ti, "Usage: sc_move add <sq> <sq> <promo>");
5103  }
5104 
5105  uint sq1 = strGetUnsigned (argv[2]);
5106  uint sq2 = strGetUnsigned (argv[3]);
5107  uint promo = strGetUnsigned (argv[4]);
5108  if (promo == 0) { promo = EMPTY; }
5109 
5110  char s[8];
5111  s[0] = square_FyleChar (sq1);
5112  s[1] = square_RankChar (sq1);
5113  s[2] = square_FyleChar (sq2);
5114  s[3] = square_RankChar (sq2);
5115  if (promo == EMPTY) {
5116  s[4] = 0;
5117  } else {
5118  s[4] = piece_Char(promo);
5119  s[5] = 0;
5120  }
5121  simpleMoveT sm;
5122  Position * pos = db->game->GetCurrentPos();
5123  errorT err = pos->ReadCoordMove(&sm, s, s[4] == 0 ? 4 : 5, true);
5124  if (err == OK) {
5125  err = db->game->AddMove(&sm);
5126  if (err == OK) {
5127  db->gameAltered = true;
5128  return TCL_OK;
5129  }
5130  }
5131  return errorResult (ti, "Error adding move.");
5132 }
5133 
5134 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5135 // sc_move_addSan:
5136 // Takes moves in regular SAN (e.g. "e4" or "Nbd2") and adds them
5137 // to the game. The moves can be in one large string, separate
5138 // list elements, or a mixture of both. Move numbers are ignored
5139 // but variations/comments/annotations are parsed and added.
5140 int sc_move_addSan(ClientData, Tcl_Interp* ti, int argc, const char** argv) {
5141  PgnParseLog parser;
5142  for (int i = 2; i < argc; ++i) {
5143  db->gameAltered = true;
5144  if (!pgnParseGame(argv[i], std::strlen(argv[i]), *db->game, parser))
5145  return UI_Result(ti, ERROR_InvalidMove, argv[i]);
5146  }
5147  return UI_Result(ti, OK);
5148 }
5149 
5150 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5151 // sc_move_addUCI:
5152 // Takes moves in engine UCI format (e.g. "g1f3") and adds them
5153 // to the game. The result is translated.
5154 // In case of an error, return the moves that could be converted.
5155 int
5156 sc_move_addUCI (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5157 {
5158  char s[8], tmp[10];
5159  if (argc < 3) { return TCL_OK; }
5160  char * ptr = (char *) argv[2];
5161 
5162  while (*ptr != 0) {
5163  s[0] = ptr[0];
5164  s[1] = ptr[1];
5165  s[2] = ptr[2];
5166  s[3] = ptr[3];
5167  if (ptr[4] == ' ') {
5168  s[4] = 0;
5169  ptr += 5;
5170  } else if (ptr[4] == 0) {
5171  s[4] = 0;
5172  ptr += 4;
5173  } else {
5174  s[4] = ptr[4];
5175  s[5] = 0;
5176  ptr += 6;
5177  }
5178  simpleMoveT sm;
5179  Position * pos = db->game->GetCurrentPos();
5180  errorT err = pos->ReadCoordMove(&sm, s, s[4] == 0 ? 4 : 5, true);
5181  if (err == OK) {
5182  err = db->game->AddMove(&sm);
5183  if (err == OK) {
5184  db->gameAltered = true;
5185  db->game->GetPrevSAN (tmp);
5186  transPieces(tmp);
5187  Tcl_AppendResult (ti, tmp, " ", NULL);
5188  } else {
5189  //Tcl_AppendResult (ti, "Error reading move(s): ", ptr, NULL);
5190  break;
5191  }
5192  } else {
5193  //Tcl_AppendResult (ti, "Error reading move(s): ", ptr, NULL);
5194  break;
5195  }
5196  }
5197 
5198  return TCL_OK;
5199 }
5200 
5201 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5202 // sc_move_back:
5203 // Moves back a specified number of moves (default = 1 move).
5204 int
5205 sc_move_back (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5206 {
5207  int numMovesTakenBack = 0;
5208  int count = 1;
5209  if (argc > 2) {
5210  count = strGetInteger (argv[2]);
5211  // if (count < 1) { count = 1; }
5212  }
5213 
5214  for (int i = 0; i < count; i++) {
5215  if (db->game->MoveBackup() != OK) { break; }
5216  numMovesTakenBack++;
5217  }
5218 
5219  setUintResult (ti, numMovesTakenBack);
5220  return TCL_OK;
5221 }
5222 
5223 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5224 // sc_move_forward:
5225 // Moves forward a specified number of moves (default = 1 move).
5226 int
5227 sc_move_forward (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5228 {
5229  int numMovesMade = 0;
5230  int count = 1;
5231  if (argc > 2) {
5232  count = strGetInteger (argv[2]);
5233  // Do we want to allow moving forward 0 moves? Yes, I think so.
5234  // if (count < 1) { count = 1; }
5235  }
5236 
5237  for (int i = 0; i < count; i++) {
5238  if (db->game->MoveForward() != OK) { break; }
5239  numMovesMade++;
5240  }
5241 
5242  setUintResult (ti, numMovesMade);
5243  return TCL_OK;
5244 }
5245 
5246 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5247 // sc_move_pgn:
5248 // Set the current board to the position closest to
5249 // the specified place in the PGN output (given as a byte count
5250 // from the start of the output).
5251 int
5252 sc_move_pgn (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5253 {
5254  if (argc != 3) {
5255  return errorResult (ti, "Usage: sc_move pgn <offset>");
5256  }
5257 
5258  uint offset = strGetUnsigned (argv[2]);
5259  db->game->MoveToLocationInPGN (offset);
5260  return TCL_OK;
5261 }
5262 
5263 
5264 
5265 //////////////////////////////////////////////////////////////////////
5266 // POSITION functions
5267 
5268 int
5269 sc_pos (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
5270 {
5271  static const char * options [] = {
5272  "addNag", "analyze", "bestSquare", "board", "clearNags",
5273  "fen", "getComment", "getNags", "hash", "html",
5274  "isAt", "isCheck", "isLegal", "isPromotion",
5275  "matchMoves", "moveNumber", "pgnOffset",
5276  "probe", "setComment", "side", "tex", "moves", "location",
5277  "attacks", "getPrevComment", NULL
5278  };
5279  enum {
5280  POS_ADDNAG, POS_ANALYZE, POS_BESTSQ, POS_BOARD, POS_CLEARNAGS,
5281  POS_FEN, POS_GETCOMMENT, POS_GETNAGS, POS_HASH, POS_HTML,
5282  POS_ISAT, POS_ISCHECK, POS_ISLEGAL, POS_ISPROMO,
5283  POS_MATCHMOVES, POS_MOVENUM, POS_PGNOFFSET,
5284  POS_PROBE, POS_SETCOMMENT, POS_SIDE, POS_TEX, POS_MOVES, LOCATION,
5285  POS_ATTACKS, POS_GETPREVCOMMENT
5286  };
5287 
5288  char boardStr[200];
5289  int index = -1;
5290  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
5291 
5292  switch (index) {
5293  case POS_ADDNAG:
5294  return sc_pos_addNag (cd, ti, argc, argv);
5295 
5296  case POS_ANALYZE:
5297  return sc_pos_analyze (cd, ti, argc, argv);
5298 
5299  case POS_BESTSQ:
5300  return sc_pos_bestSquare (cd, ti, argc, argv);
5301 
5302  case POS_BOARD:
5303  db->game->GetCurrentPos()->MakeLongStr (boardStr);
5304  Tcl_AppendResult (ti, boardStr, NULL);
5305  break;
5306 
5307  case POS_CLEARNAGS:
5308  db->game->ClearNags();
5309  db->gameAltered = true;
5310  break;
5311 
5312  case POS_FEN:
5313  db->game->GetCurrentPos()->PrintFEN (boardStr, FEN_ALL_FIELDS);
5314  Tcl_AppendResult (ti, boardStr, NULL);
5315  break;
5316 
5317  case POS_GETCOMMENT:
5318  const char * tempStr;
5319  tempStr = db->game->GetMoveComment();
5320  if (tempStr) {
5321  Tcl_AppendResult (ti, tempStr, NULL);
5322  }
5323  break;
5324 
5325  case POS_GETPREVCOMMENT:
5326  return UI_Result(ti, OK, db->game->GetPreviousMoveComment());
5327 
5328  case POS_GETNAGS:
5329  return sc_pos_getNags (cd, ti, argc, argv);
5330 
5331  case POS_HASH:
5332  return sc_pos_hash (cd, ti, argc, argv);
5333 
5334  case POS_HTML:
5335  return sc_pos_html (cd, ti, argc, argv);
5336 
5337  case POS_ISAT:
5338  return sc_pos_isAt (cd, ti, argc, argv);
5339 
5340  case POS_ISCHECK:
5341  return UI_Result(ti, OK, db->game->GetCurrentPos()->IsKingInCheck());
5342 
5343  case POS_ISLEGAL:
5344  return sc_pos_isLegal (cd, ti, argc, argv);
5345 
5346  case POS_ISPROMO:
5347  return sc_pos_isPromo (cd, ti, argc, argv);
5348 
5349  case POS_MATCHMOVES:
5350  return sc_pos_matchMoves (cd, ti, argc, argv);
5351 
5352  case POS_MOVES:
5353  return sc_pos_moves (cd, ti, argc, argv);
5354 
5355  case POS_MOVENUM:
5356  // This used to return:
5357  // (db->game->GetCurrentPly() + 2) / 2
5358  // but that value is wrong for games with non-standard
5359  // start positions. The correct value to return is:
5360  // db->game->GetCurrentPos()->GetFullMoveCount()
5361  return setUintResult (ti, db->game->GetCurrentPos()->GetFullMoveCount());
5362 
5363  case POS_PGNOFFSET:
5364  setUintResult (ti, db->game->GetPgnOffset());
5365  break;
5366 
5367  case POS_PROBE:
5368  return sc_pos_probe (cd, ti, argc, argv);
5369 
5370  case POS_SETCOMMENT:
5371  return sc_pos_setComment (cd, ti, argc, argv);
5372 
5373  case POS_SIDE:
5374  setResult (ti, (db->game->GetCurrentPos()->GetToMove() == WHITE)
5375  ? "white" : "black");
5376  break;
5377 
5378  case POS_TEX:
5379  {
5380  bool flip = false;
5381  if (argc > 2 && strIsPrefix (argv[2], "flip")) { flip = true; }
5382  DString * dstr = new DString;
5383  db->game->GetCurrentPos()->DumpLatexBoard (dstr, flip);
5384  Tcl_AppendResult (ti, dstr->Data(), NULL);
5385  delete dstr;
5386  }
5387  break;
5388 
5389  case LOCATION:
5390  return UI_Result(ti, OK, db->game->GetCurrentPly());
5391 
5392  case POS_ATTACKS:
5393  {
5394  Position pos(*db->game->GetCurrentPos());
5395  for (colorT c = WHITE; c <= BLACK; c++) {
5396  for (uint i = 0; i < pos.GetCount(c); i++) {
5397  squareT sq = pos.GetList(c)[i];
5398  pos.SetToMove(color_Flip(c));
5399  int att = pos.TreeCalcAttacks(color_Flip(c), sq);
5400  if (att) {
5401  appendUintElement(ti, sq);
5402  if (att > 1) Tcl_AppendElement(ti, "green");
5403  else if (att > 0) Tcl_AppendElement(ti, "yellow");
5404  else Tcl_AppendElement(ti, "red");
5405  }
5406  }
5407  }
5408  }
5409  break;
5410 
5411  default:
5412  return InvalidCommand (ti, "sc_pos", options);
5413  }
5414 
5415  return TCL_OK;
5416 }
5417 
5418 
5419 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5420 // sc_pos_addNag:
5421 // Adds a NAG (annotation symbol) for the current move.
5422 int
5423 sc_pos_addNag (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5424 {
5425  if (argc != 3) {
5426  return errorResult (ti, "Usage: sc_pos addNag <nagvalue>");
5427  }
5428  const char * nagStr = argv[2];
5429  if( strcmp(nagStr, "X") == 0)
5430  db->game->RemoveNag( true);
5431  else if( strcmp(nagStr, "Y") == 0)
5432  db->game->RemoveNag( false);
5433  else
5434  {
5435  byte nag = game_parseNag({nagStr, nagStr + std::strlen(nagStr)});
5436  if (nag != 0) {
5437  db->game->AddNag ((byte) nag);
5438  }
5439  db->gameAltered = true;
5440  }
5441  return TCL_OK;
5442 }
5443 
5444 
5445 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5446 // sc_pos_analyze:
5447 // Analyzes the current position for the specified number of
5448 // milliseconds.
5449 // Returns a two-element list containing the score in centipawns
5450 // (from the perspective of the side to move) and the best move.
5451 // If there are no legal moves, the second element is the empty string.
5452 int
5453 sc_pos_analyze (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5454 {
5455  const char * usage = "Usage: sc_pos analyze [<option> <value> ...]";
5456 
5457  uint searchTime = 1000; // Default = 1000 milliseconds
5458  uint hashTableKB = 1024; // Default: one-megabyte hash table.
5459  uint pawnTableKB = 32;
5460  bool postMode = false;
5461  bool pruning = false;
5462  uint mindepth = 4; // will not check time until this depth is reached
5463  uint searchdepth = 0;
5464 
5465  static const char * options [] = {
5466  "-time", "-hashkb", "-pawnkb", "-post", "-pruning", "-mindepth", "-searchdepth", NULL
5467  };
5468  enum {
5469  OPT_TIME, OPT_HASH, OPT_PAWN, OPT_POST, OPT_PRUNING, OPT_MINDEPTH, OPT_SEARCHDEPTH
5470  };
5471  int arg = 2;
5472  while (arg+1 < argc) {
5473  const char * option = argv[arg];
5474  const char * value = argv[arg+1];
5475  arg += 2;
5476  int index = strUniqueMatch (option, options);
5477  switch (index) {
5478  case OPT_TIME: searchTime = strGetUnsigned(value); break;
5479  case OPT_HASH: hashTableKB = strGetUnsigned(value); break;
5480  case OPT_PAWN: pawnTableKB = strGetUnsigned(value); break;
5481  case OPT_POST: postMode = strGetBoolean(value); break;
5482  case OPT_PRUNING: pruning = strGetBoolean(value); break;
5483  case OPT_MINDEPTH: mindepth = strGetUnsigned(value); break;
5484  case OPT_SEARCHDEPTH: searchdepth = strGetUnsigned(value); break;
5485  default:
5486  return InvalidCommand (ti, "sc_pos analyze", options);
5487  }
5488  }
5489  if (arg != argc) { return errorResult (ti, usage); }
5490 
5491  // Generate all legal moves:
5492  Position * pos = db->game->GetCurrentPos();
5493  MoveList mlist;
5494  pos->GenerateMoves(&mlist);
5495 
5496  // Start the engine:
5497  Engine * engine = new Engine();
5498  engine->SetSearchTime (searchTime);
5499  engine->SetHashTableKilobytes (hashTableKB);
5500  engine->SetPawnTableKilobytes (pawnTableKB);
5501  engine->SetMinDepthCheckTime(mindepth);
5502  if (searchdepth > 0)
5503  engine->SetSearchDepth(searchdepth);
5504  engine->SetPosition (pos);
5505  engine->SetPostMode (postMode);
5506  engine->SetPruning (pruning);
5507  int score = engine->Think (&mlist);
5508  delete engine;
5509 
5510  char moveStr[20];
5511  moveStr[0] = 0;
5512  if (mlist.Size() > 0) {
5513  pos->MakeSANString (mlist.Get(0), moveStr, SAN_MATETEST);
5514  }
5515  UI_List res(2);
5516  res.push_back(score);
5517  res.push_back(moveStr);
5518  return UI_Result(ti, OK, res);
5519 }
5520 
5521 // Lambda class used by sc_pos_bestSquare() and sc_pos_isLegal()
5522 namespace {
5523 class SelectBySquare {
5524  squareT sq_;
5525 
5526 public:
5527  explicit SelectBySquare(squareT sq) : sq_(sq) {}
5528  bool operator()(const simpleMoveT& sm) {
5529  return sm.from == sq_ || sm.to == sq_;
5530  }
5531 };
5532 }
5533 
5534 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5535 // sc_pos_bestSquare:
5536 // Takes a square and returns the best square that makes a move
5537 // with the given square. The square can be the from or to part of
5538 // a move. Used for smart move completion.
5539 // Returns -1 if no legal moves go to or from the square.
5540 int
5541 sc_pos_bestSquare (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5542 {
5543  if (argc != 3) {
5544  return errorResult (ti, "Usage: sc_pos bestSquare <square>");
5545  }
5546 
5547  Position * pos = db->game->GetCurrentPos();
5548 
5549  // Try to read the square parameter as algebraic ("h8") or numeric (63):
5550  squareT sq = strGetSquare (argv[2]);
5551  if (sq == NULL_SQUARE) {
5552  int sqInt = strGetInteger (argv[2]);
5553  if (sqInt >= 0 && sqInt <= 63) { sq = sqInt; }
5554  }
5555  if (sq == NULL_SQUARE) {
5556  return errorResult (ti, "Usage: sc_pos bestSquare <square>");
5557  }
5558 
5559  // Generate all legal moves:
5560  MoveList mlist;
5561  pos->GenerateMoves(&mlist);
5562 
5563  // Restrict the list of legal moves to contain only those that
5564  // move to or from the specified square:
5565  mlist.resize(std::distance(mlist.begin(),
5566  std::partition(mlist.begin(), mlist.end(), SelectBySquare(sq))
5567  ));
5568 
5569  // If no matching legal moves, return -1:
5570  if (mlist.Size() == 0) {
5571  return setResult (ti, "-1");
5572  }
5573 
5574  if (mlist.Size() > 1) {
5575  // We have more than one move to choose from, so first check
5576  // the ECO openings book (if it is loaded) to see if any move
5577  // in the list reaches an ECO position. If so, select the move
5578  // reaching the largest ECO code as the best move. If no ECO
5579  // position is found, do a small chess engine search to find
5580  // the best move.
5581 
5582  ecoT bestEco = ECO_None;
5583  ecoT secondBestEco = ECO_None;
5584  if (ecoBook != NULL) {
5585  for (uint i=0; i < mlist.Size(); i++) {
5586  pos->DoSimpleMove (mlist.Get(i));
5587  ecoT eco = ecoBook->findECO(pos);
5588  pos->UndoSimpleMove (mlist.Get(i));
5589  if (eco >= bestEco) {
5590  secondBestEco = bestEco;
5591  bestEco = eco;
5592  std::rotate(mlist.begin(), mlist.begin() + i,
5593  mlist.begin() + i + 1);
5594  }
5595  }
5596  }
5597 
5598  if (bestEco == ECO_None || bestEco == secondBestEco) {
5599  // No matching ECO position found, or a tie. So do a short
5600  // engine search to find the best move; 25 ms (= 1/40 s)
5601  // is enough to reach a few ply and select reasonable
5602  // moves but fast enough to seem almost instant. The
5603  // search promotes the best move to be first in the list.
5604  Engine * engine = new Engine();
5605  engine->SetSearchTime (25); // Do a 25 millisecond search
5606  engine->SetPosition (pos);
5607  engine->Think (&mlist);
5608  delete engine;
5609  }
5610  }
5611 
5612  // Now the best move is the first in the list, either because it
5613  // is the only move, or it reaches the largest ECO code, or because
5614  // the chess engine search selected it.
5615  // Find the other square in the best move and return it:
5616 
5617  simpleMoveT * sm = mlist.Get(0);
5618  ASSERT (sq == sm->from || sq == sm->to);
5619  squareT bestSq = sm->from;
5620  if (sm->from == sq) { bestSq = sm->to; }
5621  setUintResult (ti, bestSq);
5622 
5623  return TCL_OK;
5624 }
5625 
5626 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5627 // sc_pos_getNags:
5628 // Get the NAGs for the current move.
5629 int
5630 sc_pos_getNags(ClientData, Tcl_Interp* ti, int, const char**)
5631 {
5632  byte * nag = db->game->GetNags();
5633  if (nag[0] == 0) {
5634  return setResult (ti, "0");
5635  }
5636  while (*nag) {
5637  char temp[20];
5638  game_printNag (*nag, temp, true, PGN_FORMAT_Plain);
5639  Tcl_AppendResult (ti, temp, " ", NULL);
5640  nag++;
5641  }
5642 
5643  return TCL_OK;
5644 }
5645 
5646 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5647 // sc_pos_hash:
5648 // Returns the 32-bit hash value of the current position.
5649 int
5650 sc_pos_hash (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5651 {
5652  const char * usage = "Usage: sc_pos hash [full|pawn]";
5653  bool pawnHashOnly = false;
5654  if (argc > 3) { return errorResult (ti, usage); }
5655  if (argc == 3) {
5656  switch (argv[2][0]) {
5657  case 'f': pawnHashOnly = false; break;
5658  case 'p': pawnHashOnly = true; break;
5659  default: return errorResult (ti, usage);
5660  }
5661  }
5662  Position * pos = db->game->GetCurrentPos();
5663  uint hash = pos->HashValue();
5664  if (pawnHashOnly) {
5665  hash = pos->PawnHashValue();
5666  }
5667  return setUintResult (ti, hash);
5668 }
5669 
5670 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5671 // sc_pos_html:
5672 // Returns an HTML table representation of the board.
5673 // There are two styles: 0 (the default), which has
5674 // 40x40 squares and images in a "bitmaps" subdirectory;
5675 // and style 1 which has 36x35 squares and images in
5676 // a "bitmaps2" directory.
5677 // The directory can be overridden with the "-path" command.
5678 int
5679 sc_pos_html (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5680 {
5681  const char * usage = "Usage: sc_pos html [-flip <boolean>] [-path <path>] [<style:0|1>]";
5682  uint style = htmlDiagStyle;
5683  bool flip = false;
5684  int arg = 2;
5685  const char * path = NULL;
5686 
5687  if (argc > arg+1 && strEqual (argv[arg], "-flip")) {
5688  flip = strGetBoolean(argv[arg+1]);
5689  arg += 2;
5690  }
5691  if (argc > arg+1 && strEqual (argv[arg], "-path")) {
5692  path = argv[arg+1];
5693  arg += 2;
5694  }
5695  if (argc < arg || argc > arg+1) {
5696  return errorResult (ti, usage);
5697  }
5698  if (argc == arg+1) { style = strGetUnsigned (argv[arg]); }
5699 
5700  DString * dstr = new DString;
5701  db->game->GetCurrentPos()->DumpHtmlBoard (dstr, style, path, flip);
5702  Tcl_AppendResult (ti, dstr->Data(), NULL);
5703  delete dstr;
5704  return TCL_OK;
5705 }
5706 
5707 
5708 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5709 // sc_pos_isAt: returns whether the position is at the
5710 // start or end of a move list, according to the arg value.
5711 // Valid arguments are: start, end, vstart and vend (or unique
5712 // abbreviations thereof).
5713 int
5714 sc_pos_isAt (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5715 {
5716  static const char * options [] = {
5717  "start", "end", "vstart", "vend", NULL
5718  };
5719  enum {
5720  OPT_START, OPT_END, OPT_VSTART, OPT_VEND
5721  };
5722  int index = -1;
5723  if (argc == 3) { index = strUniqueMatch(argv[2], options); }
5724 
5725  switch (index) {
5726  case OPT_START:
5727  return UI_Result(ti, OK, db->game->AtStart());
5728 
5729  case OPT_END:
5730  return UI_Result(ti, OK, db->game->AtEnd());
5731 
5732  case OPT_VSTART:
5733  return UI_Result(ti, OK, db->game->AtVarStart());
5734 
5735  case OPT_VEND:
5736  return UI_Result(ti, OK, db->game->AtVarEnd());
5737 
5738  default:
5739  return errorResult (ti, "Usage: sc_pos isAt start|end|vstart|vend");
5740  }
5741  return TCL_OK;
5742 }
5743 
5744 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5745 // sc_pos_isPromo:
5746 // Takes two squares (from and to, in either order) and
5747 // returns true if they represent a pawn promotion move.
5748 int
5749 sc_pos_isPromo (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5750 {
5751  if (argc != 4) {
5752  return errorResult (ti, "Usage: sc_move isPromo <square> <square>");
5753  }
5754 
5755  Position * pos = db->game->GetCurrentPos();
5756  int fromSq = strGetInteger (argv[2]);
5757  int toSq = strGetInteger (argv[3]);
5758 
5759  if (fromSq < A1 || fromSq > H8 || toSq < A1 || toSq > H8) {
5760  return errorResult (ti, "Usage: sc_move isPromo <square> <square>");
5761  }
5762 
5763  return UI_Result(ti, OK, pos->IsPromoMove ((squareT) fromSq, (squareT) toSq));
5764 }
5765 
5766 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5767 // sc_pos_isLegal: returns true if the move between the two provided
5768 // squares (either could be the from square) is legal.
5769 int
5770 sc_pos_isLegal (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5771 {
5772  if (argc != 4) {
5773  return errorResult (ti, "Usage: sc_pos isLegal <square> <square>");
5774  }
5775 
5776  Position * pos = db->game->GetCurrentPos();
5777  int sq1 = strGetInteger (argv[2]);
5778  int sq2 = strGetInteger (argv[3]);
5779  if (sq1 < 0 || sq1 > 63 || sq2 < 0 || sq2 > 63) {
5780  return UI_Result(ti, OK, false);
5781  }
5782 
5783  // Compute all legal moves, then restrict the list to only
5784  // contain moves that include sq1 and sq2 as to/from squares:
5785  MoveList mlist;
5786  pos->GenerateMoves(&mlist);
5787  simpleMoveT* end1 = std::partition(mlist.begin(), mlist.end(), SelectBySquare(sq1));
5788  bool found = mlist.begin() !=
5789  std::partition(mlist.begin(), end1, SelectBySquare(sq2));
5790  return UI_Result(ti, OK, found);
5791 }
5792 
5793 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5794 // sc_pos_matchMoves: Return the list of legal moves matching
5795 // a specified prefix. Note that any occurrence of "x", "=", "+",
5796 // or "#" is removed from the move text of each move, and the
5797 // castling moves are "OK" and "OQ" for king and queen-side
5798 // castling respectively, so that no move is a prefix of
5799 // any other move.
5800 int
5801 sc_pos_matchMoves (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5802 {
5803  if (argc != 3 && argc != 4) {
5804  return errorResult (ti, "Usage: sc_pos matchMoves <movetext-prefix>");
5805  }
5806  bool coordMoves = false;
5807  const char * prefix = argv[2];
5808  if (argc == 4) { coordMoves = strGetBoolean (argv[3]); }
5809  char str[20];
5810  Position * p = db->game->GetCurrentPos();
5811  sanListT sanList;
5812  p->CalcSANStrings (&sanList, SAN_NO_CHECKTEST);
5813 
5814  for (uint i=0; i < sanList.num; i++) {
5815  strCopyExclude (str, sanList.list[i], "x=+#");
5816  if (strEqual (str, "O-O")) { strCopy (str, "OK"); }
5817  if (strEqual (str, "O-O-O")) { strCopy (str, "OQ"); }
5818  if (strIsPrefix (prefix, str)) {
5819  Tcl_AppendElement (ti, str);
5820  }
5821  }
5822 
5823  // If the prefix string is for b-pawn moves, also look for any
5824  // Bishop moves that could match, and add them provided they do not
5825  // clash with a pawn move.
5826  if (prefix[0] >= 'a' && prefix[0] <= 'h') {
5827  char * newPrefix = strDuplicate (prefix);
5828  newPrefix[0] = toupper(newPrefix[0]);
5829  for (uint i=0; i < sanList.num; i++) {
5830  strCopyExclude (str, sanList.list[i], "x=+#");
5831  if (strIsPrefix (newPrefix, str)) {
5832  Tcl_AppendElement (ti, str);
5833  }
5834  }
5835 #ifdef WINCE
5836  my_Tcl_Free((char*) newPrefix);
5837 #else
5838  delete[] newPrefix;
5839 #endif
5840  }
5841 
5842  // If the prefix string starts with a file (a-h), also add coordinate
5843  // moves if coordMoves is true:
5844  if (coordMoves && prefix[0] >= 'a' && prefix[0] <= 'h') {
5845  MoveList mList;
5846  p->GenerateMoves(&mList);
5847  for (uint i=0; i < mList.Size(); i++) {
5848  simpleMoveT * sm = mList.Get(i);
5849  str[0] = square_FyleChar (sm->from);
5850  str[1] = square_RankChar (sm->from);
5851  str[2] = square_FyleChar (sm->to);
5852  str[3] = square_RankChar (sm->to);
5853  if (sm->promote == EMPTY) {
5854  str[4] = 0;
5855  } else {
5856  str[4] = piece_Char (sm->promote);
5857  str[5] = 0;
5858  }
5859  if (strIsPrefix (prefix, str)) {
5860  Tcl_AppendElement (ti, str);
5861  }
5862  }
5863  }
5864 
5865  return TCL_OK;
5866 }
5867 
5868 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5869 // sc_pos_moves: Return the list of legal moves in SAN notation
5870 //
5871 int
5872 sc_pos_moves(ClientData, Tcl_Interp * ti, int argc, const char**)
5873 {
5874  if (argc != 2) {
5875  return errorResult (ti, "Usage: sc_pos moves");
5876  }
5877  Position * p = db->game->GetCurrentPos();
5878  sanListT sanList;
5879  p->CalcSANStrings (&sanList, SAN_NO_CHECKTEST);
5880 
5881  for (uint i=0; i < sanList.num; i++) {
5882  Tcl_AppendElement (ti, sanList.list[i]);
5883  }
5884  return TCL_OK;
5885 }
5886 
5887 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5888 // sc_pos_probe:
5889 // Probes tablebases for the current move.
5890 int
5891 sc_pos_probe (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
5892 {
5893  const char * usage = "Usage: sc_pos probe [score|report|optimal|board <sq>]";
5894  static const char * options[] = {
5895  "score", "report", "optimal", "board", NULL
5896  };
5897  enum { OPT_SCORE, OPT_REPORT, OPT_OPTIMAL, OPT_BOARD };
5898 
5899  int option = OPT_SCORE; // Default option is to return the score.
5900  if (argc >= 3) {
5901  option = strUniqueMatch(argv[2], options);
5902  }
5903 
5904  if (option == OPT_REPORT) {
5905  if (argc != 3) { return errorResult (ti, usage); }
5906  // Command: sc_probe report
5907  // Tablebase report:
5908  DString * tbReport = new DString;
5909  if (probe_tablebase (ti, PROBE_REPORT, tbReport)) {
5910  Tcl_AppendResult (ti, tbReport->Data(), NULL);
5911  }
5912  delete tbReport;
5913  } else if (option == OPT_OPTIMAL) {
5914  if (argc != 3) { return errorResult (ti, usage); }
5915  // Command: sc_probe optimal
5916  // Optimal moves from tablebase:
5917  DString * tbOptimal = new DString;
5918  if (probe_tablebase (ti, PROBE_OPTIMAL, tbOptimal)) {
5919  Tcl_AppendResult (ti, tbOptimal->Data(), NULL);
5920  }
5921  delete tbOptimal;
5922  } else if (option == OPT_SCORE) {
5923  int score = 0;
5924  if (scid_TB_Probe (db->game->GetCurrentPos(), &score) == OK) {
5925  setIntResult (ti, score);
5926  }
5927  } else if (option == OPT_BOARD) {
5928  return sc_pos_probe_board (cd, ti, argc, argv);
5929  } else {
5930  return errorResult (ti, usage);
5931  }
5932  return TCL_OK;
5933 }
5934 
5935 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5936 // sc_pos_probe_board:
5937 // Probes tablebases for the current position with one piece
5938 // (specified by its square) relocated to each empty board square.
5939 int
5940 sc_pos_probe_board (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5941 {
5942  const char * usage = "Usage: sc_pos probe board <square>";
5943 
5944  if (argc != 4) { return errorResult (ti, usage); }
5945 
5946  // Try to read the square parameter as algebraic ("h8") or numeric (63):
5947  squareT sq = strGetSquare (argv[3]);
5948  if (sq == NULL_SQUARE) {
5949  int sqInt = strGetInteger (argv[3]);
5950  if (sqInt >= 0 && sqInt <= 63) { sq = sqInt; }
5951  }
5952 
5953  if (sq == NULL_SQUARE) {
5954  return errorResult (ti, usage);
5955  }
5956 
5957  Position pos = *(db->game->GetCurrentPos());
5958  const pieceT * board = pos.GetBoard();
5959  if (board[sq] == EMPTY) { return TCL_OK; }
5960 
5961  for (squareT toSq = A1; toSq <= H8; toSq++) {
5962  const char * result = "";
5963  if (pos.RelocatePiece (sq, toSq) != OK) {
5964  result = "X";
5965  } else {
5966  int score = 0;
5967  if (scid_TB_Probe (&pos, &score) != OK) {
5968  result = "?";
5969  } else {
5970  if (score > 0) {
5971  result = "+";
5972  } else if (score < 0) {
5973  result = "-";
5974  } else {
5975  result = "=";
5976  }
5977  }
5978  pos.RelocatePiece (toSq, sq);
5979  }
5980  Tcl_AppendResult (ti, result, NULL);
5981  }
5982  return TCL_OK;
5983 }
5984 
5985 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5986 // sc_pos_setComment:
5987 // Set the comment for the current move.
5988 int
5989 sc_pos_setComment (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
5990 {
5991  if (argc != 3) {
5992  return errorResult (ti, "Usage: sc_pos setComment <comment-text>");
5993  }
5994  const char * str = argv[2];
5995  const char * oldComment = db->game->GetMoveComment();
5996 
5997  if (str[0] == 0 || (isspace((char)str[0]) && str[1] == 0)) {
5998  // No comment: nullify comment if necessary:
5999  if (oldComment != NULL) {
6000  db->game->SetMoveComment (NULL);
6001  db->gameAltered = true;
6002  }
6003  } else {
6004  // Only set the comment if it has actually changed:
6005  if (oldComment == NULL || !strEqual (str, oldComment)) {
6006  db->game->SetMoveComment (str);
6007  db->gameAltered = true;
6008  }
6009  }
6010  return TCL_OK;
6011 }
6012 
6013 
6014 //////////////////////////////////////////////////////////////////////
6015 // NAME commands
6016 
6017 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6018 // sc_name_correct:
6019 // Corrects specified names in the database.
6020 int
6021 sc_name_correct (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
6022 {
6023  nameT nt = NAME_INVALID;
6024  if (argc == 4) { nt = NameBase::NameTypeFromString (argv[2]); }
6025 
6026  if (! NameBase::IsValidNameType(nt)) {
6027  return errorResult (ti,
6028  "Usage: sc_name correct p|e|s|r <corrections>");
6029  }
6030 
6031  const char * str = argv[3];
6032  char oldName [512];
6033  char newName [512];
6034  char birth[128];
6035  char death[128];
6036  char line [512];
6037  uint errorCount = 0;
6038  uint correctionCount = 0;
6039  uint badDateCount = 0;
6040 
6041  scidBaseT* dbase = db;
6042  const NameBase* nb = dbase->getNameBase();
6043  std::vector<idNumberT> oldIDs;
6044  std::vector<std::string> newNames;
6045  std::vector<std::pair<dateT, dateT> > dates(nb->GetNumNames(nt),
6046  {ZERO_DATE, ZERO_DATE});
6047  while (*str != 0) {
6048  uint length = 0;
6049  while (*str != 0 && *str != '\n') {
6050  if (length < 511) { line[length++] = *str; }
6051  str++;
6052  }
6053  line[length] = 0;
6054  if (*str == '\n') { str++; }
6055  // Now parse oldName and newName out of line, if the
6056  // line starts with a double-quote:
6057  char * s = line;
6058  if (*s != '"') { continue; }
6059  birth[0] = 0;
6060  death[0] = 0;
6061  int dummyCount = 0;
6062  if (sscanf (line, "\"%[^\"]\" >> \"%[^\"]\" (%d) %[0-9.?]--%[0-9.?]",
6063  oldName, newName, &dummyCount, birth, death) < 2) {
6064  continue;
6065  }
6066 
6067  correctionCount++;
6068 
6069  // Find oldName in the NameBase:
6070  idNumberT oldID = 0;
6071  if (nb->FindExactName (nt, oldName, &oldID) != OK) {
6072  errorCount++;
6073  continue;
6074  }
6075 
6076  oldIDs.emplace_back(oldID);
6077  newNames.emplace_back(newName);
6078  dates[oldID] = {date_EncodeFromString(birth),
6079  date_EncodeFromString(death)};
6080  }
6081 
6082  std::vector<idNumberT> newIDs;
6083  auto initNewIDs = [&](const std::vector<idNumberT>& v_ids) {
6084  newIDs.resize(nb->GetNumNames(nt));
6085  std::iota(newIDs.begin(), newIDs.end(), idNumberT(0));
6086  auto it = v_ids.begin();
6087  for (auto& id : oldIDs) {
6088  newIDs[id] = *it++;
6089  }
6090  dates.resize(newIDs.size(), {ZERO_DATE, ZERO_DATE});
6091  };
6092 
6093  auto entry_op = [&](idNumberT oldID, const IndexEntry& ie) {
6094  auto newID = newIDs[oldID];
6095  if (oldID == newID)
6096  return oldID;
6097 
6098  dateT date = ie.GetDate();
6099  if (date != ZERO_DATE) {
6100  auto range = dates[oldID];
6101  if (date < range.first ||
6102  (range.second != ZERO_DATE && date > range.second)) {
6103  ++badDateCount;
6104  return oldID;
6105  }
6106  }
6107  return newID;
6108  };
6109 
6110  Progress progress = UI_CreateProgress(ti);
6111  auto filter = dbase->newFilter();
6112  auto changes = dbase->transformNames(nt, dbase->getFilter(filter), progress,
6113  newNames, initNewIDs, entry_op);
6114  dbase->deleteFilter(filter.c_str());
6115 
6116  UI_List res(4);
6117  res.push_back(correctionCount);
6118  res.push_back(errorCount);
6119  res.push_back(changes.second);
6120  res.push_back(badDateCount);
6121  return UI_Result(ti, changes.first, res);
6122 }
6123 
6124 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6125 // sc_name_edit: edits a name in the NameBase. This requires
6126 // writing the entire index file, since the ID number of
6127 // the edited name will change.
6128 // A rating, date or eventdate can also be edited.
6129 //
6130 // 1st arg: player|event|site|round|rating|date|edate
6131 // 2nd arg: "all" / "filter" / "crosstable" (which games to edit)
6132 // 3rd arg: name to edit.
6133 // 4th arg: new name -- it might already exist in the namebase.
6134 int
6135 sc_name_edit (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
6136 {
6137  const char * usage = "Usage: sc_name edit <type> <oldName> <newName>";
6138  const char * options[] = {
6139  "player", "event", "site", "round", "rating",
6140  "date", "edate", NULL
6141  };
6142  enum {
6143  OPT_PLAYER, OPT_EVENT, OPT_SITE, OPT_ROUND, OPT_RATING,
6144  OPT_DATE, OPT_EVENTDATE
6145  };
6146 
6147  scidBaseT* dbase = db;
6148  int option = -1;
6149  if (argc > 2) { option = strUniqueMatch (argv[2], options); }
6150 
6151  nameT nt = NAME_PLAYER;
6152  switch (option) {
6153  case OPT_PLAYER: nt = NAME_PLAYER; break;
6154  case OPT_EVENT: nt = NAME_EVENT; break;
6155  case OPT_SITE: nt = NAME_SITE; break;
6156  case OPT_ROUND: nt = NAME_ROUND; break;
6157  case OPT_RATING: break;
6158  case OPT_DATE: break;
6159  case OPT_EVENTDATE: break;
6160  default:
6161  return errorResult (ti, usage);
6162  }
6163 
6164  if (option == OPT_RATING) {
6165  if (argc != 7) { return errorResult (ti, usage); }
6166  } else {
6167  if (argc != 6) { return errorResult (ti, usage); }
6168  }
6169 
6170  enum { EDIT_ALL, EDIT_FILTER, EDIT_CTABLE };
6171  int editSelection = EDIT_ALL;
6172  switch (argv[3][0]) {
6173  case 'a': editSelection = EDIT_ALL; break;
6174  case 'f': editSelection = EDIT_FILTER; break;
6175  case 'c': editSelection = EDIT_CTABLE; break;
6176  default:
6177  return errorResult (ti, usage);
6178  }
6179 
6180  const char * oldName = argv[4];
6181  const char * newName = argv[5];
6182  dateT oldDate = ZERO_DATE;
6183  dateT newDate = ZERO_DATE;
6184  eloT newRating = 0;
6185  byte newRatingType = 0;
6186  if (option == OPT_RATING) {
6187  newRating = strGetUnsigned (argv[5]);
6188  newRatingType = strGetRatingType (argv[6]);
6189  }
6190  if (option == OPT_DATE || option == OPT_EVENTDATE) {
6191  oldDate = date_EncodeFromString (argv[4]);
6192  newDate = date_EncodeFromString (argv[5]);
6193  }
6194 
6195  // Find the existing name in the namebase:
6196  idNumberT newID = 0;
6197  idNumberT oldID = 0;
6198  if (option != OPT_DATE && option != OPT_EVENTDATE) {
6199  if (dbase->getNameBase()->FindExactName (nt, oldName, &oldID) != OK)
6200  return UI_Result(ti, OK, 0);
6201  }
6202 
6203  // Set up crosstable game criteria if necessary:
6204  idNumberT eventId = 0, siteId = 0;
6205  dateT eventDate = 0;
6206  if (editSelection == EDIT_CTABLE) {
6207  Game* g = dbase->game;
6208  auto nb = dbase->getNameBase();
6209  if (nb->FindExactName(NAME_EVENT, g->GetEventStr(), &eventId) != OK)
6210  return UI_Result(ti, OK, 0);
6211  if (nb->FindExactName(NAME_SITE, g->GetSiteStr(), &siteId) != OK)
6212  return UI_Result(ti, OK, 0);
6213  eventDate = g->GetEventDate();
6214  }
6215  auto inCTable = [&](const IndexEntry& ie) {
6216  if (editSelection != EDIT_CTABLE)
6217  return true;
6218  return isCrosstableGame(&ie, siteId, eventId, eventDate);
6219  };
6220 
6221  std::string filter =
6222  (editSelection == EDIT_FILTER) ? "dbfilter" : dbase->newFilter();
6223  auto hf = dbase->getFilter(filter);
6224  auto prg = UI_CreateProgress(ti);
6225  std::pair<errorT, size_t> changes;
6226  switch (option) {
6227  case OPT_DATE:
6228  changes = dbase->transformIndex(hf, prg, [&](IndexEntry& ie) {
6229  if (ie.GetDate() == oldDate && inCTable(ie)) {
6230  ie.SetDate(newDate);
6231  return true;
6232  }
6233  return false;
6234  });
6235  break;
6236  case OPT_EVENTDATE:
6237  changes = dbase->transformIndex(hf, prg, [&](IndexEntry& ie) {
6238  if (ie.GetEventDate() == oldDate && inCTable(ie)) {
6239  ie.SetEventDate(newDate);
6240  return true;
6241  }
6242  return false;
6243  });
6244  break;
6245 
6246  case OPT_RATING:
6247  changes = dbase->transformIndex(hf, prg, [&](IndexEntry& ie) {
6248  bool bTB = inCTable(ie);
6249  bool bWH = (ie.GetWhite() == oldID);
6250  bool bBK = (ie.GetBlack() == oldID);
6251  if (bTB && (bWH || bBK)) {
6252  if (bWH) {
6253  ie.SetWhiteElo(newRating);
6254  ie.SetWhiteRatingType(newRatingType);
6255  }
6256  if (bBK) {
6257  ie.SetBlackElo(newRating);
6258  ie.SetBlackRatingType(newRatingType);
6259  }
6260  return true;
6261  }
6262  return false;
6263  });
6264  break;
6265 
6266  default:
6267  changes = dbase->transformNames(
6268  nt, hf, prg, std::vector<std::string>(1, newName),
6269  [&](const std::vector<idNumberT>& v) {
6270  newID = v.front(); // store the newID
6271  },
6272  [&](idNumberT id, const IndexEntry& ie) {
6273  return (id == oldID && inCTable(ie)) ? newID : id;
6274  });
6275  }
6276 
6277  if (editSelection != EDIT_FILTER)
6278  dbase->deleteFilter(filter.c_str());
6279 
6280  return UI_Result(ti, changes.first, changes.second);
6281 }
6282 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6283 // sc_name_retrievename:
6284 // Check for the right name in spellcheck and return it.
6285 UI_res_t sc_name_retrievename (UI_handle_t ti, const SpellChecker& sp, int argc, const char ** argv)
6286 {
6287  const char * usageStr = "Usage: sc_name retrievename <player>";
6288  if (argc != 3 ) { return errorResult (ti, usageStr); }
6289  std::vector<const char*> res = sp.find(NAME_PLAYER, argv[argc-1]);
6290  return UI_Result(ti, OK, (res.size() == 1) ? res[0] : "");
6291 }
6292 
6293 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6294 // sc_name_elo:
6295 // Search for Elo-Ratings in spellcheck file.
6296 static UI_res_t sc_name_elo(UI_handle_t ti, const SpellChecker& sp, int argc,
6297  const char** argv) {
6298  auto usage = "Usage: sc_name elo [year] <player>";
6299  if (argc < 3 || argc > 4)
6300  return UI_Result(ti, ERROR_BadArg, usage);
6301 
6302  const char* playerName = argv[argc - 1];
6303  uint startYear = (argc == 4) ? strGetUnsigned(argv[2]) : 1900;
6304 
6305  UI_List res(YEAR_MAX * 12 * 2);
6306  if (auto vElo = sp.getPlayerElo(playerName)) {
6307  for (uint year = startYear; year < YEAR_MAX; year++) {
6308  for (uint month = 1; month < 13; month++) {
6309  if (eloT elo = vElo->getElo(DATE_MAKE(year, month, 15))) {
6310  char temp[500];
6311  sprintf(temp, "%4u.%02u", year, (month - 1) * 100 / 12);
6312  res.push_back(temp);
6313  sprintf(temp, "%4u", elo);
6314  res.push_back(temp);
6315  }
6316  }
6317  }
6318  }
6319  return UI_Result(ti, OK, res);
6320 }
6321 
6322 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6323 // sc_name_info:
6324 // Prints information given a player name. Reports on the players
6325 // success rate with white and black, common openings by ECO code,
6326 int
6327 sc_name_info (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
6328 {
6329  static char * lastPlayerName = NULL;
6330  const char * usageStr = "Usage: sc_name info [-htext] <player>";
6331 
6332  if (argc != 3 && argc != 4) { return errorResult (ti, usageStr); }
6333 
6334  bool htextOutput = false;
6335  bool setFilter = false; // Set filter to games by this player
6336  bool setOpponent = false; // Set filter to games vs this opponent
6337  bool filter [NUM_COLOR_TYPES][NUM_RESULT_TYPES] =
6338  {
6339  { false, false, false, false },
6340  { false, false, false, false }
6341  };
6342 
6343  if (argc == 4) {
6344  const char * opt = argv[2];
6345  if (strIsPrefix ("-h", opt) && strIsPrefix (opt, "-htext")) {
6346  htextOutput = true;
6347  } else if (opt[0] == '-' && (opt[1] == 'f' || opt[1] == 'o')) {
6348  if (opt[1] == 'f') {
6349  setFilter = true;
6350  } else {
6351  setOpponent = true;
6352  }
6353  // Parse filter options: a = all, w = wins, d = draws, l = losses
6354  // for White, and capitalise those for Black.
6355  const char * fopt = opt + 2;
6356  while (*fopt != 0) {
6357  switch (*fopt) {
6358  case 'a': // All White games:
6359  filter [WHITE][RESULT_White] = true;
6360  filter [WHITE][RESULT_Draw] = true;
6361  filter [WHITE][RESULT_Black] = true;
6362  filter [WHITE][RESULT_None] = true;
6363  break;
6364  case 'A': // All Black games:
6365  filter [BLACK][RESULT_White] = true;
6366  filter [BLACK][RESULT_Draw] = true;
6367  filter [BLACK][RESULT_Black] = true;
6368  filter [BLACK][RESULT_None] = true;
6369  break;
6370  case 'w': // White wins:
6371  filter [WHITE][RESULT_White] = true;
6372  break;
6373  case 'W': // Black wins:
6374  filter [BLACK][RESULT_White] = true;
6375  break;
6376  case 'd': // White draws:
6377  filter [WHITE][RESULT_Draw] = true;
6378  break;
6379  case 'D': // Black draws:
6380  filter [BLACK][RESULT_Draw] = true;
6381  break;
6382  case 'l': // White losses:
6383  filter [WHITE][RESULT_Black] = true;
6384  break;
6385  case 'L': // Black losses:
6386  filter [BLACK][RESULT_Black] = true;
6387  break;
6388  default:
6389  return errorResult (ti, usageStr);
6390  }
6391  fopt++;
6392  }
6393  } else {
6394  return errorResult (ti, usageStr);
6395  }
6396  }
6397 
6398  // Set up player name:
6399  const char * playerName = argv[argc-1];
6400  if (strEqual (playerName, "")) {
6401  if (lastPlayerName != NULL) { playerName = lastPlayerName; }
6402  } else {
6403  if (lastPlayerName != NULL) { delete[] lastPlayerName; }
6404  lastPlayerName = strDuplicate (playerName);
6405  }
6406 
6407  // Try to find player name in this database:
6408  idNumberT id = 0;
6409  if (db->getNameBase()->FindExactName (NAME_PLAYER, playerName, &id) != OK) {
6410  Tcl_AppendResult (ti, "The name \"", playerName,
6411  "\" does not exist in this database.", NULL);
6412  return TCL_OK;
6413  }
6414 
6415  // Try to find opponent in this database:
6416  idNumberT opponentId = 0;
6417  const char * opponent = NULL;
6418  if (strEqual (playerName, db->game->GetWhiteStr())) {
6419  opponent = db->game->GetBlackStr();
6420  } else if (strEqual (playerName, db->game->GetBlackStr())) {
6421  opponent = db->game->GetWhiteStr();
6422  }
6423 
6424  if (opponent != NULL) {
6425  if (db->getNameBase()->FindExactName (NAME_PLAYER, opponent, &opponentId) != OK) {
6426  opponent = NULL;
6427  }
6428  }
6429 
6430  enum {STATS_ALL = 0, STATS_FILTER, STATS_OPP};
6431 
6432  uint whitescore [3][NUM_RESULT_TYPES];
6433  uint blackscore [3][NUM_RESULT_TYPES];
6434  uint bothscore [3][NUM_RESULT_TYPES];
6435  uint whitecount[3] = {0};
6436  uint blackcount[3] = {0};
6437  uint totalcount[3] = {0};
6438  for (uint stat=STATS_ALL; stat <= STATS_OPP; stat++) {
6439  for (resultT r = 0; r < NUM_RESULT_TYPES; r++) {
6440  whitescore[stat][r] = 0;
6441  blackscore[stat][r] = 0;
6442  bothscore[stat][r] = 0;
6443  }
6444  }
6445 
6446  uint ecoCount [NUM_COLOR_TYPES] [50];
6447  uint ecoScore [NUM_COLOR_TYPES] [50];
6448  for (uint ecoGroup=0; ecoGroup < 50; ecoGroup++) {
6449  ecoCount[WHITE][ecoGroup] = ecoCount[BLACK][ecoGroup] = 0;
6450  ecoScore[WHITE][ecoGroup] = ecoScore[BLACK][ecoGroup] = 0;
6451  }
6452 
6453  dateT firstGameDate = ZERO_DATE;
6454  dateT lastGameDate = ZERO_DATE;
6455 
6456  if (setFilter || setOpponent) db->dbFilter->Fill(0);
6457 
6458  for (uint i=0, n = db->numGames(); i < n; i++) {
6459  const IndexEntry* ie = db->getIndexEntry(i);
6460  ecoT ecoCode = ie->GetEcoCode();
6461  int ecoClass = -1;
6462  if (ecoCode != ECO_None) {
6463  ecoStringT ecoStr;
6464  eco_ToBasicString (ecoCode, ecoStr);
6465  if (ecoStr[0] != 0) {
6466  ecoClass = ((ecoStr[0] - 'A') * 10) + (ecoStr[1] - '0');
6467  if (ecoClass < 0 || ecoClass >= 50) { ecoClass = -1; }
6468  }
6469  }
6470 
6471  resultT result = ie->GetResult();
6472  idNumberT whiteId = ie->GetWhite();
6473  idNumberT blackId = ie->GetBlack();
6474  dateT date = ZERO_DATE;
6475 
6476  // Track statistics as white and black:
6477  if (whiteId == id) {
6478  date = ie->GetDate();
6479  if (ecoClass >= 0) {
6480  ecoCount[WHITE][ecoClass]++;
6481  ecoScore[WHITE][ecoClass] += RESULT_SCORE[result];
6482  }
6483  whitescore[STATS_ALL][result]++;
6484  bothscore[STATS_ALL][result]++;
6485  whitecount[STATS_ALL]++;
6486  totalcount[STATS_ALL]++;
6487  if (db->dbFilter->Get(i) > 0) {
6488  whitescore[STATS_FILTER][result]++;
6489  bothscore[STATS_FILTER][result]++;
6490  whitecount[STATS_FILTER]++;
6491  totalcount[STATS_FILTER]++;
6492  }
6493  if (opponent != NULL && blackId == opponentId) {
6494  whitescore[STATS_OPP][result]++;
6495  bothscore[STATS_OPP][result]++;
6496  whitecount[STATS_OPP]++;
6497  totalcount[STATS_OPP]++;
6498  if (setOpponent && filter[WHITE][result]) {
6499  db->dbFilter->Set (i, 1);
6500  }
6501  }
6502  if (setFilter && filter[WHITE][result]) {
6503  db->dbFilter->Set (i, 1);
6504  }
6505  } else if (blackId == id) {
6506  date = ie->GetDate();
6507  result = RESULT_OPPOSITE[result];
6508  if (ecoClass >= 0) {
6509  ecoCount[BLACK][ecoClass]++;
6510  ecoScore[BLACK][ecoClass] += RESULT_SCORE[result];
6511  }
6512  blackscore[STATS_ALL][result]++;
6513  bothscore[STATS_ALL][result]++;
6514  blackcount[STATS_ALL]++;
6515  totalcount[STATS_ALL]++;
6516  if (db->dbFilter->Get(i) > 0) {
6517  blackscore[STATS_FILTER][result]++;
6518  bothscore[STATS_FILTER][result]++;
6519  blackcount[STATS_FILTER]++;
6520  totalcount[STATS_FILTER]++;
6521  }
6522  if (opponent != NULL && whiteId == opponentId) {
6523  blackscore[STATS_OPP][result]++;
6524  bothscore[STATS_OPP][result]++;
6525  blackcount[STATS_OPP]++;
6526  totalcount[STATS_OPP]++;
6527  if (setOpponent && filter[BLACK][result]) {
6528  db->dbFilter->Set (i, 1);
6529  }
6530  }
6531  if (setFilter && filter[BLACK][result]) {
6532  db->dbFilter->Set (i, 1);
6533  }
6534  }
6535 
6536  // Keep track of first and last games by this player:
6537  if (date != ZERO_DATE) {
6538  if (firstGameDate == ZERO_DATE || date < firstGameDate) {
6539  firstGameDate = date;
6540  }
6541  if (lastGameDate == ZERO_DATE || date > lastGameDate) {
6542  lastGameDate = date;
6543  }
6544  }
6545  }
6546 
6547  char temp [500];
6548  uint score, percent;
6549  colorT color;
6550  const char * newline = (htextOutput ? "<br>" : "\n");
6551  const char * startHeading = (htextOutput ? "<darkblue>" : "");
6552  const char * endHeading = (htextOutput ? "</darkblue>" : "");
6553  const char * startBold = (htextOutput ? "<b>" : "");
6554  const char * endBold = (htextOutput ? "</b>" : "");
6555  uint wWidth = strLength (translate (ti, "White:"));
6556  uint bWidth = strLength (translate (ti, "Black:"));
6557  uint tWidth = strLength (translate (ti, "Total:"));
6558  uint wbtWidth = wWidth;
6559  if (bWidth > wbtWidth) { wbtWidth = bWidth; }
6560  if (tWidth > wbtWidth) { wbtWidth = tWidth; }
6561  const char * fmt = \
6562  "%s %-*s %3u%c%02u%% +%s%3u%s =%s%3u%s -%s%3u%s %4u%c%c /%s%4u%s";
6563  SpellChecker* spChecker = spellChk;
6564 
6565  Tcl_AppendResult (ti, startBold, playerName, endBold, newline, NULL);
6566 
6567  // Show title, country, etc if listed in player spellcheck file:
6568  if (spChecker != NULL) {
6569  const PlayerInfo* pInfo = spChecker->getPlayerInfo(playerName);
6570  if (pInfo) { Tcl_AppendResult (ti, " ", pInfo->GetComment(), newline, NULL); }
6571  }
6572  sprintf (temp, " %s%u%s %s (%s: %u)",
6573  htextOutput ? "<red><run sc_name info -faA {}; ::windows::stats::Refresh>" : "",
6574  totalcount[STATS_ALL],
6575  htextOutput ? "</run></red>" : "",
6576  (totalcount[STATS_ALL] == 1 ?
6577  translate (ti, "game") : translate (ti, "games")),
6578  translate (ti, "Filter"),
6579  totalcount[STATS_FILTER]);
6580  Tcl_AppendResult (ti, temp, NULL);
6581  if (firstGameDate != ZERO_DATE) {
6582  date_DecodeToString (firstGameDate, temp);
6583  strTrimDate (temp);
6584  Tcl_AppendResult (ti, ", ", temp, NULL);
6585  }
6586  if (lastGameDate > firstGameDate) {
6587  date_DecodeToString (lastGameDate, temp);
6588  strTrimDate (temp);
6589  Tcl_AppendResult (ti, "--", temp, NULL);
6590  }
6591  Tcl_AppendResult (ti, newline, NULL);
6592 
6593  // Print biography if applicable:
6594  if (spChecker != NULL) {
6595  std::vector<const char*> bio;
6596  const PlayerInfo* pInfo = spChecker->getPlayerInfo(playerName, &bio);
6597  if (pInfo != 0) {
6598  for (size_t i=0, n=bio.size(); i < n; i++) {
6599  if (i == 0) {
6600  Tcl_AppendResult (ti, newline, startHeading,
6601  translate (ti, "Biography"), ":",
6602  endHeading, newline, NULL);
6603  }
6604  Tcl_AppendResult (ti, " ", bio[i], newline, NULL);
6605  }
6606  }
6607  }
6608  // Print stats for all games:
6609 
6610  strCopy (temp, translate (ti, "PInfoAll"));
6611  if (! htextOutput) { strTrimMarkup (temp); }
6612  Tcl_AppendResult (ti, newline, startHeading, temp, ":",
6613  endHeading, newline, NULL);
6614 
6615  score = percent = 0;
6616  if (whitecount[STATS_ALL] > 0) {
6617  score = whitescore[STATS_ALL][RESULT_White] * 2
6618  + whitescore[STATS_ALL][RESULT_Draw]
6619  + whitescore[STATS_ALL][RESULT_None];
6620  percent = score * 5000 / whitecount[STATS_ALL];
6621  }
6622  sprintf (temp, fmt,
6623  htextOutput ? "<tt>" : "",
6624  wbtWidth,
6625  translate (ti, "White:"),
6626  percent / 100, decimalPointChar, percent % 100,
6627  htextOutput ? "<red><run sc_name info -fw {}; ::windows::stats::Refresh>" : "",
6628  whitescore[STATS_ALL][RESULT_White],
6629  htextOutput ? "</run></red>" : "",
6630  htextOutput ? "<red><run sc_name info -fd {}; ::windows::stats::Refresh>" : "",
6631  whitescore[STATS_ALL][RESULT_Draw],
6632  htextOutput ? "</run></red>" : "",
6633  htextOutput ? "<red><run sc_name info -fl {}; ::windows::stats::Refresh>" : "",
6634  whitescore[STATS_ALL][RESULT_Black],
6635  htextOutput ? "</run></red>" : "",
6636  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6637  htextOutput ? "<red><run sc_name info -fa {}; ::windows::stats::Refresh>" : "",
6638  whitecount[STATS_ALL],
6639  htextOutput ? "</run></red></tt>" : "");
6640  Tcl_AppendResult (ti, temp, newline, NULL);
6641 
6642  score = percent = 0;
6643  if (blackcount[STATS_ALL] > 0) {
6644  score = blackscore[STATS_ALL][RESULT_White] * 2
6645  + blackscore[STATS_ALL][RESULT_Draw]
6646  + blackscore[STATS_ALL][RESULT_None];
6647  percent = score * 5000 / blackcount[STATS_ALL];
6648  }
6649  sprintf (temp, fmt,
6650  htextOutput ? "<tt>" : "",
6651  wbtWidth,
6652  translate (ti, "Black:"),
6653  percent / 100, decimalPointChar, percent % 100,
6654  htextOutput ? "<red><run sc_name info -fW {}; ::windows::stats::Refresh>" : "",
6655  blackscore[STATS_ALL][RESULT_White],
6656  htextOutput ? "</run></red>" : "",
6657  htextOutput ? "<red><run sc_name info -fD {}; ::windows::stats::Refresh>" : "",
6658  blackscore[STATS_ALL][RESULT_Draw],
6659  htextOutput ? "</run></red>" : "",
6660  htextOutput ? "<red><run sc_name info -fL {}; ::windows::stats::Refresh>" : "",
6661  blackscore[STATS_ALL][RESULT_Black],
6662  htextOutput ? "</run></red>" : "",
6663  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6664  htextOutput ? "<red><run sc_name info -fA {}; ::windows::stats::Refresh>" : "",
6665  blackcount[STATS_ALL],
6666  htextOutput ? "</run></red></tt>" : "");
6667  Tcl_AppendResult (ti, temp, newline, NULL);
6668 
6669  score = percent = 0;
6670  if (totalcount[STATS_ALL] > 0) {
6671  score = bothscore[STATS_ALL][RESULT_White] * 2
6672  + bothscore[STATS_ALL][RESULT_Draw]
6673  + bothscore[STATS_ALL][RESULT_None];
6674  percent = score * 5000 / totalcount[STATS_ALL];
6675  }
6676  sprintf (temp, fmt,
6677  htextOutput ? "<tt>" : "",
6678  wbtWidth,
6679  translate (ti, "Total:"),
6680  percent / 100, decimalPointChar, percent % 100,
6681  htextOutput ? "<red><run sc_name info -fwW {}; ::windows::stats::Refresh>" : "",
6682  bothscore[STATS_ALL][RESULT_White],
6683  htextOutput ? "</run></red>" : "",
6684  htextOutput ? "<red><run sc_name info -fdD {}; ::windows::stats::Refresh>" : "",
6685  bothscore[STATS_ALL][RESULT_Draw],
6686  htextOutput ? "</run></red>" : "",
6687  htextOutput ? "<red><run sc_name info -flL {}; ::windows::stats::Refresh>" : "",
6688  bothscore[STATS_ALL][RESULT_Black],
6689  htextOutput ? "</run></red>" : "",
6690  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6691  htextOutput ? "<red><run sc_name info -faA {}; ::windows::stats::Refresh>" : "",
6692  totalcount[STATS_ALL],
6693  htextOutput ? "</run></red></tt>" : "");
6694  Tcl_AppendResult (ti, temp, newline, NULL);
6695 
6696  // Now print stats for games in the filter:
6697 
6698  strCopy (temp, translate (ti, "PInfoFilter"));
6699  if (! htextOutput) { strTrimMarkup (temp); }
6700  Tcl_AppendResult (ti, newline, startHeading, temp, ":",
6701  endHeading, newline, NULL);
6702  score = percent = 0;
6703  if (whitecount[STATS_FILTER] > 0) {
6704  score = whitescore[STATS_FILTER][RESULT_White] * 2
6705  + whitescore[STATS_FILTER][RESULT_Draw]
6706  + whitescore[STATS_FILTER][RESULT_None];
6707  percent = score * 5000 / whitecount[STATS_FILTER];
6708  }
6709  sprintf (temp, fmt,
6710  htextOutput ? "<tt>" : "",
6711  wbtWidth,
6712  translate (ti, "White:"),
6713  percent / 100, decimalPointChar, percent % 100,
6714  "", whitescore[STATS_FILTER][RESULT_White], "",
6715  "", whitescore[STATS_FILTER][RESULT_Draw], "",
6716  "", whitescore[STATS_FILTER][RESULT_Black], "",
6717  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6718  "", whitecount[STATS_FILTER],
6719  htextOutput ? "</tt>" : "");
6720  Tcl_AppendResult (ti, temp, newline, NULL);
6721 
6722  score = percent = 0;
6723  if (blackcount[STATS_FILTER] > 0) {
6724  score = blackscore[STATS_FILTER][RESULT_White] * 2
6725  + blackscore[STATS_FILTER][RESULT_Draw]
6726  + blackscore[STATS_FILTER][RESULT_None];
6727  percent = score * 5000 / blackcount[STATS_FILTER];
6728  }
6729  sprintf (temp, fmt,
6730  htextOutput ? "<tt>" : "",
6731  wbtWidth,
6732  translate (ti, "Black:"),
6733  percent / 100, decimalPointChar, percent % 100,
6734  "", blackscore[STATS_FILTER][RESULT_White], "",
6735  "", blackscore[STATS_FILTER][RESULT_Draw], "",
6736  "", blackscore[STATS_FILTER][RESULT_Black], "",
6737  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6738  "", blackcount[STATS_FILTER],
6739  htextOutput ? "</tt>" : "");
6740  Tcl_AppendResult (ti, temp, newline, NULL);
6741 
6742  score = percent = 0;
6743  if (totalcount[STATS_FILTER] > 0) {
6744  score = bothscore[STATS_FILTER][RESULT_White] * 2
6745  + bothscore[STATS_FILTER][RESULT_Draw]
6746  + bothscore[STATS_FILTER][RESULT_None];
6747  percent = score * 5000 / totalcount[STATS_FILTER];
6748  }
6749  sprintf (temp, fmt,
6750  htextOutput ? "<tt>" : "",
6751  wbtWidth,
6752  translate (ti, "Total:"),
6753  percent / 100, decimalPointChar, percent % 100,
6754  "", bothscore[STATS_FILTER][RESULT_White], "",
6755  "", bothscore[STATS_FILTER][RESULT_Draw], "",
6756  "", bothscore[STATS_FILTER][RESULT_Black], "",
6757  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6758  "", totalcount[STATS_FILTER],
6759  htextOutput ? "</tt>" : "");
6760  Tcl_AppendResult (ti, temp, newline, NULL);
6761 
6762  // Now print stats for games against the current opponent:
6763 
6764  if (opponent != NULL) {
6765  Tcl_AppendResult (ti, newline, startHeading,
6766  translate (ti, "PInfoAgainst"), " ",
6767  startBold, opponent, endBold, ":",
6768  endHeading, newline, NULL);
6769 
6770  score = percent = 0;
6771  if (whitecount[STATS_OPP] > 0) {
6772  score = whitescore[STATS_OPP][RESULT_White] * 2
6773  + whitescore[STATS_OPP][RESULT_Draw]
6774  + whitescore[STATS_OPP][RESULT_None];
6775  percent = score * 5000 / whitecount[STATS_OPP];
6776  }
6777  sprintf (temp, fmt,
6778  htextOutput ? "<tt>" : "",
6779  wbtWidth,
6780  translate (ti, "White:"),
6781  percent / 100, decimalPointChar, percent % 100,
6782  htextOutput ? "<red><run sc_name info -ow {}; ::windows::stats::Refresh>" : "",
6783  whitescore[STATS_OPP][RESULT_White],
6784  htextOutput ? "</run></red>" : "",
6785  htextOutput ? "<red><run sc_name info -od {}; ::windows::stats::Refresh>" : "",
6786  whitescore[STATS_OPP][RESULT_Draw],
6787  htextOutput ? "</run></red>" : "",
6788  htextOutput ? "<red><run sc_name info -ol {}; ::windows::stats::Refresh>" : "",
6789  whitescore[STATS_OPP][RESULT_Black],
6790  htextOutput ? "</run></red>" : "",
6791  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6792  htextOutput ? "<red><run sc_name info -oa {}; ::windows::stats::Refresh>" : "",
6793  whitecount[STATS_OPP],
6794  htextOutput ? "</run></red></tt>" : "");
6795  Tcl_AppendResult (ti, temp, newline, NULL);
6796 
6797  score = percent = 0;
6798  if (blackcount[STATS_OPP] > 0) {
6799  score = blackscore[STATS_OPP][RESULT_White] * 2
6800  + blackscore[STATS_OPP][RESULT_Draw]
6801  + blackscore[STATS_OPP][RESULT_None];
6802  percent = score * 5000 / blackcount[STATS_OPP];
6803  }
6804  sprintf (temp, fmt,
6805  htextOutput ? "<tt>" : "",
6806  wbtWidth,
6807  translate (ti, "Black:"),
6808  percent / 100, decimalPointChar, percent % 100,
6809  htextOutput ? "<red><run sc_name info -oW {}; ::windows::stats::Refresh>" : "",
6810  blackscore[STATS_OPP][RESULT_White],
6811  htextOutput ? "</run></red>" : "",
6812  htextOutput ? "<red><run sc_name info -oD {}; ::windows::stats::Refresh>" : "",
6813  blackscore[STATS_OPP][RESULT_Draw],
6814  htextOutput ? "</run></red>" : "",
6815  htextOutput ? "<red><run sc_name info -oL {}; ::windows::stats::Refresh>" : "",
6816  blackscore[STATS_OPP][RESULT_Black],
6817  htextOutput ? "</run></red>" : "",
6818  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6819  htextOutput ? "<red><run sc_name info -oA {}; ::windows::stats::Refresh>" : "",
6820  blackcount[STATS_OPP],
6821  htextOutput ? "</run></red></tt>" : "");
6822  Tcl_AppendResult (ti, temp, newline, NULL);
6823 
6824  score = percent = 0;
6825  if (totalcount[STATS_OPP] > 0) {
6826  score = bothscore[STATS_OPP][RESULT_White] * 2
6827  + bothscore[STATS_OPP][RESULT_Draw]
6828  + bothscore[STATS_OPP][RESULT_None];
6829  percent = score * 5000 / totalcount[STATS_OPP];
6830  }
6831  sprintf (temp, fmt,
6832  htextOutput ? "<tt>" : "",
6833  wbtWidth,
6834  translate (ti, "Total:"),
6835  percent / 100, decimalPointChar, percent % 100,
6836  htextOutput ? "<red><run sc_name info -owW {}; ::windows::stats::Refresh>" : "",
6837  bothscore[STATS_OPP][RESULT_White],
6838  htextOutput ? "</run></red>" : "",
6839  htextOutput ? "<red><run sc_name info -odD {}; ::windows::stats::Refresh>" : "",
6840  bothscore[STATS_OPP][RESULT_Draw],
6841  htextOutput ? "</run></red>" : "",
6842  htextOutput ? "<red><run sc_name info -olL {}; ::windows::stats::Refresh>" : "",
6843  bothscore[STATS_OPP][RESULT_Black],
6844  htextOutput ? "</run></red>" : "",
6845  score / 2, decimalPointChar, score % 2 ? '5' : '0',
6846  htextOutput ? "<red><run sc_name info -oaA {}; ::windows::stats::Refresh>" : "",
6847  totalcount[STATS_OPP],
6848  htextOutput ? "</run></red></tt>" : "");
6849  Tcl_AppendResult (ti, temp, newline, NULL);
6850  }
6851 
6852  // Now print common openings played:
6853 
6854  for (color = WHITE; color <= BLACK; color++) {
6855  for (uint count = 0; count < 6; count++) {
6856  int mostPlayedIdx = -1;
6857  uint mostPlayed = 0;
6858  for (uint i=0; i < 50; i++) {
6859  if (ecoCount[color][i] > mostPlayed) {
6860  mostPlayedIdx = i;
6861  mostPlayed = ecoCount[color][i];
6862  }
6863  }
6864  if (mostPlayed > 0) {
6865  if (count == 0) {
6866  const char * s = (color == WHITE ? "PInfoMostWhite" :
6867  "PInfoMostBlack");
6868  Tcl_AppendResult (ti, newline, startHeading,
6869  translate (ti, s), ":",
6870  endHeading, newline, NULL);
6871  } else if (count == 3) {
6872  Tcl_AppendResult (ti, newline, NULL);
6873  }
6874  Tcl_AppendResult (ti, " ", NULL);
6875 
6876  temp[0] = mostPlayedIdx / 10 + 'A';
6877  temp[1] = mostPlayedIdx % 10 + '0';
6878  temp[2] = 0;
6879  if (htextOutput) {
6880  Tcl_AppendResult (ti, "<blue><run ::windows::eco::Refresh ",
6881  temp, ">", NULL);
6882  }
6883  Tcl_AppendResult (ti, temp, NULL);
6884  if (htextOutput) {
6885  Tcl_AppendResult (ti, "</run></blue>", NULL);
6886  }
6887  sprintf (temp, ":%3u (%u%%)", mostPlayed,
6888  ecoScore[color][mostPlayedIdx] * 50 / mostPlayed);
6889  Tcl_AppendResult (ti, temp, NULL);
6890  ecoCount[color][mostPlayedIdx] = 0;
6891  }
6892  }
6893  }
6894 
6895  return TCL_OK;
6896 }
6897 
6898 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6899 // sc_name_match: returns the first N matching names,
6900 // or fewer if there are not N matches, given a substring
6901 // to search for.
6902 // Output is a Tcl list, to be read in pairs: the first element of
6903 // each pair is the frequency, the second contains the name.
6904 //
6905 // 1st arg: "p" (player) / "e" (event) / "s" (site) / "r" (round)
6906 // 2nd arg: prefix string to search for.
6907 // 3rd arg: maximum number of matches to return.
6908 // Example: sc_nameMatch player "Speel" 10
6909 int
6910 sc_name_match (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
6911 {
6912  const char * usage = \
6913  "Usage: sc_name match [-elo] <nameType> <prefix> <maxMatches>";
6914 
6915  int arg = 2;
6916  int argsleft = argc - 2;
6917  bool eloMode = false; // In elo mode, return player peak ratings.
6918  if (argsleft < 3) { return errorResult (ti, usage); }
6919  if (argv[arg][0] == '-' && strIsPrefix (argv[arg], "-elo")) {
6920  eloMode = true;
6921  arg++;
6922  argsleft--;
6923  }
6924  if (argsleft != 3) {
6925  return errorResult (ti, usage);
6926  }
6927 
6928  nameT nt = NameBase::NameTypeFromString (argv[arg++]);
6929  if (nt == NAME_INVALID) {
6930  return errorResult (ti, usage);
6931  }
6932 
6933  const char * prefix = argv[arg++];
6934  uint maxMatches = strGetUnsigned (argv[arg++]);
6935  if (maxMatches == 0) { return TCL_OK; }
6936  auto matches = db->getNameBase()->getFirstMatches(nt, prefix, maxMatches);
6937  for (auto nameID : matches) {
6938  uint freq = db->getNameFreq(nt, nameID);
6939  const char * str = db->getNameBase()->GetName (nt, nameID);
6940  appendUintElement (ti, freq);
6941  Tcl_AppendElement (ti, str);
6942  if (nt == NAME_PLAYER && eloMode) {
6943  appendUintElement (ti, db->getNameBase()->GetElo (nameID));
6944  }
6945  }
6946  return TCL_OK;
6947 }
6948 
6949 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6950 // sc_name_plist:
6951 // Returns a list of play data matching selected criteria.
6955 
6956  PlayerActivity() : firstDate(ZERO_DATE), lastDate(ZERO_DATE) {}
6958  if (firstDate == ZERO_DATE || date < firstDate) firstDate = date;
6959  if (date > lastDate) lastDate = date;
6960  }
6961 };
6962 
6964  scidBaseT* dbase_;
6965  int sort_;
6966  const std::vector<PlayerActivity>& activity_;
6967  enum { SORT_ELO, SORT_GAMES, SORT_OLDEST, SORT_NEWEST, SORT_NAME };
6968 
6969 public:
6970  PListSort(scidBaseT* dbase, const std::vector<PlayerActivity>& activity, int sortOrder)
6971  : dbase_(dbase), sort_(sortOrder), activity_(activity) {
6972  }
6973  bool operator() (idNumberT p1, idNumberT p2)
6974  {
6975  const NameBase* nb = dbase_->getNameBase();
6976  int compare = 0;
6977  switch (sort_) {
6978  case SORT_ELO:
6979  compare = nb->GetElo(p2) - nb->GetElo(p1);
6980  break;
6981  case SORT_GAMES:
6982  compare = dbase_->getNameFreq(NAME_PLAYER, p2) - dbase_->getNameFreq(NAME_PLAYER, p1);
6983  break;
6984  case SORT_OLDEST:
6985  // Sort by oldest game year in ascending order:
6986  compare = date_GetYear(activity_[p1].firstDate) - date_GetYear(activity_[p2].firstDate);
6987  break;
6988  case SORT_NEWEST:
6989  // Sort by newest game date in descending order:
6990  compare = date_GetYear(activity_[p2].lastDate) - date_GetYear(activity_[p1].lastDate);
6991  break;
6992  }
6993 
6994  // If equal, resolve by comparing names, first case-insensitive and
6995  // then case-sensitively if still tied:
6996  if (compare == 0) {
6997  const char* name1 = nb->GetName (NAME_PLAYER, p1);
6998  const char* name2 = nb->GetName (NAME_PLAYER, p2);
6999  compare = strCaseCompare (name1, name2);
7000  if (compare == 0) { compare = strCompare (name1, name2); }
7001  }
7002  return compare < 0;
7003  }
7004 };
7005 
7006 int
7007 sc_name_plist (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
7008 {
7009  const char * usage = "Usage: sc_name plist [-<option> <value> ...]";
7010 
7011  scidBaseT* dbase = db;
7012  const char * namePrefix = "";
7013  uint minGames = 0;
7014  uint maxGames = dbase->numGames();
7015  uint minElo = 0;
7016  uint maxElo = MAX_ELO;
7017  size_t count = 10;
7018 
7019  static const char * options [] = {
7020  "-name", "-minElo", "-maxElo", "-minGames", "-maxGames",
7021  "-size", "-sort", NULL
7022  };
7023  enum {
7024  OPT_NAME, OPT_MINELO, OPT_MAXELO, OPT_MINGAMES, OPT_MAXGAMES,
7025  OPT_SIZE, OPT_SORT
7026  };
7027 
7028  // Valid sort types:
7029  static const char * sortModes [] = {
7030  "elo", "games", "oldest", "newest", "name", NULL
7031  };
7032  enum {
7033  SORT_ELO, SORT_GAMES, SORT_OLDEST, SORT_NEWEST, SORT_NAME
7034  };
7035 
7036  int sortMode = SORT_NAME;
7037 
7038  // Read parameters in pairs:
7039  int arg = 2;
7040  while (arg+1 < argc) {
7041  const char * option = argv[arg];
7042  const char * value = argv[arg+1];
7043  arg += 2;
7044  int index = strUniqueMatch (option, options);
7045  switch (index) {
7046  case OPT_NAME: namePrefix = value; break;
7047  case OPT_MINELO: minElo = strGetUnsigned (value); break;
7048  case OPT_MAXELO: maxElo = strGetUnsigned (value); break;
7049  case OPT_MINGAMES: minGames = strGetUnsigned (value); break;
7050  case OPT_MAXGAMES: maxGames = strGetUnsigned (value); break;
7051  case OPT_SIZE: count = strGetUnsigned (value); break;
7052  case OPT_SORT:
7053  sortMode = strUniqueMatch (value, sortModes);
7054  break;
7055  default:
7056  return InvalidCommand (ti, "sc_name plist", options);
7057  }
7058  }
7059 
7060  if (arg != argc) { return errorResult (ti, usage); }
7061  if (sortMode == -1) return InvalidCommand (ti, "sc_name plist -sort", sortModes);
7062 
7063 
7064  const NameBase* nb = dbase->getNameBase();
7065  idNumberT nPlayers = nb->GetNumNames(NAME_PLAYER);
7066 
7067  std::vector<idNumberT> plist;
7068  for (idNumberT id = 0; id < nPlayers; id++) {
7069  const char * name = nb->GetName (NAME_PLAYER, id);
7070  uint nGames = db->getNameFreq (NAME_PLAYER, id);
7071  eloT elo = nb->GetElo (id);
7072  if (nGames < minGames || nGames > maxGames) { continue; }
7073  if (elo < minElo || elo > maxElo) { continue; }
7074  if (! strIsCasePrefix (namePrefix, name)) { continue; }
7075  plist.push_back(id);
7076  }
7077 
7078  std::vector<PlayerActivity> activity(nPlayers);
7079  for (gamenumT gnum=0, n = dbase->numGames(); gnum < n; gnum++) {
7080  const IndexEntry* ie = dbase->getIndexEntry(gnum);
7081  dateT date = ie->GetDate();
7082  if (date_GetYear(date) > 0) {
7083  activity[ie->GetWhite()].addDate(date);
7084  activity[ie->GetBlack()].addDate(date);
7085  }
7086  }
7087 
7088  count = std::min(count, plist.size());
7089  std::partial_sort(plist.begin(), plist.begin() + count, plist.end(), PListSort(dbase, activity, sortMode));
7090 
7091  UI_List res(count);
7092  UI_List info(5);
7093  for (size_t i=0; i < count; i++) {
7094  idNumberT id = plist[i];
7095  info.clear();
7096  info.push_back(dbase->getNameFreq(NAME_PLAYER, id));
7097  info.push_back(date_GetYear(activity[id].firstDate));
7098  info.push_back(date_GetYear(activity[id].lastDate));
7099  info.push_back(nb->GetElo(id));
7100  info.push_back(nb->GetName(NAME_PLAYER, id));
7101  res.push_back(info);
7102  }
7103 
7104  return UI_Result(ti, OK, res);
7105 }
7106 
7107 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7108 // sc_name_ratings:
7109 // Scan the current database for games with unrated players who
7110 // have Elo rating information in the spellcheck file, and fill
7111 // in the missing Elo ratings.
7112 //
7113 // Boolean options:
7114 // -nomonth (default=true): indicates whether games with no month
7115 // should still have ratings allocated, assuming the month
7116 // to be January.
7117 // -update (default=true): indicates whether the database should
7118 // be updated; if it is false, no actual changes are made.
7119 // -debug (default=false): for debugging; it dumps all detected
7120 // rating changes, one per line, to stdout.
7121 // -test (default=false): tests whether a sspelling file with
7122 // rating info is loaded and can be used on this database.
7123 // -overwrite (default=false): if true, existing ratings can be
7124 // changed.
7125 //
7126 // Returns a two-integer list: the number of changed ratings, and
7127 // the number of changed games.
7128 UI_res_t sc_name_ratings (UI_handle_t ti, scidBaseT& dbase, const SpellChecker& sp, int argc, const char ** argv)
7129 {
7130  const char * options[] = {
7131  "-update", "-test", "-change", "-filter" };
7132  enum {
7133  OPT_UPDATE, OPT_TEST, OPT_CHANGE, OPT_FILTER
7134  };
7135 
7136  bool updateIndexFile = true;
7137  bool testOnly = false;
7138  bool overwrite = false;
7139  bool filterOnly = false;
7140 
7141  int arg = 2;
7142  while (arg+1 < argc) {
7143  int option = strUniqueMatch (argv[arg], options);
7144  bool value = strGetBoolean (argv[arg+1]);
7145  switch (option) {
7146  case OPT_UPDATE: updateIndexFile = value; break;
7147  case OPT_TEST: testOnly = value; break;
7148  case OPT_CHANGE: overwrite = value; break;
7149  case OPT_FILTER: filterOnly = value; break;
7150  default: return InvalidCommand (ti, "sc_name ratings", options);
7151  }
7152  arg += 2;
7153  }
7154 
7155  if (! sp.hasEloData()) {
7156  return UI_Result(ti, ERROR, "The current spellcheck file does not have "
7157  "Elo rating information.\n\n"
7158  "To use this function, you should load "
7159  "\"ratings.ssp\" (available from the Scid website) "
7160  "as your spellcheck file first.");
7161  }
7162 
7163  if (testOnly) { return UI_Result(ti, OK); }
7164 
7165  uint numChangedRatings = 0;
7166  uint numChangedGames = 0;
7167  const NameBase* nb = dbase.getNameBase();
7168  std::vector<bool> cached(nb->GetNumNames(NAME_PLAYER), false);
7169  std::vector<const PlayerElo*> vElo(nb->GetNumNames(NAME_PLAYER), NULL);
7170 
7171  auto getElo = [&](idNumberT id, dateT date) {
7172  if (!cached[id]) {
7173  cached[id] = true;
7174  vElo[id] = sp.getPlayerElo(nb->GetName(NAME_PLAYER, id));
7175  }
7176  return (vElo[id]) ? vElo[id]->getElo(date) : 0;
7177  };
7178 
7179  auto entry_op = [&](IndexEntry& ie) {
7180  dateT date = ie.GetDate();
7181  eloT eloWhite = (!overwrite && ie.GetWhiteElo() != 0)
7182  ? 0
7183  : getElo(ie.GetWhite(), date);
7184  eloT eloBlack = (!overwrite && ie.GetBlackElo() != 0)
7185  ? 0
7186  : getElo(ie.GetBlack(), date);
7187  unsigned nChanges = (eloWhite != 0) ? 1 : 0;
7188  nChanges += (eloBlack != 0) ? 1 : 0;
7189  if (nChanges) {
7190  numChangedRatings += nChanges;
7191  numChangedGames++;
7192  if (updateIndexFile) {
7193  if (eloWhite != 0) {
7194  ie.SetWhiteElo(eloWhite);
7196  }
7197  if (eloBlack != 0) {
7198  ie.SetBlackElo(eloBlack);
7200  }
7201  return true;
7202  }
7203  }
7204  return false;
7205  };
7206 
7207  std::string filter = (filterOnly) ? "dbfilter" : dbase.newFilter();
7208  auto hf = dbase.getFilter(filter);
7209  auto changes = dbase.transformIndex(hf, UI_CreateProgress(ti), entry_op);
7210  if (!filterOnly)
7211  dbase.deleteFilter(filter.c_str());
7212 
7213  UI_List res(2);
7214  res.push_back(numChangedRatings);
7215  res.push_back(numChangedGames);
7216  return UI_Result(ti, changes.first, res);
7217 }
7218 
7219 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7220 // sc_name_read:
7221 // Reads a Scid name spelling file into memory, and returns a list of
7222 // four integers: the number of player, event, site and round names in
7223 // the file.
7224 // If there is no filename argument, sc_name_read just returns the same
7225 // list for the current spellchecker status without reading a new file.
7226 int
7227 sc_name_read (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
7228 {
7229  if (argc > 5) {
7230  return UI_Result(ti, ERROR_BadArg, "Usage: sc_name read <spellcheck-file>");
7231  }
7232 
7233  if (argc > 2) {
7234  const char * filename = argv[2];
7235  Progress progress = UI_CreateProgress(ti);
7236  std::pair<errorT, SpellChecker*> newSpell = SpellChecker::Create(filename, progress);
7237  if (newSpell.first != OK) {
7238  return UI_Result(ti, newSpell.first, "Error reading name spellcheck file.");
7239  }
7240  if (spellChk != NULL) { delete spellChk; }
7241  spellChk = newSpell.second;
7242  progress.report(1, 1);
7243  }
7244 
7245  UI_List res(NUM_NAME_TYPES);
7246  for (nameT i = 0; i < NUM_NAME_TYPES; i++) {
7247  uint n = (spellChk == NULL) ? 0 : spellChk->numCorrectNames(i);
7248  res.push_back(n);
7249  }
7250  return UI_Result(ti, OK, res);
7251 }
7252 
7253 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7254 // sc_name_spellcheck:
7255 // Scan the current database for spelling corrections.
7256 UI_res_t sc_name_spellcheck (UI_handle_t ti, scidBaseT& dbase, const SpellChecker& sp, int argc, const char ** argv)
7257 {
7258  nameT nt = NAME_INVALID;
7259  uint maxCorrections = 20000;
7260  bool doSurnames = false;
7261  bool ambiguous = true;
7262  const char * usage = "Usage: sc_name spellcheck [-max <integer>] [-surnames <boolean>] [-ambiguous <boolean>] players|events|sites|rounds";
7263 
7264  const char * options[] = {
7265  "-max", "-surnames", "-ambiguous", NULL
7266  };
7267  enum {
7268  OPT_MAX, OPT_SURNAMES, OPT_AMBIGUOUS
7269  };
7270 
7271  int arg = 2;
7272  while (arg+1 < argc) {
7273  const char * option = argv[arg];
7274  const char * value = argv[arg+1];
7275  arg += 2;
7276  int index = -1;
7277  if (option[0] == '-') { index = strUniqueMatch (option, options); }
7278 
7279  switch (index) {
7280  case OPT_MAX:
7281  maxCorrections = strGetUnsigned (value);
7282  if ( maxCorrections == 0 ) {
7283  maxCorrections = (uint)-1;
7284  }
7285  break;
7286  case OPT_SURNAMES:
7287  doSurnames = strGetBoolean (value);
7288  break;
7289  case OPT_AMBIGUOUS:
7290  ambiguous = strGetBoolean (value);
7291  break;
7292  default:
7293  return InvalidCommand (ti, "sc_name spellcheck", options);
7294  }
7295  }
7296  if (arg+1 != argc) { return errorResult (ti, usage); }
7297  nt = NameBase::NameTypeFromString (argv[arg]);
7298 
7299  if (! NameBase::IsValidNameType (nt)) {
7300  return errorResult (ti, usage);
7301  }
7302 
7303  const NameBase* nb = dbase.getNameBase();
7304  std::vector<std::string> tmpRes;
7305  char tempStr[1024];
7306  uint correctionCount = 0;
7307 
7308  Progress progress = UI_CreateProgress(ti);
7309  // Check every name of the specified type:
7310  for (idNumberT id=0, n=nb->GetNumNames(nt); id < n; id++) {
7311  if (correctionCount >= maxCorrections) break;
7312 
7313  if ((id % 1000) == 0) { // Update the percentage done bar:
7314  if (!progress.report(id, n)) break;
7315  }
7316 
7317  uint frequency = db->getNameFreq(nt, id);
7318  // Do not bother trying to correct unused names:
7319  if (frequency == 0) continue;
7320 
7321  const char* origName = nb->GetName(nt, id);
7322  // If requested ignore surnames
7323  if (nt == NAME_PLAYER && !doSurnames && strIsSurnameOnly (origName)) continue;
7324 
7325  // First, check for a general prefix or suffix correction:
7326  std::string name = origName;
7327  size_t nGenCorrections = sp.getGeneralCorrections(nt).normalize(&name);
7328 
7329  // If spellchecking names, remove any country code like " (USA)"
7330  // in parentheses at the end of the name:
7331  if (nt == NAME_PLAYER) {
7332  size_t country = name.rfind(" (");
7333  if (country != std::string::npos && (country + 6) == name.length()) {
7334  if (*(name.rbegin()) == ')') name.erase(country);
7335  }
7336  }
7337 
7338  std::vector<const char*> corrections = sp.find(nt, name.c_str());
7339  // If requested ignore ambiguous corrections
7340  if (!ambiguous && corrections.size() > 1) continue;
7341 
7342  if (nGenCorrections != 0 && corrections.size() == 0) {
7343  corrections.push_back(name.c_str());
7344  }
7345 
7346  std::string correctCmd;
7347  const char* strAmbiguous = corrections.size() > 1 ? "Ambiguous: " : "";
7348  for (size_t i=0; i < corrections.size(); i++) {
7349  if (strcmp(origName, corrections[i]) == 0) {
7350  if (corrections.size() != 1) {
7351  correctCmd.append("ERROR: " + name);
7352  }
7353  continue;
7354  }
7355  if (i==0) correctionCount++;
7356 
7357  sprintf (tempStr, "%s\"%s\"\t>> \"%s\" (%u)",
7358  strAmbiguous,
7359  origName,
7360  corrections[i],
7361  frequency);
7362  correctCmd += tempStr;
7363 
7364  if (nt == NAME_PLAYER) { // Look for a player birthdate:
7365  const PlayerInfo* pInfo = sp.getPlayerInfo(corrections[i]);
7366  dateT birthdate = pInfo->getBirthdate();
7367  dateT deathdate = pInfo->getDeathdate();
7368  if (birthdate != ZERO_DATE || deathdate != ZERO_DATE) {
7369  correctCmd += " ";
7370  if (birthdate != ZERO_DATE) {
7371  date_DecodeToString (birthdate, tempStr);
7372  correctCmd += tempStr;
7373  }
7374  correctCmd += "--";
7375  if (deathdate != ZERO_DATE) {
7376  date_DecodeToString (deathdate, tempStr);
7377  correctCmd += tempStr;
7378  }
7379  }
7380  }
7381  correctCmd += "\n";
7382  }
7383 
7384  if (!correctCmd.empty()) tmpRes.push_back(correctCmd);
7385  }
7386 
7387  std::sort(tmpRes.begin(), tmpRes.end());
7388 
7389  progress.report(1,1);
7390 
7391  // Now generate the return message:
7392  static const char* NAME_TYPE_STRING[] = {"player", "event", "site", "round"};
7393  sprintf (tempStr, "Scid found %u %s name correction%s.\n",
7394  correctionCount, NAME_TYPE_STRING[nt],
7395  strPlural (correctionCount));
7396  std::string res = tempStr;
7397  res +=
7398  "Edit the list to remove any corrections you do not want.\n"
7399  "Only lines of the form:\n"
7400  " \"Old Name\" >> \"New Name\"\n"
7401  "(with no spaces before the \"Old Name\") are processed.\n"
7402  "You can discard a correction you do not want by deleting\n"
7403  "its line, or simply by adding a space or any other character\n"
7404  "at the start of the line.\n";
7405  if (nt == NAME_PLAYER && ! doSurnames) {
7406  res +=
7407  "Note: player names with a surname only, such as \"Kramnik\",\n"
7408  "have not been corrected, since such corrections are often\n"
7409  "wrong. You can choose to also show surname-only corrections\n"
7410  "using the button below.\n";
7411  }
7412  res += "\n";
7413  std::vector<std::string>::const_iterator it = tmpRes.begin();
7414  std::vector<std::string>::const_iterator it_Ambiguous =
7415  std::lower_bound(tmpRes.begin(), tmpRes.end(), "Ambig");
7416  for (;it != it_Ambiguous; it++) {
7417  res += *it;
7418  }
7419  for (;it != tmpRes.end(); it++) {
7420  res += "\n";
7421  res += *it;
7422  }
7423  return UI_Result(ti, OK, res);
7424 }
7425 
7426 UI_res_t sc_name(UI_extra_t cd, UI_handle_t ti, int argc, const char** argv) {
7427  static const char * options [] = {
7428  "correct", "edit", "info", "match", "plist",
7429  "ratings", "read", "spellcheck", "retrievename", "elo",
7430  NULL
7431  };
7432  enum {
7433  OPT_CORRECT, OPT_EDIT, OPT_INFO, OPT_MATCH, OPT_PLIST,
7434  OPT_RATINGS, OPT_READ, OPT_SPELLCHECK, OPT_RETRIEVENAME, OPT_ELO
7435  };
7436 
7437  int index = -1;
7438  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
7439 
7440  if (!db->inUse) {
7441  return errorResult (ti, ERROR_FileNotOpen, errMsgNotOpen(ti));
7442  }
7443 
7444  switch (index) {
7445  case OPT_INFO:
7446  return sc_name_info (cd, ti, argc, argv);
7447 
7448  case OPT_MATCH:
7449  return sc_name_match (cd, ti, argc, argv);
7450 
7451  case OPT_PLIST:
7452  return sc_name_plist (cd, ti, argc, argv);
7453 
7454  case OPT_READ:
7455  return sc_name_read (cd, ti, argc, argv);
7456  }
7457 
7458  if (db->isReadOnly() && index != OPT_RETRIEVENAME) {
7459  return errorResult (ti, ERROR_FileReadOnly);
7460  }
7461 
7462  switch (index) {
7463  case OPT_CORRECT:
7464  return sc_name_correct (cd, ti, argc, argv);
7465 
7466  case OPT_EDIT:
7467  return sc_name_edit (cd, ti, argc, argv);
7468  };
7469 
7470  if (spellChk == NULL) {
7471  return UI_Result(ti, ERROR,
7472  "A spellcheck file has not been loaded.\n\n"
7473  "You can load one from the Options menu.");
7474  }
7475 
7476  switch (index) {
7477  case OPT_RATINGS:
7478  return sc_name_ratings(ti, *db, *spellChk, argc, argv);
7479 
7480  case OPT_RETRIEVENAME:
7481  return sc_name_retrievename(ti, *spellChk, argc, argv);
7482 
7483  case OPT_SPELLCHECK:
7484  return sc_name_spellcheck(ti, *db, *spellChk, argc, argv);
7485 
7486  case OPT_ELO:
7487  return sc_name_elo (ti, *spellChk, argc, argv);
7488 
7489  default:
7490  return InvalidCommand (ti, "sc_name", options);
7491  }
7492 
7493  return TCL_OK;
7494 }
7495 
7496 //////////////////////////////////////////////////////////////////////
7497 // OPENING/PLAYER REPORT functions
7498 
7499 static uint
7500 avgGameLength (resultT result)
7501 {
7502  uint sum = 0;
7503  uint count = 0;
7504  for (gamenumT i=0, n = db->numGames(); i < n; i++) {
7505  const IndexEntry* ie = db->getIndexEntry(i);
7506  if (result == ie->GetResult()) {
7507  count++;
7508  sum += ((ie->GetNumHalfMoves() + 1) / 2);
7509  }
7510  }
7511  if (count == 0) { return 0; }
7512  return (sum + (count/2)) / count;
7513 }
7514 
7515 int
7516 sc_report (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
7517 {
7518  static const char * options [] = {
7519  "avgLength", "best", "counts", "create", "eco", "elo",
7520  "endmaterial", "format", "frequency", "line", "max", "moveOrders",
7521  "notes", "players", "print", "score", "select", "themes", NULL
7522  };
7523  enum {
7524  OPT_AVGLENGTH, OPT_BEST, OPT_COUNTS, OPT_CREATE, OPT_ECO, OPT_ELO,
7525  OPT_ENDMAT, OPT_FORMAT, OPT_FREQ, OPT_LINE, OPT_MAX, OPT_MOVEORDERS,
7526  OPT_NOTES, OPT_PLAYERS, OPT_PRINT, OPT_SCORE, OPT_SELECT, OPT_THEMES
7527  };
7528 
7529  static const char * usage =
7530  "Usage: sc_report opening|player <command> [<options...>]";
7531  OpTable * report = NULL;
7532  if (argc < 2) {
7533  return errorResult (ti, usage);
7534  }
7535  switch (argv[1][0]) {
7536  case 'O': case 'o': report = reports[REPORT_OPENING]; break;
7537  case 'P': case 'p': report = reports[REPORT_PLAYER]; break;
7538  default:
7539  return errorResult (ti, usage);
7540  }
7541 
7542  DString * dstr = NULL;
7543  int index = strUniqueMatch (argv[2], options);
7544 
7545  if (! db->inUse) {
7546  return errorResult (ti, errMsgNotOpen(ti));
7547  }
7548  if (index >= 0 && index != OPT_CREATE && report == NULL) {
7549  return errorResult (ti, "No report has been created yet.");
7550  }
7551 
7552  const scidBaseT::Stats& stats = db->getStats();
7553  switch (index) {
7554  case OPT_AVGLENGTH:
7555  if (argc != 4) {
7556  return errorResult (ti, "Usage: sc_report player|opening avgLength 1|=|0|*");
7557  } else {
7558  resultT result = strGetResult (argv[3]);
7559  appendUintElement (ti, report->AvgLength (result));
7560  appendUintElement (ti, avgGameLength (result));
7561  }
7562  break;
7563 
7564  case OPT_BEST:
7565  if (argc != 5) {
7566  return errorResult (ti, "Usage: sc_report opening|player best w|b|a|o|n <count>");
7567  }
7568  dstr = new DString;
7569  report->BestGames (dstr, strGetUnsigned(argv[4]), argv[3]);
7570  Tcl_AppendResult (ti, dstr->Data(), NULL);
7571  break;
7572 
7573  case OPT_COUNTS:
7574  appendUintElement (ti, report->GetTotalCount());
7575  appendUintElement (ti, report->GetTheoryCount());
7576  break;
7577 
7578  case OPT_CREATE:
7579  return sc_report_create (cd, ti, argc, argv);
7580 
7581  case OPT_ECO:
7582  if (argc > 3) {
7583  dstr = new DString();
7584  report->TopEcoCodes (dstr, strGetUnsigned(argv[3]));
7585  Tcl_AppendResult (ti, dstr->Data(), NULL);
7586  } else {
7587  Tcl_AppendResult (ti, report->GetEco(), NULL);
7588  }
7589  break;
7590 
7591  case OPT_ELO:
7592  if (argc != 4) {
7593  return errorResult (ti, "Usage: sc_report opening|player elo white|black");
7594  } else {
7595  colorT color = WHITE;
7596  uint count = 0;
7597  uint pct = 0;
7598  uint perf = 0;
7599  if (argv[3][0] == 'B' || argv[3][0] == 'b') { color = BLACK; }
7600  uint avg = report->AvgElo (color, &count, &pct, &perf);
7601  appendUintElement (ti, avg);
7602  appendUintElement (ti, count);
7603  appendUintElement (ti, pct);
7604  appendUintElement (ti, perf);
7605  }
7606  break;
7607 
7608  case OPT_ENDMAT:
7609  dstr = new DString;
7610  report->EndMaterialReport (dstr,
7611  translate (ti, "OprepReportGames", "Report games"),
7612  translate (ti, "OprepAllGames", "All games"));
7613  Tcl_AppendResult (ti, dstr->Data(), NULL);
7614  break;
7615 
7616  case OPT_FORMAT:
7617  if (argc != 4) {
7618  return errorResult (ti, "Usage: sc_report opening|player format latex|html|text|ctext");
7619  }
7620  report->SetFormat (argv[3]);
7621  break;
7622 
7623  case OPT_FREQ:
7624  if (argc != 4) {
7625  return errorResult (ti, "Usage: sc_report opening|player frequency 1|=|0|*");
7626  } else {
7627  resultT result = strGetResult (argv[3]);
7628  appendUintElement (ti, report->PercentFreq (result));
7629  uint freq = stats.nResults[result] * 1000;
7630  freq = freq / db->numGames();
7631  appendUintElement (ti, freq);
7632  }
7633  break;
7634 
7635  case OPT_LINE:
7636  dstr = new DString;
7637  report->PrintStemLine (dstr);
7638  Tcl_AppendResult (ti, dstr->Data(), NULL);
7639  break;
7640 
7641  case OPT_MAX:
7642  if (argc == 4 && argv[3][0] == 'g') {
7644  } else if (argc == 4 && argv[3][0] == 'r') {
7645  return setUintResult (ti, OPTABLE_MAX_ROWS);
7646  }
7647  return errorResult (ti, "Usage: sc_report opening|player max games|rows");
7648 
7649  case OPT_MOVEORDERS:
7650  if (argc != 4) {
7651  return errorResult (ti, "Usage: sc_report opening|player moveOrders <count>");
7652  }
7653  dstr = new DString;
7654  report->PopularMoveOrders (dstr, strGetUnsigned(argv[3]));
7655  Tcl_AppendResult (ti, dstr->Data(), NULL);
7656  break;
7657 
7658  case OPT_NOTES:
7659  if (argc < 4 || argc > 5) {
7660  return errorResult (ti, "Usage: sc_report opening|player notes <0|1> [numrows]");
7661  }
7662  report->ClearNotes ();
7663  if (strGetBoolean (argv[3]) && report->GetNumLines() > 0) {
7664  report->GuessNumRows ();
7665  if (argc > 4) {
7666  uint nrows = strGetUnsigned (argv[4]);
7667  if (nrows > 0) { report->SetNumRows (nrows); }
7668  }
7669  dstr = new DString;
7670  // Print the table just to set up notes, but there is
7671  // no need to return the result:
7672  report->PrintTable (dstr, "", "");
7673  }
7674  break;
7675 
7676  case OPT_PLAYERS:
7677  if (argc != 5) {
7678  return errorResult (ti, "Usage: sc_report opening|player players w|b <count>");
7679  } else {
7680  colorT color = WHITE;
7681  if (argv[3][0] == 'B' || argv[3][0] == 'b') { color = BLACK; }
7682  dstr = new DString;
7683  report->TopPlayers (dstr, color, strGetUnsigned(argv[4]));
7684  Tcl_AppendResult (ti, dstr->Data(), NULL);
7685  }
7686  break;
7687 
7688  case OPT_PRINT:
7689  if (argc < 3 || argc > 6) {
7690  return errorResult (ti, "Usage: sc_report opening|players print [numrows] [title] [comment]");
7691  }
7692  report->GuessNumRows ();
7693  if (argc > 3) {
7694  uint nrows = strGetUnsigned (argv[3]);
7695  if (nrows > 0) { report->SetNumRows (nrows); }
7696  }
7697  dstr = new DString;
7698  report->PrintTable (dstr, argc > 4 ? argv[4] : "",
7699  argc > 5 ? argv[5] : "");
7700  Tcl_AppendResult (ti, dstr->Data(), NULL);
7701  break;
7702 
7703  case OPT_SCORE:
7704  appendUintElement (ti, report->PercentScore());
7705  {
7706  uint percent = stats.nResults[RESULT_White] * 2;
7707  percent += stats.nResults[RESULT_Draw];
7708  percent = percent * 500;
7709  uint sum = (stats.nResults[RESULT_White] +
7710  stats.nResults[RESULT_Draw] +
7711  stats.nResults[RESULT_Black]);
7712  if (sum != 0)
7713  percent = percent / sum;
7714  else
7715  percent = 0;
7716  appendUintElement (ti, percent);
7717  }
7718  break;
7719 
7720  case OPT_SELECT:
7721  return sc_report_select (cd, ti, argc, argv);
7722 
7723  case OPT_THEMES:
7724  dstr = new DString;
7725  report->ThemeReport (dstr, argc - 3, (const char **) argv + 3);
7726  Tcl_AppendResult (ti, dstr->Data(), NULL);
7727  break;
7728 
7729  default:
7730  return InvalidCommand (ti, "sc_report", options);
7731  }
7732 
7733  if (dstr != NULL) { delete dstr; }
7734  return TCL_OK;
7735 }
7736 
7737 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7738 // sc_report_create:
7739 // Creates a new opening table.
7740 // NOTE: It assumes the filter contains the results
7741 // of a tree search for the current position, so
7742 // the Tcl code that calls this need to ensure that
7743 // is done first.
7744 int
7745 sc_report_create (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
7746 {
7747  uint maxThemeMoveNumber = 20;
7748  uint maxExtraMoves = 1;
7749  uint maxLines = OPTABLE_MAX_TABLE_LINES;
7750  static const char * usage =
7751  "Usage: sc_report opening|player create [maxExtraMoves] [maxLines] [excludeMove]";
7752 
7753  uint reportType = 0;
7754  if (argc < 2) {
7755  return errorResult (ti, usage);
7756  }
7757 
7758  switch (argv[1][0]) {
7759  case 'O': case 'o': reportType = REPORT_OPENING; break;
7760  case 'P': case 'p': reportType = REPORT_PLAYER; break;
7761  default:
7762  return errorResult (ti, usage);
7763  }
7764 
7765  if (argc > 3) {
7766  maxExtraMoves = strGetUnsigned (argv[3]);
7767  }
7768  if (argc > 4) {
7769  maxLines = strGetUnsigned (argv[4]);
7770  if (maxLines > OPTABLE_MAX_TABLE_LINES) {
7771  maxLines = OPTABLE_MAX_TABLE_LINES;
7772  }
7773  if (maxLines == 0) { maxLines = 1; }
7774  }
7775  const char * excludeMove = "";
7776  if (argc > 5) { excludeMove = argv[5]; }
7777  if (excludeMove[0] == '-') { excludeMove = ""; }
7778 
7779  if (reports[reportType] != NULL) {
7780  delete reports[reportType];
7781  }
7782  OpTable* report = new OpTable(reportTypeName[reportType], db->game, ecoBook.get());
7783  reports[reportType] = report;
7784  report->SetMaxTableLines (maxLines);
7785  report->SetExcludeMove (excludeMove);
7786  report->SetDecimalChar (decimalPointChar);
7787  report->SetMaxThemeMoveNumber (maxThemeMoveNumber);
7788 
7789  Progress progress = UI_CreateProgress(ti);
7790 
7791  for (uint gnum=0, n = db->numGames(); gnum < n; gnum++) {
7792  if ((gnum % 2000) == 0) { // Update the percentage done bar:
7793  if (!progress.report(gnum, n)) break;
7794  }
7795 
7796  byte ply = db->dbFilter->Get(gnum);
7797  const IndexEntry* ie = db->getIndexEntry(gnum);
7798  if (ply != 0) {
7799  if (db->getGame(ie, db->bbuf) != OK) {
7800  return errorResult (ti, "Error reading game file.");
7801  }
7802  if (scratchGame->Decode (db->bbuf, GAME_DECODE_ALL) != OK) {
7803  return errorResult (ti, "Error decoding game.");
7804  }
7805  scratchGame->LoadStandardTags (ie, db->getNameBase());
7806  scratchGame->MoveToPly (ply - 1);
7807  if (scratchGame->AtEnd()) ply = 0;
7808  if (ply != 0) {
7809  uint moveOrderID = report->AddMoveOrder (scratchGame);
7810  OpLine * line = new OpLine (scratchGame, ie, gnum+1,
7811  maxExtraMoves, maxThemeMoveNumber);
7812  if (report->Add (line)) {
7813  line->SetMoveOrderID (moveOrderID);
7814  } else {
7815  delete line;
7816  }
7817  }
7818  }
7819  report->AddEndMaterial (ie->GetFinalMatSig(), (ply != 0));
7820  }
7821  progress.report(1,1);
7822 
7823  return TCL_OK;
7824 }
7825 
7826 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7827 // sc_report_select:
7828 // Restricts the filter to only contain games
7829 // in the opening report matching the specified
7830 // opening/endgame theme or note number.
7831 int
7832 sc_report_select (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
7833 {
7834  static const char * usage =
7835  "Usage: sc_report opening|player select <op|eg|note> <number>";
7836  if (argc != 5) {
7837  return errorResult (ti, usage);
7838  }
7839  OpTable * report = NULL;
7840  switch (argv[1][0]) {
7841  case 'O': case 'o': report = reports[REPORT_OPENING]; break;
7842  case 'P': case 'p': report = reports[REPORT_PLAYER]; break;
7843  default:
7844  return errorResult (ti, usage);
7845  }
7846 
7847  char type = tolower (argv[3][0]);
7848  uint number = strGetUnsigned (argv[4]);
7849 
7850  uint * matches = report->SelectGames (type, number);
7851  uint * match = matches;
7852  db->dbFilter->Fill(0);
7853  while (*match != 0) {
7854  uint gnum = *match - 1;
7855  match++;
7856  uint ply = *match + 1;
7857  match++;
7858  db->dbFilter->Set (gnum, ply);
7859  }
7860  delete[] matches;
7861 
7862  return TCL_OK;
7863 }
7864 
7865 
7866 //////////////////////////////////////////////////////////////////////
7867 // SEARCH and TREE functions
7868 
7869 int
7870 sc_tree (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
7871 {
7872  static const char * options [] = {
7873  "best", "move", "positions", "search", "size",
7874  "cachesize", "cacheinfo", NULL
7875  };
7876  enum {
7877  TREE_BEST, TREE_MOVE, TREE_POSITIONS, TREE_SEARCH, TREE_SIZE,
7878  TREE_CACHESIZE, TREE_CACHEINFO
7879  };
7880 
7881  int index = -1;
7882  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
7883 
7884  switch (index) {
7885  case TREE_MOVE:
7886  return sc_tree_move (cd, ti, argc, argv);
7887 
7888  case TREE_POSITIONS:
7889  // Return the number of positions cached:
7890  return setUintResult (ti, db->treeCache.UsedSize());
7891 
7892  case TREE_SEARCH:
7893  return sc_tree_search (cd, ti, argc, argv);
7894 
7895  case TREE_SIZE:
7896  return setUintResult (ti, db->treeCache.Size());
7897 
7898  case TREE_CACHESIZE:
7899  return sc_tree_cachesize (cd, ti, argc, argv);
7900 
7901  case TREE_CACHEINFO:
7902  return sc_tree_cacheinfo (cd, ti, argc, argv);
7903 
7904  default:
7905  return InvalidCommand (ti, "sc_tree", options);
7906  }
7907 
7908  return TCL_OK;
7909 }
7910 
7911 
7912 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7913 // sc_tree_move:
7914 // Returns the move for a tree line.
7915 // Arg can be in the range [1.. numTreeLines].
7916 // It can also be "random" to request a random move selected
7917 // according to the frequency of each move in the tree.
7918 int
7919 sc_tree_move (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
7920 {
7921  if (argc != 4) {
7922  return errorResult (ti, "Usage: sc_tree move <baseNum> <lineNum>");
7923  }
7924 
7925  const scidBaseT* base = DBasePool::getBase(strGetUnsigned(argv[2]));
7926  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
7927 
7928  int selection = strGetInteger (argv[3]);
7929  if (argv[3][0] == 'r' && strIsPrefix (argv[3], "random")) {
7930  uint total = base->tree.totalCount;
7931  if (total == 0) { return TCL_OK; }
7932  uint r = rand() % total;
7933  uint sum = 0;
7934  for (uint i=0; i < base->tree.moveCount; i++) {
7935  sum += base->tree.node[i].total;
7936  if (r <= sum) {
7937  selection = i + 1;
7938  break;
7939  }
7940  }
7941  }
7942 
7943  if (selection < 1 || selection > (int)(base->tree.moveCount)) {
7944  // Not a valid selection. We ignore it (e.g. the user clicked on a
7945  // line with no move on it).
7946  return TCL_OK;
7947  }
7948 
7949  const treeNodeT* node = &(base->tree.node[selection - 1]);
7950 
7951  // If the san string first char is not a letter, it is the
7952  // empty move (e.g. "[end]") so we do NOT add a move:
7953  if (! isalpha(node->san[0])) {
7954  return TCL_OK;
7955  }
7956 
7957  Tcl_AppendResult (ti, node->san, NULL);
7958  return TCL_OK;
7959 }
7960 
7961 // Enumeration of possible move-sorting methods for tree mode:
7963 
7964 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7965 // sortTreeMoves():
7966 // Sorts the moves of a tree node according to a specified order.
7967 // Slow sort method, but the typical number of moves is under 20
7968 // so it is easily fast enough.
7969 void
7970 sortTreeMoves (treeT * tree, int sortMethod, colorT toMove)
7971 {
7972  // Only sort if there are at least two moves in the tree node:
7973  if (tree->moveCount <= 1) { return; }
7974 
7975  for (uint outer=0; outer < tree->moveCount - 1; outer++) {
7976  for (uint inner=outer+1; inner < tree->moveCount; inner++) {
7977  int result = 0;
7978 
7979  switch (sortMethod) {
7980  case SORT_FREQUENCY: // Most frequent moves first:
7981  result = tree->node[outer].total - tree->node[inner].total;
7982  break;
7983 
7984  case SORT_ALPHA: // Alphabetical order:
7985  result = strCompare (tree->node[inner].san,
7986  tree->node[outer].san);
7987  break;
7988 
7989  case SORT_ECO: // ECO code order:
7990  result = tree->node[outer].ecoCode - tree->node[inner].ecoCode;
7991  break;
7992 
7993  case SORT_SCORE: // Order by success:
7994  result = tree->node[outer].score - tree->node[inner].score;
7995  if (toMove == BLACK) { result = -result; }
7996  break;
7997 
7998  default: // Unreachable:
7999  return;
8000  }
8001 
8002  if (result < 0) {
8003  // Swap the nodes:
8004  treeNodeT temp = tree->node[outer];
8005  tree->node[outer] = tree->node[inner];
8006  tree->node[inner] = temp;
8007  }
8008  } // for (inner)
8009  } // for (outer)
8010  return;
8011 }
8012 
8013 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8014 // sc_tree_search:
8015 // Returns the tree for the current position
8016 int
8017 sc_tree_search (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
8018 {
8019  static const char * usageStr =
8020  "Usage: sc_tree search [-hideMoves <0|1>] [-sort alpha|eco|frequency|score]";
8021 
8022  // Sort options: these should match the moveSortE enumerated type.
8023  static const char * sortOptions[] = {
8024  "alpha", "eco", "frequency", "score", NULL
8025  };
8026 
8027  char tempTrans[10];
8028  bool hideMoves = false;
8029  const bool listMode = false;
8030  bool inFilterOnly = false;
8031  int sortMethod = SORT_FREQUENCY; // default move order: frequency
8032 
8033  scidBaseT * base = db;
8034  static std::set<scidBaseT**> search_pool;
8035 
8036  // Check that there is an even number of optional arguments and
8037  // parse them as option-value pairs:
8038  int arg = 2;
8039  int argsLeft = (argc - arg);
8040  if (argsLeft % 2 != 0) { return errorResult (ti, usageStr); }
8041 
8042  while (arg < argc) {
8043  if (strIsPrefix (argv[arg], "-sort")) {
8044  sortMethod = strUniqueMatch (argv[arg+1], sortOptions);
8045  } else if (strIsPrefix (argv[arg], "-hideMoves")) {
8046  hideMoves = strGetBoolean (argv[arg+1]);
8047  } else if (strIsPrefix (argv[arg], "-base")) {
8048  base = DBasePool::getBase(strGetUnsigned(argv[arg+1]));
8049  if (base == 0) return UI_Result(ti, ERROR_FileNotOpen);
8050  } else if (strIsPrefix (argv[arg], "-filtered")) {
8051  inFilterOnly = strGetBoolean (argv[arg+1]);
8052  } else if (strIsPrefix (argv[arg], "-cancel")) {
8053  search_pool.clear();
8054  return TCL_OK;
8055  } else {
8056  return errorResult (ti, usageStr);
8057  }
8058  arg += 2;
8059  }
8060 
8061  if (sortMethod < 0) { return errorResult (ti, usageStr); }
8062  if (!base->inUse) { return UI_Result(ti, ERROR_FileNotOpen); }
8063 
8064  search_pool.insert(&base);
8065  base->treeFilter->Fill(0);
8066  const HFilter filter = base->getFilter("dbfilter");
8067 
8068  Progress progress = UI_CreateProgress(ti);
8069  Timer timer; // Start timing this search.
8070  uint skipcount = 0;
8071 
8072  // 1. Cache Search
8073  bool foundInCache = false;
8074  // #TODO: cache even filtered searches (requires a filter hash)
8075  if (! inFilterOnly) {
8076  // Lookup the cache before searching:
8077  const cachedTreeT* pct = base->treeCache.Lookup (db->game->GetCurrentPos());
8078  if (pct != NULL) {
8079  // It was in the cache! Use it to save time:
8080  if (pct->restoreFilter(base->treeFilter) == OK) {
8081  base->tree = pct->getTree();
8082  foundInCache = true;
8083  }
8084  }
8085  }
8086 
8087  if (!foundInCache) {
8088  // OK, not in the cache so do the search:
8089  // 2. Set vars
8090  Position* pos = db->game->GetCurrentPos();
8091  treeT* tree = &(base->tree);
8092  tree->moveCount = tree->totalCount = 0;
8093  matSigT msig = matsig_Make (pos->GetMaterial());
8094  uint hpSig = pos->GetHPSig();
8095  simpleMoveT sm;
8096  base->treeFilter->Fill (0); // Reset the filter to be empty
8097  skipcount = 0;
8098 
8099  // 3. Set up the stored line code matches:
8100  StoredLine stored_line(pos->GetBoard(), pos->GetToMove());
8101 
8102  // 4. Search through each game:
8103  for (uint i=0, n = base->numGames(); i < n; i++) {
8104  if ((i % 5000) == 0) { // Update the percentage done slider:
8105  if (!progress.report(i,n) || search_pool.count(&base) == 0) {
8106  return setResult (ti, "canceled");
8107  }
8108  }
8109 
8110  if (inFilterOnly && filter.get(i) == 0) { continue; }
8111 
8112  const IndexEntry* ie = base->getIndexEntry(i);
8113  if (ie->GetLength() == 0) { skipcount++; continue; }
8114  // We do not skip deleted games, so next line is commented out:
8115  // if (ie->GetDeleteFlag()) { skipcount++; continue; }
8116 
8117  bool foundMatch = false;
8118 
8119  // Check the stored line result for this game:
8120  int ply = stored_line.match(ie->GetStoredLineCode());
8121  if (ply < -1) { skipcount++; continue; }
8122  if (ply >= 0) {
8124  if (m) {
8125  sm.from = m.getFrom();
8126  sm.to = m.getTo();
8127  if (m.isCastle()) sm.to = sm.from + ((sm.to > sm.from) ? 2 : -2);
8128  sm.promote = EMPTY;
8129  ply += 1;
8130  foundMatch = true;
8131  }
8132  }
8133  if (!foundMatch) {
8134  const pieceT* bd = pos->GetBoard();
8135  bool isStartPos =
8136  (bd[A1]==WR && bd[B1]==WN && bd[C1]==WB && bd[D1]==WQ &&
8137  bd[E1]==WK && bd[F1]==WB && bd[G1]==WN && bd[H1]==WR &&
8138  bd[A2]==WP && bd[B2]==WP && bd[C2]==WP && bd[D2]==WP &&
8139  bd[E2]==WP && bd[F2]==WP && bd[G2]==WP && bd[H2]==WP &&
8140  bd[A7]==BP && bd[B7]==BP && bd[C7]==BP && bd[D7]==BP &&
8141  bd[E7]==BP && bd[F7]==BP && bd[G7]==BP && bd[H7]==BP &&
8142  bd[A8]==BR && bd[B8]==BN && bd[C8]==BB && bd[D8]==BQ &&
8143  bd[E8]==BK && bd[F8]==BB && bd[G8]==BN && bd[H8]==BR);
8144  if (!isStartPos && ie->GetNumHalfMoves() == 0) {
8145  skipcount++;
8146  continue;
8147  }
8148 
8149  if (! ie->GetStartFlag()) {
8150  // Speedups that only apply to standard start games:
8151  if (hpSig != HPSIG_StdStart) { // Not the start mask
8152  if (! hpSig_PossibleMatch(hpSig, ie->GetHomePawnData())) { continue; }
8153  }
8154  }
8155 
8156  if (msig != MATSIG_StdStart
8157  && !matsig_isReachable (msig, ie->GetFinalMatSig(),
8158  ie->GetPromotionsFlag(),
8159  ie->GetUnderPromoFlag()))
8160  {
8161  skipcount++;
8162  continue;
8163  }
8164 
8165  if (base->getGame(ie, base->bbuf) != OK) {
8166  search_pool.erase(&base);
8167  return errorResult (ti, "Error reading game file.");
8168  }
8169  Game *g = scratchGame;
8170  if (g->ExactMatch (pos, base->bbuf, &sm)) {
8171  ply = g->GetCurrentPly() + 1;
8172  foundMatch = true;
8173  }
8174  }
8175 
8176  // If match was found, add it to the list of found moves:
8177  if (foundMatch) {
8178  if (ply > 255) { ply = 255; }
8179  base->treeFilter->Set (i, (byte) ply);
8180  uint search;
8181  treeNodeT* node = tree->node;
8182  for (search = 0; search < tree->moveCount; search++, node++) {
8183  if (sm.from == node->sm.from
8184  && sm.to == node->sm.to
8185  && sm.promote == node->sm.promote) {
8186  break;
8187  }
8188  }
8189 
8190  // Now node is the node to update or add.
8191  // Check for exceeding max number of nodes:
8192  if (search >= MAX_TREE_NODES) {
8193  search_pool.erase(&base);
8194  return errorResult (ti, "Too many moves.");
8195  }
8196 
8197  if (search == tree->moveCount) {
8198  // A new move to add:
8199  initTreeNode (node);
8200  node->sm = sm;
8201  if (sm.from == NULL_SQUARE) {
8202  strCopy(node->san, "[end]");
8203  } else {
8204  pos->MakeSANString (&sm, node->san, SAN_CHECKTEST);
8205  }
8206  tree->moveCount++;
8207  }
8208  node->total++;
8209  node->freq[ie->GetResult()]++;
8210  eloT elo = 0;
8211  eloT oppElo = 0;
8212  uint year = ie->GetYear();
8213  if (pos->GetToMove() == WHITE) {
8214  elo = ie->GetWhiteElo();
8215  oppElo = ie->GetBlackElo();
8216  } else {
8217  elo = ie->GetBlackElo();
8218  oppElo = ie->GetWhiteElo();
8219  }
8220  if (elo > 0) {
8221  node->eloSum += elo;
8222  node->eloCount++;
8223  }
8224  if (oppElo > 0) {
8225  node->perfSum += oppElo;
8226  node->perfCount++;
8227  }
8228  if (year != 0) {
8229  node->yearSum += year;
8230  node->yearCount++;
8231  }
8232  tree->totalCount++;
8233  } // end: if (foundMatch) ...
8234  } // end: for
8235  }
8236 
8237  treeT* tree = &(base->tree);
8238  treeNodeT* node = tree->node;
8239  if (!foundInCache) {
8240  // Now we generate the score of each move: it is the expected score per
8241  // 1000 games. Also generate the ECO code of each move.
8242 
8243  for (uint i=0; i < tree->moveCount; i++, node++) {
8244  node->score = (node->freq[RESULT_White] * 2
8245  + node->freq[RESULT_Draw] + node->freq[RESULT_None])
8246  * 500 / node->total;
8247 
8248  node->ecoCode = 0;
8249  if (ecoBook != NULL) {
8250  Position tmpPos = *(db->game->GetCurrentPos());
8251  if (node->sm.from != NULL_SQUARE) {
8252  tmpPos.DoSimpleMove (&(node->sm));
8253  }
8254  ecoT eco = ecoBook->findECO(&tmpPos);
8255  if (eco != ECO_None)
8256  node->ecoCode = eco;
8257  }
8258  }
8259 
8260  // Now we sort the move list:
8261  sortTreeMoves (tree, sortMethod, db->game->GetCurrentPos()->GetToMove());
8262 
8263  // If it wasn't in the cache, maybe it belongs there:
8264  if (!foundInCache && !inFilterOnly) {
8265  base->treeCache.Add (db->game->GetCurrentPos(), tree, base->treeFilter);
8266  }
8267  }
8268 
8269  progress.report(1,1);
8270  search_pool.erase(&base);
8271 
8272  DString * output = new DString;
8273  char temp [200];
8274  if (! listMode) {
8275  const char * titleRow =
8276  " Move ECO Frequency Score AvElo Perf AvYear %Draws";
8277  titleRow = translate (ti, "TreeTitleRow", titleRow);
8278  output->Append (titleRow);
8279  }
8280 
8281  // Now we print the list into the return string:
8282  node = tree->node;
8283  for (uint count=0; count < tree->moveCount; count++, node++) {
8284  ecoStringT ecoStr;
8285  eco_ToExtendedString (node->ecoCode, ecoStr);
8286  uint avgElo = 0;
8287  if (node->eloCount >= 1)
8288  avgElo = node->eloSum / node->eloCount;
8289 
8290  uint perf = 0;
8291  if (node->perfCount >= 10) {
8292  perf = node->perfSum / node->perfCount;
8293  uint score = (node->score + 5) / 10;
8294  if (db->game->GetCurrentPos()->GetToMove() == BLACK) { score = 100 - score; }
8295  perf = Crosstable::Performance (perf, score);
8296  }
8297  uint avgYear = 0;
8298  if (node->yearCount > 0) {
8299  avgYear = (node->yearSum + (node->yearCount/2)) / node->yearCount;
8300  }
8301  node->san[6] = 0;
8302 
8303  strcpy(tempTrans, node->san);
8304  transPieces(tempTrans);
8305 
8306  if (listMode) {
8307  if (ecoStr[0] == 0) { strCopy (ecoStr, "{}"); }
8308  sprintf (temp, "%2u %-6s %-5s %7u %3d%c%1d %3d%c%1d",
8309  count + 1,
8310  hideMoves ? "---" : tempTrans,//node->san,
8311  hideMoves ? "{}" : ecoStr,
8312  node->total,
8313  100 * node->total / tree->totalCount,
8314  decimalPointChar,
8315  (1000 * node->total / tree->totalCount) % 10,
8316  node->score / 10,
8317  decimalPointChar,
8318  node->score % 10);
8319  output->Append (temp);
8320  } else {
8321  sprintf (temp, "\n%2u: %-6s %-5s %7u:%3d%c%1d%% %3d%c%1d%%",
8322  count + 1,
8323  hideMoves ? "---" : tempTrans,//node->san,
8324  hideMoves ? "" : ecoStr,
8325  node->total,
8326  100 * node->total / tree->totalCount,
8327  decimalPointChar,
8328  (1000 * node->total / tree->totalCount) % 10,
8329  node->score / 10,
8330  decimalPointChar,
8331  node->score % 10);
8332  output->Append (temp);
8333  }
8334 
8335  if (avgElo == 0) {
8336  strCopy (temp, listMode ? " {}" : " ");
8337  } else {
8338  sprintf (temp, " %4u", avgElo);
8339  }
8340  output->Append (temp);
8341  if (perf == 0) {
8342  strCopy (temp, listMode ? " {}" : " ");
8343  } else {
8344  sprintf (temp, " %4u", perf);
8345  }
8346  output->Append (temp);
8347  if (avgYear == 0) {
8348  strCopy (temp, listMode ? " {}" : " ");
8349  } else {
8350  sprintf (temp, " %4u", avgYear);
8351  }
8352  output->Append (temp);
8353  uint pctDraws = node->freq[RESULT_Draw] * 1000 / node->total;
8354  sprintf (temp, " %3d%%", (pctDraws + 5) / 10);
8355  output->Append (temp);
8356 
8357  if (listMode) {
8358  Tcl_AppendElement (ti, (char *) output->Data());
8359  output->Clear();
8360  }
8361  }
8362 
8363  // Print a totals line as well, if there are any moves in the tree:
8364 
8365  if (tree->moveCount > 0) {
8366  int totalScore = 0;
8367  uint64_t eloSum = 0;
8368  uint64_t eloCount = 0;
8369  uint64_t perfSum = 0;
8370  uint64_t perfCount = 0;
8371  uint64_t yearCount = 0;
8372  uint64_t yearSum = 0;
8373  uint nDraws = 0;
8374  node = tree->node;
8375  for (uint count=0; count < tree->moveCount; count++, node++) {
8376  totalScore += node->freq[RESULT_White] * 2;
8377  totalScore += node->freq[RESULT_Draw] + node->freq[RESULT_None];
8378  eloCount += node->eloCount;
8379  eloSum += node->eloSum;
8380  perfCount += node->perfCount;
8381  perfSum += node->perfSum;
8382  yearCount += node->yearCount;
8383  yearSum += node->yearSum;
8384  nDraws += node->freq[RESULT_Draw];
8385  }
8386  totalScore = totalScore * 500 / tree->totalCount;
8387  uint avgElo = 0;
8388  if (eloCount >= 10) {
8389  avgElo = eloSum / eloCount;
8390  }
8391  uint perf = 0;
8392  if (perfCount >= 10) {
8393  perf = perfSum / perfCount;
8394  uint score = (totalScore + 5) / 10;
8395  if (db->game->GetCurrentPos()->GetToMove() == BLACK) { score = 100 - score; }
8396  perf = Crosstable::Performance (perf, score);
8397  }
8398 
8399  if (listMode) {
8400  sprintf (temp, "%2u %-6s %-5s %7u %3d%c%1d %3d%c%1d",
8401  0,
8402  "TOTAL",
8403  "{}",
8404  tree->totalCount,
8405  100, decimalPointChar, 0,
8406  totalScore / 10, decimalPointChar, totalScore % 10);
8407  output->Append (temp);
8408  } else {
8409  const char * totalString = translate (ti, "TreeTotal:", "TOTAL:");
8410  output->Append ("\n_______________________________________________________________\n");
8411  sprintf (temp, "%-12s %7u:100%c0%% %3d%c%1d%%",
8412  totalString, tree->totalCount, decimalPointChar,
8413  totalScore / 10, decimalPointChar, totalScore % 10);
8414  output->Append (temp);
8415  }
8416  if (avgElo == 0) {
8417  output->Append (listMode ? " {}" : " ");
8418  } else {
8419  sprintf (temp, " %4u", avgElo);
8420  output->Append (temp);
8421  }
8422  if (perf == 0) {
8423  output->Append (listMode ? " {} " : " ");
8424  } else {
8425  sprintf (temp, " %4u", perf);
8426  output->Append (temp);
8427  }
8428  if (yearCount == 0) {
8429  output->Append (listMode ? " {}" : " ");
8430  } else {
8431  sprintf (temp, " %4u", static_cast<uint>((yearSum + (yearCount/2)) / yearCount));
8432  output->Append (temp);
8433  }
8434  uint pctDraws = nDraws * 1000 / tree->totalCount;
8435  sprintf (temp, " %3d%%", (pctDraws + 5) / 10);
8436  output->Append (temp);
8437  if (listMode) {
8438  Tcl_AppendElement (ti, (char *) output->Data());
8439  output->Clear();
8440  } else {
8441  output->Append ("\n");
8442  }
8443  }
8444 
8445  if (! listMode) {
8446  Tcl_AppendResult (ti, output->Data(), NULL);
8447  }
8448  delete output;
8449  return TCL_OK;
8450 }
8451 
8452 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8453 // sc_tree_cachesize:
8454 // set cache size
8455 int
8456 sc_tree_cachesize (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
8457 {
8458  if (argc != 4) {
8459  return errorResult (ti, "Usage: sc_tree cachesize <base> <size>");
8460  }
8461  scidBaseT* base = DBasePool::getBase(strGetInteger(argv[2]));
8462  if (base) base->treeCache.CacheResize(strGetUnsigned(argv[3]));
8463  return TCL_OK;
8464 }
8465 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8466 // sc_tree_cacheinfo:
8467 // returns a list of 2 values : used slots and max cache size
8468 int
8469 sc_tree_cacheinfo (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
8470 {
8471  if (argc != 3) {
8472  return errorResult (ti, "Usage: sc_tree cacheinfo <base>");
8473  }
8474  scidBaseT* base = DBasePool::getBase(strGetInteger(argv[2]));
8475  if (base) {
8476  appendUintElement (ti, base->treeCache.UsedSize());
8477  appendUintElement (ti, base->treeCache.Size());
8478  } else {
8479  appendUintElement (ti, 0);
8480  appendUintElement (ti, 0);
8481  }
8482  return TCL_OK;
8483 }
8484 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8485 // sc_search:
8486 // Search function interface.
8487 int
8488 sc_search (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
8489 {
8490  static const char * options [] = {
8491  "board", "header", "material", NULL
8492  };
8493  enum { OPT_BOARD, OPT_HEADER, OPT_MATERIAL };
8494 
8495  int index = -1;
8496  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
8497  int ret = TCL_OK;
8498 
8499  if (!db->inUse) {
8500  return errorResult (ti, errMsgNotOpen(ti));
8501  }
8502 
8503  switch (index) {
8504  case OPT_BOARD:
8505  ret = sc_search_board (ti, db, db->getFilter("dbfilter"), argc, argv);
8506  break;
8507 
8508  case OPT_HEADER: {
8509  HFilter filter = db->getFilter("dbfilter");
8510  ret = sc_search_header (cd, ti, db, filter, argc, argv);
8511  break;
8512  }
8513 
8514  case OPT_MATERIAL:
8515  ret = sc_search_material (cd, ti, argc, argv);
8516  break;
8517 
8518  default:
8519  return InvalidCommand (ti, "sc_search", options);
8520  }
8521 
8522  return ret;
8523 }
8524 
8525 
8526 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8527 // sc_search_board:
8528 // Searches for exact match for the current position.
8529 // if <base> is present, search for current position in base <base>,
8530 // and sets <base> filter accordingly
8531 int sc_search_board(Tcl_Interp* ti, const scidBaseT* dbase, HFilter filter,
8532  int argc, const char** argv) {
8533  ASSERT(dbase != nullptr && filter != nullptr);
8534 
8535  const char* usageStr =
8536  "Usage: sc_search board <filterOp> <searchType> <searchInVars> <flip>";
8537 
8538  if (argc != 6)
8539  return errorResult(ti, usageStr);
8540 
8541  filterOpT filterOp = strGetFilterOp (argv[2]);
8542 
8543  bool useHpSigSpeedup = false;
8545 
8546  switch (argv[3][0]) {
8547  case 'E':
8548  searchType = GAME_EXACT_MATCH_Exact;
8549  useHpSigSpeedup = true;
8550  break;
8551  case 'P':
8552  searchType = GAME_EXACT_MATCH_Pawns;
8553  useHpSigSpeedup = true;
8554  break;
8555  case 'F':
8556  searchType = GAME_EXACT_MATCH_Fyles;
8557  break;
8558  case 'M':
8559  searchType = GAME_EXACT_MATCH_Material;
8560  break;
8561  default:
8562  return errorResult (ti, usageStr);
8563  }
8564 
8565  bool searchInVars = strGetBoolean (argv[4]);
8566  bool flip = false;
8567  flip = strGetBoolean (argv[5]);
8568 
8569  Position * pos = db->game->GetCurrentPos();
8570 
8571  Progress progress = UI_CreateProgress(ti);
8572  Timer timer; // Start timing this search.
8573  Position * posFlip = NULL;
8574  matSigT msig = matsig_Make (pos->GetMaterial());
8575  matSigT msigFlip = 0;
8576  uint hpSig = pos->GetHPSig();
8577  uint hpSigFlip = 0;
8578 
8579  if (flip) {
8580  posFlip = new Position;
8581  char cboard [40];
8582  pos->PrintCompactStrFlipped (cboard);
8583  posFlip->ReadFromCompactStr ((byte *) cboard);
8584  hpSigFlip = posFlip->GetHPSig();
8585  msigFlip = matsig_Make (posFlip->GetMaterial());
8586  }
8587 
8588  uint skipcount = 0;
8589 
8590  // If filter operation is to reset the filter, reset it:
8591  if (filterOp == FILTEROP_RESET) {
8592  filter->includeAll();
8593  filterOp = FILTEROP_AND;
8594  }
8595  size_t startFilterCount = filter->size();
8596 
8597  // Here is the loop that searches on each game:
8598  Game tmpGame;
8599  Game* g = &tmpGame;
8600  gamenumT gameNum = 0;
8601  for (gamenumT n = dbase->numGames(); gameNum < n; gameNum++) {
8602  if ((gameNum % 5000) == 0) { // Update the percentage done bar:
8603  if (!progress.report(gameNum, n)) break;
8604  }
8605  // First, apply the filter operation:
8606  if (filterOp == FILTEROP_AND) { // Skip any games not in the filter:
8607  if (filter.get(gameNum) == 0) {
8608  skipcount++;
8609  continue;
8610  }
8611  } else /* filterOp==FILTEROP_OR*/ { // Skip any games in the filter:
8612  if (filter.get(gameNum) != 0) {
8613  skipcount++;
8614  continue;
8615  } else {
8616  // OK, this game is NOT in the filter.
8617  // Add it so filterCounts are kept up to date:
8618  filter.set (gameNum, 1);
8619  }
8620  }
8621 
8622  const IndexEntry* ie = dbase->getIndexEntry(gameNum);
8623  if (ie->GetLength() == 0) {
8624  // Skip games with no gamefile record:
8625  filter.set (gameNum, 0);
8626  skipcount++;
8627  continue;
8628  }
8629 
8630  // Set "useVars" to true only if the search specified searching
8631  // in variations, AND this game has variations:
8632  bool useVars = searchInVars && ie->GetVariationsFlag();
8633 
8634  bool possibleMatch = true;
8635  bool possibleFlippedMatch = flip;
8636 
8637  // Apply speedups if we are not searching in variations:
8638  if (! useVars) {
8639  if (! ie->GetStartFlag()) {
8640  // Speedups that only apply to standard start games:
8641  if (useHpSigSpeedup && hpSig != 0xFFFF) {
8642  const byte * hpData = ie->GetHomePawnData();
8643  if (! hpSig_PossibleMatch (hpSig, hpData)) {
8644  possibleMatch = false;
8645  }
8646  if (possibleFlippedMatch) {
8647  if (! hpSig_PossibleMatch (hpSigFlip, hpData)) {
8648  possibleFlippedMatch = false;
8649  }
8650  }
8651  }
8652  }
8653 
8654  // If this game has no promotions, check the material of its final
8655  // position, since the searched position might be unreachable:
8656  if (possibleMatch) {
8657  if (!matsig_isReachable (msig, ie->GetFinalMatSig(),
8658  ie->GetPromotionsFlag(),
8659  ie->GetUnderPromoFlag())) {
8660  possibleMatch = false;
8661  }
8662  }
8663  if (possibleFlippedMatch) {
8664  if (!matsig_isReachable (msigFlip, ie->GetFinalMatSig(),
8665  ie->GetPromotionsFlag(),
8666  ie->GetUnderPromoFlag())) {
8667  possibleFlippedMatch = false;
8668  }
8669  }
8670  }
8671 
8672  if (!possibleMatch && !possibleFlippedMatch) {
8673  filter.set (gameNum, 0);
8674  skipcount++;
8675  continue;
8676  }
8677 
8678  // At this point, the game needs to be loaded:
8679  if (dbase->getGame(ie, dbase->bbuf) != OK) {
8680  return errorResult (ti, "Error reading game file.");
8681  }
8682  uint ply = 0;
8683  if (useVars) {
8684  g->Decode (dbase->bbuf, GAME_DECODE_NONE);
8685  // Try matching the game without variations first:
8686  if (ply == 0 && possibleMatch) {
8687  if (g->ExactMatch (pos, NULL, NULL, searchType)) {
8688  ply = g->GetCurrentPly() + 1;
8689  }
8690  }
8691  if (ply == 0 && possibleFlippedMatch) {
8692  if (g->ExactMatch (posFlip, NULL, NULL, searchType)) {
8693  ply = g->GetCurrentPly() + 1;
8694  }
8695  }
8696  if (ply == 0 && possibleMatch) {
8697  g->MoveToPly (0);
8698  if (g->VarExactMatch (pos, searchType)) {
8699  ply = g->GetCurrentPly() + 1;
8700  }
8701  }
8702  if (ply == 0 && possibleFlippedMatch) {
8703  g->MoveToPly (0);
8704  if (g->VarExactMatch (posFlip, searchType)) {
8705  ply = g->GetCurrentPly() + 1;
8706  }
8707  }
8708  } else {
8709  // No searching in variations:
8710  if (possibleMatch) {
8711  if (g->ExactMatch (pos, dbase->bbuf, NULL, searchType)) {
8712  // Set its auto-load move number to the matching move:
8713  ply = g->GetCurrentPly() + 1;
8714  }
8715  }
8716  if (ply == 0 && possibleFlippedMatch) {
8717  dbase->bbuf->BackToStart();
8718  if (g->ExactMatch (posFlip, dbase->bbuf, NULL, searchType)) {
8719  ply = g->GetCurrentPly() + 1;
8720  }
8721  }
8722  }
8723  if (ply > 255) { ply = 255; }
8724  filter.set (gameNum, ply);
8725  }
8726 
8727  progress.report(1,1);
8728  if (flip) { delete posFlip; }
8729 
8730  // Now print statistics and time for the search:
8731  char temp[200];
8732  int centisecs = timer.CentiSecs();
8733  if (gameNum != dbase->numGames()) {
8734  Tcl_AppendResult (ti, errMsgSearchInterrupted(ti), " ", NULL);
8735  }
8736  sprintf (temp, "%lu / %lu (%d%c%02d s)",
8737  static_cast<unsigned long>(filter->size()),
8738  static_cast<unsigned long>(startFilterCount),
8739  centisecs / 100, decimalPointChar, centisecs % 100);
8740  Tcl_AppendResult (ti, temp, NULL);
8741 #ifdef SHOW_SKIPPED_STATS
8742  sprintf(temp, " Skipped %u games.", skipcount);
8743  Tcl_AppendResult (ti, temp, NULL);
8744 #endif
8745 
8746 return TCL_OK;
8747 }
8748 
8749 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8750 // addPattern():
8751 // Called by the parameter parsing section of sc_search_material()
8752 // to add a pattern to a pattern list.
8753 // Returns the new head of the pattern list.
8754 patternT *
8755 addPattern (patternT * pattHead, patternT * addPatt)
8756 {
8757  // Create a new pattern structure:
8758 #ifdef WINCE
8759  patternT * newPatt = (patternT *) my_Tcl_Alloc(sizeof(new patternT));
8760 #else
8761  patternT * newPatt = new patternT;
8762 #endif
8763 
8764  // Initialise it:
8765  newPatt->flag = addPatt->flag;
8766  newPatt->pieceMatch = addPatt->pieceMatch;
8767  newPatt->fyleMatch = addPatt->fyleMatch;
8768  newPatt->rankMatch = addPatt->rankMatch;
8769 
8770  // Add to the head of the list of patterns, and return:
8771  newPatt->next = pattHead;
8772  return newPatt;
8773 }
8774 
8775 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8776 // freePatternList():
8777 // Frees the memory used by a list of patterns.
8778 void
8780 {
8781  patternT * nextPatt;
8782  while (patt) {
8783  nextPatt = patt->next;
8784 #ifdef WINCE
8785  my_Tcl_Free((char*)patt);
8786 #else
8787  delete patt;
8788 #endif
8789  patt = nextPatt;
8790  }
8791 }
8792 
8793 void
8795 {
8796  if (patt->rankMatch != NO_RANK) {
8797  patt->rankMatch = (RANK_1 + RANK_8) - patt->rankMatch;
8798  }
8799  patt->pieceMatch = PIECE_FLIP[patt->pieceMatch];
8800 }
8801 
8802 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8803 // parsePattern:
8804 // Called by sc_search_material to extract the details of
8805 // a pattern parameter (e.g "no wp c ?" for no White pawn on
8806 // the d file).
8807 errorT
8808 parsePattern (const char * str, patternT * patt)
8809 {
8810  ASSERT (str != NULL && patt != NULL);
8811 
8812  // Set up pointers to the four whitespace-separated pattern
8813  // parameter values in the string:
8814  str = strFirstWord (str);
8815  const char * flagStr = str;
8816  str = strNextWord (str);
8817  const char * colorPieceStr = str;
8818  str = strNextWord (str);
8819  const char * fyleStr = str;
8820  str = strNextWord (str);
8821  const char * rankStr = str;
8822 
8823  // Parse the color parameter: "w", "b", or "?" for no pattern.
8824  if (*colorPieceStr == '?') {
8825  // Empty pattern:
8826  patt->pieceMatch = EMPTY;
8827  return OK;
8828  }
8829 
8830  colorT color = WHITE;
8831  switch (tolower(*colorPieceStr)) {
8832  case 'w': color = WHITE; break;
8833  case 'b': color = BLACK; break;
8834  default: return ERROR;
8835  }
8836 
8837  // Parse the piece type parameter for this pattern:
8838  pieceT p = EMPTY;
8839  switch (tolower(colorPieceStr[1])) {
8840  case 'k': p = KING; break;
8841  case 'q': p = QUEEN; break;
8842  case 'r': p = ROOK; break;
8843  case 'b': p = BISHOP; break;
8844  case 'n': p = KNIGHT; break;
8845  case 'p': p = PAWN; break;
8846  default: return ERROR;
8847  }
8848 
8849  patt->pieceMatch = piece_Make (color, p);
8850  patt->flag = strGetBoolean (flagStr);
8851 
8852  // Parse the fyle parameter for this pattern:
8853  char ch = *fyleStr;
8854  if (ch == '?') {
8855  patt->fyleMatch = NO_FYLE;
8856  } else if (ch >= 'a' && ch <= 'h') {
8857  patt->fyleMatch = A_FYLE + (ch - 'a');
8858  } else {
8859  return ERROR;
8860  }
8861 
8862  // Parse the rank parameter for this pattern:
8863  ch = *rankStr;
8864  if (ch == '?') {
8865  patt->rankMatch = NO_RANK;
8866  } else if (ch >= '1' && ch <= '8') {
8867  patt->rankMatch = RANK_1 + (ch - '1');
8868  } else {
8869  return ERROR;
8870  }
8871  return OK;
8872 }
8873 
8874 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8875 // sc_search_material:
8876 // Searches by material and/or pattern.
8877 int
8878 sc_search_material (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
8879 {
8880  if (! db->inUse) {
8881  return errorResult (ti, "Not an open database.");
8882  }
8883 
8884  uint minMoves = 0;
8885  uint minPly = 0;
8886  uint maxPly = 999;
8887  uint matchLength = 1;
8888  byte min[MAX_PIECE_TYPES] = {0};
8889  byte minFlipped[MAX_PIECE_TYPES] = {0};
8890  byte max[MAX_PIECE_TYPES] = {0};
8891  byte maxFlipped[MAX_PIECE_TYPES] = {0};
8892  max[WM] = max[BM] = 9;
8893  int matDiff[2];
8894  matDiff[0] = -40;
8895  matDiff[1] = 40;
8896  filterOpT filterOp = FILTEROP_RESET;
8897  bool flip = false;
8898  bool oppBishops = true;
8899  bool sameBishops = true;
8900  uint hpExcludeMask = HPSIG_Empty;
8901  uint hpExMaskFlip = HPSIG_Empty;
8902  patternT * patt = NULL;
8903  patternT * flippedPatt = NULL;
8904  patternT tempPatt;
8905 
8906  const char * options[] = {
8907  "wq", "bq", "wr", "br", "wb", "bb", "wn", "bn",
8908  "wp", "bp", "wm", "bm", "flip", "filter", "range",
8909  "length", "bishops", "diff", "pattern", NULL
8910  };
8911  enum {
8912  OPT_WQ, OPT_BQ, OPT_WR, OPT_BR, OPT_WB, OPT_BB, OPT_WN, OPT_BN,
8913  OPT_WP, OPT_BP, OPT_WM, OPT_BM, OPT_FLIP, OPT_FILTER, OPT_RANGE,
8914  OPT_LENGTH, OPT_BISHOPS, OPT_DIFF, OPT_PATTERN
8915  };
8916 
8917  int arg = 2;
8918  while (arg+1 < argc) {
8919  const char * option = argv[arg];
8920  const char * value = argv[arg+1];
8921  arg += 2;
8922  int index = -1;
8923  if (option[0] == '-') {
8924  index = strUniqueMatch (&(option[1]), options);
8925  }
8926  uint counts [2] = {0, 0};
8927  if (index >= OPT_WQ && index <= OPT_BM) {
8928  strGetUnsigneds (value, counts, 2);
8929  }
8930 
8931  switch (index) {
8932 
8933  case OPT_WQ: min[WQ] = counts[0]; max[WQ] = counts[1]; break;
8934  case OPT_BQ: min[BQ] = counts[0]; max[BQ] = counts[1]; break;
8935  case OPT_WR: min[WR] = counts[0]; max[WR] = counts[1]; break;
8936  case OPT_BR: min[BR] = counts[0]; max[BR] = counts[1]; break;
8937  case OPT_WB: min[WB] = counts[0]; max[WB] = counts[1]; break;
8938  case OPT_BB: min[BB] = counts[0]; max[BB] = counts[1]; break;
8939  case OPT_WN: min[WN] = counts[0]; max[WN] = counts[1]; break;
8940  case OPT_BN: min[BN] = counts[0]; max[BN] = counts[1]; break;
8941  case OPT_WP: min[WP] = counts[0]; max[WP] = counts[1]; break;
8942  case OPT_BP: min[BP] = counts[0]; max[BP] = counts[1]; break;
8943  case OPT_WM: min[WM] = counts[0]; max[WM] = counts[1]; break;
8944  case OPT_BM: min[BM] = counts[0]; max[BM] = counts[1]; break;
8945 
8946  case OPT_FLIP:
8947  flip = strGetBoolean (value);
8948  break;
8949 
8950  case OPT_FILTER:
8951  filterOp = strGetFilterOp (value);
8952  break;
8953 
8954  case OPT_RANGE:
8955  strGetUnsigneds (value, counts, 2);
8956  minPly = counts[0]; maxPly = counts[1];
8957  break;
8958 
8959  case OPT_LENGTH:
8960  matchLength = strGetUnsigned (value);
8961  if (matchLength < 1) { matchLength = 1; }
8962  break;
8963 
8964  case OPT_BISHOPS:
8965  switch (toupper(value[0])) {
8966  case 'S': oppBishops = false; sameBishops = true; break;
8967  case 'O': oppBishops = true; sameBishops = false; break;
8968  default: oppBishops = true; sameBishops = true; break;
8969  }
8970  break;
8971 
8972  case OPT_DIFF:
8973  strGetIntegers (value, matDiff, 2);
8974  break;
8975 
8976  case OPT_PATTERN:
8977  if (parsePattern (value, &tempPatt) != OK) {
8978  Tcl_AppendResult (ti, "Invalid pattern: ", value, NULL);
8979  return TCL_ERROR;
8980  }
8981  // Only add to lists if a pattern was specified:
8982  if (tempPatt.pieceMatch == EMPTY) { break; }
8983  // Update home-pawn exclude masks if appropriate:
8984  if (!tempPatt.flag
8985  && piece_Type(tempPatt.pieceMatch) == PAWN
8986  && tempPatt.rankMatch == NO_RANK
8987  && tempPatt.fyleMatch != NO_FYLE) {
8988  colorT color = piece_Color (tempPatt.pieceMatch);
8989  colorT flipColor = (color == WHITE ? BLACK : WHITE);
8990  fyleT fyle = tempPatt.fyleMatch;
8991  hpExcludeMask = hpSig_AddPawn (hpExcludeMask, color, fyle);
8992  hpExMaskFlip = hpSig_AddPawn (hpExMaskFlip, flipColor, fyle);
8993  }
8994  // Add the pattern and its flipped equivalent:
8995  patt = addPattern (patt, &tempPatt);
8996  flipPattern (&tempPatt);
8997  flippedPatt = addPattern (flippedPatt, &tempPatt);
8998  break;
8999 
9000  default:
9001  return InvalidCommand (ti, "sc_search material", options);
9002  }
9003  }
9004  if (arg != argc) { return errorResult (ti, "Odd number of parameters."); }
9005 
9006  // Sanity check of values:
9007  if (max[WQ] < min[WQ]) { max[WQ] = min[WQ]; }
9008  if (max[BQ] < min[BQ]) { max[BQ] = min[BQ]; }
9009  if (max[WR] < min[WR]) { max[WR] = min[WR]; }
9010  if (max[BR] < min[BR]) { max[BR] = min[BR]; }
9011  if (max[WB] < min[WB]) { max[WB] = min[WB]; }
9012  if (max[BB] < min[BB]) { max[BB] = min[BB]; }
9013  if (max[WN] < min[WN]) { max[WN] = min[WN]; }
9014  if (max[BN] < min[BN]) { max[BN] = min[BN]; }
9015  if (max[WP] < min[WP]) { max[WP] = min[WP]; }
9016  if (max[BP] < min[BP]) { max[BP] = min[BP]; }
9017  // Minor piece range should be at least the sum of the Bishop
9018  // and Knight minimums, and at most the sum of the maximums:
9019  if (min[WM] < min[WB]+min[WN]) { min[WM] = min[WB] + min[WN]; }
9020  if (min[BM] < max[BB]+min[BN]) { min[BM] = min[BB] + min[BN]; }
9021  if (max[WM] > max[WB]+max[WN]) { max[WM] = max[WB] + max[WN]; }
9022  if (max[BM] > max[BB]+max[BN]) { max[BM] = max[BB] + max[BN]; }
9023 
9024  // Swap material difference range values if necessary:
9025  if (matDiff[0] > matDiff[1]) {
9026  int temp = matDiff[0]; matDiff[0] = matDiff[1]; matDiff[1] = temp;
9027  }
9028 
9029  // Set up flipped piece counts if necessary:
9030  if (flip) {
9031  minFlipped[WQ] = min[BQ]; maxFlipped[WQ] = max[BQ];
9032  minFlipped[WR] = min[BR]; maxFlipped[WR] = max[BR];
9033  minFlipped[WB] = min[BB]; maxFlipped[WB] = max[BB];
9034  minFlipped[WN] = min[BN]; maxFlipped[WN] = max[BN];
9035  minFlipped[WP] = min[BP]; maxFlipped[WP] = max[BP];
9036  minFlipped[WM] = min[BM]; maxFlipped[WM] = max[BM];
9037  minFlipped[BQ] = min[WQ]; maxFlipped[BQ] = max[WQ];
9038  minFlipped[BR] = min[WR]; maxFlipped[BR] = max[WR];
9039  minFlipped[BB] = min[WB]; maxFlipped[BB] = max[WB];
9040  minFlipped[BN] = min[WN]; maxFlipped[BN] = max[WN];
9041  minFlipped[BP] = min[WP]; maxFlipped[BP] = max[WP];
9042  minFlipped[BM] = min[WM]; maxFlipped[BM] = max[WM];
9043  }
9044 
9045  // Convert move numbers to halfmoves (ply counts):
9046  minMoves = minPly;
9047  minPly = minPly * 2 - 1;
9048  maxPly = maxPly * 2;
9049 
9050  // Set up the material Sig: it is the signature of the MAXIMUMs.
9051  matSigT msig, msigFlipped;
9052  int checkMsig = 1;
9053  if (max[WQ] > 3 || max[BQ] > 3 || max[WR] > 3 || max[BR] > 3 ||
9054  max[WB] > 3 || max[BB] > 3 || max[WN] > 3 || max[BN] > 3) {
9055  // It is an unusual search, we cannot use material sig!
9056  checkMsig = 0;
9057  }
9058  msig = matsig_Make (max);
9059  msigFlipped = MATSIG_FlipColor(msig);
9060 
9061  Progress progress = UI_CreateProgress(ti);
9062  Timer timer; // Start timing this search.
9063 
9064  uint skipcount = 0;
9065  char temp [250];
9066  Game * g = scratchGame;
9067  HFilter filter = db->getFilter("dbfilter");
9068 
9069  // If filter operation is to reset the filter, reset it:
9070  if (filterOp == FILTEROP_RESET) {
9071  filter->includeAll();
9072  filterOp = FILTEROP_AND;
9073  }
9074  size_t startFilterCount = filter->size();
9075 
9076  // Here is the loop that searches on each game:
9077  gamenumT gameNum = 0, n = db->numGames();
9078  for (; gameNum < n; gameNum++) {
9079  if ((gameNum % 1000) == 0) { // Update the percentage done bar:
9080  if (!progress.report(gameNum, n)) break;
9081  }
9082  // First, apply the filter operation:
9083  if (filterOp == FILTEROP_AND) { // Skip any games not in the filter:
9084  if (filter.get(gameNum) == 0) {
9085  skipcount++;
9086  continue;
9087  }
9088  } else /* filterOp == FILTEROP_OR*/ { // Skip any games in the filter:
9089  if (filter.get(gameNum) != 0) {
9090  skipcount++;
9091  continue;
9092  }
9093  // OK, this game is NOT in the filter.
9094  // Add it so filterCounts are kept up to date:
9095  filter.set (gameNum, 1);
9096  }
9097 
9098  const IndexEntry* ie = db->getIndexEntry(gameNum);
9099  if (ie->GetLength() == 0) { // Skip games with no gamefile record
9100  filter.set (gameNum, 0);
9101  skipcount++;
9102  continue;
9103  }
9104 
9105  if (ie->GetNumHalfMoves() < minMoves && ! ie->GetStartFlag()) {
9106  // Skip games without enough moves to match, if they
9107  // have the standard starting position:
9108  filter.set (gameNum, 0);
9109  skipcount++;
9110  continue;
9111  }
9112 
9113  bool possibleMatch = true;
9114  bool possibleFlippedMatch = flip;
9115 
9116  // First, eliminate games that cannot match from their final
9117  // material signature:
9118  if (checkMsig && !matsig_isReachable (msig, ie->GetFinalMatSig(),
9119  ie->GetPromotionsFlag(),
9120  ie->GetUnderPromoFlag()))
9121  {
9122  possibleMatch = false;
9123  }
9124  if (flip && checkMsig
9125  && !matsig_isReachable (msigFlipped, ie->GetFinalMatSig(),
9126  ie->GetPromotionsFlag(),
9127  ie->GetUnderPromoFlag()))
9128  {
9129  possibleFlippedMatch = false;
9130  }
9131 
9132  // If the game has a final home pawn that cannot appear in the
9133  // patterns, exclude it. For example, a White IQP search has no
9134  // white c or e pawns, so any game that ends with a c2 or e2 pawn
9135  // at home need not be loaded:
9136 
9137  if (possibleMatch && hpExcludeMask != HPSIG_Empty) {
9138  uint gameFinalHP = hpSig_Final (ie->GetHomePawnData());
9139  // If any bit is set in both, this game cannot match:
9140  if ((gameFinalHP & hpExcludeMask) != 0) {
9141  possibleMatch = false;
9142  }
9143  }
9144  if (possibleFlippedMatch && hpExMaskFlip != HPSIG_Empty) {
9145  uint gameFinalHP = hpSig_Final (ie->GetHomePawnData());
9146  // If any bit is set in both, this game cannot match:
9147  if ((gameFinalHP & hpExMaskFlip) != 0) {
9148  possibleFlippedMatch = false;
9149  }
9150  }
9151 
9152  if (!possibleMatch && !possibleFlippedMatch) {
9153  filter.set (gameNum, 0);
9154  skipcount++;
9155  continue;
9156  }
9157 
9158  // Now, the game must be loaded and searched:
9159 
9160  if (db->getGame(ie, db->bbuf) != OK) {
9161  continue;
9162  }
9163 
9164  bool result = false;
9165  if (possibleMatch) {
9166  result = g->MaterialMatch (db->bbuf, min, max, patt,
9167  minPly, maxPly, matchLength,
9168  oppBishops, sameBishops,
9169  matDiff[0], matDiff[1]);
9170  }
9171  if (result == 0 && possibleFlippedMatch) {
9172  db->bbuf->BackToStart();
9173  result = g->MaterialMatch (db->bbuf, minFlipped, maxFlipped,
9174  flippedPatt, minPly, maxPly,
9175  matchLength, oppBishops, sameBishops,
9176  matDiff[0], matDiff[1]);
9177  }
9178 
9179  if (result) {
9180  // update the filter value to the current ply:
9181  uint plyOfMatch = g->GetCurrentPly() + 1 - matchLength;
9182  byte b = (byte) (plyOfMatch + 1);
9183  if (b == 0) { b = 1; }
9184  filter.set (gameNum, b);
9185  } else {
9186  // This game did NOT match:
9187  filter.set (gameNum, 0);
9188  }
9189  }
9190 
9191  freePatternList (patt);
9192  freePatternList (flippedPatt);
9193  progress.report(1,1);
9194 
9195  int centisecs = timer.CentiSecs();
9196 
9197  if (gameNum != n) {
9198  Tcl_AppendResult (ti, errMsgSearchInterrupted(ti), " ", NULL);
9199  }
9200  sprintf (temp, "%lu / %lu (%d%c%02d s)",
9201  static_cast<unsigned long>(filter->size()),
9202  static_cast<unsigned long>(startFilterCount),
9203  centisecs / 100, decimalPointChar, centisecs % 100);
9204  Tcl_AppendResult (ti, temp, NULL);
9205 #ifdef SHOW_SKIPPED_STATS
9206  sprintf(temp, " Skipped %u games.", skipcount);
9207  Tcl_AppendResult (ti, temp, NULL);
9208 #endif
9209 
9210  return TCL_OK;
9211 }
9212 
9213 
9214 const uint NUM_TITLES = 8;
9215 enum {
9219 };
9220 const char * titleStr [NUM_TITLES] = {
9221  "gm", "im", "fm", "wgm", "wim", "wfm", "w", "none"
9222 };
9223 
9224 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9225 // parseTitles:
9226 // Called from sc_search_header to parse a list
9227 // of player titles to be searched for. The provided
9228 // string should have some subset of the elements
9229 // gm, im, fm, wgm, wim, w and none, each separated
9230 // by whitespace. Example: "gm wgm" would indicate
9231 // to only search for games by a GM or WIM.
9232 bool *
9233 parseTitles (const char * str)
9234 {
9235  bool * titles = new bool [NUM_TITLES];
9236 
9237  for (uint t=0; t < NUM_TITLES; t++) { titles[t] = false; }
9238 
9239  str = strFirstWord (str);
9240  while (*str != 0) {
9241  for (uint i=0; i < NUM_TITLES; i++) {
9242  if (strIsCasePrefix (titleStr[i], str)) {
9243  titles[i] = true;
9244  break;
9245  }
9246  }
9247  str = strNextWord (str);
9248  }
9249  return titles;
9250 }
9251 
9252 
9253 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9254 // sc_search_header:
9255 // Searches by header information.
9256 int
9257 sc_search_header (ClientData, Tcl_Interp * ti, scidBaseT* base, HFilter& filter, int argc, const char ** argv)
9258 {
9259  ASSERT(argc >= 2);
9260  Progress progress = UI_CreateProgress(ti);
9261  errorT res = search_index(base, filter, argc -2, argv +2, progress);
9262  if (res != OK) return UI_Result(ti, res);
9263 
9264  //TODO: the old options that follows do not work with FILTEROP_OR
9265  // at the moment there is no tcl code that use them with FILTEROP_OR
9266 
9267  std::string sAnnotator;
9268 
9269  bool bAnnotated = false;
9270 
9271  bool * wTitles = NULL;
9272  bool * bTitles = NULL;
9273 
9274  bool wToMove = true;
9275  bool bToMove = true;
9276 
9277  int pgnTextCount = 0;
9278  char ** sPgnText = NULL;
9279 
9280  const char * options[] = {
9281  "annotator", "annotated",
9282  "wtitles", "btitles", "toMove",
9283  "pgn", NULL
9284  };
9285  enum {
9286  OPT_ANNOTATOR, OPT_ANNOTATED,
9287  OPT_WTITLES, OPT_BTITLES, OPT_TOMOVE,
9288  OPT_PGN
9289  };
9290 
9291  int arg = 2;
9292  while (arg+1 < argc) {
9293  const char * option = argv[arg];
9294  const char * value = argv[arg+1];
9295  arg += 2;
9296  int index = -1;
9297  if (option[0] == '-') {
9298  index = strUniqueMatch (&(option[1]), options);
9299  }
9300 
9301  switch (index) {
9302  case OPT_ANNOTATOR:
9303  sAnnotator = value;
9304  break;
9305 
9306  case OPT_ANNOTATED:
9307  bAnnotated = strGetBoolean(value);
9308  break;
9309 
9310  case OPT_WTITLES:
9311  delete[] wTitles;
9312  wTitles = parseTitles (value);
9313  break;
9314 
9315  case OPT_BTITLES:
9316  delete[] bTitles;
9317  bTitles = parseTitles (value);
9318  break;
9319 
9320  case OPT_TOMOVE:
9321  wToMove = false;
9322  if (strFirstChar (value, 'w') || strFirstChar (value, 'W')) {
9323  wToMove = true;
9324  }
9325  bToMove = false;
9326  if (strFirstChar (value, 'b') || strFirstChar (value, 'B')) {
9327  bToMove = true;
9328  }
9329  break;
9330 
9331  case OPT_PGN:
9332  if (Tcl_SplitList (ti, (char *)value, &pgnTextCount,
9333  (CONST84 char ***) &sPgnText) != TCL_OK) {
9334  delete[] wTitles;
9335  delete[] bTitles;
9336  return TCL_ERROR;
9337  }
9338  break;
9339 
9340  }
9341  }
9342 
9343  // Set up White name matches array:
9344  std::vector<bool> mWhite;
9345  if (wTitles != NULL && spellChk != NULL) {
9346  bool allTitlesOn = true;
9347  for (uint t=0; t < NUM_TITLES; t++) {
9348  if (! wTitles[t]) { allTitlesOn = false; break; }
9349  }
9350  if (! allTitlesOn) {
9351  idNumberT i;
9352  idNumberT numNames = base->getNameBase()->GetNumNames(NAME_PLAYER);
9353  mWhite.resize(numNames, true);
9354  for (i=0; i < numNames; i++) {
9355  const char * name = base->getNameBase()->GetName (NAME_PLAYER, i);
9356  const PlayerInfo* pInfo = spellChk->getPlayerInfo(name);
9357  const char * title = (pInfo) ? pInfo->getTitle() : "";
9358  if ((!wTitles[TITLE_GM] && strEqual(title, "gm"))
9359  || (!wTitles[TITLE_GM] && strEqual(title, "hgm"))
9360  || (!wTitles[TITLE_IM] && strEqual(title, "im"))
9361  || (!wTitles[TITLE_FM] && strEqual(title, "fm"))
9362  || (!wTitles[TITLE_WGM] && strEqual(title, "wgm"))
9363  || (!wTitles[TITLE_WIM] && strEqual(title, "wim"))
9364  || (!wTitles[TITLE_WFM] && strEqual(title, "wfm"))
9365  || (!wTitles[TITLE_W] && strEqual(title, "w"))
9366  || (!wTitles[TITLE_NONE] && strEqual(title, ""))
9367  || (!wTitles[TITLE_NONE] && strEqual(title, "cgm"))
9368  || (!wTitles[TITLE_NONE] && strEqual(title, "cim"))) {
9369  mWhite[i] = false;
9370  }
9371  }
9372  }
9373  }
9374 
9375  // Set up Black name matches array:
9376  std::vector<bool> mBlack;
9377  if (bTitles != NULL && spellChk != NULL) {
9378  bool allTitlesOn = true;
9379  for (uint t=0; t < NUM_TITLES; t++) {
9380  if (!bTitles[t]) { allTitlesOn = false; break; }
9381  }
9382  if (! allTitlesOn) {
9383  idNumberT i;
9384  idNumberT numNames = base->getNameBase()->GetNumNames(NAME_PLAYER);
9385  mBlack.resize(numNames, true);
9386  for (i=0; i < numNames; i++) {
9387  const char * name = base->getNameBase()->GetName (NAME_PLAYER, i);
9388  const PlayerInfo* pInfo = spellChk->getPlayerInfo(name);
9389  const char * title = (pInfo) ? pInfo->getTitle() : "";
9390  if ((!bTitles[TITLE_GM] && strEqual(title, "gm"))
9391  || (!bTitles[TITLE_GM] && strEqual(title, "hgm"))
9392  || (!bTitles[TITLE_IM] && strEqual(title, "im"))
9393  || (!bTitles[TITLE_FM] && strEqual(title, "fm"))
9394  || (!bTitles[TITLE_WGM] && strEqual(title, "wgm"))
9395  || (!bTitles[TITLE_WIM] && strEqual(title, "wim"))
9396  || (!bTitles[TITLE_WFM] && strEqual(title, "wfm"))
9397  || (!bTitles[TITLE_W] && strEqual(title, "w"))
9398  || (!bTitles[TITLE_NONE] && strEqual(title, ""))
9399  || (!bTitles[TITLE_NONE] && strEqual(title, "cgm"))
9400  || (!bTitles[TITLE_NONE] && strEqual(title, "cim"))) {
9401  mBlack[i] = false;
9402  }
9403  }
9404  }
9405  }
9406 
9407  bool skipSearch = false;
9408  if (sAnnotator.empty() && mWhite.empty() && mBlack.empty() &&
9409  bAnnotated == false && wToMove == true && bToMove == true &&
9410  pgnTextCount == 0) {
9411  skipSearch = true;
9412  }
9413 
9414  // Here is the loop that searches on each game:
9415  errorT result = OK;
9416  if (!skipSearch)
9417  for (uint i=0, n = base->numGames(); i < n; i++) {
9418  if ((i % 5000) == 0) { // Update the percentage done bar:
9419  if (!progress.report(i,n)) {
9420  result = ERROR_UserCancel;
9421  break;
9422  }
9423  }
9424  // Skip any games not in the filter:
9425  if (filter.get(i) == 0) continue;
9426 
9427  const IndexEntry* ie = base->getIndexEntry(i);
9428 
9429  auto matchGameHeader = [&]() {
9430  //TODO: this trick does not work if there is a custom start position
9431  uint halfmoves = ie->GetNumHalfMoves();
9432  if ((halfmoves % 2) == 0) { // This game ends with White to move
9433  if (!wToMove) {
9434  return false;
9435  }
9436  } else { // This game ends with Black to move
9437  if (!bToMove) {
9438  return false;
9439  }
9440  }
9441 
9442  // Last, we check the players
9443  if (!mWhite.empty() && !mWhite[ie->GetWhite()]) {
9444  return false;
9445  }
9446  if (!mBlack.empty() && !mBlack[ie->GetBlack()]) {
9447  return false;
9448  }
9449 
9450  if (bAnnotated && !(ie->GetCommentsFlag() ||
9451  ie->GetVariationsFlag() || ie->GetNagsFlag()))
9452  return false;
9453 
9454  if (!sAnnotator.empty() && !ie->GetCommentsFlag() &&
9455  !ie->GetVariationsFlag())
9456  return false;
9457 
9458  // If we reach here, this game matches all criteria.
9459  return true;
9460  };
9461  bool match = matchGameHeader();
9462 
9463  // Now try to match the comment text if applicable:
9464  // Note that it is not worth using a faster staring search
9465  // algorithm like Boyer-Moore or Knuth-Morris-Pratt since
9466  // profiling showed most that most of the time is spent
9467  // generating the PGN representation of each game.
9468  if (match && (pgnTextCount > 0 || !sAnnotator.empty())) {
9469  if (match && (base->getGame(ie, base->bbuf) != OK)) {
9470  match = false;
9471  }
9472 
9473  if (!sAnnotator.empty()) {
9474  // Need the annotator flag, so decode the flags
9475  scratchGame->Clear();
9476  if (match && scratchGame->DecodeTags(base->bbuf, true) != OK)
9477  match = false;
9478  if (match) {
9479  auto ann = scratchGame->FindExtraTag("Annotator");
9480  match = (ann != NULL) && strAlphaContains(ann, sAnnotator.c_str());
9481  }
9482  }
9483 
9484  if(pgnTextCount > 0)
9485  {
9486  scratchGame->Clear();
9487  if (match && scratchGame->Decode (base->bbuf, GAME_DECODE_ALL) != OK) {
9488  match = false;
9489  }
9490  if (match) {
9491  scratchGame->LoadStandardTags (ie, base->getNameBase());
9492  scratchGame->ResetPgnStyle ();
9493  scratchGame->AddPgnStyle (PGN_STYLE_TAGS);
9494  scratchGame->AddPgnStyle (PGN_STYLE_COMMENTS);
9495  scratchGame->AddPgnStyle (PGN_STYLE_VARS);
9496  scratchGame->AddPgnStyle (PGN_STYLE_SYMBOLS);
9497  scratchGame->SetPgnFormat (PGN_FORMAT_Plain);
9498  const char* buf = scratchGame->WriteToPGN().first;
9499  for (int m=0; m < pgnTextCount; m++) {
9500  if (match) { match = strContains (buf, sPgnText[m]); }
9501  }
9502  }
9503  }
9504  }
9505 
9506  if (match) {
9507  filter.set (i, 1);
9508  } else {
9509  // This game did NOT match:
9510  filter.set (i, 0);
9511  }
9512  }
9513  if (wTitles != NULL) { delete[] wTitles; }
9514  if (bTitles != NULL) { delete[] bTitles; }
9515  Tcl_Free ((char *) sPgnText);
9516 
9517  progress.report(1,1);
9518 
9519  return UI_Result(ti, result);;
9520 }
9521 
9522 
9523 //////////////////////////////////////////////////////////////////////
9524 // VARIATION creation/deletion/navigation functions.
9525 
9526 int
9527 sc_var (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
9528 {
9529  static const char * options [] = {
9530  "count", "number", "create", "delete", "enter", "exit", "first",
9531  "level", "list", "moveInto", "promote", NULL
9532  };
9533  enum {
9534  VAR_COUNT, VAR_NUMBER, VAR_CREATE, VAR_DELETE, VAR_ENTER, VAR_EXIT, VAR_FIRST,
9535  VAR_LEVEL, VAR_LIST, VAR_MOVEINTO, VAR_PROMOTE
9536  };
9537  int index = -1;
9538 
9539  if (argc > 1) { index = strUniqueMatch (argv[1], options); }
9540 
9541  switch (index) {
9542  case VAR_COUNT:
9543  return setUintResult (ti, db->game->GetNumVariations());
9544 
9545  case VAR_NUMBER:
9546  return setUintResult (ti, db->game->GetVarNumber());
9547 
9548  case VAR_CREATE:
9549  if (! (db->game->AtVarStart() && db->game->AtVarEnd())) {
9550  db->game->MoveForward();
9551  db->game->AddVariation();
9552  db->gameAltered = true;
9553  }
9554  break;
9555 
9556  case VAR_DELETE:
9557  return sc_var_delete (cd, ti, argc, argv);
9558 
9559  case VAR_ENTER:
9560  return sc_var_enter (cd, ti, argc, argv);
9561 
9562  case VAR_EXIT:
9563  db->game->MoveExitVariation();
9564  break;
9565 
9566  case VAR_FIRST:
9567  return sc_var_first (cd, ti, argc, argv);
9568 
9569  case VAR_LEVEL:
9570  return setUintResult (ti, db->game->GetVarLevel());
9571 
9572  case VAR_LIST:
9573  return sc_var_list (cd, ti, argc, argv);
9574 
9575  case VAR_MOVEINTO:
9576  return sc_var_enter (cd, ti, argc, argv);
9577 
9578  case VAR_PROMOTE:
9579  db->gameAltered = true;
9580  return UI_Result(ti, db->game->MainVariation ());
9581 
9582  default:
9583  return InvalidCommand (ti, "sc_var", options);
9584  }
9585 
9586  return TCL_OK;
9587 }
9588 
9589 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9590 // sc_var_delete:
9591 // Deletes a specified variation.
9592 int sc_var_delete(ClientData, Tcl_Interp* ti, int, const char**) {
9593  auto err = db->game->DeleteVariation();
9594  if (err != ERROR_NoVariation)
9595  db->gameAltered = true;
9596  return UI_Result(ti, err);
9597 }
9598 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9599 // sc_var_first:
9600 // Promotes the specified variation of the current to be the
9601 // first in the list.
9602 int sc_var_first(ClientData, Tcl_Interp* ti, int, const char**) {
9603  auto err = db->game->FirstVariation();
9604  if (err != ERROR_NoVariation)
9605  db->gameAltered = true;
9606  return UI_Result(ti, err);
9607 }
9608 
9609 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9610 // sc_var_list:
9611 // Returns a Tcl list of the variations for the current move.
9612 int
9613 sc_var_list (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9614 {
9615  bool uci = (argc > 2) && ! strCompare("UCI", argv[2]);
9616  uint varCount = db->game->GetNumVariations();
9617  char s[100];
9618  for (uint varNumber = 0; varNumber < varCount; varNumber++) {
9619  db->game->MoveIntoVariation (varNumber);
9620  if (uci) db->game->GetNextMoveUCI (s);
9621  else db->game->GetSAN (s);
9622  // if (s[0] == 0) { strCopy (s, "(empty)"); }
9623  Tcl_AppendElement (ti, s);
9624  db->game->MoveExitVariation ();
9625  }
9626  return TCL_OK;
9627 }
9628 
9629 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9630 // sc_var_enter:
9631 // Moves into a specified variation.
9632 int
9633 sc_var_enter (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9634 {
9635  if (argc != 3) {
9636  return errorResult (ti, "Usage: sc_var enter <number>");
9637  }
9638 
9639  uint varNumber = strGetUnsigned (argv[2]);
9640  if (varNumber >= db->game->GetNumVariations()) {
9641  return errorResult (ti, "No such variation!");
9642  }
9643 
9644  db->game->MoveIntoVariation (varNumber);
9645  // Should moving into a variation also automatically play
9646  // the first variation move? Maybe it should depend on
9647  // whether there is a comment before the first move.
9648  // Uncomment the following line to auto-play the first move:
9649  db->game->MoveForward();
9650 
9651  return TCL_OK;
9652 }
9653 
9654 //////////////////////////////////////////////////////////////////////
9655 /// BOOK functions
9656 
9657 int
9658 sc_book (ClientData cd, Tcl_Interp * ti, int argc, const char ** argv)
9659 {
9660  static const char * options [] = {
9661  "load", "close", "moves", "positions", "movesupdate", "update", NULL
9662  };
9663  enum {
9664  BOOK_LOAD, BOOK_CLOSE, BOOK_MOVE, BOOK_POSITIONS, BOOK_MOVES_UPDATE, BOOK_UPDATE,
9665  };
9666  int index = -1;
9667 
9668  if (argc > 1) { index = strUniqueMatch (argv[1], options);}
9669 
9670  switch (index) {
9671  case BOOK_LOAD:
9672  return sc_book_load (cd, ti, argc, argv);
9673 
9674  case BOOK_CLOSE:
9675  return sc_book_close (cd, ti, argc, argv);
9676 
9677  case BOOK_MOVE:
9678  return sc_book_moves (cd, ti, argc, argv);
9679 
9680  case BOOK_POSITIONS:
9681  return sc_book_positions (cd, ti, argc, argv);
9682 
9683  case BOOK_UPDATE:
9684  return sc_book_update (cd, ti, argc, argv);
9685 
9686  case BOOK_MOVES_UPDATE:
9687  return sc_book_movesupdate (cd, ti, argc, argv);
9688 
9689  default:
9690  return InvalidCommand (ti, "sc_book", options);
9691  }
9692 
9693  return TCL_OK;
9694 }
9695 
9696 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9697 // sc_book_load:
9698 // Opens and loads a .bin book (fruit format)
9699 int
9700 sc_book_load (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9701 {
9702  if (argc != 4) {
9703  return errorResult (ti, "Usage: sc_book load bookfile slot");
9704  }
9705 
9706  uint slot = strGetUnsigned (argv[3]);
9707 
9708  int bookstate = polyglot_open(argv[2], slot);
9709 
9710  if (bookstate == -1 ) {
9711  return errorResult (ti, "Unable to load book");
9712  }
9713  if (bookstate > 0 ) {
9714  // state == 1: book is read only
9715  return setIntResult (ti, bookstate);
9716  }
9717  return TCL_OK;
9718 
9719 //--// if (polyglot_open(argv[2], slot) == -1 ) {
9720 //--// return errorResult (ti, "Unable to load book");
9721 //--// }
9722 //--// return TCL_OK;
9723 }
9724 
9725 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9726 // sc_book_close:
9727 // Closes the previously loaded book
9728 int
9729 sc_book_close (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9730 {
9731  if (argc != 3) {
9732  return errorResult (ti, "Usage: sc_book close slot");
9733  }
9734  uint slot = strGetUnsigned (argv[2]);
9735  if (polyglot_close(slot) == -1 ) {
9736  return errorResult (ti, "Error closing book");
9737  }
9738  return TCL_OK;
9739 }
9740 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9741 // sc_book_moves:
9742 // returns a list of all moves contained in opened book and their probability in a TCL list
9743 int
9744 sc_book_moves (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9745 {
9746  char moves[200] = "";
9747  char boardStr[100];
9748  if (argc != 3) {
9749  return errorResult (ti, "Usage: sc_book moves slot");
9750  }
9751  uint slot = strGetUnsigned (argv[2]);
9752  db->game->GetCurrentPos()->PrintFEN (boardStr, FEN_ALL_FIELDS);
9753  polyglot_moves(moves, (const char *) boardStr, slot);
9754  Tcl_AppendResult (ti, moves, NULL);
9755  return TCL_OK;
9756 }
9757 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9758 // sc_positions:
9759 // returns a TCL list of moves to a position in the book
9760 int
9761 sc_book_positions (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9762 {
9763  char moves[200] = "";
9764  char boardStr[100];
9765  if (argc != 3) {
9766  return errorResult (ti, "Usage: sc_book positions slot");
9767  }
9768  uint slot = strGetUnsigned (argv[2]);
9769  db->game->GetCurrentPos()->PrintFEN (boardStr, FEN_ALL_FIELDS);
9770  polyglot_positions(moves, (const char *) boardStr, slot);
9771  Tcl_AppendResult (ti, moves, NULL);
9772  return TCL_OK;
9773 }
9774 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9775 // sc_book_update:
9776 // updates the opened book with probability values
9777 int
9778 sc_book_update (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9779 {
9780  if (argc != 4) {
9781  return errorResult (ti, "Usage: sc_book update <probs> slot");
9782  }
9783  uint slot = strGetUnsigned (argv[3]);
9784  scid_book_update( (char*) argv[2], slot );
9785  return TCL_OK;
9786 }
9787 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9788 // sc_book_movesupdate:
9789 // updates the opened book with moves and probability values
9790 int
9791 sc_book_movesupdate (ClientData, Tcl_Interp * ti, int argc, const char ** argv)
9792 {
9793  if (argc != 6) {
9794  return errorResult (ti, "Usage: sc_book movesupdate <moves> <probs> slot tempfile");
9795  }
9796  uint slot = strGetUnsigned (argv[4]);
9797  scid_book_movesupdate( (char*) argv[2], (char*) argv[3], slot, (char*) argv[5] );
9798  return TCL_OK;
9799 }
9800 //////////////////////////////////////////////////////////////////////
9801 /// END of tkscid.cpp
9802 //////////////////////////////////////////////////////////////////////
const pieceT WK
Definition: common.h:241
int polyglot_open(const char *BookFile, const int BookNumber)
uint score
Definition: tree.h:49
int sc_name_correct(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:6021
unsigned char byte
Definition: common.h:89
#define PGN_STYLE_COMMENTS
Definition: game.h:145
void DumpHtmlBoard(DString *dstr, uint style, const char *dir, bool flip)
Definition: position.cpp:2879
errorT Random(const char *material)
Definition: position.cpp:3025
const IndexEntry * getIndexEntry(gamenumT g) const
Definition: scidbase.h:131
void PrintCompactStr(char *cboard)
Definition: position.cpp:2578
uint date_GetYear(dateT date)
Definition: date.h:52
squareT getTo() const
Definition: fullmove.h:66
const uint YEAR_MAX
Definition: date.h:43
void Truncate()
Definition: game.cpp:1002
int setUintResult(Tcl_Interp *ti, uint i)
Definition: tkscid.cpp:148
int sc_base_export(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:369
enginearglist
Definition: analysis.tcl:167
dateT lastDate
Definition: tkscid.cpp:6954
const squareT F2
Definition: common.h:349
const uint NUM_COLOR_TYPES
Definition: common.h:205
uint GetNumLines(void)
Definition: optable.h:212
byte resultT
Definition: common.h:187
const pieceT BB
Definition: common.h:242
void SetElos(bool b)
Definition: crosstab.h:168
int sc_move_forward(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5227
int sc_book_positions(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9761
void deleteFilter(const char *filterId)
Definition: scidbase.cpp:296
int polyglot_positions(char *moves, const char *fen, const int BookNumber)
byte * GetNags() const
Definition: game.h:343
errorT MoveIntoVariation(uint varNumber)
Definition: game.cpp:775
errorT setExtraInfo(const char *tagname, const char *new_value)
Store an extra information about the database (type, description, etc..)
Definition: scidbase.h:108
const uint HPSIG_Empty
Definition: matsig.h:246
bool pgnParseGame(const char *input, size_t inputLen, Game &game, PgnParseLog &log)
Convert PGN text into a SCID&#39;s Game object.
Definition: pgnparse.h:374
crosstableModeT BestMode(void)
Definition: crosstab.cpp:399
uint strLength(const char *str)
Definition: misc.h:411
bool GetStartFlag() const
Definition: indexentry.h:252
const colorT WHITE
Definition: common.h:207
int sc_filter_first(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:1939
void Fill(byte value)
Sets all values.
Definition: hfilter.h:109
dateT GetDate() const
Definition: indexentry.h:107
void SortByElo()
Definition: crosstab.h:162
void SetEventStr(const char *str)
Definition: game.h:381
int sc_name_match(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:6910
pieceT piece_Type(pieceT p)
Definition: common.h:292
void ClearNotes()
Definition: optable.cpp:540
int sc_game_import(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:2678
int strExactMatch(const char *keyStr, const char **strTable)
Definition: misc.h:258
const char * strTrimLeft(const char *target, const char *trimChars)
Definition: misc.cpp:314
void MakeSANString(simpleMoveT *sm, char *s, sanFlagT flag)
Definition: position.cpp:1888
int sc_name_info(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:6327
int polyglot_close(const int BookNumber)
const pieceT BK
Definition: common.h:242
const squareT C8
Definition: common.h:355
int sc_pos_addNag(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5423
sqsqname
Definition: board.tcl:292
void SetMaxThemeMoveNumber(uint x)
Definition: optable.h:213
const pieceT BN
Definition: common.h:242
bool sameYear
Definition: tkscid.cpp:753
eloT AvgRating()
Definition: crosstab.cpp:421
int sc_eco_game(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1416
const uint OPTABLE_MAX_ROWS
Definition: optable.h:26
int sc_pos_isPromo(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5749
int sc_game_pgn(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:3861
uint AvgElo(colorT color, uint *count, uint *oppScore, uint *oppPerf)
Definition: optable.cpp:1925
void GetPrevMoveUCI(char *str) const
Definition: game.cpp:1721
#define PGN_STYLE_INDENT_COMMENTS
Definition: game.h:147
const char * titleStr[NUM_TITLES]
Definition: tkscid.cpp:9220
Definition: timer.h:23
iterator end()
Definition: movelist.h:95
bool SetPgnFormatFromString(const char *str)
Definition: game.cpp:597
const char * getTitle() const
Definition: spellchk.cpp:388
uint PercentFreq(resultT result)
Definition: optable.cpp:629
uint scid_TB_Init(const char *)
Definition: probe.cpp:350
void SetDecimalPointChar(char ch)
Definition: crosstab.h:174
const char * GetBlackName(const NameBase *nb) const
Definition: indexentry.h:228
int sc_search_board(Tcl_Interp *ti, const scidBaseT *dbase, HFilter filter, int argc, const char **argv)
Definition: tkscid.cpp:8531
int sc_book_load(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9700
uint perfCount
Definition: tree.h:54
bool sameColors
Definition: tkscid.cpp:748
const byte * GetHomePawnData() const
Definition: indexentry.h:118
#define PGN_STYLE_UNICODE
Definition: game.h:156
int sc_game_tags_share(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4572
bool strEqual(const char *str1, const char *str2)
Definition: misc.h:224
errorT FindExactName(nameT nt, const char *str, idNumberT *idPtr) const
Finds an exact full, case-sensitive name.
Definition: namebase.h:190
uint freq[NUM_RESULT_TYPES]
Definition: tree.h:47
size_t redoSize() const
Definition: containers.h:44
const errorT OK
Definition: error.h:23
void SetMoveComment(const char *comment)
Definition: game.cpp:687
int str_prefix_len(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:308
#define PGN_STYLE_NO_NULL_MOVES
Definition: game.h:155
Format and store errors.
Definition: pgnparse.h:332
FastGame getGame(const IndexEntry *ie) const
Definition: scidbase.h:141
const uint NUM_RESULT_TYPES
Definition: common.h:186
patternT * addPattern(patternT *pattHead, patternT *addPatt)
Definition: tkscid.cpp:8755
bool strIsPrefix(const char *prefix, const char *longStr)
Definition: misc.h:331
void addDate(dateT date)
Definition: tkscid.cpp:6957
void SetSearchDepth(uint ply)
Definition: engine.h:218
const squareT F8
Definition: common.h:355
const char * strFirstChar(const char *target, char matchChar)
Definition: misc.cpp:264
UndoRedo< Game, 100 > gameAlterations
Definition: scidbase.h:359
ecoT GetEco() const
Definition: game.h:410
Definition: optable.h:74
bool MaterialMatch(ByteBuffer *buf, byte *min, byte *max, patternT *pattern, int minPly, int maxPly, int matchLength, bool oppBishops, bool sameBishops, int minDiff, int maxDiff)
Definition: game.cpp:1176
int sc_search_material(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:8878
void SetTiebreaks(bool b)
Definition: crosstab.h:171
void SetPruning(bool b)
Definition: engine.h:239
const squareT H1
Definition: common.h:348
void AddEndMaterial(matSigT ms, bool inFilter)
Definition: optable.cpp:2169
simpleMoveT * Get(size_t index)
Definition: movelist.h:116
void SetBlackRatingType(byte b)
Definition: game.h:392
void restoreLocation(const GameSavedPos &savedPos)
Definition: game.h:282
bool operator<(const gNumListT &a) const
Definition: tkscid.cpp:743
void SetSeparateScoreGroups(bool b)
Definition: crosstab.h:173
static std::pair< errorT, std::unique_ptr< PBook > > ReadEcoFile(const char *FileName)
Read a file with a list of ECO codes and creates a PBook object.
Definition: pbook.cpp:135
resultT GetResult() const
Definition: indexentry.h:109
const ecoT ECO_None
Definition: common.h:168
const squareT NULL_SQUARE
Definition: common.h:357
bool isCastle() const
Definition: fullmove.h:65
int sc_move_addUCI(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5156
char * strDuplicate(const char *original)
Definition: misc.cpp:206
const char * errMsgNotOpen(Tcl_Interp *ti)
Definition: tkscid.cpp:270
bool IsKingInCheck()
Definition: position.h:255
const squareT D2
Definition: common.h:349
errorT AddVariation()
Definition: game.cpp:912
promopiece
Definition: calvar.tcl:258
const char * FindExtraTag(const char *tag) const
Definition: game.cpp:634
uint max(int a, int b)
Definition: crosstab.cpp:237
const rankT NO_RANK
Definition: common.h:361
const Stats & getStats() const
Statistics.
Definition: scidbase.cpp:336
void SetSwissColors(bool b)
Definition: crosstab.h:172
void SetDeleteFlag(bool b)
Definition: indexentry.h:268
bool scid_TB_Available(matSigT)
Definition: probe.cpp:354
uint64_t hash
Definition: tkscid.cpp:741
Definition: indexentry.h:295
size_t normalize(std::string *name) const
Definition: spellchk.h:66
const cachedTreeT * Lookup(Position *pos)
Definition: tree.h:166
idNumberT GetSite() const
Definition: indexentry.h:105
const errorT ERROR_FileWrite
Definition: error.h:32
UI_res_t sc_info_priority(UI_extra_t, UI_handle_t ti, int argc, const char **argv)
Definition: sc_info.cpp:35
void init()
init() - initialize the pool of databases.
Definition: dbasepool.cpp:37
void SetBlackRatingType(byte b)
Definition: indexentry.h:150
errorT SetStartFen(const char *fenStr)
Setup the start position from a FEN string and remove all the moves.
Definition: game.cpp:603
uint dateT
Definition: common.h:147
static FullMove getMove(uint code, uint ply=0)
Definition: stored.h:40
#define ASSERT(f)
Definition: common.h:59
const char SCID_VERSION_STRING[]
Definition: common.h:46
const squareT H7
Definition: common.h:354
void scid_book_update(char *probs, const int BookNumber)
void GuessNumRows(void)
Definition: optable.cpp:716
const squareT C1
Definition: common.h:348
const char * GetWhiteName(const NameBase *nb) const
Definition: indexentry.h:225
const errorT ERROR_BadArg
Definition: error.h:28
const char * GetEco() const
Definition: optable.h:197
void SetWhiteElo(eloT elo)
Definition: game.h:389
const rankT RANK_8
Definition: common.h:361
const char * ratingTypeNames[17]
Definition: game.cpp:71
void AddPgnTag(const char *tag, const char *value)
Definition: game.cpp:619
const char * GetMoveComment() const
Definition: game.h:357
int sc_filter_prev(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:1985
const char * Data()
Definition: dstring.h:28
const pieceT BQ
Definition: common.h:242
const char * GetEventName(const NameBase *nb) const
Definition: indexentry.h:231
int sc_game_info(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:3042
int sc_pos_isAt(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5714
uint32_t matSigT
Definition: matsig.h:25
crosstableModeT
Definition: crosstab.h:49
uint getNameFreq(nameT nt, idNumberT id)
Definition: scidbase.h:209
void PrintCompactStrFlipped(char *cboard)
Definition: position.cpp:2607
const pieceT WQ
Definition: common.h:241
idNumberT GetEvent() const
Definition: indexentry.h:104
const pieceT KING
Definition: common.h:226
void includeAll()
Definition: hfilter.h:253
const resultT RESULT_Black
Definition: common.h:191
void initTreeNode(treeNodeT *tnode)
Definition: tree.h:60
int sc_report_create(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7745
Definition: misc.h:63
errorT DecodeNextMove(ByteBuffer *buf, simpleMoveT *sm)
Definition: game.cpp:3442
int scid_book_movesupdate(char *moves, char *probs, const int BookNumber, char *tempfile)
names
Definition: tablebase.tcl:257
int sc_name_plist(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7007
errorT Close()
Definition: scidbase.cpp:114
squareT strGetSquare(const char *str)
Definition: misc.cpp:614
const pieceT BP
Definition: common.h:242
uint PawnHashValue(void)
Definition: position.h:227
void SetEventDate(dateT date)
Definition: game.h:387
int strUniqueMatch(const char *keyStr, const char **strTable)
Definition: misc.h:255
uint gNumber
Definition: tkscid.cpp:742
const colorT BLACK
Definition: common.h:208
byte get(gamenumT gnum) const
Definition: hfilter.h:260
errorT ReadFromCompactStr(const byte *str)
Definition: position.cpp:2550
Definition: pfinder.tcl:6
int Think(MoveList *mlist)
Definition: engine.cpp:1379
errorT RelocatePiece(squareT fromSq, squareT toSq)
Definition: position.cpp:1805
const resultT RESULT_OPPOSITE[4]
Definition: common.h:198
bool AtStart() const
Definition: game.h:329
uint eloSum
Definition: tree.h:53
void DoSimpleMove(simpleMoveT *sm)
Definition: position.cpp:1578
bool hpSig_PossibleMatch(uint hpSig, const byte *changeList)
Definition: matsig.cpp:97
errorT DecodeTags(ByteBuffer *buf, bool storeTags)
Definition: game.cpp:3192
const squareT E7
Definition: common.h:354
void store(TElem *current)
Stores a copy of an element into the undo queue.
Definition: containers.h:51
uint Size()
Definition: movelist.h:96
uint moveCount
Definition: tree.h:83
TreeCache treeCache
Definition: scidbase.h:349
int sc_pos_hash(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5650
matSigT matsig_setCount(matSigT m, pieceT p, uint count)
Definition: matsig.h:159
int sc_var(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9527
errorT saveGame(Game *game, gamenumT replacedGameId=INVALID_GAMEID)
Add or replace a game into the database.
Definition: scidbase.cpp:177
void strTrimMarkCodes(char *str)
Definition: misc.cpp:371
byte fyleT
Definition: common.h:108
void SortByName()
Definition: crosstab.h:161
int appendUintResult(Tcl_Interp *ti, uint i)
Definition: tkscid.cpp:161
uint strPrefix(const char *s1, const char *s2)
Definition: misc.h:314
errorT endTransaction(gamenumT gameId=INVALID_GAMEID)
Update caches and flush the database&#39;s files.
Definition: scidbase.cpp:157
class SpellChecker - name spelling
Definition: spellchk.h:259
const uint MAX_LEGAL_MOVES
Definition: movelist.h:27
const eloT MAX_ELO
Definition: indexentry.h:37
const gamenumT INVALID_GAMEID
Definition: scidbase.h:37
void SetRound(idNumberT id)
Definition: indexentry.h:162
rankT rankMatch
Definition: game.h:119
Index * idx
Definition: scidbase.h:346
size_t UsedSize()
Definition: tree.h:153
void setDuplicates(uint *duplicates)
Definition: scidbase.h:277
const errorT ERROR_NoVariation
Definition: error.h:70
Definition: book.tcl:8
dateT GetDate() const
Definition: game.h:401
bool sameRound
Definition: tkscid.cpp:751
void scid_Exit(void *)
Definition: tkscid.cpp:66
std::vector< uint32_t > generateHashMap(nameT nt) const
For every name generates a 32bit hash with the first 4 chars.
Definition: namebase.h:206
scidBaseT * getBase(int baseHandle)
getBase() - get a database from the pool.
Definition: dbasepool.cpp:59
int sc_game_pop(ClientData, Tcl_Interp *, int, const char **)
Definition: tkscid.cpp:3992
bool * parseTitles(const char *str)
Definition: tkscid.cpp:9233
matSigT GetFinalMatSig() const
Definition: indexentry.h:114
dateT GetEventDate() const
Definition: game.h:402
errorT MoveToLocationInPGN(unsigned stopLocation)
Definition: game.cpp:847
int sc_game_push(ClientData, Tcl_Interp *, int argc, const char **argv)
Definition: tkscid.cpp:4012
byte Get(gamenumT index) const
Gets the value at index.
Definition: hfilter.h:83
char ecoStringT[6]
Definition: common.h:166
bool isReadOnly() const
Definition: scidbase.h:98
int UI_res_t
Definition: ui_tcltk.h:30
#define PROBE_OPTIMAL
Definition: tkscid.cpp:110
void SetMaxTableLines(uint nlines)
Definition: optable.h:200
uint16_t GetNumHalfMoves() const
Definition: indexentry.h:113
const char * GetName(nameT nt, idNumberT id) const
Retrieve a name.
Definition: namebase.h:162
uint GetVarNumber() const
Definition: game.h:311
bool sameDay
Definition: tkscid.cpp:755
gamenumT Count() const
Return the number of nonzero values in filter.
Definition: hfilter.h:54
void resize(size_t count)
Definition: movelist.h:108
int sc_eco_translate(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1534
const squareT D7
Definition: common.h:354
#define PGN_STYLE_INDENT_VARS
Definition: game.h:148
void SetFormat(const char *str)
Definition: optable.cpp:575
const squareT A8
Definition: common.h:355
void SetResult(resultT res)
Definition: game.h:388
void date_DecodeToString(dateT date, char *str)
Definition: date.h:87
void scid_TB_SetCacheSize(uint)
Definition: probe.cpp:346
colorT color_Flip(colorT c)
Definition: common.h:214
bool AtVarEnd() const
Definition: game.h:328
int sc_move_add(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5098
void SetLaTeXOutput()
Definition: crosstab.h:156
bool hpSig_Prefix(const byte *changeListA, const byte *changeListB)
Definition: matsig.cpp:140
int str_is_prefix(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:294
errorT search_index(const scidBaseT *base, HFilter &filter, int argc, const char **argv, const Progress &progress)
search_index() - search for games using game&#39;s IndexEntry info
const fyleT NO_FYLE
Definition: common.h:366
int sc_tree(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7870
const resultT RESULT_Draw
Definition: common.h:192
const char * errMsgSearchInterrupted(Tcl_Interp *ti)
Definition: tkscid.cpp:276
int sc_tree_cachesize(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:8456
transmsg
TODO Put in the right letters for greek.
Definition: language.tcl:46
uint64_t yearSum
Definition: tree.h:57
int sc_game_moves(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:3692
bool inUse
Definition: scidbase.h:347
void GetNextMoveUCI(char *str)
Definition: game.cpp:1738
uint32_t idNumberT
Definition: common.h:152
uint TotalMaterial()
Definition: position.h:172
byte GetWhiteRatingType() const
Definition: game.h:408
const byte NAG_GoodMove
Definition: game.h:46
char san[8]
Definition: tree.h:46
uint sc_base_duplicates(scidBaseT *dbase, UI_handle_t ti, int argc, const char **argv)
Definition: tkscid.cpp:826
int sc_move_back(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5205
#define DATE_MAKE(y, m, d)
Definition: date.h:45
int sc_tree_cacheinfo(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:8469
const dateT ZERO_DATE
Definition: date.h:35
void SetWhiteStr(const char *str)
Definition: game.h:383
const uint HPSIG_StdStart
Definition: matsig.h:249
iterator begin()
Definition: movelist.h:94
const char * GetPreviousMoveComment() const
Return the comment on the move previously played by CurrentPos->ToMove If there are no previous moves...
Definition: game.h:350
void SetPosition(Position *pos)
Definition: engine.cpp:1336
void beginTransaction()
This function must be called before modifying the games of the database.
Definition: scidbase.cpp:151
patternT * next
Definition: game.h:122
eloT GetWhiteElo() const
Definition: indexentry.h:99
void GetSAN(char *str)
Definition: game.cpp:1691
void TopPlayers(DString *dstr, colorT c, uint count)
Definition: optable.cpp:1587
#define PGN_STYLE_MOVENUM_SPACE
Definition: game.h:151
const pieceT BISHOP
Definition: common.h:229
std::pair< errorT, size_t > transformIndex(HFilter hfilter, const Progress &progress, TOper entry_op)
Transform the IndexEntries of the games included in hfilter.
Definition: scidbase.h:297
bool RemoveExtraTag(const char *tag)
Definition: game.cpp:645
const squareT A1
Definition: common.h:348
errorT FirstVariation()
Definition: game.cpp:934
Definition: game.h:116
const pieceT KNIGHT
Definition: common.h:230
const squareT A7
Definition: common.h:354
#define PGN_STYLE_VARS
Definition: game.h:146
void BackToStart()
Definition: bytebuf.cpp:37
size_t undoSize() const
Definition: containers.h:43
byte game_parseNag(std::pair< const char *, const char *> strview)
Definition: game.cpp:140
int sc_base_piecetrack(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:546
UI_res_t sc_name_retrievename(UI_handle_t ti, const SpellChecker &sp, int argc, const char **argv)
Definition: tkscid.cpp:6285
int sc_pos_analyze(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5453
ushort GetCurrentPly() const
Definition: game.h:298
Definition: uci.tcl:8
const char RESULT_STR[4][4]
Definition: common.h:196
int sc_game_startBoard(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4086
byte GetBlackRatingType() const
Definition: game.h:409
ecoT ecoCode
Definition: tree.h:51
eloT getElo(dateT date) const
Definition: spellchk.h:149
const treeT & getTree() const
Definition: tree.h:112
int sc_game_firstMoves(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:2659
std::string newFilter()
A Filter is a selection of games, usually obtained searching the database.
Definition: scidbase.cpp:262
Filter * dbFilter
Definition: scidbase.h:351
int sc_eco_read(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1456
errorT DeleteVariation()
Definition: game.cpp:986
int sc_pos_isLegal(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5770
void SetNumberedColumns(bool b)
Definition: crosstab.h:175
This class stores the database&#39;s names (players, events, sites and rounds).
Definition: namebase.h:33
void SetTallies(bool b)
Definition: crosstab.h:170
int sc_tree_move(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7919
int main(int argc, char *argv[])
Definition: tkscid.cpp:83
gameFormatT
Definition: game.h:137
Search for an exact position (same material in the same squares).
Definition: searchpos.h:67
bool sameEvent
Definition: tkscid.cpp:749
const pieceT PIECE_FLIP[MAX_PIECE_TYPES]
Definition: common.h:256
void SetTitles(bool b)
Definition: crosstab.h:167
uint GetFlagStr(char *dest, const char *flags) const
Definition: indexentry.h:548
errorT RemoveNag(bool isMoveNag)
Definition: game.cpp:400
const pieceT * GetBoard() const
Definition: position.h:197
filterOpT strGetFilterOp(const char *str)
Definition: misc.h:93
UI_res_t sc_name_ratings(UI_handle_t ti, scidBaseT &dbase, const SpellChecker &sp, int argc, const char **argv)
Definition: tkscid.cpp:7128
void SetSiteStr(const char *str)
Definition: game.h:382
void EndMaterialReport(DString *dstr, const char *repGames, const char *allGames)
Definition: optable.cpp:2179
int sc_pos_html(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5679
uint perfSum
Definition: tree.h:55
void PrintStemLine(DString *dstr, uint format, bool exclude)
Definition: optable.cpp:1005
void SetDate(dateT date)
Definition: indexentry.h:166
void SetPlainOutput()
Definition: crosstab.h:153
errorT LoadStandardTags(const IndexEntry *ie, const NameBase *nb)
Definition: game.cpp:2584
#define MAX_TREE_NODES
Definition: tree.h:38
const errorT ERROR_UserCancel
Definition: error.h:27
void SetThreeWin(bool threewin)
Definition: crosstab.h:159
const matSigT MATSIG_Empty
Definition: matsig.h:176
Game * game
Definition: scidbase.h:356
const byte NAG_DubiousMove
Definition: game.h:51
const pieceT EMPTY
Definition: common.h:239
const pieceT WB
Definition: common.h:241
bool Add(Position *pos, treeT *tree, Filter *filter)
Definition: tree.h:215
const squareT G1
Definition: common.h:348
const squareT F7
Definition: common.h:354
void UndoSimpleMove(simpleMoveT *sm)
Definition: position.cpp:1721
int sc_var_enter(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9633
resultT GetResult() const
Definition: game.h:403
void AddChar(char ch)
Definition: dstring.h:32
static bool IsValidNameType(nameT nt)
Validate a nameT type.
Definition: namebase.h:220
sort?type?
Definition: analysis.tcl:320
int sc_tree_search(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:8017
ushort GetFullMoveCount() const
Definition: position.h:165
char transPiecesChar(char c)
Definition: game.cpp:59
bool strIsCasePrefix(const char *prefix, const char *longStr)
Definition: misc.h:347
int sc_game_novelty(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:3779
const pieceT QUEEN
Definition: common.h:227
const resultT RESULT_White
Definition: common.h:190
void CalcSANStrings(sanListT *sanList, sanFlagT flag)
Definition: position.cpp:2474
bool Add(OpLine *line)
Definition: optable.cpp:646
const matSigT MATSIG_StdStart
Definition: matsig.h:178
void SetRoundStr(const char *str)
Definition: game.h:385
uint32_t uint
Definition: common.h:91
void TopEcoCodes(DString *dstr, uint count)
Definition: optable.cpp:1806
void PrintFEN(char *str, uint flags) const
Definition: position.cpp:2804
std::pair< errorT, size_t > transformNames(nameT nt, HFilter hfilter, const Progress &progress, const std::vector< std::string > &newNames, TInitFunc fnInit, TMapFunc getID)
Transform the names of the games included in hfilter.
Definition: scidbase.h:471
int errorResult(Tcl_Interp *ti, errorT err, const char *errorMsg=0)
Definition: tkscid.cpp:227
errorT saveGameHelper(Game *game, gamenumT gameId)
Definition: scidbase.cpp:184
moveSortE
Definition: tkscid.cpp:7962
const uint MAX_PIECE_TYPES
Definition: common.h:247
Definition: board.tcl:276
bool strIsSurnameOnly(const char *name)
Definition: misc.cpp:477
uint matsig_getCount(matSigT m, pieceT p)
Definition: matsig.h:149
#define PROBE_SUMMARY
Definition: tkscid.cpp:108
void SetBlackStr(const char *str)
Definition: game.h:384
bool gameAltered
Definition: scidbase.h:358
const squareT H2
Definition: common.h:349
int appendCharResult(Tcl_Interp *ti, char ch)
Definition: tkscid.cpp:200
uint appendUintElement(Tcl_Interp *ti, uint i)
Definition: tkscid.cpp:174
static uint Performance(uint oppAvg, uint percentage)
Definition: crosstab.cpp:36
const squareT B2
Definition: common.h:349
bool strIsUnknownName(const char *str)
Definition: misc.cpp:465
idNumberT GetRound() const
Definition: indexentry.h:106
idNumberT GetWhite() const
Definition: indexentry.h:98
void strGetIntegers(const char *str, int *results, uint nResults)
Definition: misc.cpp:539
void SetDate(dateT date)
Definition: game.h:386
const squareT C2
Definition: common.h:349
const pieceT ROOK
Definition: common.h:228
const char * GetBlackStr() const
Definition: game.h:399
IndexEntry * FetchEntry(gamenumT g)
FetchEntry() - return a modifiable pointer to a game&#39;s IndexEntry.
Definition: index.h:204
bool GetDeleteFlag() const
Definition: indexentry.h:258
long long CentiSecs() const
Definition: timer.h:32
std::pair< const char *, unsigned > WriteToPGN(uint lineWidth=0, bool NewLineAtEnd=false, bool newLineToSpaces=true)
Definition: game.cpp:2563
Definition: move.tcl:20
void SetExcludeMove(const char *s)
Definition: optable.h:192
uint GetTotalCount()
Definition: optable.h:189
int sc_name_edit(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:6135
int sc_game_save(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4040
void SetAges(bool b)
Definition: crosstab.h:166
TElem * redo(TElem *current)
Retrieve the last element from the redo queue.
Definition: containers.h:76
int sc_filter_last(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:1953
const char RESULT_CHAR[4]
Definition: common.h:195
uint GetDay() const
Definition: indexentry.h:219
void SetCountries(bool b)
Definition: crosstab.h:169
bool sameSite
Definition: tkscid.cpp:750
byte GetStoredLineCode() const
Definition: indexentry.h:115
std::string getMoveSAN(int ply_to_skip, int count)
Definition: fastgame.h:439
eloT GetBlackElo() const
Definition: indexentry.h:102
uint totalCount
Definition: tree.h:84
size_t numCorrectNames(const nameT &nt) const
Definition: spellchk.h:373
filterOpT
Definition: misc.h:91
const errorT ERROR_InvalidMove
Definition: error.h:63
bool strAlphaContains(const char *longStr, const char *keyStr)
Definition: misc.h:401
int sc_pos_probe(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5891
Implements a parser that converts PGN text into SCID&#39;s Game objects.
int sc_pos_getNags(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:5630
int sc_info(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4813
const squareT H8
Definition: common.h:355
bool AtVarStart() const
Definition: game.h:327
statsfmt
Definition: optable.tcl:704
uint MaterialValue(colorT c)
Definition: position.cpp:1860
int strGetInteger(const char *str)
Definition: misc.h:185
const squareT B1
Definition: common.h:348
void closeAll()
closeAll() - close all the databases in the pool.
Definition: dbasepool.cpp:46
bool checkDuplicate(scidBaseT *base, const IndexEntry *ie1, const IndexEntry *ie2, dupCriteriaT *cr)
Definition: tkscid.cpp:761
errorT MoveBackup()
Definition: game.cpp:759
ushort eloT
Definition: common.h:164
flipw ?newstate?
Definition: board.tcl:1465
uint eloCount
Definition: tree.h:52
uint PercentScore(void)
Definition: optable.cpp:587
const NameNormalizer & getGeneralCorrections(const nameT &nt) const
Definition: spellchk.h:334
uint GetNumVariations() const
Definition: game.h:302
errorT restoreFilter(Filter *filter) const
Definition: tree.h:109
TElem * undo(TElem *current)
Retrieve the last element from the undo queue.
Definition: containers.h:67
bool AtEnd() const
Definition: game.h:330
const pieceT PAWN
Definition: common.h:231
void DumpLatexBoard(DString *dstr, bool flip)
Definition: position.cpp:2955
int InvalidCommand(Tcl_Interp *ti, const char *majorCmd, const char **minorCmds)
Definition: tkscid.cpp:249
Position * GetCurrentPos()
Definition: game.h:292
const squareT G8
Definition: common.h:355
eloT GetWhiteElo() const
Definition: game.h:404
simpleMoveT * GetCurrentMove()
Definition: game.h:295
const char * GetWhiteStr() const
Definition: game.h:398
const squareT G7
Definition: common.h:354
byte GetWhiteRatingType() const
Definition: indexentry.h:100
Definition: board.tcl:17
void SetEventDate(dateT edate)
Definition: indexentry.h:170
void AddPgnStyle(uint mask)
Definition: game.h:430
Filter * treeFilter
Definition: scidbase.h:352
errorT ReadCoordMove(simpleMoveT *m, const char *s, int slen, bool reverse)
Definition: position.cpp:2081
bool IsPromoMove(squareT from, squareT to)
Definition: position.cpp:1555
uint HashValue(void)
Definition: position.h:226
void SetPgnFormat(gameFormatT gf)
Definition: game.h:433
ushort ecoT
Definition: common.h:165
void sortTreeMoves(treeT *tree, int sortMethod, colorT toMove)
Definition: tkscid.cpp:7970
bool ExactMatch(Position *pos, ByteBuffer *buf, simpleMoveT *sm, gameExactMatchT searchType, bool *neverMatch)
Definition: game.cpp:1317
int sc_book(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
BOOK functions.
Definition: tkscid.cpp:9658
int strCaseCompare(const char *str1, const char *str2)
Definition: misc.h:200
decltype(extraTags_) const & GetExtraTags() const
Definition: game.h:376
uint getDuplicates(gamenumT gNum)
Definition: scidbase.h:281
#define MATSIG_FlipColor(x)
Definition: matsig.h:108
int sc_info_limit(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4914
void SetMoveOrderID(uint id)
Definition: optable.h:118
#define GAME_DECODE_ALL
Definition: game.h:128
errorT MoveForward()
Definition: game.cpp:742
const squareT D1
Definition: common.h:348
ecoT GetEcoCode() const
Definition: indexentry.h:116
void eco_ToExtendedString(ecoT ecoCode, char *ecoStr)
Definition: misc.h:113
uint32_t strGetUnsigned(const char *str)
Definition: misc.h:195
const squareT B8
Definition: common.h:355
pieceT pieceMatch
Definition: game.h:118
idNumberT GetBlack() const
Definition: indexentry.h:101
void GenerateMoves(MoveList *mlist, pieceT mask, genMovesT genType, bool maybeInCheck)
Definition: position.cpp:784
std::vector< idNumberT > getFirstMatches(nameT nt, const char *str, size_t maxMatches) const
Get the first few matches of a name prefix.
Definition: namebase.h:140
int sc_info_tb(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4980
unsigned short errorT
Definition: error.h:20
void SetMinDepthCheckTime(uint depth)
Definition: engine.h:231
const sanFlagT SAN_NO_CHECKTEST
Definition: position.h:38
void SortByCountry()
Definition: crosstab.h:164
void Set(gamenumT index, byte value)
Sets the value at index.
Definition: hfilter.h:89
const sanFlagT SAN_CHECKTEST
Definition: position.h:39
int sc_eco_base(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1308
uint nResults[NUM_RESULT_TYPES]
Definition: scidbase.h:46
Definition: errors.tcl:17
Definition: tree.h:81
const char * strNextWord(const char *str)
Definition: misc.cpp:451
const PlayerElo * getPlayerElo(const char *name) const
Definition: spellchk.h:361
eloT GetBlackElo() const
Definition: game.h:405
pieceT piece_FromChar(int x)
Definition: common.h:328
char square_FyleChar(squareT sq)
Definition: common.h:520
errorT MainVariation()
Definition: game.cpp:949
void ClearExtraTags()
Definition: game.h:377
std::vector< const char * > find(const nameT &nt, const char *name, uint nMaxRes=10) const
Definition: spellchk.h:318
char * strAppend(char *target, const char *extra)
Definition: misc.cpp:189
int language
Definition: game.cpp:28
int setResult(Tcl_Interp *ti, const char *str)
Definition: tkscid.cpp:124
const pieceT WM
Definition: common.h:245
int sc_var_first(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:9602
const squareT B7
Definition: common.h:354
int sc_pos_setComment(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5989
uint strGetRatingType(const char *name)
Definition: game.cpp:80
Defines the classes used to search for positions.
const errorT ERROR_FileReadOnly
Definition: error.h:41
const char INDEX_SUFFIX[]
Definition: index.h:40
static bool PgnFormatFromString(const char *str, gameFormatT *fmt)
Definition: game.cpp:574
UI_res_t UI_Result(UI_handle_t ti, errorT res)
UI_Result() - pass the result of an operation from c++ to UI.
Definition: ui.h:140
void strTrimMarkup(char *str)
Definition: misc.cpp:415
GameSavedPos currentLocation() const
Definition: game.h:279
void Append(uint i)
Definition: dstring.h:46
const byte RATING_Elo
Definition: common.h:172
int sc_report_select(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7832
int getClipBase()
getClipBase() - return the handle of the clipbase
Definition: dbasepool.cpp:65
int sc_game_tags(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4280
int sc_pos_moves(ClientData, Tcl_Interp *ti, int argc, const char **)
Definition: tkscid.cpp:5872
eloT GetElo(idNumberT id) const
Definition: namebase.h:128
byte * GetNextNags() const
Definition: game.h:344
void SetDecimalChar(char c)
Definition: optable.h:187
size_t size() const
Definition: hfilter.h:240
void push_back(Tcl_Obj *value)
Definition: ui_tcltk.h:154
int sc_pos_matchMoves(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5801
#define GAME_DECODE_NONE
Definition: game.h:125
squareT to
Definition: movelist.h:39
errorT MoveExitVariation()
Definition: game.cpp:795
void SetHtmlStyle(uint style)
Definition: game.h:441
bool report(size_t done, size_t total) const
Definition: misc.h:75
int sc_move(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5033
colorpct ?col?
Definition: ptracker.tcl:77
bool scid_TB_compiled(void)
Definition: probe.cpp:333
void SetHtmlOutput()
Definition: crosstab.h:154
const uint RESULT_SCORE[4]
Definition: common.h:194
int setIntResult(Tcl_Interp *ti, int i)
Definition: tkscid.cpp:135
squareT from
Definition: movelist.h:38
colorT piece_Color(pieceT p)
Definition: common.h:285
errorT WriteEntry(const IndexEntry *ie, gamenumT idx)
WriteEntry() - modify a game in the Index.
Definition: index.cpp:160
Definition: game.h:167
void SetWhiteElo(eloT elo)
Definition: indexentry.h:134
errorT DecodeStart(ByteBuffer *buf, bool decodeTags=false)
Definition: game.cpp:3508
void TruncateStart()
Definition: game.cpp:1021
ByteBuffer * bbuf
Definition: scidbase.h:350
void strip(bool variations, bool comments, bool NAGs)
Definition: game.cpp:491
int polyglot_moves(char *moves, const char *fen, const int BookNumber)
void MoveToPly(int hmNumber)
Definition: game.h:274
int sc_var_list(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9613
ECO Classification functions.
Definition: tkscid.cpp:1237
const pieceT WP
Definition: common.h:241
dateT getBirthdate() const
Definition: spellchk.cpp:461
void SetWhiteRatingType(byte b)
Definition: game.h:391
UI_res_t sc_name(UI_extra_t cd, UI_handle_t ti, int argc, const char **argv)
Definition: tkscid.cpp:7426
PListSort(scidBaseT *dbase, const std::vector< PlayerActivity > &activity, int sortOrder)
Definition: tkscid.cpp:6970
const squareT E2
Definition: common.h:349
const char * strFirstWord(const char *str)
Definition: misc.cpp:438
simpleMoveT sm
Definition: tree.h:45
int gameNumber
Definition: scidbase.h:357
int sc_report(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7516
const char * translate(Tcl_Interp *ti, const char *name, const char *defaultText)
Definition: tkscid.cpp:214
std::vector< scidBaseT::TreeStat > getTreeStat(const HFilter &filter)
Definition: scidbase.cpp:458
const errorT ERROR_FileNotOpen
Definition: error.h:36
void ThemeReport(DString *dstr, uint argc, const char **argv)
Definition: optable.cpp:2074
bool hasEloData() const
Definition: spellchk.h:369
const char RESULT_LONGSTR[4][8]
Definition: common.h:197
void ClearNags()
Definition: game.h:339
int sc_move_pgn(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5252
bool GetVariationsFlag() const
Definition: indexentry.h:256
fyleT fyleMatch
Definition: game.h:120
int sc_search_header(ClientData, Tcl_Interp *ti, scidBaseT *base, HFilter &filter, int argc, const char **argv)
Definition: tkscid.cpp:9257
dateT firstDate
Definition: tkscid.cpp:6953
const int MAX_BASES
Definition: tkscid.cpp:57
UI_res_t sc_name_spellcheck(UI_handle_t ti, scidBaseT &dbase, const SpellChecker &sp, int argc, const char **argv)
Definition: tkscid.cpp:7256
const PlayerInfo * getPlayerInfo(const char *name, std::vector< const char *> *bio=0) const
Definition: spellchk.h:351
const char * GetSiteStr() const
Definition: game.h:397
gamenumT numGames() const
Definition: scidbase.h:99
void flipPattern(patternT *patt)
Definition: tkscid.cpp:8794
int sc_filter_stats(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:2005
errorT AddNag(byte nag)
Definition: game.cpp:367
void MakeLongStr(char *str)
Definition: position.cpp:2526
const uint OPTABLE_MAX_TABLE_LINES
Definition: optable.h:31
An heterogeneous container used to pass a list of values from c++ to UI.
Definition: ui.h:162
bool VarExactMatch(Position *searchPos, gameExactMatchT searchType)
Definition: game.cpp:1546
bool sameResult
Definition: tkscid.cpp:752
bool strContains(const char *longStr, const char *keyStr)
Definition: misc.h:385
bool exactNames
Definition: tkscid.cpp:747
void strCopyExclude(char *target, const char *original, const char *excludeChars)
Definition: misc.cpp:163
eloT GetBlackEstimateElo() const
Definition: game.h:407
int setUintWidthResult(Tcl_Interp *ti, uint i, uint width)
Definition: tkscid.cpp:187
int sc_clipbase(ClientData, Tcl_Interp *ti, int argc, const char **argv)
CLIPBASE functions.
Definition: tkscid.cpp:1193
Tcl_Interp * UI_handle_t
Definition: ui_tcltk.h:32
void SetNumRows(uint nrows)
Definition: optable.h:198
materialw
Definition: board.tcl:1506
uint GetMonth() const
Definition: indexentry.h:218
uint64_t yearCount
Definition: tree.h:56
const char * GetSiteName(const NameBase *nb) const
Definition: indexentry.h:234
colorT GetToMove() const
Definition: position.h:162
int sc_game_new(ClientData, Tcl_Interp *, int, const char **)
Definition: tkscid.cpp:3764
int sc_game_find(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:2590
treeT tree
Definition: scidbase.h:348
const squareT A2
Definition: common.h:349
std::pair< Game *, bool > deprecated_push_pop
Definition: scidbase.h:360
void strCopy(char *target, const char *original)
Definition: misc.h:298
errorT scid_TB_Probe(Position *, int *)
Definition: probe.cpp:358
int sc_filter_freq(scidBaseT *dbase, const HFilter &filter, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1823
const squareT E8
Definition: common.h:355
ecoT eco_FromString(const char *ecoStr)
Definition: misc.cpp:36
bool GetCommentsFlag() const
Definition: indexentry.h:255
unsigned GetPgnOffset() const
Definition: game.cpp:868
ushort num
Definition: position.h:66
HFilter getFilter(const std::string &filterId) const
Definition: scidbase.h:200
sansqno
Definition: board.tcl:307
void PrintTable(DString *dstr, crosstableModeT mode, uint playerLimit, int currentGame)
Definition: crosstab.cpp:438
uint GetYear() const
Definition: indexentry.h:217
int sc_eco_summary(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1480
uint AvgLength(resultT result)
Definition: optable.cpp:1910
void SetEvent(idNumberT id)
Definition: indexentry.h:154
void BestGames(DString *dstr, uint count, const char *rtype)
Definition: optable.cpp:1451
int sc_name_read(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:7227
const byte * GetMaterial() const
Definition: position.h:158
int sc_pos_bestSquare(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5541
void freePatternList(patternT *patt)
Definition: tkscid.cpp:8779
uint GetTheoryCount()
Definition: optable.h:190
resultsreportType fmt
Definition: optable.tcl:629
errorT AddPlayer(idNumberT id, const char *name, eloT elo, const SpellChecker *)
Definition: crosstab.cpp:188
byte colorT
Definition: common.h:104
int sc_game_crosstable(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:2282
bool sameMoves
Definition: tkscid.cpp:757
char sanStringT[10]
Definition: common.h:129
void SetSearchTime(uint ms)
Definition: engine.h:223
idNumberT GetNumNames(nameT nt) const
Definition: namebase.h:178
uint hpSig_Final(const byte *changeList)
Definition: matsig.cpp:177
Definition: tree.h:44
errorT parsePattern(const char *str, patternT *patt)
Definition: tkscid.cpp:8808
#define PROBE_REPORT
Definition: tkscid.cpp:109
void exportGame(Game *g, FILE *exportFile, gameFormatT format, uint pgnStyle)
Definition: tkscid.cpp:337
const pieceT BR
Definition: common.h:242
Game * clone()
Definition: game.cpp:489
ecoTranslateT * next
Definition: tkscid.cpp:1241
void RemovePgnStyle(uint mask)
Definition: game.h:431
void CacheResize(size_t max_size)
Definition: tree.h:156
cmd
Definition: fics.tcl:439
scidBaseT * db
Definition: dbasepool.cpp:26
unsigned nameT
Definition: common.h:153
uint * SelectGames(char type, uint number)
Definition: optable.cpp:2292
bool probe_tablebase(Tcl_Interp *ti, int mode, DString *dstr)
Definition: tkscid.cpp:2704
uint32_t GetLength() const
Definition: indexentry.h:97
int sc_search(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:8488
byte flag
Definition: game.h:121
const char * GetComment() const
Definition: spellchk.h:247
void transPieces(char *s)
Definition: game.cpp:41
#define PGN_STYLE_COLUMN
Definition: game.h:152
bool GetUnderPromoFlag() const
Definition: indexentry.h:254
void SetBlackElo(eloT elo)
Definition: indexentry.h:146
const char * strPlural(uint x)
Definition: misc.h:168
bool sameEcoCode
Definition: tkscid.cpp:756
const pieceT WN
Definition: common.h:241
Progress UI_CreateProgress(UI_handle_t ti)
UI_CreateProgress() - create a Progress object.
Definition: ui.h:122
void Clear()
Definition: game.cpp:541
char * from
Definition: tkscid.cpp:1239
bool HasNonStandardStart(char *outFEN=nullptr) const
Definition: game.h:231
const squareT E1
Definition: common.h:348
const IndexEntry * getIndexEntry_bounds(gamenumT g) const
Definition: scidbase.h:134
int UI_Main(int argc, char *argv[], void(*exit)(void *))
UI_Main() - Init the UI.
Definition: ui.h:104
resultT strGetResult(const char *str)
Definition: misc.cpp:566
errorT Open(ICodecDatabase::Codec dbtype, fileModeT mode, const char *filename=0, const Progress &progress=Progress())
Definition: scidbase.cpp:84
void SetEco(ecoT eco)
Definition: game.h:395
int sc_var_delete(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:9592
int sc_game_tags_reload(ClientData, Tcl_Interp *, int, const char **)
Definition: tkscid.cpp:4548
eloT GetWhiteEstimateElo() const
Definition: game.h:406
int sc_move_addSan(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5140
ecoT eco_BasicCode(ecoT eco)
Definition: misc.cpp:110
uint hpSig_AddPawn(uint hpSig, colorT color, fyleT fyle)
Definition: matsig.h:271
bool strGetBoolean(const char *str)
Definition: misc.cpp:500
uint gamenumT
Definition: common.h:163
const char * GetEventStr() const
Definition: game.h:396
void strGetUnsigneds(const char *str, uint *results, uint nResults)
Definition: misc.cpp:553
int sc_book_close(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9729
uint GetVarLevel() const
Definition: game.h:310
void game_printNag(byte nag, char *str, bool asSymbol, gameFormatT format)
Definition: game.cpp:98
int sc_game_summary(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4136
int sc_filter_next(ClientData, Tcl_Interp *ti, int, const char **)
Definition: tkscid.cpp:1967
errorT AddMove(const simpleMoveT *sm)
Definition: game.cpp:889
const rankT RANK_1
Definition: common.h:360
bool matsig_isReachable(matSigT mStart, matSigT mTarget, bool promos, bool upromo)
Definition: matsig.cpp:56
int sc_book_update(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9778
dateT getDeathdate() const
Definition: spellchk.cpp:481
byte GetBlackRatingType() const
Definition: indexentry.h:103
const pieceT BM
Definition: common.h:245
const resultT RESULT_None
Definition: common.h:189
const errorT ERROR
Definition: error.h:26
ushort GetNumHalfMoves()
Definition: game.h:250
const squareT G2
Definition: common.h:349
static uint FideCategory(eloT rating)
Definition: crosstab.cpp:77
size_t Size()
Definition: tree.h:150
void SetBlackElo(eloT elo)
Definition: game.h:390
Definition: tree.tcl:6
int sc_game_tags_get(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4310
void GetPrevSAN(char *str)
Definition: game.cpp:1701
bool sameMonth
Definition: tkscid.cpp:754
const NameBase * getNameBase() const
Definition: scidbase.h:138
const fyleT A_FYLE
Definition: common.h:365
#define PROBE_RESULT
Definition: tkscid.cpp:107
pieceT movingPiece
Definition: movelist.h:41
void SetHashTableKilobytes(uint sizeKB)
Definition: engine.cpp:1083
void SetPawnTableKilobytes(uint sizeKB)
Definition: engine.cpp:1106
void PopularMoveOrders(DString *dstr, uint count)
Definition: optable.cpp:2025
int sc_base_tag(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1070
#define PGN_STYLE_SYMBOLS
Definition: game.h:149
bool GetNagsFlag() const
Definition: indexentry.h:257
#define PGN_STYLE_SHORT_HEADER
Definition: game.h:150
void eco_ToBasicString(ecoT ecoCode, char *ecoStr)
Definition: misc.h:110
int sc_info_suffix(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4952
const char * GetRoundStr() const
Definition: game.h:400
int sc_game_strip(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4118
const char * GetRoundName(const NameBase *nb) const
Definition: indexentry.h:237
void SetWhiteRatingType(byte b)
Definition: indexentry.h:138
int strCompare(const char *s1, const char *s2)
Definition: misc.h:280
void PrintTable(DString *dstr, const char *title, const char *comment)
Definition: optable.cpp:1021
uint NumPlayers()
Definition: crosstab.h:177
matSigT matsig_Make(const byte *materialCounts)
Definition: matsig.h:225
Definition: engine.h:106
void translateECO(Tcl_Interp *ti, const char *strFrom, DString *dstrTo)
Definition: tkscid.cpp:1558
uint GetHPSig()
Definition: position.cpp:523
int sc_book_moves(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9744
uint date_GetMonth(dateT date)
Definition: date.h:61
void SetScidFlags(const char *s, size_t len)
Definition: game.h:243
const squareT F1
Definition: common.h:348
int sc_base_inUse(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:321
errorT GetPartialMoveList(DString *str, uint plyCount)
Definition: game.cpp:1639
static std::pair< errorT, SpellChecker * > Create(const char *filename, const Progress &progress)
Create() - Create a new SpellChecker object.
Definition: spellchk.h:295
const squareT C7
Definition: common.h:354
void set(gamenumT gnum, byte value)
Definition: hfilter.h:274
pieceT piece_Make(colorT c, pieceT p)
Definition: common.h:295
Definition: pgn_lexer.h:359
char square_RankChar(squareT sq)
Definition: common.h:526
int sc_book_movesupdate(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:9791
sanStringT list[MAX_LEGAL_MOVES]
Definition: position.h:67
class PlayerInfo - player informations
Definition: spellchk.h:233
uint AddMoveOrder(Game *g)
Definition: optable.cpp:1965
static nameT NameTypeFromString(const char *str)
Match a string to a nameT.
Definition: namebase.h:229
const errorT ERROR_FileOpen
Definition: error.h:31
int sc_game_load(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:3505
#define PGN_STYLE_TAGS
Definition: game.h:144
squareT getFrom() const
Definition: fullmove.h:67
void strTrimDate(char *str)
Definition: misc.cpp:356
char piece_Char(pieceT p)
Definition: common.h:325
bool GetPromotionsFlag() const
Definition: indexentry.h:253
void clear()
Definition: containers.h:39
#define PGN_STYLE_STRIP_MARKS
Definition: game.h:154
void SetHypertextOutput()
Definition: crosstab.h:155
int sc_eco(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:1248
int sc_pos(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5269
gameExactMatchT
Definition: game.h:130
pieceT promote
Definition: movelist.h:40
Definition: indexentry.h:54
int sc_game_tags_set(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:4458
void clear()
Definition: ui_tcltk.h:149
Progress UI_CreateProgressPosMask(UI_handle_t ti)
Definition: ui.h:125
errorT Decode(ByteBuffer *buf, byte flags)
Definition: game.cpp:3549
int sc_game(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
GAME functions.
Definition: tkscid.cpp:2091
dateT date_EncodeFromString(const char *str)
Definition: date.h:127
byte squareT
Definition: common.h:105
size_t listGames(const char *criteria, size_t start, size_t count, const HFilter &filter, gamenumT *destCont)
Retrieve a list of ordered game indexes sorted by criteria.
Definition: scidbase.cpp:707
const uint NUM_TITLES
Definition: tkscid.cpp:9214
const squareT D8
Definition: common.h:355
const sanFlagT SAN_MATETEST
Definition: position.h:40
byte pieceT
Definition: common.h:103
int sc_pos_probe_board(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:5940
treeNodeT node[MAX_TREE_NODES]
Definition: tree.h:82
std::string log
Definition: pgnparse.h:333
int sc_game_merge(ClientData, Tcl_Interp *ti, int argc, const char **argv)
Definition: tkscid.cpp:3553
uint total
Definition: tree.h:48
dateT GetEventDate() const
Definition: indexentry.h:108
const pieceT WR
Definition: common.h:241
errorT AddResult(uint gameNumber, idNumberT white, idNumberT black, resultT result, uint round, dateT date)
Definition: crosstab.cpp:243
int sc_filter_old(ClientData cd, Tcl_Interp *ti, int argc, const char **argv)
FILTER functions.
Definition: tkscid.cpp:1602
ClientData UI_extra_t
Definition: ui_tcltk.h:31
void ResetPgnStyle(void)
Definition: game.h:423
const uint FEN_ALL_FIELDS
Definition: position.h:49
uint date_GetDay(dateT date)
Definition: date.h:70
void SetPostMode(bool b)
Definition: engine.h:235
void Clear()
Definition: dstring.h:26