Line data Source code
1 : //////////////////////////////////////////////////////////////////////
2 : //
3 : // FILE: game.cpp
4 : // Game class methods
5 : //
6 : // Part of: Scid (Shane's Chess Information Database)
7 : // Version: 3.5
8 : //
9 : // Notice: Copyright (c) 2000-2003 Shane Hudson. All rights reserved.
10 : //
11 : // Author: Shane Hudson (sgh@users.sourceforge.net)
12 : //
13 : //////////////////////////////////////////////////////////////////////
14 :
15 : #include "game.h"
16 : #include "bytebuf.h"
17 : #include "common.h"
18 : #include "dstring.h"
19 : #include "naglatex.h"
20 : #include "nagtext.h"
21 : #include "position.h"
22 : #include "stored.h"
23 : #include "textbuf.h"
24 : #include <algorithm>
25 : #include <cstring>
26 :
27 : // Piece letters translation
28 : int language = 0; // default to english
29 : // 0 = en,
30 : // 1 = fr, 2 = es, 3 = de, 4 = it, 5 = ne, 6 = cz
31 : // 7 = hu, 8 = no, 9 = sw, 10 = ca, 11 = fi, 12 = gr
32 : // TODO Piece translations for greek
33 : const char * langPieces[] = { "",
34 : "PPKRQDRTBFNC", "PPKRQDRTBANC", "PBKKQDRTBLNS",
35 : "PPKRQDRTBANC", "PpKKQDRTBLNP", "PPKKQDRVBSNJ",
36 : "PGKKQVRBBFNH", "PBKKQDRTBLNS", "PBKKQDRTBLNS", "PPKRQDRTBANC", "PSKKQDRTBLNR", "" };
37 :
38 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
39 : // transPieces():
40 : // Given a string, will translate pieces from english to another language
41 1561972 : void transPieces(char *s) {
42 1561972 : if (language == 0) return;
43 0 : char * ptr = s;
44 : int i;
45 :
46 0 : while (*ptr) {
47 0 : if (*ptr >= 'A' && *ptr <= 'Z') {
48 0 : for (i=0; i<12; i+=2) {
49 0 : if (*ptr == langPieces[language][i]) {
50 0 : *ptr = langPieces[language][i+1];
51 0 : break;
52 : }
53 : }
54 : }
55 0 : ptr++;
56 : }
57 : }
58 :
59 0 : char transPiecesChar(char c) {
60 0 : char ret = c;
61 0 : if (language == 0) return c;
62 0 : for (int i=0; i<12; i+=2) {
63 0 : if (c == langPieces[language][i]) {
64 0 : ret = langPieces[language][i+1];
65 0 : break;
66 : }
67 : }
68 0 : return ret;
69 : }
70 :
71 : const char * ratingTypeNames [17] = {
72 : "Elo", "Rating", "Rapid", "ICCF", "USCF", "DWZ", "ECF",
73 : // Reserved for future use:
74 : NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
75 : // End of array marker:
76 : NULL
77 : };
78 :
79 : uint
80 0 : strGetRatingType (const char * name) {
81 0 : uint i = 0;
82 0 : while (ratingTypeNames[i] != NULL) {
83 0 : if (strEqual (name, ratingTypeNames[i])) { return i; }
84 0 : i++;
85 : }
86 0 : return 0;
87 : }
88 :
89 : typedef Game * GamePtr;
90 :
91 :
92 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
93 : // printNag(): converts a numeric NAG to its string equivalent.
94 : // The parameter <str> should point to a string at least 10 bytes long.
95 : // TODO
96 : // replace < and > in NAG codes by <lt> and <gt>
97 : void
98 288 : game_printNag (byte nag, char * str, bool asSymbol, gameFormatT format)
99 : {
100 288 : ASSERT (str != NULL);
101 :
102 288 : if (nag == 0) {
103 0 : *str = 0;
104 0 : return;
105 : }
106 :
107 288 : if (nag >= (sizeof evalNagsRegular / sizeof (const char *))) {
108 0 : if (format == PGN_FORMAT_LaTeX) *str = 0;
109 0 : else sprintf (str, "$%u", nag);
110 0 : return;
111 : }
112 :
113 288 : if (asSymbol) {
114 0 : if (format == PGN_FORMAT_LaTeX) {
115 0 : strcpy (str, evalNagsLatex[nag]);
116 : } else {
117 0 : strcpy (str, evalNagsRegular[nag]);
118 : }
119 0 : if (nag == NAG_Diagram) {
120 0 : if (format == PGN_FORMAT_LaTeX) {
121 0 : strcpy (str, evalNagsLatex[nag]);
122 0 : } else if (format == PGN_FORMAT_HTML) {
123 0 : strcpy(str, "<i>(D)</i>");
124 : } else {
125 0 : str[0] = 'D'; str[1] = 0;
126 : }
127 : }
128 0 : return;
129 : } else {
130 288 : sprintf (str, "%s$%d", format == PGN_FORMAT_LaTeX ? "\\" : "", nag);
131 : }
132 : }
133 :
134 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135 : // game_parseNag():
136 : // Parses an annotation symbol into its numeric equivalent.
137 : // Accepts numeric format ($51) or symbols such as
138 : // !, ?, +=, -/+, N, etc.
139 : //
140 386 : byte game_parseNag(std::pair<const char*, const char*> strview) {
141 386 : auto slen = std::distance(strview.first, strview.second);
142 386 : if (slen == 0 || slen > 7)
143 1 : return 0;
144 :
145 385 : char strbuf[8] = {0};
146 385 : std::copy_n(strview.first, slen, strbuf);
147 385 : const char* str = strbuf;
148 :
149 385 : if (*str == '$') {
150 384 : str++;
151 384 : return (byte) strGetUnsigned(str);
152 : }
153 1 : if ((*str <= '9' && *str >= '0')) {
154 0 : return (byte) strGetUnsigned(str);
155 : }
156 :
157 1 : if (*str == '!') {
158 : // Must be "!", "!!", "!?", or invalid:
159 0 : str++;
160 0 : if (*str == 0) { return NAG_GoodMove; } // ! $1
161 0 : if (*str == '!') { return NAG_ExcellentMove; } // !! $3
162 0 : if (*str == '?') { return NAG_InterestingMove; } // !? $5
163 0 : return 0;
164 : }
165 :
166 1 : if (*str == '?') {
167 : // Must be "?", "??", "?!", or invalid:
168 0 : str++;
169 0 : if (*str == 0) { return NAG_PoorMove; } // ? $2
170 0 : if (*str == '?') { return NAG_Blunder; } // ?? $4
171 0 : if (*str == '!') { return NAG_DubiousMove; } // ?! $6
172 0 : return 0;
173 : }
174 :
175 1 : if (*str == '+') {
176 : // Must be "+=", "+/=", "+/-", "+-", "+--", "+>" or invalid:
177 0 : str++;
178 0 : if (*str == '=') { return NAG_WhiteSlight; } // += $14
179 0 : if (*str == '-' && str[1] == 0) { // +- $18
180 0 : return NAG_WhiteDecisive; }
181 0 : if (*str == '>') { return NAG_WithAttack; } // +> $40
182 0 : if (*str == '/' && str[1] == '-') { // +/- $16
183 0 : return NAG_WhiteClear; }
184 0 : if (*str == '/' && str[1] == '=') { // +/= $14
185 0 : return NAG_WhiteSlight; }
186 0 : if (*str == '-' && str[1] == '-') { // +-- $20
187 0 : return NAG_WhiteCrushing; }
188 0 : return 0;
189 : }
190 :
191 1 : if (*str == '=') {
192 : // Must be "=" (equal), "=+", "=/+", "=/&" or invalid:
193 0 : str++;
194 0 : if (*str == 0) { return NAG_Equal; } // = $10
195 0 : if (*str == '+') { return NAG_BlackSlight; } // =+ $15
196 0 : if (*str == '/' && str[1] == '+') { // =/+ $15
197 0 : return NAG_BlackSlight; }
198 0 : if (*str == '/' && str[1] == '&') { // =/& $44
199 0 : return NAG_Compensation; }
200 0 : return 0;
201 : }
202 :
203 1 : if (*str == '-') {
204 : // Must be "-+", "-/+" or "--+", "->":
205 0 : str++;
206 0 : if (*str == '+') { return NAG_BlackDecisive; } // -+ $19
207 0 : if (*str == '>') { return NAG_WithBlackAttack; } // -> $41
208 0 : if (*str == '/' && str[1] == '+') { // -/+ $17
209 0 : return NAG_BlackClear; }
210 0 : if (*str == '-' && str[1] == '+') { // --+ $21
211 0 : return NAG_BlackCrushing; }
212 0 : if (*str == '-' && str[1] == 0) { // -- $210
213 0 : return NAG_See; }
214 0 : return 0;
215 : }
216 :
217 1 : if (*str == '/') {
218 : // Must be "/\" or "/"
219 0 : str++;
220 0 : if (*str == 0) { return NAG_Diagonal; } // / $150
221 0 : if (*str == '\\') { return NAG_WithIdea; } // Tri $140
222 0 : return 0;
223 : }
224 :
225 1 : if (*str == 'R') {
226 : // Must be "R", "RR"
227 0 : str++;
228 0 : if (*str == 0) { return NAG_VariousMoves; } // R $144
229 0 : if (*str == 'R') { return NAG_Comment; } // RR $145
230 0 : return 0;
231 : }
232 :
233 1 : if (*str == 'z') {
234 : // Must be "zz"
235 0 : str++;
236 0 : if (*str == 'z') { return NAG_BlackZugZwang; } // zz $23
237 0 : return 0;
238 : }
239 1 : if (*str == 'Z') {
240 : // Must be "ZZ"
241 0 : str++;
242 0 : if (*str == 'Z') { return NAG_ZugZwang; } // ZZ $22
243 0 : return 0;
244 : }
245 :
246 1 : if (*str == 'B') {
247 : // Must be "BB", "Bb"
248 0 : str++;
249 0 : if (*str == 'B') { return NAG_BishopPair; } // BB $151
250 0 : if (*str == 'b') { return NAG_OppositeBishops; } // Bb $153
251 0 : return 0;
252 : }
253 :
254 1 : if (*str == 'o') {
255 : // Must be "BB", "Bb"
256 0 : str++;
257 0 : if (*str == '-' && str[1] == 'o') { // o-o $192
258 0 : return NAG_SeparatedPawns; }
259 0 : if (*str == 'o' && str[1] == 0) { // [+] $193
260 0 : return NAG_UnitedPawns; }
261 0 : if (*str == '^' && str[1] == 0) { // o^ $212
262 0 : return NAG_PassedPawn; }
263 0 : return 0;
264 : }
265 :
266 1 : if (*str == '(') {
267 : // Must be (_)
268 0 : str++;
269 0 : if (*str == '_' && str[1] == ')') { // (_) $142
270 0 : return NAG_BetterIs; }
271 0 : return 0;
272 : }
273 :
274 1 : if (*str == '[') {
275 : // Must be (_)
276 0 : str++;
277 0 : if (*str == ']' && str[1] == 0) { // [] $8
278 0 : return NAG_OnlyMove; }
279 0 : if (*str == '+' && str[1] == ']') { // [+] $48
280 0 : return NAG_SlightCentre; }
281 0 : if (*str == '+' &&
282 0 : str[1] == '+' && str[2] == ']') { // [++] $50
283 0 : return NAG_Centre; }
284 0 : return 0;
285 : }
286 :
287 1 : if (*str == '_') {
288 : // must be _|_ or _|
289 0 : str++;
290 0 : if (*str == '|' && str[1] == '_') { // _|_ $148
291 0 : return NAG_Ending; }
292 0 : if (*str == '|' && str[1] == 0) { // _| $215
293 0 : return NAG_Without; }
294 0 : return 0;
295 : }
296 :
297 1 : if (*str == '|') {
298 : // must be ||, |_
299 0 : str++;
300 0 : if (*str == '|' ) { return NAG_Etc; } // || $190
301 0 : if (*str == '_') { return NAG_With; } // |_ $214
302 0 : return 0;
303 : }
304 :
305 1 : if (*str == '>') {
306 : // must be >, >>, >>>
307 0 : str++;
308 0 : if (*str == 0) { return NAG_SlightKingSide; } // > $54
309 0 : if (*str == '>' && str[1] == 0) { // >> $56
310 0 : return NAG_ModerateKingSide; }
311 0 : if (*str == '>' && str[1] == '>') { // >>> $58
312 0 : return NAG_KingSide; }
313 0 : return 0;
314 : }
315 :
316 1 : if (*str == '<') {
317 : // must be <, <<, <<<, <=>
318 0 : str++;
319 0 : if (*str == 0) { return NAG_SlightQueenSide; } // < $60
320 0 : if (*str == '<' && str[1] == 0) { // << $62
321 0 : return NAG_ModerateQueenSide; }
322 0 : if (*str == '<' && // <<< $64
323 0 : str[1] == '<' && str[2] == 0) { return NAG_QueenSide; }
324 0 : if (*str == '=' && // <=> $149
325 0 : str[1] == '>' && str[2] == 0) { return NAG_File; }
326 0 : if (*str == '+' && // <+> $130
327 0 : str[1] == '>' && str[2] == 0) { return NAG_SlightCounterPlay; }
328 0 : if (*str == '-' && // <-> $131
329 0 : str[1] == '>' && str[2] == 0) { return NAG_BlackSlightCounterPlay; }
330 0 : if (*str == '+' && // <++> $132
331 0 : str[1] == '+' && str[2] == '>' && str[3] == 0) { return NAG_CounterPlay; }
332 0 : if (*str == '-' && // <--> $133
333 0 : str[1] == '-' && str[2] == '>' && str[3] == 0) { return NAG_BlackCounterPlay; }
334 0 : if (*str == '+' && // <+++> $134
335 0 : str[1] == '+' && str[2] == '+' && str[3] == '>') { return NAG_DecisiveCounterPlay; }
336 0 : if (*str == '-' && // <---> $135
337 0 : str[1] == '-' && str[2] == '-' && str[3] == '>') { return NAG_BlackDecisiveCounterPlay; }
338 0 : return 0;
339 : }
340 :
341 1 : if (*str == '~' && *(str+1) == '=') { // ~= $44
342 : // alternative Compensation symbol:
343 0 : return NAG_Compensation;
344 : }
345 :
346 1 : if (*str == '~') { // ~ $13
347 : // Unclear symbol:
348 0 : return NAG_Unclear;
349 : }
350 :
351 1 : if (*str == 'x') { // x $147
352 0 : return NAG_WeakPoint;
353 : }
354 :
355 1 : if (str[0] == 'N' && str[1] == 0) { // N $146
356 : // Novelty symbol:
357 0 : return NAG_Novelty;
358 : }
359 :
360 1 : if (str[0] == 'D' && str[1] == 0) { // D $201
361 : // Diagram symbol:
362 0 : return NAG_Diagram;
363 : }
364 1 : return 0;
365 : }
366 :
367 824 : errorT Game::AddNag (byte nag) {
368 824 : moveT * m = CurrentMove->prev;
369 824 : if (m->nagCount + 1 >= MAX_NAGS) { return ERROR_GameFull; }
370 824 : if (nag == 0) { /* Nags cannot be zero! */ return OK; }
371 : // If it is a move nag replace an existing
372 824 : if( nag >= 1 && nag <= 6)
373 626 : for( int i=0; i<m->nagCount; i++)
374 0 : if( m->nags[i] >= 1 && m->nags[i] <= 6)
375 : {
376 0 : m->nags[i] = nag;
377 0 : return OK;
378 : }
379 : // If it is a position nag replace an existing
380 824 : if( nag >= 10 && nag <= 21)
381 208 : for( int i=0; i<m->nagCount; i++)
382 10 : if( m->nags[i] >= 10 && m->nags[i] <= 21)
383 : {
384 0 : m->nags[i] = nag;
385 0 : return OK;
386 : }
387 824 : if( nag >= 1 && nag <= 6)
388 : {
389 : // Put Move Nags at the beginning
390 626 : for( int i=m->nagCount; i>0; i--) m->nags[i] = m->nags[i-1];
391 626 : m->nags[0] = nag;
392 : }
393 : else
394 198 : m->nags[m->nagCount] = nag;
395 824 : m->nagCount += 1;
396 824 : m->nags[m->nagCount] = 0;
397 824 : return OK;
398 : }
399 :
400 0 : errorT Game::RemoveNag (bool isMoveNag) {
401 0 : moveT * m = CurrentMove->prev;
402 0 : if( isMoveNag)
403 : {
404 0 : for( int i=0; i<m->nagCount; i++)
405 0 : if( m->nags[i] >= 1 && m->nags[i] <= 6)
406 : {
407 0 : m->nagCount -= 1;
408 0 : for( int j=i; j<m->nagCount; j++) m->nags[j] = m->nags[j+1];
409 0 : m->nags[m->nagCount] = 0;
410 0 : return OK;
411 : }
412 : }
413 : else
414 : {
415 0 : for( int i=0; i<m->nagCount; i++)
416 0 : if( m->nags[i] >= 10 && m->nags[i] <= 21)
417 : {
418 0 : m->nagCount -= 1;
419 0 : for( int j=i; j<m->nagCount; j++) m->nags[j] = m->nags[j+1];
420 0 : m->nags[m->nagCount] = 0;
421 0 : return OK;
422 : }
423 : }
424 0 : return OK;
425 : }
426 :
427 : //////////////////////////////////////////////////////////////////////
428 : // PUBLIC FUNCTIONS
429 : //////////////////////////////////////////////////////////////////////
430 :
431 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
432 : // Move allocation:
433 : // moves are allocated in chunks to save memory and for faster
434 : // performance.
435 : //
436 : constexpr int MOVE_CHUNKSIZE = 128;
437 :
438 4375098 : moveT* Game::allocMove() {
439 4375098 : if (moveChunkUsed_ == MOVE_CHUNKSIZE) {
440 34276 : moveChunks_.emplace_front(new moveT[MOVE_CHUNKSIZE]);
441 34276 : moveChunkUsed_ = 0;
442 : }
443 4375098 : return moveChunks_.front().get() + moveChunkUsed_++;
444 : }
445 :
446 4373367 : moveT* Game::NewMove(markerT marker) {
447 4373367 : moveT* res = allocMove();
448 4373367 : res->clear();
449 4373367 : res->marker = marker;
450 4373367 : return res;
451 : }
452 :
453 6 : Game::Game(const Game& obj) {
454 3 : extraTags_ = obj.extraTags_;
455 3 : WhiteStr = obj.WhiteStr;
456 3 : BlackStr = obj.BlackStr;
457 3 : EventStr = obj.EventStr;
458 3 : SiteStr = obj.SiteStr;
459 3 : RoundStr = obj.RoundStr;
460 3 : Date = obj.Date;
461 3 : EventDate = obj.EventDate;
462 3 : EcoCode = obj.EcoCode;
463 3 : WhiteElo = obj.WhiteElo;
464 3 : BlackElo = obj.BlackElo;
465 3 : WhiteRatingType = obj.WhiteRatingType;
466 3 : BlackRatingType = obj.BlackRatingType;
467 3 : Result = obj.Result;
468 3 : std::copy_n(obj.ScidFlags, sizeof(obj.ScidFlags), ScidFlags);
469 :
470 3 : if (obj.StartPos)
471 0 : StartPos = std::make_unique<Position>(*obj.StartPos);
472 :
473 3 : NumHalfMoves = obj.NumHalfMoves;
474 3 : PromotionsFlag = obj.PromotionsFlag;
475 3 : KeepDecodedMoves = obj.KeepDecodedMoves;
476 3 : WhiteEstimateElo = obj.WhiteEstimateElo;
477 3 : BlackEstimateElo = obj.BlackEstimateElo;
478 3 : NumMovesPrinted = obj.NumMovesPrinted;
479 3 : PgnStyle = obj.PgnStyle;
480 3 : PgnFormat = obj.PgnFormat;
481 3 : HtmlStyle = obj.HtmlStyle;
482 :
483 3 : moveChunkUsed_ = MOVE_CHUNKSIZE;
484 3 : FirstMove = obj.FirstMove->cloneLine(nullptr,
485 1731 : [this]() { return allocMove(); });
486 :
487 3 : MoveToLocationInPGN(obj.GetLocationInPGN());
488 3 : }
489 3 : Game* Game::clone() { return new Game(*this); }
490 :
491 0 : void Game::strip(bool variations, bool comments, bool NAGs) {
492 0 : while (variations && MoveExitVariation() == OK) { // Go to main line
493 : }
494 :
495 0 : for (auto& chunk : moveChunks_) {
496 0 : moveT* move = chunk.get();
497 0 : moveT* end = (chunk == moveChunks_.front()) ? move + moveChunkUsed_
498 0 : : move + MOVE_CHUNKSIZE;
499 0 : for (; move != end; ++move) {
500 0 : if (variations) {
501 0 : move->numVariations = 0;
502 0 : move->varChild = nullptr;
503 : }
504 0 : if (comments)
505 0 : move->comment.clear();
506 :
507 0 : if (NAGs) {
508 0 : move->nagCount = 0;
509 0 : std::fill_n(move->nags, sizeof(move->nags), 0);
510 : }
511 : }
512 : }
513 0 : }
514 :
515 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
516 : // Game::ClearMoves(): clear all moves.
517 5209 : void Game::ClearMoves() {
518 : // Delete any chunks of moves except the first:
519 5209 : if (moveChunks_.empty()) {
520 2139 : moveChunkUsed_ = MOVE_CHUNKSIZE;
521 : } else {
522 3070 : moveChunks_.erase_after(moveChunks_.begin(), moveChunks_.end());
523 3070 : moveChunkUsed_ = 0;
524 : }
525 5209 : StartPos = nullptr;
526 5209 : CurrentPos->StdStart();
527 :
528 : // Initialize FirstMove: start and end of movelist markers
529 5209 : FirstMove = NewMove(START_MARKER);
530 5209 : CurrentMove = NewMove(END_MARKER);
531 5209 : FirstMove->setNext(CurrentMove);
532 :
533 5209 : VarDepth = 0;
534 5209 : NumHalfMoves = 0;
535 5209 : }
536 :
537 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
538 : // Game::Clear():
539 : // Reset the game to its normal empty state.
540 : //
541 5204 : void Game::Clear() {
542 5204 : extraTags_.clear();
543 5204 : WhiteStr.clear();
544 5204 : BlackStr.clear();
545 5204 : EventStr.clear();
546 5204 : SiteStr.clear();
547 5204 : RoundStr.clear();
548 5204 : Date = ZERO_DATE;
549 5204 : EventDate = ZERO_DATE;
550 5204 : EcoCode = 0;
551 5204 : WhiteElo = BlackElo = 0;
552 5204 : WhiteEstimateElo = BlackEstimateElo = 0;
553 5204 : WhiteRatingType = BlackRatingType = RATING_Elo;
554 5204 : Result = RESULT_None;
555 5204 : ScidFlags[0] = 0;
556 :
557 5204 : NumMovesPrinted = 0;
558 5204 : PgnStyle = PGN_STYLE_TAGS | PGN_STYLE_VARS | PGN_STYLE_COMMENTS;
559 5204 : PgnFormat = PGN_FORMAT_Plain;
560 5204 : HtmlStyle = 0;
561 :
562 5204 : ClearMoves();
563 5204 : KeepDecodedMoves = true;
564 5204 : PromotionsFlag = false;
565 5204 : }
566 :
567 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
568 : // Game::PgnFormatFromString():
569 : // Converts a string to a gameFormatT, returning true on success
570 : // or false on error.
571 : // The string should be a case-insensitive unique prefix of
572 : // "plain" (or "pgn"), "HTML", "LaTeX" or "Color".
573 : bool
574 0 : Game::PgnFormatFromString (const char * str, gameFormatT * fmt)
575 : {
576 0 : if (strIsCasePrefix (str, "Plain")) {
577 0 : *fmt = PGN_FORMAT_Plain;
578 0 : } else if (strIsCasePrefix (str, "PGN")) {
579 0 : *fmt = PGN_FORMAT_Plain;
580 0 : } else if (strIsCasePrefix (str, "HTML")) {
581 0 : *fmt = PGN_FORMAT_HTML;
582 0 : } else if (strIsCasePrefix (str, "LaTeX")) {
583 0 : *fmt = PGN_FORMAT_LaTeX;
584 0 : } else if (strIsCasePrefix (str, "Color")) {
585 0 : *fmt = PGN_FORMAT_Color;
586 : } else {
587 0 : return false;
588 : }
589 0 : return true;
590 : }
591 :
592 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
593 : // Game::SetPgnFormatFromString():
594 : // Sets the PgnFormat from the provided string.
595 : // Returns true if the PgnFormat was successfully set.
596 : bool
597 0 : Game::SetPgnFormatFromString (const char * str)
598 : {
599 0 : return PgnFormatFromString (str, &PgnFormat);
600 : }
601 :
602 : errorT
603 5 : Game::SetStartFen (const char * fenStr)
604 : {
605 10 : auto pos = std::make_unique<Position>();
606 5 : errorT err = pos->ReadFromFEN (fenStr);
607 5 : if (err != OK)
608 0 : return err;
609 :
610 5 : ClearMoves();
611 5 : StartPos = std::move(pos);
612 5 : *CurrentPos = *StartPos;
613 5 : return OK;
614 : }
615 :
616 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
617 : // Game::AddPgnTag(): Add a PGN Tag.
618 : //
619 15 : void Game::AddPgnTag(const char* tag, const char* value) {
620 : // First, try to replace an existing tag:
621 24 : for (auto& e : extraTags_) {
622 9 : if (e.first == tag) {
623 0 : e.second.assign(value);
624 0 : return;
625 : }
626 : }
627 15 : extraTags_.emplace_back(tag, value);
628 : }
629 :
630 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
631 : // Game::FindExtraTag():
632 : // Finds and returns an extra PGN tag if it
633 : // exists, or NULL if it does not exist.
634 0 : const char* Game::FindExtraTag(const char* tag) const {
635 0 : for (auto& e : extraTags_) {
636 0 : if (e.first == tag)
637 0 : return e.second.c_str();
638 : }
639 0 : return NULL;
640 : }
641 :
642 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
643 : // Game::RemoveExtraTag():
644 : // Remove an extra PGN tag if it exists.
645 0 : bool Game::RemoveExtraTag(const char* tag) {
646 : auto it = std::remove_if(extraTags_.begin(), extraTags_.end(),
647 0 : [&](const std::pair<std::string, std::string>& e) {
648 0 : return e.first == tag;
649 0 : });
650 0 : if (it != extraTags_.end()) {
651 0 : extraTags_.erase(it, extraTags_.end());
652 0 : return true;
653 : }
654 0 : return false;
655 : }
656 :
657 10921 : std::string& Game::accessTagValue(const char* tag, size_t tagLen) {
658 10921 : if (tagLen == 5) {
659 8213 : if (std::equal(tag, tag + 5, "Event"))
660 2040 : return EventStr;
661 6173 : if (std::equal(tag, tag + 5, "Round"))
662 2039 : return RoundStr;
663 4134 : if (std::equal(tag, tag + 5, "White"))
664 2039 : return WhiteStr;
665 2095 : if (std::equal(tag, tag + 5, "Black"))
666 2039 : return BlackStr;
667 2708 : } else if (tagLen == 4) {
668 2040 : if (std::equal(tag, tag + 4, "Site"))
669 2040 : return SiteStr;
670 : }
671 :
672 9332 : for (auto& elem : extraTags_) {
673 8608 : if (std::equal(tag, tag + tagLen, elem.first.begin(), elem.first.end()))
674 0 : return elem.second;
675 : }
676 724 : extraTags_.emplace_back();
677 724 : extraTags_.back().first.assign(tag, tagLen);
678 724 : return extraTags_.back().second;
679 : }
680 :
681 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
682 : // Game::SetMoveComment():
683 : // Sets the comment for a move. A comment before the game itself
684 : // is stored as a comment of FirstMove.
685 : //
686 : void
687 111904 : Game::SetMoveComment (const char * comment)
688 : {
689 111904 : ASSERT (CurrentMove != NULL && CurrentMove->prev != NULL);
690 111904 : moveT * m = CurrentMove->prev;
691 111904 : if (comment == NULL) {
692 0 : m->comment.clear();
693 : } else {
694 111904 : m->comment = comment;
695 : // CommentsFlag = 1;
696 : }
697 111904 : }
698 :
699 73 : int Game::setRating(colorT col, const char* ratingType, size_t ratingTypeLen,
700 : std::pair<const char*, const char*> rating) {
701 73 : auto begin = ratingTypeNames;
702 73 : const size_t ratingSz = 7;
703 148 : auto it = std::find_if(begin, begin + ratingSz, [&](auto rType) {
704 75 : return std::equal(ratingType, ratingType + ratingTypeLen, rType,
705 75 : rType + std::strlen(rType));
706 148 : });
707 73 : byte rType = static_cast<byte>(std::distance(begin, it));
708 73 : if (rType >= ratingSz)
709 0 : return -1;
710 :
711 73 : int res = 1;
712 73 : auto elo = strGetUnsigned(std::string{rating.first, rating.second}.c_str());
713 73 : if (elo > MAX_ELO) {
714 0 : elo = 0;
715 0 : res = 0;
716 : }
717 73 : if (col == WHITE) {
718 37 : SetWhiteElo(static_cast<eloT>(elo));
719 37 : SetWhiteRatingType(rType);
720 : } else {
721 36 : SetBlackElo(static_cast<eloT>(elo));
722 36 : SetBlackRatingType(rType);
723 : }
724 73 : return res;
725 : }
726 :
727 : ///////////////////////////////////////////////////////////////////////////
728 : // A "location" in the game is represented by a position (Game::CurrentPos), the
729 : // next move to be played (Game::CurrentMove) and the number of parent variations
730 : // (Game::VarDepth). Since CurrentMove is the next move to be played, some
731 : // invariants must hold: it is never nullptr and it never points to a
732 : // START_MARKER (it will point to a END_MARKER if there are no more moves). This
733 : // also means that CurrentMove->prev is always valid: it will point to a
734 : // previous move or to a START_MARKER.
735 : // The following functions modify ONLY the current location of the game.
736 :
737 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
738 : // Move current position forward one move.
739 : // Also update all the necessary fields in the simpleMove structure
740 : // (CurrentMove->moveData) so it can be undone.
741 : //
742 8579393 : errorT Game::MoveForward(void) {
743 8579393 : if (CurrentMove->endMarker())
744 49453 : return ERROR_EndOfMoveList;
745 :
746 8529386 : CurrentPos->DoSimpleMove(&CurrentMove->moveData);
747 8529940 : CurrentMove = CurrentMove->next;
748 :
749 : // Invariants
750 8529940 : ASSERT(CurrentMove && CurrentMove->prev);
751 8529940 : ASSERT(!CurrentMove->startMarker());
752 8529940 : return OK;
753 : }
754 :
755 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
756 : // Game::MoveBackup():
757 : // Backup one move.
758 : //
759 5150374 : errorT Game::MoveBackup(void) {
760 5150374 : if (CurrentMove->prev->startMarker())
761 970562 : return ERROR_StartOfMoveList;
762 :
763 4179812 : CurrentMove = CurrentMove->prev;
764 4179812 : CurrentPos->UndoSimpleMove(&CurrentMove->moveData);
765 :
766 : // Invariants
767 4179812 : ASSERT(CurrentMove && CurrentMove->prev);
768 4179812 : ASSERT(!CurrentMove->startMarker());
769 4179812 : return OK;
770 : }
771 :
772 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
773 : // Game::MoveIntoVariation():
774 : // Move into a subvariation. Variations are numbered from 0.
775 382822 : errorT Game::MoveIntoVariation(uint varNumber) {
776 429336 : for (auto subVar = CurrentMove; subVar->varChild; --varNumber) {
777 395960 : subVar = subVar->varChild;
778 395960 : if (varNumber == 0) {
779 349446 : CurrentMove = subVar->next; // skip the START_MARKER
780 349446 : ++VarDepth;
781 :
782 : // Invariants
783 349446 : ASSERT(CurrentMove && CurrentMove->prev);
784 349446 : ASSERT(!CurrentMove->startMarker());
785 349446 : return OK;
786 : }
787 : }
788 33376 : return ERROR_NoVariation; // there is no such variation
789 : }
790 :
791 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
792 : // Game::MoveExitVariation():
793 : // Move out of a variation, to the parent.
794 : //
795 964410 : errorT Game::MoveExitVariation(void) {
796 964410 : if (VarDepth == 0) // not in a variation!
797 0 : return ERROR_NoVariation;
798 :
799 : // Algorithm: go back previous moves as far as possible, then
800 : // go up to the parent of the variation.
801 4490547 : while (MoveBackup() == OK) {
802 : }
803 964410 : CurrentMove = CurrentMove->getParent().first;
804 964410 : --VarDepth;
805 :
806 : // Invariants
807 964410 : ASSERT(CurrentMove && CurrentMove->prev);
808 964410 : ASSERT(!CurrentMove->startMarker());
809 964410 : return OK;
810 : }
811 :
812 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
813 : // Move to the beginning of the game.
814 : //
815 13660 : void Game::MoveToStart() {
816 13660 : if (StartPos) {
817 0 : *CurrentPos = *StartPos;
818 : } else {
819 13660 : CurrentPos->StdStart();
820 : }
821 13660 : VarDepth = 0;
822 13660 : CurrentMove = FirstMove->next;
823 :
824 : // Invariants
825 13660 : ASSERT(CurrentMove && CurrentMove->prev);
826 13660 : ASSERT(!CurrentMove->startMarker());
827 13660 : }
828 :
829 423596 : errorT Game::MoveForwardInPGN() {
830 423596 : if (CurrentMove->prev->varChild && MoveBackup() == OK)
831 34927 : return MoveIntoVariation(0);
832 :
833 33376 : while (MoveForward() != OK) {
834 39435 : if (VarDepth == 0)
835 18 : return ERROR_EndOfMoveList;
836 :
837 39417 : auto varnum = GetVarNumber();
838 39417 : MoveExitVariation();
839 39417 : if (MoveIntoVariation(varnum + 1) == OK)
840 6041 : return OK;
841 :
842 33376 : MoveForward();
843 : }
844 382610 : return OK;
845 : }
846 :
847 1590 : errorT Game::MoveToLocationInPGN(unsigned stopLocation) {
848 1590 : MoveToPly(0);
849 422329 : for (unsigned loc = 1; loc < stopLocation; ++loc) {
850 420742 : errorT err = MoveForwardInPGN();
851 420742 : if (err != OK)
852 3 : return err;
853 : }
854 1587 : return OK;
855 : }
856 :
857 3165 : unsigned Game::GetLocationInPGN() const {
858 3165 : unsigned res = 1;
859 3165 : const moveT* last_move = CurrentMove->prev;
860 3165 : const moveT* move = FirstMove;
861 1829909 : for (; move != last_move; move = move->nextMoveInPGN()) {
862 913372 : if (!move->endMarker())
863 835655 : ++res;
864 : }
865 3165 : return res;
866 : }
867 :
868 1443 : unsigned Game::GetPgnOffset() const {
869 1443 : unsigned res = 1;
870 1443 : const moveT* last_move = CurrentMove->getPrevMove();
871 1443 : if (last_move) {
872 1443 : const moveT* move = FirstMove;
873 839719 : for (; move != last_move; move = move->nextMoveInPGN()) {
874 419138 : if (!move->endMarker())
875 383488 : ++res;
876 : }
877 : }
878 1443 : return res;
879 : }
880 :
881 : ///////////////////////////////////////////////////////////////////////////
882 : // The following functions modify the moves graph in order to add or delete
883 : // moves. Promoting variations also modifies the moves graph.
884 :
885 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
886 : // Game::AddMove():
887 : // Add a move at current position and do it.
888 : //
889 3127677 : errorT Game::AddMove(const simpleMoveT* sm) {
890 3127677 : ASSERT(sm != NULL);
891 :
892 : // We must be at the end of a game/variation to add a move:
893 3127677 : if (!CurrentMove->endMarker())
894 0 : Truncate();
895 :
896 3127677 : CurrentMove->setNext(NewMove(END_MARKER));
897 3127677 : CurrentMove->marker = NO_MARKER;
898 3127677 : CurrentMove->moveData.from = sm->from;
899 3127677 : CurrentMove->moveData.to = sm->to;
900 3127677 : CurrentMove->moveData.promote = sm->promote;
901 3127677 : CurrentMove->moveData.movingPiece = sm->movingPiece;
902 3127677 : if (VarDepth == 0)
903 941265 : ++NumHalfMoves;
904 :
905 3127677 : return MoveForward();
906 : }
907 :
908 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
909 : // Game::AddVariation():
910 : // Add a variation for the current move.
911 : // Also moves into the variation.
912 617636 : errorT Game::AddVariation() {
913 617636 : auto err = MoveBackup();
914 617636 : if (err != OK)
915 0 : return err;
916 :
917 617636 : auto newVar = NewMove(START_MARKER);
918 617636 : newVar->setNext(NewMove(END_MARKER));
919 617636 : CurrentMove->appendChild(newVar);
920 :
921 : // Move into variation
922 617636 : CurrentMove = newVar->next;
923 617636 : ++VarDepth;
924 :
925 : // Invariants
926 617636 : ASSERT(CurrentMove && CurrentMove->prev);
927 617636 : ASSERT(!CurrentMove->startMarker());
928 617636 : return OK;
929 : }
930 :
931 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
932 : // Game::FirstVariation():
933 : // Promotes the current variation to first variation.
934 0 : errorT Game::FirstVariation() {
935 0 : auto parent = CurrentMove->getParent();
936 0 : auto root = parent.first;
937 0 : if (!root)
938 0 : return ERROR_NoVariation;
939 :
940 0 : root->detachChild(parent.second);
941 0 : root->insertChild(parent.second, 0);
942 0 : return OK;
943 : }
944 :
945 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
946 : // Game::MainVariation():
947 : // Like FirstVariation, but promotes the variation to the main line,
948 : // demoting the main line to be the first variation.
949 0 : errorT Game::MainVariation() {
950 0 : auto parent = CurrentMove->getParent();
951 0 : auto root = parent.first;
952 0 : if (!root)
953 0 : return ERROR_NoVariation;
954 0 : if (parent.second->next->endMarker()) // Do not promote empty variations
955 0 : return OK;
956 :
957 : // Make the current variation the first variation
958 0 : root->detachChild(parent.second);
959 0 : root->insertChild(parent.second, 0);
960 :
961 : // Swap the mainline with the current variation
962 0 : root->swapLine(*parent.second->next);
963 :
964 0 : ASSERT(VarDepth);
965 0 : --VarDepth;
966 :
967 : // Now, the information about the material at the end of the
968 : // game, pawn promotions, will be wrong if the variation was
969 : // promoted to an actual game move, so call MakeHomePawnList()
970 : // to go through the game moves and ensure it is correct.
971 0 : auto location = currentLocation();
972 : byte tempPawnList[9];
973 0 : MakeHomePawnList(tempPawnList);
974 0 : restoreLocation(location);
975 :
976 0 : return OK;
977 : }
978 :
979 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
980 : // Game::DeleteVariation():
981 : // Deletes a variation. Variations are numbered from 0.
982 : // Note that for speed and simplicity, freed moves are not
983 : // added to the free list. This means that repeatedly adding and
984 : // deleting variations will waste memory until the game is cleared.
985 : //
986 0 : errorT Game::DeleteVariation() {
987 0 : auto parent = CurrentMove->getParent();
988 0 : auto root = parent.first;
989 0 : if (!root || MoveExitVariation() != OK)
990 0 : return ERROR_NoVariation;
991 :
992 0 : root->detachChild(parent.second);
993 0 : return OK;
994 : }
995 :
996 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
997 : // Game::Truncate():
998 : // Truncate game at the current move.
999 : // For speed and simplicity, moves and comments are not freed.
1000 : // So repeatedly adding moves and truncating a game will waste
1001 : // memory until the game is cleared.
1002 0 : void Game::Truncate() {
1003 0 : if (CurrentMove->endMarker())
1004 0 : return;
1005 :
1006 0 : auto endMove = NewMove(END_MARKER);
1007 0 : CurrentMove->prev->setNext(endMove);
1008 :
1009 0 : CurrentMove = endMove;
1010 0 : if (VarDepth == 0)
1011 0 : NumHalfMoves = GetCurrentPly();
1012 :
1013 : // Invariants
1014 0 : ASSERT(CurrentMove && CurrentMove->prev);
1015 0 : ASSERT(!CurrentMove->startMarker());
1016 : }
1017 :
1018 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1019 : // Game::TruncateStart():
1020 : // Truncate all moves leading to current position.
1021 0 : void Game::TruncateStart() {
1022 : // It is necessary to rebuild the current position using ReadFromFEN()
1023 : // because the order of pieces is important when encoding to SCIDv4 format.
1024 : char tempStr[256];
1025 0 : CurrentPos->PrintFEN(tempStr, FEN_ALL_FIELDS);
1026 0 : auto pos = std::make_unique<Position>();
1027 0 : if (pos->ReadFromFEN(tempStr) != OK)
1028 0 : return;
1029 :
1030 0 : if (VarDepth != 0 && MainVariation() != OK)
1031 0 : return;
1032 :
1033 0 : NumHalfMoves -= GetCurrentPly();
1034 0 : StartPos = std::move(pos);
1035 0 : *CurrentPos = *StartPos;
1036 0 : FirstMove->setNext(CurrentMove);
1037 : }
1038 :
1039 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1040 : // Game::MakeHomePawnList():
1041 : // Is passed an array of 9 bytes and fills it with the game's
1042 : // home pawn delta information.
1043 : // This function also ensures that other information about the
1044 : // game that will be stored in the index file and used to speed
1045 : // up searches (material at end of game, etc) is up to date.
1046 :
1047 10018 : bool Game::MakeHomePawnList(byte* pbPawnList) {
1048 10018 : ASSERT(pbPawnList != nullptr);
1049 : // We zero out the list first:
1050 10018 : std::fill_n(pbPawnList, 9, 0);
1051 :
1052 10018 : uint count = 0;
1053 10018 : uint halfByte = 0;
1054 10018 : errorT err = OK;
1055 10018 : uint hpOld = HPSIG_StdStart; // All 16 pawns are on their home squares.
1056 10018 : byte* pbList = pbPawnList +1;
1057 :
1058 10018 : NumHalfMoves = 0;
1059 10018 : PromotionsFlag = false;
1060 10018 : bool UnderPromosFlag = false;
1061 10018 : MoveToPly(0);
1062 :
1063 5643410 : while (err == OK) {
1064 2816696 : uint hpNew = CurrentPos->GetHPSig();
1065 2816696 : uint changed = hpOld - hpNew;
1066 2816696 : if (changed != 0 && !HasNonStandardStart()) {
1067 : // Find the idx of the moved pawn
1068 149217 : uint changeValue = 0;
1069 : while (1) {
1070 2386485 : changed = changed >> 1;
1071 1267851 : if (changed == 0) break;
1072 1118634 : changeValue++;
1073 : }
1074 :
1075 : // There are only 16 pawns, so we can store two pawn moves
1076 : // in every byte
1077 149217 : if (halfByte == 0) {
1078 75507 : *pbList = (changeValue << 4); halfByte = 1;
1079 : } else {
1080 73710 : *pbList |= (changeValue & 15); pbList++; halfByte = 0;
1081 : }
1082 149217 : hpOld = hpNew;
1083 149217 : count++;
1084 : }
1085 2816696 : if (CurrentMove->marker != END_MARKER) {
1086 2806678 : if (CurrentMove->moveData.promote != EMPTY) {
1087 21976 : PromotionsFlag = true;
1088 21976 : if (piece_Type(CurrentMove->moveData.promote) != QUEEN) {
1089 18808 : UnderPromosFlag = true;
1090 : }
1091 : }
1092 : }
1093 2816696 : err = MoveForward();
1094 2816696 : if (err == OK) { NumHalfMoves++; }
1095 : }
1096 : // First byte in pawnlist array stores the count:
1097 10018 : pbPawnList[0] = (byte) count;
1098 :
1099 10018 : return UnderPromosFlag;
1100 : }
1101 :
1102 : namespace {
1103 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1104 : // calcHomePawnMask():
1105 : // Computes the homePawn mask for a position.
1106 : //
1107 0 : int calcHomePawnMask (pieceT pawn, const pieceT* board)
1108 : {
1109 0 : ASSERT (pawn == WP || pawn == BP);
1110 0 : const pieceT* bd = &(board[ (pawn == WP ? H2 : H7) ]);
1111 0 : int result = 0;
1112 0 : if (*bd == pawn) { result |= 128; } bd--; // H-fyle pawn
1113 0 : if (*bd == pawn) { result |= 64; } bd--; // G-fyle pawn
1114 0 : if (*bd == pawn) { result |= 32; } bd--; // F-fyle pawn
1115 0 : if (*bd == pawn) { result |= 16; } bd--; // E-fyle pawn
1116 0 : if (*bd == pawn) { result |= 8; } bd--; // D-fyle pawn
1117 0 : if (*bd == pawn) { result |= 4; } bd--; // C-fyle pawn
1118 0 : if (*bd == pawn) { result |= 2; } bd--; // B-fyle pawn
1119 0 : if (*bd == pawn) { result |= 1; } // A-fyle pawn
1120 0 : return result;
1121 : }
1122 :
1123 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1124 : // patternsMatch():
1125 : // Used by Game::MaterialMatch() to test patterns.
1126 : // Returns 1 if all the patterns in the list match, 0 otherwise.
1127 : //
1128 0 : int patternsMatch(const Position* pos, patternT* ptn) {
1129 0 : const pieceT* board = pos->GetBoard();
1130 0 : while (ptn != NULL) {
1131 0 : if (ptn->rankMatch == NO_RANK) {
1132 :
1133 0 : if (ptn->fyleMatch == NO_FYLE) { // Nothing to test!
1134 : } else { // Test this fyle:
1135 0 : squareT sq = square_Make (ptn->fyleMatch, RANK_1);
1136 0 : int found = 0;
1137 0 : for (uint i=0; i < 8; i++, sq += 8) {
1138 0 : if (board[sq] == ptn->pieceMatch) { found = 1; break; }
1139 : }
1140 0 : if (found != ptn->flag) { return 0; }
1141 : }
1142 :
1143 : } else { // rankMatch is a rank from 1 to 8:
1144 :
1145 0 : if (ptn->fyleMatch == NO_FYLE) { // Test the whole rank:
1146 0 : int found = 0;
1147 0 : squareT sq = square_Make (A_FYLE, ptn->rankMatch);
1148 0 : for (uint i=0; i < 8; i++, sq++) {
1149 0 : if (board[sq] == ptn->pieceMatch) { found = 1; break; }
1150 : }
1151 0 : if (found != ptn->flag) { return 0; }
1152 : } else { // Just test one square:
1153 0 : squareT sq = square_Make(ptn->fyleMatch, ptn->rankMatch);
1154 0 : int found = 0;
1155 0 : if (board[sq] == ptn->pieceMatch) { found = 1; }
1156 0 : if (found != ptn->flag) { return 0; }
1157 : }
1158 : }
1159 :
1160 : // If we get this far, this pattern matched. Try the next one:
1161 0 : ptn = ptn->next;
1162 : }
1163 :
1164 : // If we reach here, all patterns matched:
1165 0 : return 1;
1166 : }
1167 : } // end of anonymous namespace
1168 :
1169 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1170 : // Game::MaterialMatch(): Material search test.
1171 : // The parameters min and max should each be an array of 15
1172 : // counts, to specify the maximum and minimum number of counts
1173 : // of each type of piece.
1174 : //
1175 : bool
1176 0 : Game::MaterialMatch (ByteBuffer * buf, byte * min, byte * max,
1177 : patternT * patterns, int minPly, int maxPly,
1178 : int matchLength, bool oppBishops, bool sameBishops,
1179 : int minDiff, int maxDiff)
1180 : {
1181 : // If buf is NULL, the game is in memory. Otherwise, Decode only
1182 : // the necessary moves:
1183 0 : errorT err = OK;
1184 :
1185 0 : if (buf == NULL) {
1186 0 : MoveToPly(0);
1187 : } else {
1188 0 : Clear();
1189 0 : err = DecodeStart (buf);
1190 0 : KeepDecodedMoves = false;
1191 : }
1192 :
1193 0 : ASSERT (matchLength >= 1);
1194 :
1195 0 : int matchesNeeded = matchLength;
1196 : int matDiff;
1197 0 : uint plyCount = 0;
1198 0 : while (err == OK) {
1199 0 : bool foundMatch = false;
1200 : byte wMinor, bMinor;
1201 :
1202 : // If current pos has LESS than the minimum of pawns, this
1203 : // game can never match so return false;
1204 0 : if (CurrentPos->PieceCount(WP) < min[WP]) { return false; }
1205 0 : if (CurrentPos->PieceCount(BP) < min[BP]) { return false; }
1206 :
1207 : // If not in the valid move range, go to the next move or return:
1208 0 : if ((int)plyCount > maxPly) { return false; }
1209 0 : if ((int)plyCount < minPly) { goto Next_Move; }
1210 :
1211 : // For these comparisons, we really could only do half of them each move,
1212 : // according to which side just moved.
1213 : // For non-pawns, the count could be increased by promotions:
1214 0 : if (CurrentPos->PieceCount(WQ) < min[WQ]) { goto Check_Promotions; }
1215 0 : if (CurrentPos->PieceCount(BQ) < min[BQ]) { goto Check_Promotions; }
1216 0 : if (CurrentPos->PieceCount(WR) < min[WR]) { goto Check_Promotions; }
1217 0 : if (CurrentPos->PieceCount(BR) < min[BR]) { goto Check_Promotions; }
1218 0 : if (CurrentPos->PieceCount(WB) < min[WB]) { goto Check_Promotions; }
1219 0 : if (CurrentPos->PieceCount(BB) < min[BB]) { goto Check_Promotions; }
1220 0 : if (CurrentPos->PieceCount(WN) < min[WN]) { goto Check_Promotions; }
1221 0 : if (CurrentPos->PieceCount(BN) < min[BN]) { goto Check_Promotions; }
1222 0 : wMinor = CurrentPos->PieceCount(WB) + CurrentPos->PieceCount(WN);
1223 0 : bMinor = CurrentPos->PieceCount(BB) + CurrentPos->PieceCount(BN);
1224 0 : if (wMinor < min[WM]) { goto Check_Promotions; }
1225 0 : if (bMinor < min[BM]) { goto Check_Promotions; }
1226 :
1227 : // Now test maximum counts:
1228 0 : if (CurrentPos->PieceCount(WQ) > max[WQ]) { goto Next_Move; }
1229 0 : if (CurrentPos->PieceCount(BQ) > max[BQ]) { goto Next_Move; }
1230 0 : if (CurrentPos->PieceCount(WR) > max[WR]) { goto Next_Move; }
1231 0 : if (CurrentPos->PieceCount(BR) > max[BR]) { goto Next_Move; }
1232 0 : if (CurrentPos->PieceCount(WB) > max[WB]) { goto Next_Move; }
1233 0 : if (CurrentPos->PieceCount(BB) > max[BB]) { goto Next_Move; }
1234 0 : if (CurrentPos->PieceCount(WN) > max[WN]) { goto Next_Move; }
1235 0 : if (CurrentPos->PieceCount(BN) > max[BN]) { goto Next_Move; }
1236 0 : if (CurrentPos->PieceCount(WP) > max[WP]) { goto Next_Move; }
1237 0 : if (CurrentPos->PieceCount(BP) > max[BP]) { goto Next_Move; }
1238 0 : if (wMinor > max[WM]) { goto Next_Move; }
1239 0 : if (bMinor > max[BM]) { goto Next_Move; }
1240 :
1241 : // If both sides have ONE bishop, we need to check if the search
1242 : // was restricted to same-color or opposite-color bishops:
1243 0 : if (CurrentPos->PieceCount(WB) == 1
1244 0 : && CurrentPos->PieceCount(BB) == 1) {
1245 0 : if (!oppBishops || !sameBishops) { // Check the restriction:
1246 0 : colorT whiteBishCol = NOCOLOR;
1247 0 : colorT blackBishCol = NOCOLOR;
1248 :
1249 : // Search for the white and black bishop, to find their
1250 : // square color:
1251 0 : const pieceT* bd = CurrentPos->GetBoard();
1252 0 : for (squareT sq = A1; sq <= H8; sq++) {
1253 0 : if (bd[sq] == WB) {
1254 0 : whiteBishCol = BOARD_SQUARECOLOR [sq];
1255 0 : } else if (bd[sq] == BB) {
1256 0 : blackBishCol = BOARD_SQUARECOLOR [sq];
1257 : }
1258 : }
1259 : // They should be valid colors:
1260 0 : ASSERT (blackBishCol != NOCOLOR && whiteBishCol != NOCOLOR);
1261 :
1262 : // If the square colors do not match the restriction,
1263 : // then this game cannot match:
1264 0 : if (oppBishops && blackBishCol == whiteBishCol) {
1265 0 : return false;
1266 : }
1267 0 : if (sameBishops && blackBishCol != whiteBishCol) {
1268 0 : return false;
1269 : }
1270 : }
1271 : }
1272 :
1273 : // Now check if the material difference is in-range:
1274 0 : matDiff = (int)CurrentPos->MaterialValue(WHITE) -
1275 0 : (int)CurrentPos->MaterialValue(BLACK);
1276 0 : if (matDiff < minDiff || matDiff > maxDiff) { goto Next_Move; }
1277 :
1278 : // At this point, the Material matches; do the patterns match?
1279 0 : if (patterns == NULL || patternsMatch(currentPos(), patterns)) {
1280 0 : foundMatch = true;
1281 0 : matchesNeeded--;
1282 0 : if (matchesNeeded <= 0) { return true; }
1283 : }
1284 : // No? well, keep trying...
1285 0 : goto Next_Move;
1286 :
1287 0 : Check_Promotions:
1288 : // We only continue if this game has promotion moves:
1289 0 : if (! PromotionsFlag) { return false; }
1290 :
1291 0 : Next_Move:
1292 0 : if (buf == NULL) {
1293 0 : MoveForward();
1294 0 : if (CurrentMove->marker == END_MARKER) {
1295 0 : err = ERROR_EndOfMoveList;
1296 : }
1297 : } else {
1298 0 : err = DecodeNextMove (buf, NULL);
1299 : }
1300 0 : plyCount++;
1301 0 : if (! foundMatch) { matchesNeeded = matchLength; }
1302 : }
1303 :
1304 : // End of game reached, and no match:
1305 0 : return false;
1306 : }
1307 :
1308 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1309 : // Game::ExactMatch():
1310 : // Exact position search test.
1311 : // If sm is not NULL, its from, to, promote etc will be filled with
1312 : // the next move at the matching position, if there is one.
1313 : // If neverMatch is non-NULL, the boolean it points to is set to
1314 : // true if the game could never match even with extra moves.
1315 : //
1316 : bool
1317 0 : Game::ExactMatch (Position * searchPos, ByteBuffer * buf, simpleMoveT * sm,
1318 : gameExactMatchT searchType, bool * neverMatch)
1319 : {
1320 : // If buf is NULL, the game is in memory. Otherwise, Decode only
1321 : // the necessary moves:
1322 0 : errorT err = OK;
1323 :
1324 0 : if (buf == NULL) {
1325 0 : MoveToPly(0);
1326 : } else {
1327 0 : Clear ();
1328 0 : err = DecodeStart (buf);
1329 0 : KeepDecodedMoves = false;
1330 : }
1331 :
1332 0 : uint plyCount = 0;
1333 : //uint skip = 0; // Just for statistics on number of moves skipped.
1334 0 : uint search_whiteHPawns = 0;
1335 0 : uint search_blackHPawns = 0;
1336 : uint current_whiteHPawns, current_blackHPawns;
1337 : bool check_pawnMaskWhite, check_pawnMaskBlack;
1338 0 : bool doHomePawnChecks = false;
1339 :
1340 0 : uint wpawnFyle [8] = {0, 0, 0, 0, 0, 0, 0, 0};
1341 0 : uint bpawnFyle [8] = {0, 0, 0, 0, 0, 0, 0, 0};;
1342 :
1343 0 : if (searchType == GAME_EXACT_MATCH_Fyles) {
1344 0 : const pieceT* board = searchPos->GetBoard();
1345 0 : uint fyle = 0;
1346 0 : for (squareT sq = A1; sq <= H8; sq++, board++) {
1347 0 : if (*board == WP) {
1348 0 : wpawnFyle[fyle]++;
1349 0 : } else if (*board == BP) {
1350 0 : bpawnFyle[fyle]++;
1351 : }
1352 0 : fyle = (fyle + 1) & 7;
1353 : }
1354 : }
1355 :
1356 : // If neverMatch is null, point it at a dummy value
1357 : bool dummy;
1358 0 : if (neverMatch == NULL) { neverMatch = &dummy; }
1359 0 : *neverMatch = false;
1360 :
1361 0 : if (searchType == GAME_EXACT_MATCH_Exact ||
1362 : searchType == GAME_EXACT_MATCH_Pawns) {
1363 0 : doHomePawnChecks = true;
1364 0 : search_whiteHPawns = calcHomePawnMask (WP, searchPos->GetBoard());
1365 0 : search_blackHPawns = calcHomePawnMask (BP, searchPos->GetBoard());
1366 : }
1367 0 : check_pawnMaskWhite = check_pawnMaskBlack = false;
1368 :
1369 0 : while (err == OK) {
1370 0 : const pieceT* currentBoard = CurrentPos->GetBoard();
1371 0 : const pieceT* board = searchPos->GetBoard();
1372 0 : const pieceT* b1 = currentBoard;
1373 0 : const pieceT* b2 = board;
1374 0 : bool found = true;
1375 :
1376 : // If NO_SPEEDUPS is defined, a slower search is done without
1377 : // optimisations that detect insufficient material.
1378 : #ifndef NO_SPEEDUPS
1379 : // Insufficient material optimisation:
1380 0 : if (searchPos->GetCount(WHITE) > CurrentPos->GetCount(WHITE) ||
1381 0 : searchPos->GetCount(BLACK) > CurrentPos->GetCount(BLACK)) {
1382 0 : *neverMatch = true;
1383 0 : return false;
1384 : }
1385 : // Insufficient pawns optimisation:
1386 0 : if (searchPos->PieceCount(WP) > CurrentPos->PieceCount(WP) ||
1387 0 : searchPos->PieceCount(BP) > CurrentPos->PieceCount(BP)) {
1388 0 : *neverMatch = true;
1389 0 : return false;
1390 : }
1391 :
1392 : // HomePawn mask optimisation:
1393 : // If current pos doesn't have a pawn on home rank where
1394 : // the search pos has one, it can never match.
1395 : // This happens when (current_xxHPawns & search_xxHPawns) is
1396 : // not equal to search_xxHPawns.
1397 : // We do not do this optimisation for a pawn files search,
1398 : // because the exact pawn squares are not important there.
1399 :
1400 0 : if (searchType != GAME_EXACT_MATCH_Fyles) {
1401 0 : if (check_pawnMaskWhite) {
1402 0 : current_whiteHPawns = calcHomePawnMask (WP, currentBoard);
1403 0 : if ((current_whiteHPawns & search_whiteHPawns)
1404 : != search_whiteHPawns) {
1405 0 : *neverMatch = true;
1406 0 : return false;
1407 : }
1408 : }
1409 0 : if (check_pawnMaskBlack) {
1410 0 : current_blackHPawns = calcHomePawnMask (BP, currentBoard);
1411 0 : if ((current_blackHPawns & search_blackHPawns)
1412 : != search_blackHPawns) {
1413 0 : *neverMatch = true;
1414 0 : return false;
1415 : }
1416 : }
1417 : }
1418 : #endif // #ifndef NO_SPEEDUPS
1419 :
1420 : // Not correct color: skip to next move
1421 0 : if (searchPos->GetToMove() != CurrentPos->GetToMove()) {
1422 : //skip++;
1423 0 : goto Move_Forward;
1424 : }
1425 :
1426 : // Extra material: skip to next move
1427 0 : if (searchPos->GetCount(WHITE) < CurrentPos->GetCount(WHITE) ||
1428 0 : searchPos->GetCount(BLACK) < CurrentPos->GetCount(BLACK)) {
1429 : //skip++;
1430 0 : goto Move_Forward;
1431 : }
1432 : // Extra pawns/pieces: skip to next move
1433 0 : if (searchPos->PieceCount(WP) != CurrentPos->PieceCount(WP) ||
1434 0 : searchPos->PieceCount(BP) != CurrentPos->PieceCount(BP) ||
1435 0 : searchPos->PieceCount(WN) != CurrentPos->PieceCount(WN) ||
1436 0 : searchPos->PieceCount(BN) != CurrentPos->PieceCount(BN) ||
1437 0 : searchPos->PieceCount(WB) != CurrentPos->PieceCount(WB) ||
1438 0 : searchPos->PieceCount(BB) != CurrentPos->PieceCount(BB) ||
1439 0 : searchPos->PieceCount(WR) != CurrentPos->PieceCount(WR) ||
1440 0 : searchPos->PieceCount(BR) != CurrentPos->PieceCount(BR) ||
1441 0 : searchPos->PieceCount(WQ) != CurrentPos->PieceCount(WQ) ||
1442 0 : searchPos->PieceCount(BQ) != CurrentPos->PieceCount(BQ)) {
1443 : //skip++;
1444 0 : goto Move_Forward;
1445 : }
1446 :
1447 : // NOW, compare the actual boards piece-by-piece.
1448 0 : if (searchType == GAME_EXACT_MATCH_Exact) {
1449 0 : if (searchPos->HashValue() == CurrentPos->HashValue()) {
1450 0 : for (squareT sq = A1; sq <= H8; sq++, b1++, b2++) {
1451 0 : if (*b1 != *b2) { found = false; break; }
1452 : }
1453 : } else {
1454 0 : found = false;
1455 : }
1456 0 : } else if (searchType == GAME_EXACT_MATCH_Pawns) {
1457 0 : if (searchPos->PawnHashValue() == CurrentPos->PawnHashValue()) {
1458 0 : for (squareT sq = A1; sq <= H8; sq++, b1++, b2++) {
1459 0 : if (*b1 != *b2 && (*b1 == WP || *b1 == BP)) {
1460 0 : found = false;
1461 0 : break;
1462 : }
1463 : }
1464 : } else {
1465 0 : found = false;
1466 : }
1467 0 : } else if (searchType == GAME_EXACT_MATCH_Fyles) {
1468 0 : for (fyleT f = A_FYLE; f <= H_FYLE; f++) {
1469 0 : if (searchPos->FyleCount(WP,f) != CurrentPos->FyleCount(WP,f)
1470 0 : || searchPos->FyleCount(BP,f) != CurrentPos->FyleCount(BP,f)) {
1471 0 : found = false;
1472 0 : break;
1473 : }
1474 : }
1475 : } else {
1476 : // searchType == GAME_EXACT_Match_Material, so do nothing.
1477 : }
1478 :
1479 0 : if (found) {
1480 : // Found a match! Set the returned next-move:
1481 0 : if (sm) { // We need to decode the next move.
1482 0 : if (buf == NULL) {
1483 0 : MoveForward();
1484 0 : if (CurrentMove->marker == END_MARKER) {
1485 : // Position matched at last move in the game.
1486 0 : sm->from = sm->to = NULL_SQUARE;
1487 0 : sm->promote = EMPTY;
1488 : } else {
1489 0 : *sm = CurrentMove->prev->moveData;
1490 0 : MoveBackup();
1491 : }
1492 : } else {
1493 0 : err = DecodeNextMove (buf, sm);
1494 0 : if (err != OK) {
1495 : // Position matched at last move in the game.
1496 0 : sm->from = sm->to = NULL_SQUARE;
1497 0 : sm->promote = EMPTY;
1498 : } else {
1499 : // Backup to the matching position:
1500 0 : CurrentPos->UndoSimpleMove (sm);
1501 : }
1502 : }
1503 : }
1504 0 : return true;
1505 : }
1506 :
1507 0 : Move_Forward:
1508 : #ifndef NO_SPEEDUPS
1509 0 : if (doHomePawnChecks) {
1510 0 : check_pawnMaskWhite = false;
1511 0 : check_pawnMaskBlack = false;
1512 0 : rankT rTo = square_Rank (CurrentMove->moveData.to);
1513 0 : rankT rFrom = square_Rank (CurrentMove->moveData.from);
1514 : // We only re-check the home pawn masks when something moves
1515 : // to or from the 2nd/7th rank:
1516 0 : if (rTo == RANK_2 || rFrom == RANK_2) {
1517 0 : check_pawnMaskWhite = true;
1518 : }
1519 0 : if (rTo == RANK_7 || rFrom == RANK_7) {
1520 0 : check_pawnMaskBlack = true;
1521 : }
1522 : }
1523 : #endif
1524 0 : if (buf == NULL) {
1525 0 : MoveForward ();
1526 0 : if (CurrentMove->marker == END_MARKER) {
1527 0 : err = ERROR_EndOfMoveList;
1528 : }
1529 : } else {
1530 0 : err = DecodeNextMove (buf, NULL);
1531 0 : if (err != OK && err != ERROR_EndOfMoveList) {
1532 0 : return false;
1533 : }
1534 : }
1535 0 : plyCount++;
1536 : }
1537 0 : return false;
1538 : }
1539 :
1540 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1541 : // Game::VarExactMatch():
1542 : // Like ExactMatch(), but also searches in variations.
1543 : // This is much slower than ExactMatch(), since it will
1544 : // search every position until a match is found.
1545 : bool
1546 0 : Game::VarExactMatch (Position * searchPos, gameExactMatchT searchType)
1547 : {
1548 0 : uint wpawnFyle [8] = {0, 0, 0, 0, 0, 0, 0, 0};
1549 0 : uint bpawnFyle [8] = {0, 0, 0, 0, 0, 0, 0, 0};;
1550 :
1551 0 : if (searchType == GAME_EXACT_MATCH_Fyles) {
1552 0 : const pieceT* board = searchPos->GetBoard();
1553 0 : uint fyle = 0;
1554 0 : for (squareT sq = A1; sq <= H8; sq++, board++) {
1555 0 : if (*board == WP) {
1556 0 : wpawnFyle[fyle]++;
1557 0 : } else if (*board == BP) {
1558 0 : bpawnFyle[fyle]++;
1559 : }
1560 0 : fyle = (fyle + 1) & 7;
1561 : }
1562 : }
1563 :
1564 0 : errorT err = OK;
1565 0 : while (err == OK) {
1566 : // Check if this position matches:
1567 0 : bool match = false;
1568 0 : if (searchPos->GetToMove() == CurrentPos->GetToMove()
1569 0 : && searchPos->GetCount(WHITE) == CurrentPos->GetCount(WHITE)
1570 0 : && searchPos->GetCount(BLACK) == CurrentPos->GetCount(BLACK)
1571 0 : && searchPos->PieceCount(WP) == CurrentPos->PieceCount(WP)
1572 0 : && searchPos->PieceCount(BP) == CurrentPos->PieceCount(BP)
1573 0 : && searchPos->PieceCount(WN) == CurrentPos->PieceCount(WN)
1574 0 : && searchPos->PieceCount(BN) == CurrentPos->PieceCount(BN)
1575 0 : && searchPos->PieceCount(WB) == CurrentPos->PieceCount(WB)
1576 0 : && searchPos->PieceCount(BB) == CurrentPos->PieceCount(BB)
1577 0 : && searchPos->PieceCount(WR) == CurrentPos->PieceCount(WR)
1578 0 : && searchPos->PieceCount(BR) == CurrentPos->PieceCount(BR)
1579 0 : && searchPos->PieceCount(WQ) == CurrentPos->PieceCount(WQ)
1580 0 : && searchPos->PieceCount(BQ) == CurrentPos->PieceCount(BQ)) {
1581 0 : match = true;
1582 0 : const pieceT* b1 = CurrentPos->GetBoard();
1583 0 : const pieceT* b2 = searchPos->GetBoard();
1584 0 : if (searchType == GAME_EXACT_MATCH_Pawns) {
1585 0 : for (squareT sq = A1; sq <= H8; sq++, b1++, b2++) {
1586 0 : if (*b1 != *b2 && (*b1 == WP || *b1 == BP)) {
1587 0 : match = false; break;
1588 : }
1589 : }
1590 0 : } else if (searchType == GAME_EXACT_MATCH_Fyles) {
1591 0 : uint wpf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
1592 0 : uint bpf[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
1593 0 : uint fyle = 0;
1594 0 : for (squareT sq = A1; sq <= H8; sq++, b1++) {
1595 0 : if (*b1 == WP) {
1596 0 : wpf[fyle]++;
1597 0 : if (wpf[fyle] > wpawnFyle[fyle]) { match = false; break; }
1598 0 : } else if (*b1 == BP) {
1599 0 : bpf[fyle]++;
1600 0 : if (bpf[fyle] > bpawnFyle[fyle]) { match = false; break; }
1601 : }
1602 0 : fyle = (fyle + 1) & 7;
1603 : }
1604 0 : } else if (searchType == GAME_EXACT_MATCH_Exact) {
1605 0 : if (searchPos->HashValue() == CurrentPos->HashValue()) {
1606 0 : for (squareT sq = A1; sq <= H8; sq++, b1++, b2++) {
1607 0 : if (*b1 != *b2) { match = false; break; }
1608 : }
1609 : } else {
1610 0 : match = false;
1611 : }
1612 : } else {
1613 : // searchType == GAME_EXACT_MATCH_Material, so do nothing.
1614 : }
1615 : }
1616 0 : if (match) { return true; }
1617 :
1618 : // Now try searching each variation in turn:
1619 0 : for (uint i=0; i < CurrentMove->numVariations; i++) {
1620 0 : MoveIntoVariation (i);
1621 0 : match = VarExactMatch (searchPos, searchType);
1622 0 : MoveExitVariation();
1623 0 : if (match) { return true; }
1624 : }
1625 : // Continue down this variation:
1626 0 : MoveForward();
1627 0 : if (CurrentMove->marker == END_MARKER) {
1628 0 : err = ERROR_EndOfMoveList;
1629 : }
1630 : }
1631 0 : return false;
1632 : }
1633 :
1634 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1635 : // Game::GetPartialMoveList():
1636 : // Write the first few moves of a game.
1637 : //
1638 : errorT
1639 0 : Game::GetPartialMoveList (DString * outStr, uint plyCount)
1640 : {
1641 : // First, copy the relevant data so we can leave the game state
1642 : // unaltered:
1643 0 : auto location = currentLocation();
1644 :
1645 0 : MoveToPly(0);
1646 : char temp [80];
1647 0 : for (uint i=0; i < plyCount; i++) {
1648 0 : if (CurrentMove->marker == END_MARKER) {
1649 0 : break;
1650 : }
1651 0 : if (i != 0) { outStr->Append (" "); }
1652 0 : if (i == 0 || CurrentPos->GetToMove() == WHITE) {
1653 0 : sprintf (temp, "%d%s", CurrentPos->GetFullMoveCount(),
1654 0 : (CurrentPos->GetToMove() == WHITE ? "." : "..."));
1655 0 : outStr->Append (temp);
1656 : }
1657 0 : moveT * m = CurrentMove;
1658 0 : if (m->san[0] == 0) {
1659 0 : CurrentPos->MakeSANString(&(m->moveData),
1660 : m->san, SAN_CHECKTEST);
1661 : }
1662 : // add one space for indenting to work out right
1663 0 : outStr->Append (" ");
1664 0 : outStr->Append (m->san);
1665 0 : MoveForward();
1666 : }
1667 :
1668 : // Now reconstruct the original game state:
1669 0 : restoreLocation(location);
1670 0 : return OK;
1671 : }
1672 :
1673 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1674 : // Returns the SAN representation of the next move or an empty string ("") if
1675 : // not at a move.
1676 3168 : const char* Game::GetNextSAN() {
1677 3168 : ASSERT(!CurrentMove->endMarker() || *CurrentMove->san == '\0');
1678 :
1679 3168 : if (!CurrentMove->endMarker() && *CurrentMove->san == '\0') {
1680 5752 : CurrentPos->MakeSANString(
1681 2876 : &CurrentMove->moveData, CurrentMove->san,
1682 1438 : CurrentMove->next->endMarker() ? SAN_MATETEST : SAN_CHECKTEST);
1683 : }
1684 3168 : return CurrentMove->san;
1685 : }
1686 :
1687 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1688 : // Game::GetSAN():
1689 : // Print the SAN representation of the current move to a string.
1690 : // Prints an empty string ("") if not at a move.
1691 6 : void Game::GetSAN(char* str) {
1692 6 : ASSERT(str != NULL);
1693 6 : strcpy(str, GetNextSAN());
1694 6 : }
1695 :
1696 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1697 : // Game::GetPrevSAN():
1698 : // Print the SAN representation of the current move to a string.
1699 : // Prints an empty string ("") if not at a move.
1700 : void
1701 1124 : Game::GetPrevSAN (char * str)
1702 : {
1703 1124 : ASSERT (str != NULL);
1704 1124 : moveT * m = CurrentMove->prev;
1705 1124 : if (m->marker == START_MARKER || m->marker == END_MARKER) {
1706 12 : str[0] = 0;
1707 12 : return;
1708 : }
1709 1112 : if (m->san[0] == 0) {
1710 1112 : MoveBackup();
1711 1112 : CurrentPos->MakeSANString (&(m->moveData), m->san, SAN_MATETEST);
1712 1112 : MoveForward();
1713 : }
1714 1112 : strcpy (str, m->san);
1715 : }
1716 :
1717 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1718 : // Game::GetPrevMoveUCI():
1719 : // Print the UCI representation of the current move to a string.
1720 : // Prints an empty string ("") if not at a move.
1721 0 : void Game::GetPrevMoveUCI(char* str) const {
1722 0 : ASSERT(str != NULL);
1723 0 : moveT* m = CurrentMove->prev;
1724 0 : if (m->marker == START_MARKER) {
1725 0 : str[0] = 0;
1726 : } else {
1727 0 : Position pos(*CurrentPos);
1728 0 : pos.UndoSimpleMove(&m->moveData);
1729 0 : pos.MakeUCIString(&m->moveData, str);
1730 : }
1731 0 : }
1732 :
1733 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1734 : // Game::GetNextMoveUCI():
1735 : // Print the UCI representation of the next move to a string.
1736 : // Prints an empty string ("") if not at a move.
1737 : void
1738 0 : Game::GetNextMoveUCI (char * str)
1739 : {
1740 0 : ASSERT (str != NULL);
1741 0 : moveT * m = CurrentMove;
1742 0 : if (m->marker == START_MARKER || m->marker == END_MARKER) {
1743 0 : str[0] = 0;
1744 0 : return;
1745 : }
1746 0 : CurrentPos->MakeUCIString (&(m->moveData), str);
1747 : }
1748 :
1749 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1750 : // commentEmpty:
1751 : // Called by WriteMoveList to check there is really
1752 : // something to print given display options.
1753 : // comment is supposed to be non null
1754 : bool
1755 223469 : Game::CommentEmpty ( const char * comment)
1756 : {
1757 223469 : char * s = NULL;
1758 223469 : bool ret = false;
1759 :
1760 223469 : if (comment == NULL)
1761 0 : return true;
1762 :
1763 223469 : if (comment[0] == '\0')
1764 0 : return true;
1765 :
1766 223469 : if (PgnStyle & PGN_STYLE_STRIP_MARKS) {
1767 0 : s = strDuplicate (comment);
1768 0 : strTrimMarkCodes (s);
1769 0 : char * tmp = s;
1770 0 : bool empty = true;
1771 0 : while (tmp[0] != 0) {
1772 0 : if (tmp[0] != ' ') {
1773 0 : empty = false;
1774 0 : break;
1775 : }
1776 0 : tmp++;
1777 : }
1778 0 : ret = empty;
1779 :
1780 0 : delete[] s;
1781 : }
1782 :
1783 223469 : return ret;
1784 : }
1785 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1786 : // writeComment:
1787 : // Called by WriteMoveList to write a single comment.
1788 : void
1789 223518 : Game::WriteComment (TextBuffer * tb, const char * preStr,
1790 : const char * comment, const char * postStr)
1791 : {
1792 :
1793 223518 : char * s = NULL;
1794 :
1795 223518 : if (PgnStyle & PGN_STYLE_STRIP_MARKS) {
1796 0 : s = strDuplicate (comment);
1797 0 : strTrimMarkCodes (s);
1798 : } else {
1799 223518 : s = (char *) comment;
1800 : }
1801 :
1802 223518 : if (s[0] != '\0') {
1803 :
1804 223518 : if (IsColorFormat()) {
1805 0 : tb->PrintString ("<c_");
1806 0 : tb->PrintInt (NumMovesPrinted);
1807 0 : tb->PrintChar ('>');
1808 : }
1809 :
1810 223518 : if (IsColorFormat()) {
1811 : // Translate "<", ">" in comments:
1812 0 : tb->AddTranslation ('<', "<lt>");
1813 0 : tb->AddTranslation ('>', "<gt>");
1814 : // S.A any issues ?
1815 0 : tb->NewlinesToSpaces (0);
1816 0 : tb->PrintString (s);
1817 0 : tb->ClearTranslation ('<');
1818 0 : tb->ClearTranslation ('>');
1819 : } else {
1820 223518 : tb->PrintString (preStr);
1821 223518 : tb->PrintString (s);
1822 223518 : tb->PrintString (postStr);
1823 : }
1824 :
1825 223518 : if (IsColorFormat()) { tb->PrintString ("</c>"); }
1826 : }
1827 :
1828 223518 : if (PgnStyle & PGN_STYLE_STRIP_MARKS) {
1829 0 : delete[] s;
1830 : }
1831 223518 : }
1832 :
1833 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1834 : // Game::WriteMoveList():
1835 : // Write the moves, variations and comments in PGN notation.
1836 : // Recursive; calls itself to write variations.
1837 : //
1838 310515 : errorT Game::WriteMoveList(TextBuffer* tb, moveT* oldCurrentMove,
1839 : bool printMoveNum, bool inComment) {
1840 : char tempTrans[10];
1841 310515 : const char * preCommentStr = "{";
1842 310515 : const char * postCommentStr = "}";
1843 310515 : const char * startTable = "\n";
1844 310515 : const char * startColumn = "\t";
1845 310515 : const char * nextColumn = "\t";
1846 310515 : const char * endColumn = "\n";
1847 310515 : const char * endTable = "\n";
1848 310515 : bool printDiagrams = false;
1849 :
1850 310515 : if (IsHtmlFormat()) {
1851 0 : preCommentStr = "";
1852 0 : postCommentStr = "";
1853 0 : startTable = "<table width=\"50%\">\n";
1854 0 : startColumn = "<tr align=left>\n <td width=\"15%\"><b>";
1855 0 : nextColumn = "</b></td>\n <td width=\"45%\" align=left><b>";
1856 0 : endColumn = "</b></td>\n</tr>\n";
1857 0 : endTable = "</table>\n";
1858 0 : printDiagrams = true;
1859 : }
1860 310515 : if (IsLatexFormat()) {
1861 0 : preCommentStr = "\\begin{nochess}{\\rm ";
1862 0 : postCommentStr = "}\\end{nochess}";
1863 0 : startTable = "\n\\begin{tabular}{p{1cm}p{2cm}p{2cm}}\n";
1864 0 : startColumn = "";
1865 0 : nextColumn = "&";
1866 0 : endColumn = "\\\\\n";
1867 0 : endTable = "\\end{tabular}\n\n";
1868 0 : printDiagrams = true;
1869 : }
1870 310515 : if (IsColorFormat()) {
1871 0 : startTable = "<br>";
1872 0 : endColumn = "<br>";
1873 : }
1874 :
1875 310515 : if (IsHtmlFormat() && VarDepth == 0) { tb->PrintString ("<b>"); }
1876 310515 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
1877 0 : tb->PrintString (startTable);
1878 : }
1879 :
1880 310515 : if (IsPlainFormat() && inComment) {
1881 0 : preCommentStr = "";
1882 0 : postCommentStr = "";
1883 : }
1884 310515 : moveT * m = CurrentMove;
1885 :
1886 : // Print null moves:
1887 621030 : if ((PgnStyle & PGN_STYLE_NO_NULL_MOVES) && !inComment &&
1888 310515 : IsPlainFormat() && m->isNull()) {
1889 0 : inComment = true;
1890 0 : tb->PrintString(preCommentStr);
1891 0 : preCommentStr = "";
1892 0 : postCommentStr = "";
1893 : }
1894 :
1895 : // If this is a variation and it starts with a comment, print it:
1896 621030 : if ((VarDepth > 0 || CurrentMove->prev == FirstMove) &&
1897 310515 : ! CurrentMove->prev->comment.empty()) {
1898 49 : if (PgnStyle & PGN_STYLE_COMMENTS) {
1899 49 : WriteComment (tb, preCommentStr, CurrentMove->prev->comment.c_str(),
1900 : postCommentStr);
1901 49 : tb->PrintSpace();
1902 49 : if (!VarDepth) {
1903 1 : tb->ClearTranslation ('\n');
1904 1 : tb->NewLine();
1905 1 : if (IsColorFormat() || IsLatexFormat()) {
1906 0 : tb->NewLine();
1907 : }
1908 : }
1909 : }
1910 : }
1911 :
1912 3434459 : while (CurrentMove->marker != END_MARKER) {
1913 1561972 : moveT *m = CurrentMove;
1914 1561972 : bool commentLine = false;
1915 :
1916 1561972 : if (m->san[0] == 0) {
1917 : // If there is a next move we can skip the SAN_MATETEST
1918 3123936 : CurrentPos->MakeSANString(
1919 : &(m->moveData), m->san,
1920 1561968 : (m->next->marker != END_MARKER) ? SAN_CHECKTEST : SAN_MATETEST);
1921 : }
1922 :
1923 1561972 : bool printThisMove = true;
1924 1561972 : if (m->isNull()) {
1925 : // Null moves are not printed in LaTeX or HTML:
1926 0 : if (IsLatexFormat() || IsHtmlFormat()) {
1927 0 : printThisMove = false;
1928 0 : printMoveNum = true;
1929 : }
1930 : // If Plain PGN format, check whether to convert the
1931 : // null move and remainder of the line to a comment:
1932 0 : if ((PgnStyle & PGN_STYLE_NO_NULL_MOVES) && IsPlainFormat()) {
1933 0 : if (!inComment) {
1934 : // Enter inComment mode to convert rest of line
1935 : // to a comment:
1936 0 : inComment = true;
1937 0 : tb->PrintString(preCommentStr);
1938 0 : preCommentStr = "";
1939 0 : postCommentStr = "";
1940 : }
1941 0 : printThisMove = false;
1942 0 : printMoveNum = true;
1943 : }
1944 : }
1945 1561972 : int colWidth = 6;
1946 1561972 : NumMovesPrinted++;
1947 :
1948 1561972 : if (printThisMove) {
1949 : // Print the move number and following dots if necessary:
1950 1561972 : if (IsColorFormat()) {
1951 0 : tb->PrintString ("<m_");
1952 0 : tb->PrintInt (NumMovesPrinted);
1953 0 : tb->PrintChar ('>');
1954 : }
1955 1561972 : if (printMoveNum || (CurrentPos->GetToMove() == WHITE)) {
1956 1155998 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
1957 0 : tb->PrintString (startColumn);
1958 : char temp [10];
1959 0 : sprintf (temp, "%4u.", CurrentPos->GetFullMoveCount());
1960 0 : tb->PrintString (temp);
1961 0 : if (CurrentPos->GetToMove() == BLACK) {
1962 0 : tb->PauseTranslations();
1963 0 : tb->PrintString (nextColumn);
1964 0 : tb->PrintString ("...");
1965 0 : if (IsPlainFormat() || IsColorFormat()) {
1966 0 : tb->PrintString (" ");
1967 : }
1968 0 : tb->ResumeTranslations();
1969 0 : }
1970 : } else {
1971 1155998 : if (PgnStyle & PGN_STYLE_MOVENUM_SPACE) {
1972 0 : tb->PrintInt(CurrentPos->GetFullMoveCount(), (CurrentPos->GetToMove() == WHITE ? "." : ". ..."));
1973 : } else {
1974 1155998 : tb->PrintInt(CurrentPos->GetFullMoveCount(), (CurrentPos->GetToMove() == WHITE ? "." : "..."));
1975 : }
1976 1155998 : if (PgnStyle & PGN_STYLE_MOVENUM_SPACE) {
1977 0 : if (IsLatexFormat()) {
1978 0 : tb->PrintChar ('~');
1979 : } else {
1980 0 : tb->PrintChar (' ');
1981 : }
1982 : }
1983 : }
1984 1155998 : printMoveNum = false;
1985 : }
1986 :
1987 : // Now print the move: only regenerate the SAN string if necessary.
1988 :
1989 1561972 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
1990 0 : tb->PauseTranslations();
1991 0 : tb->PrintString (nextColumn);
1992 0 : tb->ResumeTranslations();
1993 : }
1994 1561972 : if (IsColorFormat() && (PgnStyle & PGN_STYLE_UNICODE)) {
1995 : char buf[100];
1996 0 : char* q = buf;
1997 :
1998 0 : for (char const* p = m->san; *p; ++p) {
1999 0 : ASSERT(q - buf < static_cast<std::ptrdiff_t>(sizeof(buf) - 4));
2000 :
2001 0 : switch (*p) {
2002 0 : case 'K': q = std::copy_n("\xe2\x99\x94", 3, q); break;
2003 0 : case 'Q': q = std::copy_n("\xe2\x99\x95", 3, q); break;
2004 0 : case 'R': q = std::copy_n("\xe2\x99\x96", 3, q); break;
2005 0 : case 'B': q = std::copy_n("\xe2\x99\x97", 3, q); break;
2006 0 : case 'N': q = std::copy_n("\xe2\x99\x98", 3, q); break;
2007 0 : case 'P': q = std::copy_n("\xe2\x99\x99", 3, q); break;
2008 0 : default: *q++ = *p; break;
2009 : }
2010 :
2011 : }
2012 0 : *q = '\0';
2013 0 : tb->PrintWord (buf);
2014 : } else {
2015 : // translate pieces
2016 1561972 : strcpy(tempTrans, m->san);
2017 1561972 : transPieces(tempTrans);
2018 : //tb->PrintWord (m->san);
2019 1561972 : tb->PrintWord (tempTrans);
2020 : }
2021 1561972 : colWidth -= (int) std::strlen(m->san);
2022 1561972 : if (IsColorFormat()) {
2023 0 : tb->PrintString ("</m>");
2024 : }
2025 : }
2026 :
2027 1561972 : bool endedColumn = false;
2028 :
2029 : // Print NAGs and comments if the style indicates:
2030 :
2031 1561972 : if (PgnStyle & PGN_STYLE_COMMENTS) {
2032 1561972 : bool printDiagramHere = false;
2033 1561972 : if (IsColorFormat() && m->nagCount > 0) {
2034 0 : tb->PrintString ("<nag>");
2035 : }
2036 1562260 : for (uint i = 0; i < (uint) m->nagCount; i++) {
2037 : char temp[20];
2038 288 : game_printNag (m->nags[i], temp, PgnStyle & PGN_STYLE_SYMBOLS,
2039 : PgnFormat);
2040 :
2041 : // Do not print a space before the Nag if it is the
2042 : // first nag and starts with "!" or "?" -- those symbols
2043 : // look better printed next to the move:
2044 :
2045 288 : if (i > 0 || (temp[0] != '!' && temp[0] != '?')) {
2046 288 : tb->PrintSpace();
2047 288 : colWidth--;
2048 : }
2049 288 : if (printDiagrams && m->nags[i] == NAG_Diagram) {
2050 0 : printDiagramHere = true;
2051 : }
2052 288 : tb->PrintWord (temp);
2053 288 : colWidth -= (int) std::strlen(temp);
2054 :
2055 : }
2056 1561972 : if (IsColorFormat() && m->nagCount > 0) {
2057 0 : tb->PrintString ("</nag>");
2058 : }
2059 1561972 : tb->PrintSpace();
2060 1561972 : colWidth--;
2061 1561972 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
2062 0 : if (IsPlainFormat() || IsColorFormat()) {
2063 0 : while (colWidth-- > 0) { tb->PrintSpace(); }
2064 : }
2065 : }
2066 :
2067 1561972 : if (printDiagramHere) {
2068 0 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
2069 0 : if (! endedColumn) {
2070 0 : if (CurrentPos->GetToMove() == WHITE) {
2071 0 : tb->PauseTranslations ();
2072 0 : tb->PrintString (nextColumn);
2073 0 : tb->ResumeTranslations ();
2074 : }
2075 0 : tb->PrintString (endColumn);
2076 0 : tb->PrintString (endTable);
2077 0 : endedColumn = true;
2078 : }
2079 : }
2080 0 : if (IsHtmlFormat() && VarDepth == 0) {
2081 0 : tb->PrintString ("</b>");
2082 : }
2083 0 : if (IsLatexFormat()) {
2084 : // The commented-out code below will print diagrams
2085 : // in variations smaller than game diagrams:
2086 : //if (VarDepth == 0) {
2087 : // tb->PrintString("\n\\font\\Chess=chess20\n");
2088 : //} else {
2089 : // tb->PrintString("\n\\font\\Chess=chess10\n");
2090 : //}
2091 0 : tb->PrintString ("\n\\begin{diagram}\n");
2092 : }
2093 0 : MoveForward ();
2094 0 : DString * dstr = new DString;
2095 0 : if (IsHtmlFormat()) {
2096 0 : CurrentPos->DumpHtmlBoard (dstr, HtmlStyle, NULL);
2097 : } else {
2098 0 : CurrentPos->DumpLatexBoard (dstr);
2099 : }
2100 0 : MoveBackup ();
2101 0 : tb->PrintString (dstr->Data());
2102 0 : delete dstr;
2103 0 : if (IsHtmlFormat() && VarDepth == 0) {
2104 0 : tb->PrintString ("<b>");
2105 : }
2106 0 : if (IsLatexFormat()) {
2107 0 : tb->PrintString ("\n\\end{diagram}\n");
2108 : }
2109 0 : printMoveNum = true;
2110 : }
2111 :
2112 1561972 : if (!m->comment.empty() && ! CommentEmpty(m->comment.c_str()) ) {
2113 446938 : if (!inComment && IsPlainFormat() &&
2114 223469 : (PgnStyle & PGN_STYLE_NO_NULL_MOVES)) {
2115 : // If this move has no variations, but the next move
2116 : // is a null move, enter inComment mode:
2117 0 : if (m->next->isNull() &&
2118 0 : ((!(PgnStyle & PGN_STYLE_VARS)) ||
2119 0 : (CurrentMove->next->numVariations == 0))) {
2120 0 : inComment = true;
2121 0 : tb->PrintString(preCommentStr);
2122 0 : preCommentStr = "";
2123 0 : postCommentStr = "";
2124 : }
2125 : }
2126 :
2127 : /* Code commented to remove extra lines
2128 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
2129 : if (! endedColumn) {
2130 : if (CurrentPos->GetToMove() == WHITE) {
2131 : tb->PauseTranslations ();
2132 : tb->PrintString (nextColumn);
2133 : tb->ResumeTranslations ();
2134 : }
2135 : tb->PrintString (endColumn);
2136 : tb->PrintString (endTable);
2137 : endedColumn = true;
2138 : }
2139 : }
2140 : */
2141 223469 : if (IsHtmlFormat() && VarDepth == 0) {
2142 0 : tb->PrintString ("</b><dl><dd>");
2143 : }
2144 223469 : if ((PgnStyle & PGN_STYLE_INDENT_COMMENTS) && VarDepth == 0) {
2145 0 : if (IsColorFormat()) {
2146 0 : tb->PrintString ("<br><ip1>");
2147 : } else {
2148 0 : tb->SetIndent (tb->GetIndent() + 4); tb->Indent();
2149 : }
2150 : }
2151 :
2152 223469 : WriteComment (tb, preCommentStr, m->comment.c_str(), postCommentStr);
2153 :
2154 223469 : if ((PgnStyle & PGN_STYLE_INDENT_COMMENTS) && VarDepth == 0) {
2155 0 : if (IsColorFormat()) {
2156 0 : tb->PrintString ("</ip1><br>");
2157 0 : commentLine = true;
2158 : } else {
2159 0 : tb->SetIndent (tb->GetIndent() - 4); tb->Indent();
2160 : }
2161 : } else {
2162 223469 : tb->PrintSpace();
2163 : }
2164 223469 : if (printDiagrams && strIsPrefix ("#", m->comment.c_str())) {
2165 0 : if (IsLatexFormat()) {
2166 0 : tb->PrintString ("\n\\begin{diagram}\n");
2167 : }
2168 0 : MoveForward ();
2169 0 : DString * dstr = new DString;
2170 0 : if (IsHtmlFormat()) {
2171 0 : CurrentPos->DumpHtmlBoard (dstr, HtmlStyle, NULL);
2172 : } else {
2173 0 : CurrentPos->DumpLatexBoard (dstr);
2174 : }
2175 0 : MoveBackup ();
2176 0 : tb->PrintString (dstr->Data());
2177 0 : if (IsLatexFormat()) {
2178 0 : tb->PrintString ("\n\\end{diagram}\n");
2179 : }
2180 0 : delete dstr;
2181 : }
2182 223469 : if (IsHtmlFormat() && VarDepth == 0) {
2183 0 : tb->PrintString ("</dl><b>");
2184 : }
2185 223469 : printMoveNum = true;
2186 : }
2187 : } else {
2188 0 : tb->PrintSpace();
2189 : }
2190 :
2191 : // Print any variations if the style indicates:
2192 1561972 : if ((PgnStyle & PGN_STYLE_VARS) && (m->numVariations > 0)) {
2193 308438 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
2194 0 : if (! endedColumn) {
2195 0 : if (CurrentPos->GetToMove() == WHITE) {
2196 0 : tb->PauseTranslations ();
2197 0 : tb->PrintString (nextColumn);
2198 0 : tb->ResumeTranslations ();
2199 : }
2200 : // Doesn't seem wanted!! S.A (see a few lines below)
2201 : // tb->PrintString (endColumn);
2202 0 : tb->PrintString (endTable);
2203 0 : endedColumn = true;
2204 : }
2205 : }
2206 308438 : if (IsColorFormat() && VarDepth == 0) { tb->PrintString ("<var>"); }
2207 : // Doesn't indent first var in column mode properly
2208 : // if including !(PgnStyle & PGN_STYLE_COLUMN) here.
2209 : // But as-is, depth 3 vars don't indent in COLUMN mode (bug)
2210 308438 : if ((PgnStyle & PGN_STYLE_INDENT_VARS) && IsColorFormat()) {
2211 0 : if ( !commentLine ) {
2212 0 : tb->PrintString ("<br>");
2213 : }
2214 : }
2215 616916 : for (uint i=0; i < m->numVariations; i++) {
2216 308478 : if (PgnStyle & PGN_STYLE_INDENT_VARS) {
2217 0 : if (IsColorFormat()) {
2218 0 : switch (VarDepth) {
2219 0 : case 0: tb->PrintString ("<ip1>"); break;
2220 0 : case 1: tb->PrintString ("<ip2>"); break;
2221 0 : case 2: tb->PrintString ("<ip3>"); break;
2222 0 : case 3: tb->PrintString ("<ip4>"); break;
2223 : }
2224 : } else {
2225 0 : tb->SetIndent (tb->GetIndent() + 4); tb->Indent();
2226 : }
2227 : }
2228 308478 : if (IsHtmlFormat()) {
2229 0 : if (VarDepth == 0) { tb->PrintString ("</b><dl><dd>"); }
2230 : }
2231 308478 : if (IsLatexFormat() && VarDepth == 0) {
2232 0 : if (PgnStyle & PGN_STYLE_INDENT_VARS) {
2233 0 : tb->PrintLine ("\\begin{variation}");
2234 : } else {
2235 0 : tb->PrintString ("{\\rm ");
2236 : }
2237 : }
2238 308478 : if (IsColorFormat()) { tb->PrintString ("<blue>"); }
2239 :
2240 : // Note tabs in column mode don't work after this VarDepth>1 for some reason
2241 : // this VarDepth check is redundant i think
2242 308478 : if (!IsLatexFormat() || VarDepth != 0) {
2243 308478 : tb->PrintChar ('(');
2244 : }
2245 :
2246 308478 : MoveIntoVariation (i);
2247 308478 : NumMovesPrinted++;
2248 308478 : tb->PrintSpace();
2249 :
2250 : // Recursively print the variation:
2251 308478 : WriteMoveList (tb, oldCurrentMove, true, inComment);
2252 :
2253 308478 : MoveExitVariation();
2254 308478 : if (!IsLatexFormat() || VarDepth != 0) {
2255 308478 : tb->PrintChar (')');
2256 : }
2257 308478 : if (IsColorFormat()) { tb->PrintString ("<blue>"); }
2258 308478 : if (IsHtmlFormat()) {
2259 0 : if (VarDepth == 0) { tb->PrintString ("</dl><b>"); }
2260 : }
2261 308478 : if (IsLatexFormat() && VarDepth == 0) {
2262 0 : if (PgnStyle & PGN_STYLE_INDENT_VARS) {
2263 0 : tb->PrintLine ("\\end{variation}");
2264 : } else {
2265 0 : tb->PrintString ("}");
2266 : }
2267 : }
2268 308478 : if (PgnStyle & PGN_STYLE_INDENT_VARS) {
2269 0 : if (IsColorFormat()) {
2270 0 : switch (VarDepth) {
2271 0 : case 0: tb->PrintString ("</ip1><br>"); break;
2272 0 : case 1: tb->PrintString ("</ip2><br>"); break;
2273 0 : case 2: tb->PrintString ("</ip3><br>"); break;
2274 0 : case 3: tb->PrintString ("</ip4><br>"); break;
2275 : }
2276 : } else {
2277 0 : tb->SetIndent (tb->GetIndent() - 4); tb->Indent();
2278 : }
2279 308478 : } else { tb->PrintSpace(); }
2280 308478 : printMoveNum = true;
2281 : }
2282 308438 : if (IsColorFormat() && VarDepth == 0) {
2283 0 : tb->PrintString ("</var>");
2284 : }
2285 : }
2286 1561972 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
2287 0 : if (endedColumn) { tb->PrintString(startTable); }
2288 0 : if (!endedColumn && CurrentPos->GetToMove() == BLACK) {
2289 0 : tb->PrintString (endColumn);
2290 0 : endedColumn = true;
2291 : }
2292 : }
2293 1561972 : MoveForward();
2294 : }
2295 310515 : if (inComment) { tb->PrintString ("}"); }
2296 310515 : if (IsHtmlFormat() && VarDepth == 0) { tb->PrintString ("</b>"); }
2297 310515 : if ((PgnStyle & PGN_STYLE_COLUMN) && VarDepth == 0) {
2298 0 : tb->PrintString(endTable);
2299 : }
2300 310515 : return OK;
2301 : }
2302 :
2303 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2304 : // Game::WritePGN():
2305 : // Write a game in PGN to a textbuffer.
2306 : //
2307 2037 : errorT Game::WritePGN(TextBuffer* tb) {
2308 : char temp[256];
2309 : char dateStr [20];
2310 2037 : const char * newline = "\n";
2311 2037 : tb->NewlinesToSpaces (false);
2312 2037 : if (IsHtmlFormat()) { newline = "<br>\n"; }
2313 2037 : if (IsLatexFormat()) {
2314 0 : newline = "\\\\\n";
2315 0 : tb->AddTranslation ('#', "\\#");
2316 0 : tb->AddTranslation ('%', "\\%");
2317 0 : tb->AddTranslation ('&', "\\&");
2318 0 : tb->AddTranslation ('<', "$<$");
2319 0 : tb->AddTranslation ('>', "$>$");
2320 0 : tb->AddTranslation ('_', "\\_");
2321 : // tb->AddTranslation ('[', "$[$");
2322 : // tb->AddTranslation (']', "$]$");
2323 : }
2324 2037 : if (IsColorFormat()) {
2325 0 : newline = "<br>";
2326 : }
2327 :
2328 2037 : if (PgnStyle & PGN_STYLE_COLUMN) {
2329 0 : PgnStyle |= PGN_STYLE_INDENT_COMMENTS;
2330 0 : PgnStyle |= PGN_STYLE_INDENT_VARS;
2331 : }
2332 :
2333 : // First: is there a pre-game comment? If so, print it:
2334 : // if (FirstMove->comment != NULL && (PgnStyle & PGN_STYLE_COMMENTS)
2335 : // && ! strIsAllWhitespace (FirstMove->comment)) {
2336 : // tb->AddTranslation ('\n', newline);
2337 : // char * s = FirstMove->comment;
2338 : // if (PgnStyle & PGN_STYLE_STRIP_MARKS) {
2339 : // s = strDuplicate (FirstMove->comment);
2340 : // strTrimMarkCodes (s);
2341 : // }
2342 : // if (IsColorFormat()) {
2343 : // sprintf (temp, "<c_%u>", NumMovesPrinted);
2344 : // tb->PrintString (temp);
2345 : // tb->AddTranslation ('<', "<lt>");
2346 : // tb->AddTranslation ('>', "<gt>");
2347 : // tb->PrintString (s);
2348 : // tb->ClearTranslation ('<');
2349 : // tb->ClearTranslation ('>');
2350 : // tb->PrintLine ("</c>");
2351 : // } else {
2352 : // tb->PrintLine (s);
2353 : // }
2354 : // if (PgnStyle & PGN_STYLE_STRIP_MARKS) { delete[] s; }
2355 : // tb->ClearTranslation ('\n');
2356 : // tb->NewLine();
2357 : // }
2358 :
2359 2037 : date_DecodeToString (Date, dateStr);
2360 2037 : if (IsHtmlFormat()) { tb->PrintLine("<p><b>"); }
2361 2037 : if (IsLatexFormat()) { tb->PrintLine ("{\\bf"); }
2362 :
2363 : // if (IsColorFormat()) {
2364 : // tb->AddTranslation ('<', "<lt>");
2365 : // tb->AddTranslation ('>', "<gt>");
2366 : // }
2367 :
2368 2037 : if (PgnStyle & PGN_STYLE_SHORT_HEADER) {
2369 : // Print tags in short, 3-line format:
2370 :
2371 : //if (IsHtmlFormat()) { tb->PrintString ("<font size=+1>"); }
2372 0 : if (IsLatexFormat()) { tb->PrintString ("$\\circ$ "); }
2373 0 : if (PgnFormat==PGN_FORMAT_Color) {tb->PrintString ("<tag>"); }
2374 0 : tb->PrintString (GetWhiteStr());
2375 0 : if (WhiteElo > 0) {
2376 0 : sprintf (temp, " (%u)", WhiteElo);
2377 0 : tb->PrintString (temp);
2378 : }
2379 0 : switch (PgnFormat) {
2380 0 : case PGN_FORMAT_HTML:
2381 0 : tb->PrintString (" -- ");
2382 0 : break;
2383 0 : case PGN_FORMAT_LaTeX:
2384 0 : tb->PrintString (newline);
2385 0 : tb->PrintString ("$\\bullet$ ");
2386 0 : break;
2387 0 : default:
2388 0 : tb->PrintString (" -- ");
2389 0 : break;
2390 : }
2391 0 : tb->PrintString (GetBlackStr());
2392 0 : if (BlackElo > 0) {
2393 0 : sprintf (temp, " (%u)", BlackElo);
2394 0 : tb->PrintString (temp);
2395 : }
2396 : //if (IsHtmlFormat()) { tb->PrintString ("</font>"); }
2397 0 : tb->PrintString (newline);
2398 :
2399 0 : tb->PrintString (GetEventStr());
2400 0 : if (!RoundStr.empty() && RoundStr != "?") {
2401 0 : tb->PrintString (IsHtmlFormat() ? " (" : " (");
2402 0 : tb->PrintString (GetRoundStr());
2403 0 : tb->PrintString (")");
2404 : }
2405 0 : tb->PrintString (IsHtmlFormat() ? " " : " ");
2406 0 : if (IsLatexFormat()) { tb->PrintString (newline); }
2407 0 : if (!SiteStr.empty() && SiteStr != "?") {
2408 0 : tb->PrintString (GetSiteStr());
2409 0 : tb->PrintString (newline);
2410 : }
2411 :
2412 : // Remove ".??" or ".??.??" from end of dateStr, then print it:
2413 0 : if (dateStr[4] == '.' && dateStr[5] == '?') { dateStr[4] = 0; }
2414 0 : if (dateStr[7] == '.' && dateStr[8] == '?') { dateStr[7] = 0; }
2415 0 : tb->PrintString (dateStr);
2416 :
2417 : // Print ECO code:
2418 0 : tb->PrintString (IsHtmlFormat() ? " " : " ");
2419 0 : if (IsLatexFormat()) { tb->PrintString ("\\hfill "); }
2420 0 : tb->PrintString (RESULT_LONGSTR[Result]);
2421 0 : if (EcoCode != 0) {
2422 0 : tb->PrintString (IsHtmlFormat() ? " " : " ");
2423 0 : if (IsLatexFormat()) { tb->PrintString ("\\hfill "); }
2424 : ecoStringT ecoStr;
2425 0 : eco_ToExtendedString (EcoCode, ecoStr);
2426 0 : tb->PrintString (ecoStr);
2427 : }
2428 0 : auto annotator = FindExtraTag("Annotator");
2429 0 : if (annotator != NULL) {
2430 0 : sprintf(temp, " (%s)", annotator);
2431 0 : tb->PrintString(temp);
2432 : }
2433 :
2434 0 : tb->PrintString (newline);
2435 0 : if (PgnFormat==PGN_FORMAT_Color) {tb->PrintString ("</tag>"); }
2436 :
2437 : // Print FEN if non-standard start:
2438 0 : if (StartPos) {
2439 0 : if (IsLatexFormat()) {
2440 0 : tb->PrintString ("\n\\begin{diagram}\n");
2441 0 : DString dstr;
2442 0 : StartPos->DumpLatexBoard (&dstr);
2443 0 : tb->PrintString (dstr.Data());
2444 0 : tb->PrintString ("\n\\end{diagram}\n");
2445 0 : } else if (IsHtmlFormat()) {
2446 0 : DString dstr;
2447 0 : StartPos->DumpHtmlBoard (&dstr, HtmlStyle, NULL);
2448 0 : tb->PrintString (dstr.Data());
2449 : } else {
2450 0 : StartPos->PrintFEN(std::copy_n("Position: ", 10, temp),
2451 : FEN_ALL_FIELDS);
2452 0 : std::strcat(temp, newline);
2453 0 : tb->PrintString (temp);
2454 : }
2455 : }
2456 : } else {
2457 : // Print tags in standard PGN format, one per line:
2458 : // Note: we want no line-wrapping when printing PGN tags
2459 : // so set it to a huge value for now:
2460 2037 : uint wrapColumn = tb->GetWrapColumn();
2461 2037 : tb->SetWrapColumn (99999);
2462 2037 : if (IsColorFormat()) { tb->PrintString ("<tag>"); }
2463 2037 : sprintf (temp, "[Event \"%s\"]%s", GetEventStr(), newline);
2464 2037 : tb->PrintString (temp);
2465 2037 : sprintf (temp, "[Site \"%s\"]%s", GetSiteStr(), newline);
2466 2037 : tb->PrintString (temp);
2467 2037 : sprintf (temp, "[Date \"%s\"]%s", dateStr, newline);
2468 2037 : tb->PrintString (temp);
2469 2037 : sprintf (temp, "[Round \"%s\"]%s", GetRoundStr(), newline);
2470 2037 : tb->PrintString (temp);
2471 2037 : sprintf (temp, "[White \"%s\"]%s", GetWhiteStr(), newline);
2472 2037 : tb->PrintString (temp);
2473 2037 : sprintf (temp, "[Black \"%s\"]%s", GetBlackStr(), newline);
2474 2037 : tb->PrintString (temp);
2475 2037 : sprintf (temp, "[Result \"%s\"]%s", RESULT_LONGSTR[Result], newline);
2476 2037 : tb->PrintString (temp);
2477 :
2478 : // Print all tags, not just the standard seven, if applicable:
2479 2037 : if (PgnStyle & PGN_STYLE_TAGS) {
2480 2037 : if (WhiteElo > 0) {
2481 60 : sprintf (temp, "[White%s \"%u\"]%s",
2482 60 : ratingTypeNames [WhiteRatingType], WhiteElo, newline);
2483 30 : tb->PrintString (temp);
2484 : }
2485 2037 : if (BlackElo > 0) {
2486 60 : sprintf (temp, "[Black%s \"%u\"]%s",
2487 60 : ratingTypeNames [BlackRatingType], BlackElo, newline);
2488 30 : tb->PrintString (temp);
2489 : }
2490 2037 : if (EcoCode != 0) {
2491 : ecoStringT ecoStr;
2492 30 : eco_ToExtendedString (EcoCode, ecoStr);
2493 30 : sprintf (temp, "[ECO \"%s\"]%s", ecoStr, newline);
2494 30 : tb->PrintString (temp);
2495 : }
2496 2037 : if (EventDate != ZERO_DATE) {
2497 : char edateStr [20];
2498 30 : date_DecodeToString (EventDate, edateStr);
2499 30 : sprintf (temp, "[EventDate \"%s\"]%s", edateStr, newline);
2500 30 : tb->PrintString (temp);
2501 : }
2502 :
2503 2037 : if (PgnStyle & PGN_STYLE_SCIDFLAGS && *ScidFlags != 0) {
2504 0 : sprintf (temp, "[ScidFlags \"%s\"]%s", ScidFlags, newline);
2505 0 : tb->PrintString (temp);
2506 : }
2507 :
2508 : // Now print other tags
2509 2761 : for (auto& e : extraTags_) {
2510 724 : sprintf(temp, "[%s \"%s\"]%s", e.first.c_str(),
2511 : e.second.c_str(), newline);
2512 724 : tb->PrintString(temp);
2513 : }
2514 : }
2515 : // Finally, write the FEN tag if necessary:
2516 2037 : if (StartPos) {
2517 0 : StartPos->PrintFEN(std::copy_n("[FEN \"", 6, temp), FEN_ALL_FIELDS);
2518 0 : auto it_end = std::copy_n("\"]", 2, temp + std::strlen(temp));
2519 0 : std::strcpy(it_end, newline);
2520 0 : tb->PrintString (temp);
2521 : }
2522 2037 : if (IsColorFormat()) { tb->PrintString ("</tag>"); }
2523 : // Now restore the linewrap column:
2524 2037 : tb->SetWrapColumn (wrapColumn);
2525 : }
2526 :
2527 : // if (IsColorFormat()) {
2528 : // tb->ClearTranslation ('<');
2529 : // tb->ClearTranslation ('>');
2530 : // }
2531 :
2532 2037 : if (IsHtmlFormat()) { tb->PrintLine("</b></p>"); }
2533 2037 : if (IsLatexFormat()) {
2534 0 : tb->PrintLine ("}\n\\begin{chess}{\\bf ");
2535 : } else {
2536 2037 : tb->PrintString (newline);
2537 : }
2538 :
2539 2037 : MoveToPly(0);
2540 :
2541 2037 : if (IsHtmlFormat()) { tb->PrintString ("<p>"); }
2542 2037 : NumMovesPrinted = 1;
2543 2037 : WriteMoveList(tb, CurrentMove, true, false);
2544 2037 : if (IsHtmlFormat()) { tb->PrintString ("<b>"); }
2545 2037 : if (IsLatexFormat()) { tb->PrintString ("\n}\\end{chess}\n{\\bf "); }
2546 2037 : if (IsColorFormat()) { tb->PrintString ("<tag>"); }
2547 2037 : tb->PrintWord (RESULT_LONGSTR [Result]);
2548 2037 : if (IsLatexFormat()) {
2549 0 : tb->PrintString ("}\n\\begin{center} \\hrule \\end{center}");
2550 : }
2551 2037 : if (IsHtmlFormat()) { tb->PrintString ("</b><hr></p>"); }
2552 2037 : if (IsColorFormat()) { tb->PrintString ("</tag>"); }
2553 2037 : tb->NewLine();
2554 :
2555 2037 : return OK;
2556 : }
2557 :
2558 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2559 : // Game::WriteToPGN():
2560 : // Print the entire game.
2561 : //
2562 : std::pair<const char*, unsigned>
2563 2037 : Game::WriteToPGN(uint lineWidth, bool NewLineAtEnd, bool newLineToSpaces)
2564 : {
2565 2037 : static TextBuffer tbuf;
2566 :
2567 2037 : auto location = currentLocation();
2568 2037 : tbuf.Empty();
2569 2037 : tbuf.SetWrapColumn(lineWidth ? lineWidth : tbuf.GetBufferSize());
2570 2037 : tbuf.NewlinesToSpaces(newLineToSpaces);
2571 2037 : WritePGN(&tbuf);
2572 2037 : if (NewLineAtEnd) tbuf.NewLine();
2573 2037 : restoreLocation(location);
2574 2037 : return std::make_pair(tbuf.GetBuffer(), tbuf.GetByteCount());
2575 : }
2576 :
2577 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2578 : // Game::LoadStandardTags():
2579 : // Sets the standard tag values for this game, given an
2580 : // index file entry and a namebase that stores the
2581 : // player/site/event/round names.
2582 : //
2583 : errorT
2584 1000 : Game::LoadStandardTags (const IndexEntry* ie, const NameBase* nb)
2585 : {
2586 1000 : ASSERT (ie != NULL && nb != NULL);
2587 1000 : SetEventStr (ie->GetEventName (nb));
2588 1000 : SetSiteStr (ie->GetSiteName (nb));
2589 1000 : SetWhiteStr (ie->GetWhiteName (nb));
2590 1000 : SetBlackStr (ie->GetBlackName (nb));
2591 1000 : SetRoundStr (ie->GetRoundName (nb));
2592 1000 : SetDate (ie->GetDate());
2593 1000 : SetEventDate (ie->GetEventDate());
2594 1000 : SetWhiteElo (ie->GetWhiteElo());
2595 1000 : SetBlackElo (ie->GetBlackElo());
2596 1000 : WhiteEstimateElo = nb->GetElo (ie->GetWhite());
2597 1000 : BlackEstimateElo = nb->GetElo (ie->GetBlack());
2598 1000 : SetWhiteRatingType (ie->GetWhiteRatingType());
2599 1000 : SetBlackRatingType (ie->GetBlackRatingType());
2600 1000 : SetResult (ie->GetResult());
2601 1000 : SetEco (ie->GetEcoCode());
2602 1000 : ie->GetFlagStr (ScidFlags, NULL);
2603 1000 : return OK;
2604 : }
2605 :
2606 : eloT
2607 0 : Game::GetAverageElo ()
2608 : {
2609 0 : eloT white = WhiteElo;
2610 0 : eloT black = BlackElo;
2611 0 : if (white == 0) { white = WhiteEstimateElo; }
2612 0 : if (black == 0) { black = BlackEstimateElo; }
2613 0 : return (white + black) / 2;
2614 : }
2615 :
2616 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2617 : // makeMoveByte(): inline routine used for encoding most moves.
2618 : //
2619 : static inline byte
2620 9497630 : makeMoveByte (byte pieceNum, byte value)
2621 : {
2622 9497630 : ASSERT (pieceNum <= 15 && value <= 15);
2623 9497630 : return (byte)((pieceNum & 15) << 4) | (byte)(value & 15);
2624 : }
2625 :
2626 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2627 : // encodeKing(): encoding of King moves.
2628 : //
2629 : static inline void
2630 3489963 : encodeKing (ByteBuffer * buf, simpleMoveT * sm)
2631 : {
2632 : // Valid King difference-from-old-square values are:
2633 : // -9, -8, -7, -1, 1, 7, 8, 9, and -2 and 2 for castling.
2634 : // To convert this to a val in the range [1-10], we add 9 and
2635 : // then look up the val[] table.
2636 : // Coded values 1-8 are one-square moves; 9 and 10 are Castling.
2637 :
2638 3489963 : ASSERT(sm->pieceNum == 0); // Kings MUST be piece Number zero.
2639 3489963 : int diff = (int) sm->to - (int) sm->from;
2640 : static const byte val[] = {
2641 : /* -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 */
2642 : 1, 2, 3, 0, 0, 0, 0, 9, 4, 0, 5, 10, 0, 0, 0, 0, 6, 7, 8
2643 : };
2644 :
2645 : // If target square is the from square, it is the null move, which
2646 : // is represented as a king move to its own square and is encoded
2647 : // as the byte value zero.
2648 3489963 : if (sm->to == sm->from) {
2649 0 : buf->PutByte (makeMoveByte (0, 0));
2650 0 : return;
2651 : }
2652 :
2653 : // Verify we have a valid King move:
2654 3489963 : ASSERT(diff >= -9 && diff <= 9 && val[diff+9] != 0);
2655 3489963 : buf->PutByte (makeMoveByte (0, val [diff + 9]));
2656 : }
2657 :
2658 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2659 : // decodeKing(): decoding of King moves.
2660 : //
2661 : static inline errorT
2662 256038 : decodeKing (byte val, simpleMoveT * sm)
2663 : {
2664 : static const int sqdiff[] = {
2665 : 0, -9, -8, -7, -1, 1, 7, 8, 9, -2, 2
2666 : };
2667 :
2668 256038 : if (val == 0) {
2669 0 : sm->to = sm->from; // Null move
2670 0 : return OK;
2671 : }
2672 :
2673 256038 : if (val < 1 || val > 10) { return ERROR_Decode; }
2674 256038 : sm->to = sm->from + sqdiff[val];
2675 256038 : return OK;
2676 : }
2677 :
2678 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2679 : // encodeKnight(): encoding Knight moves.
2680 : //
2681 : static inline void
2682 1028594 : encodeKnight (ByteBuffer * buf, simpleMoveT * sm)
2683 : {
2684 : // Valid Knight difference-from-old-square values are:
2685 : // -17, -15, -10, -6, 6, 10, 15, 17.
2686 : // To convert this to a value in the range [1-8], we add 17 to
2687 : // the difference and then look up the val[] table.
2688 :
2689 1028594 : int diff = (int) sm->to - (int) sm->from;
2690 : static const byte val[] = {
2691 : /* -17 -16 -15 -14 -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 */
2692 : 1, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
2693 :
2694 : /* 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 */
2695 : 0, 0, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 0, 7, 0, 8
2696 : };
2697 :
2698 : // Verify we have a valid knight move:
2699 1028594 : ASSERT (diff >= -17 && diff <= 17 && val[diff + 17] != 0);
2700 1028594 : buf->PutByte (makeMoveByte (sm->pieceNum, val [diff + 17]));
2701 1028594 : }
2702 :
2703 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2704 : // decodeKnight(): decoding Knight moves.
2705 : //
2706 : static inline errorT
2707 101250 : decodeKnight (byte val, simpleMoveT * sm)
2708 : {
2709 : static const int sqdiff[] = {
2710 : 0, -17, -15, -10, -6, 6, 10, 15, 17
2711 : };
2712 101250 : if (val < 1 || val > 8) { return ERROR_Decode; }
2713 101250 : sm->to = sm->from + sqdiff[val];
2714 101250 : return OK;
2715 : }
2716 :
2717 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2718 : // encodeRook(): encoding rook moves.
2719 : //
2720 : static inline void
2721 1735752 : encodeRook (ByteBuffer * buf, simpleMoveT * sm)
2722 : {
2723 : // Valid Rook moves are to same rank, OR to same fyle.
2724 : // We encode the 8 squares on the same rank 0-8, and the 8
2725 : // squares on the same fyle 9-15. This means that for any particular
2726 : // rook move, two of the values in the range [0-15] will be
2727 : // meaningless, as they will represent the from-square.
2728 :
2729 1735752 : ASSERT (sm->from <= H8 && sm->to <= H8);
2730 : byte val;
2731 :
2732 : // Check if the two squares share the same rank:
2733 1735752 : if (square_Rank(sm->from) == square_Rank(sm->to)) {
2734 911338 : val = square_Fyle(sm->to);
2735 : } else {
2736 824414 : val = 8 + square_Rank(sm->to);
2737 : }
2738 1735752 : buf->PutByte (makeMoveByte (sm->pieceNum, val));
2739 1735752 : }
2740 :
2741 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2742 : // decodeRook(): decoding Rook moves.
2743 : //
2744 : static inline errorT
2745 138987 : decodeRook (byte val, simpleMoveT * sm)
2746 : {
2747 138987 : if (val >= 8) {
2748 : // This is a move along a Fyle, to a different rank:
2749 67200 : sm->to = square_Make (square_Fyle(sm->from), (val - 8));
2750 : } else {
2751 71787 : sm->to = square_Make (val, square_Rank(sm->from));
2752 : }
2753 138987 : return OK;
2754 : }
2755 :
2756 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2757 : // encodeBishop(): encoding Bishop moves.
2758 : //
2759 : static inline void
2760 1621121 : encodeBishop (ByteBuffer * buf, simpleMoveT * sm)
2761 : {
2762 : // We encode a Bishop move as the Fyle moved to, plus
2763 : // a one-bit flag to indicate if the direction was
2764 : // up-right/down-left or vice versa.
2765 :
2766 1621121 : ASSERT (sm->to <= H8 && sm->from <= H8);
2767 : byte val;
2768 1621121 : val = square_Fyle(sm->to);
2769 1621121 : int rankdiff = (int)square_Rank(sm->to) - (int)square_Rank(sm->from);
2770 1621121 : int fylediff = (int)square_Fyle(sm->to) - (int)square_Fyle(sm->from);
2771 :
2772 : // If (rankdiff * fylediff) is negative, it's up-left/down-right:
2773 1621121 : if (rankdiff * fylediff < 0) { val += 8; }
2774 :
2775 1621121 : buf->PutByte (makeMoveByte (sm->pieceNum, val));
2776 1621121 : }
2777 :
2778 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2779 : // decodeBishop(): decoding Bishop moves.
2780 : //
2781 : static inline errorT
2782 122008 : decodeBishop (byte val, simpleMoveT * sm)
2783 : {
2784 122008 : byte fyle = (val & 7);
2785 122008 : int fylediff = (int)fyle - (int)square_Fyle(sm->from);
2786 122008 : if (val >= 8) {
2787 : // It is an up-left/down-right direction move.
2788 60740 : sm->to = sm->from - 7 * fylediff;
2789 : } else {
2790 61268 : sm->to = sm->from + 9 * fylediff;
2791 : }
2792 122008 : if (sm->to > H8) { return ERROR_Decode;}
2793 122008 : return OK;
2794 : }
2795 :
2796 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2797 : // encodeQueen(): encoding Queen moves.
2798 : //
2799 : static inline void
2800 628431 : encodeQueen (ByteBuffer * buf, simpleMoveT * sm)
2801 : {
2802 : // We cannot fit all Queen moves in one byte, so Rooklike moves
2803 : // are in one byte (encoded the same way as Rook moves),
2804 : // while diagonal moves are in two bytes.
2805 :
2806 628431 : ASSERT (sm->to <= H8 && sm->from <= H8);
2807 : byte val;
2808 :
2809 628431 : if (square_Rank(sm->from) == square_Rank(sm->to)) {
2810 : // Rook-horizontal move:
2811 :
2812 174788 : val = square_Fyle(sm->to);
2813 174788 : buf->PutByte (makeMoveByte (sm->pieceNum, val));
2814 :
2815 453643 : } else if (square_Fyle(sm->from) == square_Fyle(sm->to)) {
2816 : // Rook-vertical move:
2817 :
2818 157163 : val = 8 + square_Rank(sm->to);
2819 157163 : buf->PutByte (makeMoveByte (sm->pieceNum, val));
2820 :
2821 : } else {
2822 : // Diagonal move:
2823 296480 : ASSERT(std::abs(sm->to / 8 - sm->from / 8) ==
2824 : std::abs(sm->to % 8 - sm->from % 8));
2825 :
2826 : // First, we put a rook-horizontal move to the from square (which
2827 : // is illegal of course) to indicate it is NOT a rooklike move:
2828 :
2829 296480 : val = square_Fyle(sm->from);
2830 296480 : buf->PutByte (makeMoveByte (sm->pieceNum, val));
2831 :
2832 : // Now we put the to-square in the next byte. We add a 64 to it
2833 : // to make sure that it cannot clash with the Special tokens (which
2834 : // are in the range 0 to 15, since they are special King moves).
2835 :
2836 296480 : buf->PutByte (sm->to + 64);
2837 : }
2838 628431 : }
2839 :
2840 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2841 : // decodeQueen(): decoding Queen moves.
2842 : //
2843 : static inline errorT
2844 66120 : decodeQueen (ByteBuffer * buf, byte val, simpleMoveT * sm)
2845 : {
2846 66120 : if (val >= 8) {
2847 : // Rook-vertical move:
2848 17482 : sm->to = square_Make (square_Fyle(sm->from), (val - 8));
2849 :
2850 48638 : } else if (val != square_Fyle(sm->from)) {
2851 : // Rook-horizontal move:
2852 18943 : sm->to = square_Make (val, square_Rank(sm->from));
2853 :
2854 : } else {
2855 : // Diagonal move: coded in TWO bytes.
2856 29695 : val = buf->GetByte();
2857 29695 : if (val < 64 || val > 127) { return ERROR_Decode; }
2858 29695 : sm->to = val - 64;
2859 : }
2860 66120 : return OK;
2861 : }
2862 :
2863 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2864 : // encodePawn(): encoding Pawn moves.
2865 : //
2866 : static inline void
2867 993769 : encodePawn (ByteBuffer * buf, simpleMoveT * sm)
2868 : {
2869 : // Pawn moves require a promotion encoding.
2870 : // The pawn moves are:
2871 : // 0 = capture-left,
2872 : // 1 = forward,
2873 : // 2 = capture-right (all no promotion);
2874 : // 3/4/5 = 0/1/2 with Queen promo;
2875 : // 6/7/8 = 0/1/2 with Rook promo;
2876 : // 9/10/11 = 0/1/2 with Bishop promo;
2877 : // 12/13/14 = 0/1/2 with Knight promo;
2878 : // 15 = forward TWO squares.
2879 :
2880 : byte val;
2881 993769 : int diff = (int)(sm->to) - (int)(sm->from);
2882 :
2883 993769 : if (diff < 0) { diff = -diff; }
2884 993769 : if (diff == 16) { // Move forward two squares
2885 175996 : val = 15;
2886 175996 : ASSERT (sm->promote == EMPTY);
2887 :
2888 : } else {
2889 817773 : if (diff == 7) { val = 0; }
2890 738974 : else if (diff == 8) { val = 1; }
2891 : else { // diff is 9:
2892 85075 : ASSERT (diff == 9);
2893 85075 : val = 2;
2894 : }
2895 817773 : if (sm->promote != EMPTY) {
2896 : // Handle promotions.
2897 : // sm->promote must be Queen=2,Rook=3, Bishop=4 or Knight=5.
2898 : // We add 3 for Queen, 6 for Rook, 9 for Bishop, 12 for Knight.
2899 :
2900 55474 : ASSERT (sm->promote >= QUEEN && sm->promote <= KNIGHT);
2901 55474 : val += 3 * ((sm->promote) - 1);
2902 : }
2903 : }
2904 993769 : buf->PutByte (makeMoveByte (sm->pieceNum, val));
2905 993769 : }
2906 :
2907 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2908 : // decodePawn(): decoding Pawn moves.
2909 : //
2910 : static inline errorT
2911 98648 : decodePawn (byte val, simpleMoveT * sm, colorT toMove)
2912 : {
2913 : static const int
2914 : toSquareDiff [16] = {
2915 : 7,8,9, 7,8,9, 7,8,9, 7,8,9, 7,8,9, 16
2916 : };
2917 :
2918 : static const pieceT
2919 : promoPieceFromVal [16] = {
2920 : EMPTY,EMPTY,EMPTY,
2921 : QUEEN,QUEEN,QUEEN,
2922 : ROOK,ROOK,ROOK,
2923 : BISHOP,BISHOP,BISHOP,
2924 : KNIGHT,KNIGHT,KNIGHT,
2925 : EMPTY
2926 : };
2927 :
2928 98648 : if (toMove == WHITE) {
2929 48574 : sm->to = sm->from + toSquareDiff[val];
2930 : } else {
2931 50074 : sm->to = sm->from - toSquareDiff[val];
2932 : }
2933 :
2934 98648 : sm->promote = promoPieceFromVal[val];
2935 :
2936 98648 : return OK;
2937 : }
2938 :
2939 :
2940 : // Special-move tokens:
2941 : // Since king-move values 1-10 are taken for actual King moves, only
2942 : // 11-15 (and zero) are available for non-move information.
2943 :
2944 : #define ENCODE_NAG 11
2945 : #define ENCODE_COMMENT 12
2946 : #define ENCODE_START_MARKER 13
2947 : #define ENCODE_END_MARKER 14
2948 : #define ENCODE_END_GAME 15
2949 :
2950 : #define ENCODE_FIRST 11
2951 : #define ENCODE_LAST 15
2952 :
2953 : // The end-game and end-variation tokens could be the same single token,
2954 : // but having two different tokens allows for detecting corruption, since
2955 : // a game must end with the end-game token.
2956 :
2957 :
2958 : // The inline routine isSpecialMoveCode() returns true is a byte value
2959 : // has the value of a special non-move token:
2960 : inline bool
2961 : isSpecialMoveCode (byte val)
2962 : {
2963 : return (val <= ENCODE_LAST && val >= ENCODE_FIRST);
2964 : }
2965 :
2966 :
2967 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2968 : // decodeMove():
2969 : // Decode a move from a bytebuffer. Assumes the byte val is an
2970 : // actual move, not the value of a "special" (non-move) token.
2971 : // This function needs to be passed the bytebuffer because some
2972 : // moves (only Queen diagonal moves) are encoded in two bytes, so
2973 : // it may be necessary to read the next byte as well.
2974 : //
2975 783051 : static errorT decodeMove(ByteBuffer* buf, simpleMoveT* sm, byte val,
2976 : const Position* pos) {
2977 783051 : const squareT* sqList = pos->GetList(pos->GetToMove());
2978 783051 : sm->from = sqList[val >> 4];
2979 783051 : if (sm->from > H8) { return ERROR_Decode; }
2980 :
2981 783051 : const pieceT* board = pos->GetBoard();
2982 783051 : sm->movingPiece = board[sm->from];
2983 783051 : sm->promote = EMPTY;
2984 :
2985 783051 : errorT err = OK;
2986 783051 : pieceT pt = piece_Type (sm->movingPiece);
2987 783051 : switch (pt) {
2988 98648 : case PAWN:
2989 98648 : err = decodePawn (val & 15, sm, pos->GetToMove());
2990 98648 : break;
2991 101250 : case KNIGHT:
2992 101250 : err = decodeKnight (val & 15, sm);
2993 101250 : break;
2994 138987 : case ROOK:
2995 138987 : err = decodeRook (val & 15, sm);
2996 138987 : break;
2997 122008 : case BISHOP:
2998 122008 : err = decodeBishop (val & 15, sm);
2999 122008 : break;
3000 256038 : case KING:
3001 256038 : err = decodeKing (val & 15, sm);
3002 256038 : break;
3003 : // For queen moves: Rook-like moves are in 1 byte, diagonals are in 2.
3004 66120 : case QUEEN:
3005 66120 : err = decodeQueen (buf, val & 15, sm);
3006 66120 : break;
3007 0 : default:
3008 0 : err = ERROR_Decode;
3009 : }
3010 783051 : return err;
3011 : }
3012 :
3013 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3014 : // Game::EncodeMove():
3015 : // Encode one move and output it to the bytebuffer.
3016 : //
3017 9497630 : static void encodeMove(ByteBuffer* buf, moveT* m) {
3018 9497630 : simpleMoveT* sm = &(m->moveData);
3019 9497630 : pieceT pt = piece_Type(sm->movingPiece);
3020 :
3021 : typedef void encodeFnType (ByteBuffer *, simpleMoveT *);
3022 : static encodeFnType * encodeFn[] = {
3023 : NULL /* 0 */,
3024 : encodeKing /*1=KING*/,
3025 : encodeQueen /*2=QUEEN*/,
3026 : encodeRook /*3=ROOK*/,
3027 : encodeBishop /*4=BISHOP*/,
3028 : encodeKnight /*5=KNIGHT*/,
3029 : encodePawn /*6=PAWN*/
3030 : };
3031 9497630 : ASSERT (pt >= KING && pt <= PAWN);
3032 9497630 : (encodeFn[pt]) (buf, sm);
3033 9497630 : }
3034 :
3035 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3036 : // encodeVariation(): Used by Encode() to encode the game's moves.
3037 : // Recursive; calls itself to encode subvariations.
3038 : //
3039 1908932 : static errorT encodeVariation(ByteBuffer* buf, moveT* m, uint* subVarCount,
3040 : uint* nagCount, uint depth) {
3041 1908932 : ASSERT(m != NULL);
3042 :
3043 : // Check if there is a pre-game or start-of-variation comment:
3044 1908932 : if (! m->prev->comment.empty()) {
3045 59 : buf->PutByte (ENCODE_COMMENT);
3046 : }
3047 :
3048 20904192 : while (m->marker != END_MARKER) {
3049 9497630 : encodeMove (buf, m);
3050 9498114 : for (uint i=0; i < (uint) m->nagCount; i++) {
3051 484 : buf->PutByte (ENCODE_NAG);
3052 484 : buf->PutByte (m->nags[i]);
3053 484 : *nagCount += 1;
3054 : }
3055 9497630 : if (! m->comment.empty()) {
3056 1390835 : buf->PutByte (ENCODE_COMMENT);
3057 : }
3058 9497630 : if (m->numVariations > 0) {
3059 1898860 : moveT * subVar = m->varChild;
3060 3797774 : for (uint i=0; i < m->numVariations; i++) {
3061 1898914 : *subVarCount += 1;
3062 1898914 : buf->PutByte (ENCODE_START_MARKER);
3063 1898914 : encodeVariation (buf, subVar->next, subVarCount, nagCount, depth+1);
3064 1898914 : subVar = subVar->varChild;
3065 : }
3066 : }
3067 9497630 : m = m->next;
3068 : }
3069 : // At end, we output the end-variation or end-game token.
3070 1908932 : if (depth == 0) {
3071 10018 : buf->PutByte (ENCODE_END_GAME);
3072 : } else {
3073 1898914 : buf->PutByte (ENCODE_END_MARKER);
3074 : }
3075 1908932 : return buf->Status();
3076 : }
3077 :
3078 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3079 : // Game::DecodeVariation():
3080 : // Decodes the game moves. Recursively decodes subvariations.
3081 : //
3082 : errorT
3083 155499 : Game::DecodeVariation (ByteBuffer * buf, byte flags, uint level)
3084 : {
3085 : simpleMoveT sm;
3086 : errorT err;
3087 155499 : byte b = buf->GetByte ();
3088 2255397 : while (b != ENCODE_END_GAME && b != ENCODE_END_MARKER) {
3089 1049949 : switch (b) {
3090 154481 : case ENCODE_START_MARKER:
3091 154481 : err = AddVariation();
3092 154481 : if (err != OK) { return err; }
3093 154481 : err = DecodeVariation (buf, flags, level + 1);
3094 154481 : if (err != OK) { return err; }
3095 154481 : err = MoveExitVariation();
3096 154481 : if (err != OK) { return err; }
3097 154481 : err = MoveForward();
3098 154481 : if (err != OK) { return err; }
3099 154481 : break;
3100 :
3101 440 : case ENCODE_NAG:
3102 440 : AddNag (buf->GetByte ());
3103 440 : break;
3104 :
3105 111977 : case ENCODE_COMMENT:
3106 111977 : if (flags & GAME_DECODE_COMMENTS) {
3107 : // Mark this comment as needing to be read
3108 111977 : CurrentMove->prev->comment = "*";
3109 : }
3110 111977 : break;
3111 :
3112 783051 : default: // It is a regular move
3113 783051 : err = decodeMove(buf, &sm, b, currentPos());
3114 783051 : if (err != OK) { return err; }
3115 783051 : AddMove(&sm);
3116 : }
3117 :
3118 1049949 : b = buf->GetByte ();
3119 1049949 : if (buf->Status() != OK) { return buf->Status(); }
3120 : }
3121 :
3122 155499 : if (level == 0 && b != ENCODE_END_GAME) { return ERROR_Decode; }
3123 155499 : if (level > 0 && b != ENCODE_END_MARKER) { return ERROR_Decode; }
3124 155499 : return buf->Status();
3125 : }
3126 :
3127 :
3128 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3129 : // Common tags are encoded in one byte, as a value over 240.
3130 : // This means that the maximum length of a non-common tag is 240
3131 : // bytes, and the maximum number of common tags is 15.
3132 : //
3133 : constexpr size_t MAX_TAG_LEN = 240;
3134 :
3135 : static const char* commonTags[255 - MAX_TAG_LEN] = {
3136 : // 241, 242: Country
3137 : "WhiteCountry", "BlackCountry",
3138 : // 243: Annotator
3139 : "Annotator",
3140 : // 244: PlyCount
3141 : "PlyCount",
3142 : // 245: EventDate (plain text encoding)
3143 : "EventDate",
3144 : // 246, 247: Opening, Variation
3145 : "Opening", "Variation",
3146 : // 248-250: Setup and Source
3147 : "Setup", "Source", "SetUp",
3148 : // 252-254: spare for future use
3149 : NULL, NULL, NULL, NULL,
3150 : // 255: Reserved for compact EventDate encoding
3151 : NULL
3152 : };
3153 :
3154 :
3155 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3156 : // encodeTags():
3157 : // Encodes the non-standard tags.
3158 : //
3159 : template <typename TCont>
3160 10018 : errorT encodeTags(ByteBuffer* buf, const TCont& tagList) {
3161 10036 : for (auto& tag : tagList) {
3162 18 : uint tagnum = 1;
3163 18 : const char ** common = commonTags;
3164 108 : while (*common != NULL) {
3165 63 : if (tag.first == *common) {
3166 18 : buf->PutByte ((byte) MAX_TAG_LEN + tagnum);
3167 18 : break;
3168 : } else {
3169 45 : common++;
3170 45 : tagnum++;
3171 : }
3172 : }
3173 18 : if (*common == NULL) { // This is not a common tag.
3174 0 : auto length = std::min(tag.first.length(), MAX_TAG_LEN);
3175 0 : buf->PutByte ((byte) length);
3176 0 : buf->PutFixedString(tag.first.c_str(), length);
3177 : }
3178 :
3179 18 : auto valueLen = std::min<size_t>(tag.second.length(), 255);
3180 18 : buf->PutByte ((byte) valueLen);
3181 18 : buf->PutFixedString(tag.second.c_str(), valueLen);
3182 : }
3183 10018 : buf->PutByte (0);
3184 10018 : return buf->Status();
3185 : }
3186 :
3187 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3188 : // Game::DecodeTags():
3189 : // Decodes the non-standard tags of the game.
3190 : //
3191 : errorT
3192 1018 : Game::DecodeTags (ByteBuffer * buf, bool storeTags)
3193 : {
3194 : byte b;
3195 : char tag [255];
3196 : char value [255];
3197 :
3198 1018 : b = buf->GetByte ();
3199 1042 : while (b != 0 && buf->Status() == OK) {
3200 12 : if (b == 255) {
3201 : // Special binary 3-byte encoding of EventDate:
3202 0 : dateT date = 0;
3203 0 : b = buf->GetByte(); date = (date << 8) | b;
3204 0 : b = buf->GetByte(); date = (date << 8) | b;
3205 0 : b = buf->GetByte(); date = (date << 8) | b;
3206 0 : SetEventDate (date);
3207 : //char dateStr[20];
3208 : //date_DecodeToString (date, dateStr);
3209 : //if (storeTags) { AddPgnTag ("EventDate", dateStr); }
3210 12 : } else if (b > MAX_TAG_LEN) {
3211 : // A common tag name, not explicitly stored:
3212 12 : char * ctag = (char *) commonTags[b - MAX_TAG_LEN - 1];
3213 12 : b = buf->GetByte ();
3214 12 : buf->GetFixedString (value, b);
3215 12 : value[b] = '\0';
3216 12 : if (storeTags) { AddPgnTag (ctag, value); }
3217 : } else {
3218 0 : buf->GetFixedString (tag, b);
3219 0 : tag[b] = '\0';
3220 0 : b = buf->GetByte ();
3221 0 : buf->GetFixedString (value, b);
3222 0 : value[b] = '\0';
3223 0 : if (storeTags) { AddPgnTag (tag, value); }
3224 : }
3225 12 : b = buf->GetByte();
3226 : }
3227 1018 : return buf->Status();
3228 : }
3229 :
3230 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3231 : // skipTags():
3232 : // Called instead of DecodeTags() to skip over the tags of the
3233 : // game when decoding it. Called from DecodeStart() since the
3234 : // nonstandard tags are not needed for searches.
3235 : //
3236 : static errorT
3237 0 : skipTags (ByteBuffer * buf)
3238 : {
3239 : byte b;
3240 0 : b = buf->GetByte ();
3241 0 : while (b != 0 && buf->Status() == OK) {
3242 0 : if (b == 255) {
3243 : // Special 3-byte binary encoding of EventDate:
3244 0 : buf->Skip (3);
3245 : } else {
3246 0 : if (b > MAX_TAG_LEN) {
3247 : // Do nothing.
3248 : } else {
3249 0 : buf->Skip (b);
3250 : }
3251 0 : b = buf->GetByte ();
3252 0 : buf->Skip (b);
3253 : }
3254 0 : b = buf->GetByte();
3255 : }
3256 0 : return buf->Status ();
3257 : }
3258 :
3259 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3260 : // encodeComments():
3261 : // Encode the comments of the game. Recurses the moves of the game
3262 : // and writes the comment whenever a move with a comment is found.
3263 : //
3264 1908932 : static errorT encodeComments(ByteBuffer* buf, moveT* m, uint* commentCounter) {
3265 1908932 : ASSERT(buf != NULL && m != NULL);
3266 :
3267 24722056 : while (m->marker != END_MARKER) {
3268 11406562 : if (! m->comment.empty()) {
3269 1390894 : buf->PutTerminatedString (m->comment.c_str());
3270 1390894 : *commentCounter += 1;
3271 : }
3272 11406562 : if (m->numVariations) {
3273 1898860 : moveT * subVar = m->varChild;
3274 3797774 : for (uint i=0; i < m->numVariations; i++) {
3275 1898914 : encodeComments (buf, subVar, commentCounter);
3276 1898914 : subVar = subVar->varChild;
3277 : }
3278 : }
3279 11406562 : m = m->next;
3280 : }
3281 1908932 : return buf->Status();
3282 : }
3283 :
3284 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3285 : // decodeComments():
3286 : // Decodes the comments of the game. When decoding the moves, the
3287 : // comment field of each move that has a comment is marked (made
3288 : // not empty), so this function recurses the movelist and subvariations
3289 : // and allocates each comment to its move.
3290 : //
3291 155499 : static errorT decodeComments(ByteBuffer* buf, moveT* m) {
3292 155499 : ASSERT (buf != NULL && m != NULL);
3293 :
3294 2032599 : while (m->marker != END_MARKER) {
3295 938550 : if (! m->comment.empty()) {
3296 111977 : ASSERT (m->comment == "*");
3297 : char * str;
3298 111977 : buf->GetTerminatedString(&str);
3299 111977 : m->comment = str;
3300 : }
3301 :
3302 938550 : if (m->numVariations) {
3303 154439 : moveT * subVar = m->varChild;
3304 308920 : for (uint i=0; i < m->numVariations; i++) {
3305 154481 : decodeComments (buf, subVar);
3306 154481 : subVar = subVar->varChild;
3307 : }
3308 : }
3309 938550 : m = m->next;
3310 : }
3311 155499 : return buf->Status();
3312 : }
3313 :
3314 :
3315 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3316 : // Game::Encode(): Encode the game to a buffer for disk storage.
3317 : // If passed a NON-null IndexEntry pointer, it will fill in the
3318 : // following fields of that index entry, which are computed as
3319 : // the game is encoded:
3320 : // - result, ecoCode, whiteElo, blackElo
3321 : // - promotion flag
3322 : // - nMoves: the number of halfmoves
3323 : // - finalMatSig: the material signature of the final position.
3324 : // - homePawnData: the home pawn change list.
3325 : //
3326 : errorT
3327 10018 : Game::Encode (ByteBuffer * buf, IndexEntry * ie)
3328 : {
3329 10018 : ASSERT (buf != NULL);
3330 :
3331 : // Make the home pawn change list and update PromotionFlag:
3332 : byte homePawnList[9];
3333 10018 : auto UnderPromosFlag = MakeHomePawnList(homePawnList);
3334 :
3335 10018 : buf->Empty();
3336 : // First, encode info not already stored in the index
3337 : // This will be the non-STR (non-"seven tag roster") PGN tags.
3338 10018 : errorT err = encodeTags(buf, GetExtraTags());
3339 10018 : if (err != OK) { return err; }
3340 : // Now the game flags:
3341 10018 : byte flags = 0;
3342 10018 : if (HasNonStandardStart()) { flags += 1; }
3343 10018 : if (PromotionsFlag) { flags += 2; }
3344 10018 : if (UnderPromosFlag) { flags += 4; }
3345 10018 : buf->PutByte (flags);
3346 : // Now encode the startBoard, if there is one.
3347 10018 : if (StartPos) {
3348 : char tempStr [256];
3349 0 : StartPos->PrintFEN (tempStr, FEN_ALL_FIELDS);
3350 0 : buf->PutTerminatedString (tempStr);
3351 : }
3352 :
3353 : // Now the movelist:
3354 10018 : uint varCount = 0;
3355 10018 : uint nagCount = 0;
3356 10018 : err = encodeVariation (buf, FirstMove->next, &varCount, &nagCount, 0);
3357 10018 : if (err != OK) { return err; }
3358 :
3359 : // Now do the comments
3360 10018 : uint commentCount = 0;
3361 :
3362 10018 : err = encodeComments (buf, FirstMove, &commentCount);
3363 :
3364 : // Set the fields in the IndexEntry:
3365 10018 : if (ie != NULL) {
3366 9015 : ie->SetDate (Date);
3367 9015 : ie->SetEventDate (EventDate);
3368 9015 : ie->SetResult (Result);
3369 9015 : ie->SetEcoCode (EcoCode);
3370 9015 : ie->SetWhiteElo (WhiteElo);
3371 9015 : ie->SetBlackElo (BlackElo);
3372 9015 : ie->SetWhiteRatingType (WhiteRatingType);
3373 9015 : ie->SetBlackRatingType (BlackRatingType);
3374 :
3375 9015 : ie->clearFlags();
3376 9015 : ie->SetStartFlag (HasNonStandardStart());
3377 9015 : ie->SetCommentCount (commentCount);
3378 9015 : ie->SetVariationCount (varCount);
3379 9015 : ie->SetNagCount (nagCount);
3380 9015 : ie->SetFlag(IndexEntry::StrToFlagMask(ScidFlags), true);
3381 :
3382 9015 : std::copy_n(homePawnList, sizeof(homePawnList), ie->GetHomePawnData());
3383 : // Set other data updated by MakeHomePawnList():
3384 9015 : ie->SetPromotionsFlag (PromotionsFlag);
3385 9015 : ie->SetUnderPromoFlag (UnderPromosFlag);
3386 9015 : ie->SetNumHalfMoves (NumHalfMoves);
3387 9015 : ASSERT(AtEnd());
3388 9015 : ie->SetFinalMatSig(matsig_Make(CurrentPos->GetMaterial()));
3389 :
3390 : // Find the longest matching stored line for this game:
3391 9015 : uint storedLineCode = 0;
3392 9015 : if (!HasNonStandardStart()) {
3393 9015 : uint longestMatch = 0;
3394 2298825 : for (uint i = 1; i < StoredLine::count(); i++) {
3395 2289810 : moveT* gameMove = FirstMove->next;
3396 2289810 : uint matchLength = 0;
3397 2289810 : FullMove m = StoredLine::getMove(i, matchLength);
3398 2561650 : while (m) {
3399 4837656 : if (gameMove->marker == END_MARKER
3400 2418764 : || gameMove->moveData.from != m.getFrom()
3401 2653135 : || gameMove->moveData.to != m.getTo())
3402 : {
3403 2282908 : matchLength = 0; break;
3404 : }
3405 135920 : gameMove = gameMove->next;
3406 135920 : m = StoredLine::getMove(i, ++matchLength);
3407 : }
3408 2289810 : if (matchLength > longestMatch) {
3409 6902 : longestMatch = matchLength;
3410 6902 : storedLineCode = i;
3411 : }
3412 : }
3413 : }
3414 9015 : ASSERT(storedLineCode == static_cast<byte>(storedLineCode));
3415 9015 : ie->SetStoredLineCode(static_cast<byte>(storedLineCode));
3416 : }
3417 :
3418 : // as each game entry length is coded on 17 bits, and game must fit in a block
3419 : // return an error if there is an overflow
3420 10018 : if (buf->GetByteCount() >= MAX_GAME_LENGTH) {
3421 0 : err = ERROR_GameFull;
3422 : }
3423 :
3424 10018 : return err;
3425 : }
3426 :
3427 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3428 : // Game::DecodeNextMove():
3429 : // Decodes one more mainline move of the game from the bytebuffer.
3430 : // Used in searches for speed, since it is usually possible to
3431 : // determine if a game matches the search criteria without decoding
3432 : // all of it.
3433 : // If the game flag KeepDecodedMoves is true, the move decodes is
3434 : // added normally. If it is false, only the current position is
3435 : // updated and the list of moves is not updated -- this is done
3436 : // in searches for speed.
3437 : // Returns OK if a move was found, or ERROR_EndOfMoveList if all the
3438 : // moves have been decoded. Returns ERROR_Game if some corruption was
3439 : // detected.
3440 : //
3441 : errorT
3442 0 : Game::DecodeNextMove (ByteBuffer * buf, simpleMoveT * sm)
3443 : {
3444 0 : ASSERT (buf != NULL);
3445 : errorT err;
3446 : byte b;
3447 : while (1) {
3448 0 : b = buf->GetByte ();
3449 0 : if (buf->Status() != OK) { return ERROR_Game; }
3450 0 : switch (b) {
3451 0 : case ENCODE_NAG:
3452 : // We ignore NAGS but have to read it from the buffer
3453 0 : buf->GetByte();
3454 0 : break;
3455 :
3456 0 : case ENCODE_COMMENT: // We also ignore comments
3457 0 : break;
3458 :
3459 0 : case ENCODE_START_MARKER:
3460 : // Find the end of this variation and its children
3461 : uint nestCount;
3462 0 : nestCount= 1;
3463 0 : while (nestCount > 0) {
3464 0 : b = buf->GetByte();
3465 0 : if (buf->Status() != OK) { return ERROR_Game; }
3466 0 : if (b == ENCODE_NAG) { buf->GetByte(); }
3467 0 : else if (b == ENCODE_START_MARKER) { nestCount++; }
3468 0 : else if (b == ENCODE_END_MARKER) { nestCount--; }
3469 0 : else if (b == ENCODE_END_GAME) {
3470 : // Open var at end of game: should never happen!
3471 0 : return ERROR_Game;
3472 : }
3473 : }
3474 0 : break;
3475 :
3476 0 : case ENCODE_END_MARKER: // End marker in main game: error!
3477 0 : return ERROR_Game;
3478 :
3479 0 : case ENCODE_END_GAME: // We reached the end of the game:
3480 0 : return ERROR_EndOfMoveList;
3481 :
3482 0 : default: // It's a move in the game; decode it:
3483 : simpleMoveT tempMove;
3484 0 : if (!sm) { sm = &tempMove; }
3485 0 : err = decodeMove (buf, sm, b, currentPos());
3486 0 : if (err != OK) { return err; }
3487 0 : if (KeepDecodedMoves) {
3488 0 : AddMove(sm);
3489 : } else {
3490 0 : CurrentPos->DoSimpleMove (sm);
3491 : }
3492 0 : return OK;
3493 : }
3494 0 : }
3495 :
3496 : // We never reach here:
3497 : ASSERT(0);
3498 : return ERROR_Game;
3499 : }
3500 :
3501 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3502 : // Game::DecodeStart():
3503 : // Decodes the starting information from the game's on-disk
3504 : // representation in the bytebuffer. After this is called,
3505 : // DecodeNextMove() can be called to decode each successive
3506 : // mainline move.
3507 : //
3508 1018 : errorT Game::DecodeStart(ByteBuffer* buf, bool decodeTags) {
3509 1018 : ASSERT(buf != NULL);
3510 1018 : errorT err = buf->Status();
3511 1018 : if (err != OK)
3512 0 : return err;
3513 :
3514 1018 : err = decodeTags ? DecodeTags(buf, true) : skipTags(buf);
3515 1018 : if (err != OK)
3516 0 : return err;
3517 :
3518 : // Now the flags:
3519 1018 : byte flags = buf->GetByte();
3520 1018 : if (flags & 2) { PromotionsFlag = true; }
3521 :
3522 : // Now decode the startBoard, if there is one.
3523 1018 : if (flags & 1) {
3524 : char * tempStr;
3525 0 : buf->GetTerminatedString (&tempStr);
3526 0 : if ((err = buf->Status()) != OK)
3527 0 : return err;
3528 :
3529 0 : StartPos = std::make_unique<Position>();
3530 0 : err = StartPos->ReadFromFEN (tempStr);
3531 0 : if (err != OK) {
3532 0 : StartPos = nullptr;
3533 0 : return err;
3534 : }
3535 0 : *CurrentPos = *StartPos;
3536 : }
3537 :
3538 1018 : return err;
3539 : }
3540 :
3541 : //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3542 : // Game::Decode():
3543 : // Decodes a game from its on-disk representation in a bytebuffer.
3544 : // Decodes all the information: comments, variations, non-standard
3545 : // tags, etc, or selectively can ignore comments and/or tags for
3546 : // speed if the argument "flags" indicates.
3547 : //
3548 : errorT
3549 1018 : Game::Decode (ByteBuffer * buf, byte flags)
3550 : {
3551 1018 : ASSERT (buf != NULL);
3552 :
3553 1018 : Clear();
3554 1018 : errorT err = DecodeStart(buf, flags & GAME_DECODE_TAGS);
3555 1018 : if (err == OK)
3556 1018 : err = DecodeVariation (buf, flags, 0);
3557 :
3558 1018 : if (err == OK && (flags & GAME_DECODE_COMMENTS))
3559 1018 : err = decodeComments (buf, FirstMove);
3560 :
3561 1018 : return (err != OK) ? err : buf->Status();
3562 : }
3563 :
3564 : //////////////////////////////////////////////////////////////////////
3565 : // EOF: game.cpp
3566 : //////////////////////////////////////////////////////////////////////
|