1 /*
2
3 eboard - chess client
4 http://www.bergo.eng.br/eboard
5 https://github.com/fbergo/eboard
6 Copyright (C) 2000-2016 Felipe Bergo
7 fbergo/at/gmail/dot/com
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 */
24
25 #include <iostream>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <ctype.h>
31 #include <time.h>
32 #include <errno.h>
33 #include "eboard.h"
34 #include "chess.h"
35 #include "movelist.h"
36 #include "tstring.h"
37 #include "util.h"
38 #include "global.h"
39
40 // --- stream operators
41
operator <<(ostream & s,PGNheader h)42 ostream &operator<<(ostream &s, PGNheader h) {
43 unsigned int i;
44 for(i=0;i<h.header.size();i++)
45 s << '[' << h.header[i]->name << " \"" << h.header[i]->value << "\"]\n";
46 return(s);
47 }
48
49 // --- classes
50
51 bool ChessGame::GlyphsInited = false;
52 vector<string *> ChessGame::Glyphs;
53
ChessGame()54 ChessGame::ChessGame() {
55 int i;
56
57 for(i=0;i<8;i++)
58 protodata[i]=0;
59
60 GameNumber=-1;
61 Rated=0;
62 Variant=REGULAR;
63 memset(PlayerName[0],0,64);
64 memset(PlayerName[1],0,64);
65 Rating[0][0]=Rating[1][0]=0;
66 last_half_move=-1;
67 myboard=0;
68 clock_regressive=1;
69 info0[0]=0;
70 cursor=moves.end();
71 mymovelist=0;
72 over=0;
73 MyColor=WHITE;
74 StopClock=0;
75 LocalEdit=false;
76 Loaded=true;
77 PGNSource[0]=0;
78 SourceOffset=0;
79
80 source=GS_Other;
81 source_data="n/a";
82 AmPlaying=false;
83 }
84
ChessGame(int _number,int _tyme,int _inc,int _rated,variant _variant,const char * p1,const char * p2)85 ChessGame::ChessGame(int _number,int _tyme,int _inc, int _rated,
86 variant _variant,
87 const char *p1,const char *p2) {
88 int i;
89
90 GameNumber=_number;
91 timecontrol.setIcs(_tyme,_inc);
92 Rated=_rated;
93 Variant=_variant;
94 PlayerName[0][63]=PlayerName[1][63]=0;
95 g_strlcpy(PlayerName[0],p1,64);
96 g_strlcpy(PlayerName[1],p2,64);
97 last_half_move=-1;
98 myboard=0;
99 cursor=moves.end();
100 mymovelist=0;
101 over=0;
102 MyColor=WHITE;
103 StopClock=0;
104 Rating[0][0]=Rating[1][0]=0;
105 LocalEdit=false;
106 Loaded=true;
107 PGNSource[0]=0;
108 SourceOffset=0;
109
110 source=GS_Other;
111 source_data="n/a";
112 AmPlaying=false;
113
114 for(i=0;i<8;i++)
115 protodata[i]=0;
116 }
117
ChessGame(ChessGame * src)118 ChessGame::ChessGame(ChessGame *src) {
119 list<Position>::iterator i;
120 int j;
121 bool cset=false;
122
123 GameNumber = src->GameNumber;
124 Rated = src->Rated;
125 Variant = src->Variant;
126 strcpy(PlayerName[0],src->PlayerName[0]);
127 strcpy(PlayerName[1],src->PlayerName[1]);
128 last_half_move = src->last_half_move;
129 myboard=0;
130 mymovelist=0;
131 over=0;
132 MyColor=WHITE;
133 StopClock=0;
134 Rating[0][0]=Rating[1][0]=0;
135 LocalEdit=false;
136 timecontrol = src->timecontrol;
137
138 Loaded=true;
139 PGNSource[0]=0;
140 SourceOffset=0;
141
142 for(j=0;j<8;j++)
143 protodata[j] = src->protodata[j];
144
145 // clone move list
146 for(i=src->moves.begin();i!=src->moves.end();i++) {
147 moves.push_back(Position(*i));
148 if (src->cursor == i) { cursor = moves.end(); cursor--; cset = true; }
149 }
150
151 if (moves.empty())
152 moves.push_back(Position());
153 if (!cset) { cursor = moves.end(); cursor--; }
154
155 source=src->source;
156 source_data=src->source_data;
157 AmPlaying=src->AmPlaying;
158 }
159
~ChessGame()160 ChessGame::~ChessGame() {
161 moves.clear();
162 }
163
operator ==(int gnum)164 int ChessGame::operator==(int gnum) {
165 return(gnum==GameNumber);
166 }
167
setFree()168 void ChessGame::setFree() {
169 if (myboard)
170 myboard->FreeMove=true;
171 }
172
acknowledgeInfo()173 void ChessGame::acknowledgeInfo() {
174 char s[64],tz[128];
175
176 if (myboard==NULL)
177 return;
178 myboard->freeze();
179
180 // P2P and engine games
181 if (GameNumber > 7000 && GameNumber < 9000) {
182 timecontrol.toString(s,64);
183 } else if (GameNumber >= 9000) { // client-side games - rated/unrated unknown
184 timecontrol.toShortString(tz,128);
185 snprintf(s,64,_("Game #%d - %s"), // TRANSLATE
186 GameNumber,tz);
187 } else { // live ICS games
188 timecontrol.toShortString(tz,128);
189 snprintf(s,64,_("Game #%d - %s - %s"), // TRANSLATE?
190 GameNumber,tz,
191 Rated?_("rated"):
192 _("unrated"));
193 }
194
195 myboard->setInfo(0,s);
196
197 strcpy(s,variantName(Variant));
198 myboard->setInfo(1,s);
199
200 if (!moves.empty())
201 myboard->setInfo(4,moves.back().getMaterialString(Variant));
202
203 if (over)
204 showResult();
205
206 myboard->invalidate();
207 myboard->thaw();
208 myboard->updateClock();
209 }
210
showResult()211 void ChessGame::showResult() {
212 char str[256];
213 if ((!over)||(!myboard))
214 return;
215 switch(result) {
216 case WHITE_WIN: strcpy(str,"1-0 "); break;
217 case BLACK_WIN: strcpy(str,"0-1 "); break;
218 case DRAW: strcpy(str,"1/2-1/2 "); break;
219 default: strcpy(str,"(*) "); break;
220 }
221 if (strlen(ereason)>2)
222 g_strlcat(str,ereason,256);
223 myboard->setInfo(3,str);
224 }
225
updateStock()226 void ChessGame::updateStock() {
227 if (over)
228 return;
229 if (!myboard)
230 return;
231 if (!moves.empty())
232 myboard->setInfo(5,moves.back().getHouseString());
233
234 // bughouse partner game
235 if (protodata[2])
236 global.bugpane->setPosition(moves.back());
237 }
238
isFresh()239 bool ChessGame::isFresh() {
240 return((bool)(moves.empty()));
241 }
242
243 // usually called to remove an illegal move from the movelist
retreat(int nmoves)244 void ChessGame::retreat(int nmoves) {
245 for(;nmoves;nmoves--)
246 if (!moves.empty())
247 moves.pop_back();
248 cursor=moves.end();
249 cursor--;
250 }
251
fixExamineZigZag(Position & suspect)252 void ChessGame::fixExamineZigZag(Position &suspect) {
253 list<Position>::iterator i;
254
255 for(i=moves.begin();i!=moves.end();i++)
256 if ( (*i) == suspect )
257 if (! suspect.getLastMove().compare( (*i).getLastMove() ) ) {
258 moves.erase(i,moves.end());
259 return;
260 }
261 }
262
263 // updates clocks and just-below-clocks strings
updateClockAndInfo2(int wclockms,int bclockms,int blacktomove,const char * infoline,bool sndflag)264 void ChessGame::updateClockAndInfo2(int wclockms, int bclockms,
265 int blacktomove, const char *infoline,
266 bool sndflag)
267 {
268 if (myboard) {
269 myboard->freeze();
270
271 if (StopClock)
272 myboard->setClock2(wclockms,bclockms,blacktomove?2:-2,clock_regressive);
273 else
274 myboard->setClock2(wclockms,bclockms,blacktomove?1:-1,clock_regressive);
275
276 if (infoline)
277 myboard->setInfo(2,infoline);
278
279 if (!moves.empty()) {
280 myboard->setInfo(4,moves.back().getMaterialString(Variant));
281 myboard->setInfo(5,moves.back().getHouseString());
282 }
283 myboard->update(sndflag);
284 myboard->thaw();
285 myboard->contentUpdated();
286 }
287 }
288
updatePosition2(Position & p,int movenum,int blacktomove,int wclockms,int bclockms,const char * infoline,bool sndflag)289 void ChessGame::updatePosition2(Position &p,int movenum,int blacktomove,
290 int wclockms,int bclockms,const char *infoline,
291 bool sndflag) {
292 global.debug("ChessGame","updatePosition");
293
294 if (over)
295 return;
296
297 // bughouse partner's game
298 if (protodata[2]) {
299 global.bugpane->freeze();
300 global.bugpane->setPosition(p);
301
302 if (StopClock)
303 global.bugpane->setClock2(wclockms,bclockms,blacktomove?2:-2,clock_regressive);
304 else
305 global.bugpane->setClock2(wclockms,bclockms,blacktomove?1:-1,clock_regressive);
306
307 global.bugpane->thaw();
308 }
309
310 if (last_half_move>=0)
311 if (! moves.empty() )
312 if (p == moves.back() ) {
313 updateClockAndInfo2(wclockms,bclockms,blacktomove,infoline,
314 sndflag);
315 return;
316 }
317
318 Position np;
319 np=p;
320 np.setLastMove(infoline);
321 // cerr << "LastMove = [" << infoline << "]\n";
322
323 fixExamineZigZag(np);
324
325 moves.push_back(np);
326 cursor=moves.end();
327 cursor--;
328 last_half_move=(movenum*2)+(blacktomove?0:1);
329
330 updateClockAndInfo2(wclockms,bclockms,blacktomove,infoline,
331 sndflag);
332
333 if (mymovelist)
334 mymovelist->updateList(moves);
335 }
336
incrementActiveClock(int secs)337 void ChessGame::incrementActiveClock(int secs) {
338 ChessClock *c;
339 if (myboard) {
340 c = myboard->getClock();
341 c->incrementClock2(0,secs*1000);
342 }
343 }
344
fireWhiteClock(int wval,int bval)345 void ChessGame::fireWhiteClock(int wval,int bval) {
346 myboard->setClock2(wval*1000,bval*1000,-1,clock_regressive);
347 }
348
setBoard(Board * b)349 void ChessGame::setBoard(Board *b) {
350 myboard=b;
351 if (myboard)
352 myboard->setGame(this);
353 }
354
getBoard()355 Board * ChessGame::getBoard() {
356 return(myboard);
357 }
358
getLastPosition()359 Position & ChessGame::getLastPosition() {
360 if (moves.empty())
361 return(startpos);
362 return(moves.back());
363 }
364
getCurrentPosition()365 Position & ChessGame::getCurrentPosition() {
366 if (moves.empty())
367 return(startpos);
368 if (cursor!=moves.end())
369 return(*cursor);
370 else
371 return(moves.back());
372 }
373
getPreviousPosition()374 Position & ChessGame::getPreviousPosition() {
375 list<Position>::iterator pv;
376 if (moves.empty())
377 return(startpos);
378 if (cursor!=moves.end()) {
379 pv=cursor;
380 if (pv!=moves.begin())
381 pv--;
382 return(*pv);
383 } else {
384 pv=moves.end();
385 pv--;
386 if (pv!=moves.begin())
387 pv--;
388 return(*pv);
389 }
390 }
391
goBack1()392 void ChessGame::goBack1() {
393 if (moves.empty())
394 return;
395 if (cursor!=moves.begin())
396 cursor--;
397 }
398
goBackAll()399 void ChessGame::goBackAll() {
400 if (moves.empty())
401 return;
402 cursor=moves.begin();
403 }
404
goForward1()405 void ChessGame::goForward1() {
406 if (moves.empty())
407 return;
408 cursor++;
409 if (cursor==moves.end())
410 cursor--;
411 }
412
goForwardAll()413 void ChessGame::goForwardAll() {
414 if (moves.empty())
415 return;
416 cursor=moves.end();
417 cursor--;
418 }
419
openMoveList()420 void ChessGame::openMoveList() {
421 if (mymovelist) {
422 gtk_window_activate_focus(GTK_WINDOW(mymovelist->widget));
423 } else {
424 mymovelist=new MoveListWindow(PlayerName[0],PlayerName[1],
425 GameNumber,moves,over,result,ereason);
426 mymovelist->setListener(this);
427 mymovelist->show();
428 }
429 }
430
closeMoveList()431 void ChessGame::closeMoveList() {
432 if (mymovelist)
433 mymovelist->close();
434 }
435
moveListClosed()436 void ChessGame::moveListClosed() {
437 delete mymovelist;
438 mymovelist=0;
439 }
440
editEmpty()441 void ChessGame::editEmpty() {
442 Position p;
443 int i,j;
444
445 if (!LocalEdit) return;
446
447 moves.clear();
448 last_half_move=1;
449
450 for(i=0;i<8;i++) for(j=0;j<8;j++) p.setPiece(i,j,EMPTY);
451 p.sidehint=true;
452
453 moves.push_back(p);
454 cursor=moves.end();
455 cursor--;
456 if (myboard) {
457 myboard->setInfo(2,(*cursor).getLastMove());
458 myboard->setInfo(4,p.getMaterialString());
459 myboard->update();
460 }
461 }
462
editStartPos()463 void ChessGame::editStartPos() {
464 Position p;
465
466 if (!LocalEdit) return;
467
468 moves.clear();
469 startpos.setStartPos(); // sidehint might need to be fixed
470 last_half_move=1;
471
472 moves.push_back(p);
473 cursor=moves.end();
474 cursor--;
475 if (myboard) {
476 myboard->setInfo(2,(*cursor).getLastMove());
477 myboard->setInfo(4,p.getMaterialString());
478 myboard->update();
479 }
480 }
481
sendDrop(piece p,int x,int y)482 void ChessGame::sendDrop(piece p, int x, int y) {
483 list<Position>::iterator z;
484
485 if (LocalEdit) {
486 Position o;
487 o = *cursor;
488 o.setPiece(x,y,p);
489
490 z=cursor; z++;
491 moves.erase(z,moves.end());
492
493 moves.push_back(o);
494 last_half_move++;
495 cursor=moves.end();
496 cursor--;
497 if (myboard) { myboard->setInfo(2,(*cursor).getLastMove()); myboard->update(); }
498 } else {
499 if (global.protocol) global.protocol->sendDrop(p&PIECE_MASK,x,y);
500 }
501
502 }
503
sendMove(int x1,int y1,int x2,int y2)504 void ChessGame::sendMove(int x1,int y1,int x2,int y2) {
505 int promote=0;
506 piece q;
507 list<Position>::iterator z;
508 char v[16];
509
510 if (!moves.empty()) {
511 Position p;
512 p=moves.back();
513
514 if ((p.getPiece(x1,y1)==(PAWN|WHITE))&&
515 (y1==6)&&(y2==7))
516 promote=1;
517 if ((p.getPiece(x1,y1)==(PAWN|BLACK))&&
518 (y1==1)&&(y2==0))
519 promote=1;
520 }
521
522 // ====================== BEGIN SCRATCH BOARD ONLY ===============================
523 if (LocalEdit) {
524 Position p;
525 p = *cursor;
526
527 q=p.getPiece(x1,y1)&COLOR_MASK;
528
529 if (p.isMoveLegalCartesian(x1,y1,x2,y2,q,REGULAR)) {
530 p.stdNotationForMove(x1,y1,x2,y2,
531 promote ? q | global.promotion->getPiece() : EMPTY,
532 v, REGULAR);
533 p.setLastMove(v);
534
535 p.moveCartesian(x1,y1,x2,y2,REGULAR);
536 if (promote) p.setPiece(x2,y2, q | global.promotion->getPiece() );
537
538 } else {
539
540 p.setPiece(x2,y2,p.getPiece(x1,y1));
541 p.setPiece(x1,y1,EMPTY);
542
543 }
544
545 z=cursor; z++; if (z!=moves.end()) moves.erase(z,moves.end());
546
547 moves.push_back(p);
548 last_half_move++;
549 cursor=moves.end();
550 cursor--;
551 if (myboard) { myboard->setInfo(2,(*cursor).getLastMove()); myboard->update(); }
552 }
553 // ====================== END SCRATCH BOARD ONLY ===============================
554
555 else {
556 if (global.protocol) global.protocol->sendMove(x1,y1,x2,y2,promote);
557 }
558
559 }
560
getSideHint()561 bool ChessGame::getSideHint() {
562 return (getCurrentPosition().sidehint);
563 }
564
setSideHint(bool white)565 void ChessGame::setSideHint(bool white) {
566 getCurrentPosition().sidehint = white;
567 }
568
flipHint(int flip)569 void ChessGame::flipHint(int flip) {
570 if (myboard)
571 myboard->setFlipped(flip!=0);
572 }
573
enableMoving(bool flag)574 void ChessGame::enableMoving(bool flag) {
575 if (!myboard)
576 return;
577 myboard->setCanMove(flag);
578 }
579
endGame(const char * reason,GameResult _result)580 void ChessGame::endGame(const char *reason,GameResult _result) {
581 if (over)
582 return; // can't end twice
583 result=_result;
584
585 // bughouse partner game
586 if (protodata[2])
587 global.bugpane->stopClock();
588
589 if (myboard)
590 myboard->stopClock();
591
592 over=1;
593 g_strlcpy(ereason,reason,128);
594
595 showResult();
596 if (myboard)
597 myboard->setCanMove(false);
598
599 if (pgn.empty())
600 guessPGNFromInfo();
601
602 if (mymovelist)
603 mymovelist->updateList(moves,1,result,ereason);
604 }
605
isOver()606 int ChessGame::isOver() {
607 return(over);
608 }
609
updateGame(list<Position> & gamedata)610 void ChessGame::updateGame(list<Position> &gamedata) {
611 list<Position>::iterator li;
612 Position startpos;
613 int hmn=-1;
614
615 moves.clear();
616
617 for(li=gamedata.begin();li!=gamedata.end();li++) {
618 Position p;
619 p = *li;
620 moves.push_back(p);
621 ++hmn;
622 }
623
624 // should fix the adding of FEN fields on followed games
625 if (moves.empty() && IS_NOT_WILD(Variant)) {
626 moves.push_back(Position());
627 hmn=0;
628 }
629
630 cursor=moves.end();
631 cursor--;
632 last_half_move=hmn;
633 if (mymovelist)
634 mymovelist->updateList(moves);
635 }
636
dump()637 void ChessGame::dump() {
638 cerr.setf(ios::hex,ios::basefield);
639 cerr.setf(ios::showbase);
640
641 cerr << "[game " << ((uint64_t) this) << "] ";
642
643 cerr.setf(ios::dec,ios::basefield);
644
645 cerr << "game#=" << GameNumber << " ";
646 cerr << "rated=" << Rated << " ";
647 cerr << "white=" << PlayerName[0] << " ";
648 cerr << "black=" << PlayerName[1] << " ";
649 cerr << "over=" << over << " ";
650
651 cerr.setf(ios::hex,ios::basefield);
652 cerr.setf(ios::showbase);
653
654 cerr << "board=[" << ((uint64_t)myboard) << "]" << endl;
655 }
656
getPlayerString(int index)657 char * ChessGame::getPlayerString(int index) {
658 index%=2;
659 PrivateString[0]=0;
660 if ((global.ShowRating)&&(strlen(Rating[index])))
661 snprintf(PrivateString,96,"%s (%s)",PlayerName[index],Rating[index]);
662 else
663 strcpy(PrivateString,PlayerName[index]);
664 return(PrivateString);
665 }
666
667 // ---- PGN
668
guessInfoFromPGN()669 void ChessGame::guessInfoFromPGN() {
670 const char *cp;
671
672 cp=pgn.get("White");
673 if (cp!=NULL) g_strlcpy(PlayerName[0],cp,64);
674
675 cp=pgn.get("Black");
676 if (cp!=NULL) g_strlcpy(PlayerName[1],cp,64);
677
678 cp=pgn.get("Result");
679 result = UNDEF;
680 if (cp!=NULL) {
681 if (cp[0]=='0') result=BLACK_WIN;
682 else if (cp[1]=='/') result=DRAW;
683 else if (cp[0]=='1') result=WHITE_WIN;
684 }
685
686 timecontrol.mode = TC_NONE;
687 cp=pgn.get("TimeControl");
688 if (cp!=NULL) {
689 int a,b;
690 ExtPatternMatcher FischerClock, XMoves, SecsPerMove;
691
692 FischerClock.set("%N+%N*");
693 XMoves.set("%N/%N*");
694 SecsPerMove.set("0+%N*");
695
696 if (SecsPerMove.match(cp)) {
697
698 a = atoi(SecsPerMove.getNToken(0));
699 timecontrol.setSecondsPerMove(a);
700
701 } else if (FischerClock.match(cp)) {
702
703 a = atoi(FischerClock.getNToken(0));
704 b = atoi(FischerClock.getNToken(1));
705 timecontrol.setIcs(a,b);
706
707 } else if (XMoves.match(cp)) {
708
709 a = atoi(FischerClock.getNToken(0));
710 b = atoi(FischerClock.getNToken(1));
711 timecontrol.setXMoves(a,b);
712
713 }
714 }
715
716 cp=pgn.get("Variant");
717 if (cp!=NULL) Variant = variantFromName(cp);
718
719 }
720
guessPGNFromInfo()721 void ChessGame::guessPGNFromInfo() {
722 time_t now;
723 struct tm *snow;
724 char z[64],y[256],x[128];
725 Position startpos;
726
727 char rz[64];
728 int i,j;
729
730 now=time(0);
731 snow=localtime(&now);
732
733 pgn.set("Event","?");
734 switch(source) {
735 case GS_ICS:
736 strcpy(x,variantName(Variant));
737 x[0] = toupper(x[0]);
738
739 // ICS Rated Chess Match
740 snprintf(y,256,"ICS %s %s Match",
741 Rated?"Rated":"Unrated",
742 x);
743 pgn.set("Event",y);
744 break;
745 case GS_Engine:
746 strcpy(x,variantName(Variant));
747 x[0] = toupper(x[0]);
748
749 // Engine Chess Match
750 snprintf(y,256,"Engine %s Match",x);
751 pgn.set("Event",y);
752 break;
753 case GS_Other:
754 case GS_PGN_File:
755 default:
756 pgn.set("Event","?");
757 break;
758 }
759
760 pgn.set("Site","?");
761
762 snprintf(z,64,"%d.%.2d.%.2d",1900+snow->tm_year,
763 1+snow->tm_mon,snow->tm_mday);
764
765 pgn.set("Date",z);
766 pgn.set("Round","?");
767
768 if (strlen(PlayerName[0])) pgn.set("White",PlayerName[0]);
769 if (strlen(PlayerName[1])) pgn.set("Black",PlayerName[1]);
770
771 // get rating, but prevent strings like (1967), (----), (UNR)...
772 memset(rz,0,64);
773 j=strlen(Rating[0]);
774 for(i=0;i<j;i++)
775 if (isdigit(Rating[0][i]))
776 rz[strlen(rz)]=Rating[0][i];
777 if (strlen(rz)) pgn.set("WhiteElo",rz);
778
779 memset(rz,0,64);
780 j=strlen(Rating[1]);
781 for(i=0;i<j;i++)
782 if (isdigit(Rating[1][i]))
783 rz[strlen(rz)]=Rating[1][i];
784 if (strlen(rz)) pgn.set("BlackElo",rz);
785
786 timecontrol.toPGN(rz,64);
787 pgn.set("TimeControl",rz);
788
789 if (Variant != REGULAR) {
790 pgn.set("Variant",(char *) variantName(Variant));
791 }
792
793 // strcpy(rz,Rated?"yes":"no");
794 // pgn.set("Rated",rz);
795
796 if (over) {
797 switch(result) {
798 case WHITE_WIN: pgn.set("Result","1-0"); break;
799 case BLACK_WIN: pgn.set("Result","0-1"); break;
800 case DRAW: pgn.set("Result","1/2-1/2"); break;
801 case UNDEF: pgn.set("Result","*"); break;
802 }
803 }
804
805 if ( startpos != moves.front() )
806 pgn.set("FEN", moves.front().getFEN() );
807 }
808
savePGN(char * filename,bool append)809 bool ChessGame::savePGN(char *filename, bool append) {
810 char z[512];
811 const char *cp;
812 list<Position>::iterator li;
813 int xp,lm,mn;
814 tstring t;
815 string sp, path, *p;
816
817 if (moves.size() < 4) {
818 global.status->setText(_("savePGN failed: Won't save game with less than 2 moves"),5);
819 return false;
820 }
821
822 // FIXME ~username won't work, only ~/something
823 if (filename[0]=='~') {
824 path=global.env.Home;
825 path+='/';
826 path+=(&filename[2]);
827 } else
828 path=filename;
829
830 ofstream f(path.c_str(),append? ios::app : ios::out);
831
832 if (!f) {
833 snprintf(z,512,_("savePGN failed: %s"),filename);
834 global.status->setText(z,10);
835 return false;
836 }
837
838 if (pgn.empty())
839 guessPGNFromInfo();
840
841 f << pgn << endl;
842
843 lm=0; xp=0;
844 for(li=moves.begin();li!=moves.end();li++) {
845
846 sp=(*li).getLastMove();
847
848 if (sp.empty())
849 continue;
850
851 t.set(sp);
852
853 mn=t.tokenvalue(".");
854 if (!mn)
855 continue;
856 if (mn>lm) {
857 f << mn << ". ";
858 lm=mn;
859 xp+=(lm>9)?4:3; // MINOR BUG FOR GAMES WITH 100+ MOVES
860 }
861 p=t.token("(). \t");
862 f << (*p) << ' ';
863 xp+=1+p->length();
864
865 if (xp>70) {
866 xp=0;
867 f << endl;
868 }
869 }
870
871 if (strlen(ereason) > 5) {
872 if (xp) f << endl;
873 f << '{' << ereason << "} ";
874 }
875
876 cp=pgn.get("Result");
877
878 if (cp)
879 f << cp;
880 else
881 f << '*';
882
883 f << endl << endl;
884
885 f.close();
886
887 snprintf(z,512,_("--- %s game to PGN file %s"),
888 append?_("Appended"):
889 _("Wrote"),filename);
890 global.output->append(z,0xc0ff00);
891 return true;
892 }
893
894 // indexonly: do not load the move text now
ParsePgnGame(zifstream & f,char * filename,bool indexonly,int gameid,variant v,ChessGame * updatee)895 bool ChessGame::ParsePgnGame(zifstream &f,
896 char * filename,
897 bool indexonly,
898 int gameid,
899 variant v,
900 ChessGame *updatee) {
901 /*
902 ok, now (0.4.x) we do this in a DFA-like manner
903 0 = state 0 (whitespace before movetext)
904
905 PGN header states:
906 1 = found [, consuming PGN field name
907 2 = consuming whitespace between field name and field data
908 3 = found " , consuming field data
909 4 = found ending " , waiting ]
910
911 6 = reading token
912 7 = inside { comment
913 8 = like level 0, but can't have headers
914 9 = got result, waiting end of line (== end of game)
915
916 10 = initial state ;-)
917 whitespace (and comments like in twic pgns) between games
918 11 = inside alternate line
919 */
920
921 int state = 10;
922 int halfmove = 0;
923 int altmoves = 0;
924 piece color = WHITE;
925 tstring t;
926
927 list<Position> gl;
928 ChessGame *game=0;
929
930 ExtPatternMatcher Legacy0, Legacy1, Legacy2, Result;
931 PatternBinder Binder, Binder2;
932
933 char alpha[256], beta[256], omega[256];
934 int ap=0, bp=0;
935 char line[512], *p;
936 long pstart;
937 GameResult r;
938
939 static const char *whitespace = " \t\n\r";
940 static const char *token = "1234567890*/KQBNRP@kqbnrp=+#abcdefgh-Ox$";
941 static const char *digits = "1234567890";
942 static const char *resultset = "01*";
943
944 Legacy0.set("%%eboard:variant:%s*");
945 Legacy1.set("%%eboard:clock/r:%n:%n:%n*");
946 Legacy2.set("%%eboard:clue:*");
947 Binder.add(&Legacy0,&Legacy1,&Legacy2,NULL);
948
949 Result.set("*%N-%N*");
950 Binder2.add(&Result,NULL);
951
952 ChessGame::initGlyphs();
953
954 pstart=f.tellg();
955 if (!updatee) {
956 game = new ChessGame();
957 game->Variant = v;
958 game->GameNumber = gameid;
959 game->source = GS_PGN_File;
960 game->source_data = filename;
961
962 if (indexonly) {
963 game->Loaded=false;
964 g_strlcpy(game->PGNSource,filename,256);
965 game->SourceOffset = pstart;
966 }
967 }
968
969 omega[0]=0;
970
971 // #define PGN_DEBUG 1
972
973 #ifdef PGN_DEBUG
974 cerr << "entering new game\n";
975 #endif
976
977 while( memset(line,0,512), f.getline(line,510,'\n') ) {
978 line[strlen(line)]='\n';
979
980 #ifdef PGN_DEBUG
981 cerr << "state = " << state << ", current line is: " << line;
982 #endif
983
984 // %-comments
985 if (line[0]=='%') {
986 Binder.prepare(line);
987
988 if (Legacy0.match()) {
989 p=Legacy0.getSToken(0);
990 v=variantFromName(p);
991 if (v!=REGULAR)
992 if (game)
993 game->Variant=v;
994 continue;
995 } // match Legacy0
996
997 if (Legacy1.match()) {
998 if (game) {
999 int a,b;
1000 a = atoi(Legacy1.getNToken(0));
1001 b = atoi(Legacy1.getNToken(1));
1002 game->timecontrol.setIcs(a,b);
1003 game->Rated=atoi(Legacy1.getNToken(2));
1004 }
1005 continue;
1006 } // match Legacy1
1007
1008 if (Legacy2.match()) {
1009 g_strlcpy(omega,Legacy2.getStarToken(0),128);
1010 if (omega[strlen(omega)-1]=='\n')
1011 omega[strlen(omega)-1]=0;
1012 continue;
1013 }
1014
1015 continue;
1016 }
1017
1018 for(p=line;*p;p++) {
1019
1020 switch(state) {
1021 // whitespace
1022 case 10:
1023 if (*p == '[') { state=1; memset(alpha,0,256); ap=0; }
1024 break;
1025 case 0:
1026 if (*p == '[') { state=1; memset(alpha,0,256); ap=0; break; }
1027 case 8:
1028 if (*p == '{') { state=7; memset(alpha,0,256); ap=0; break; }
1029
1030 if (*p == '(') { state=11; ap=0; altmoves++; break; }
1031
1032 if (strchr(token, *p)) {
1033 state=6;
1034 memset(alpha,0,256);
1035 alpha[0]=*p;
1036 ap=1;
1037 break;
1038 }
1039 break; // case 0/8
1040
1041 // header
1042 case 1:
1043 if (strchr(whitespace, *p)) { state=2; memset(beta,0,256); bp=0; break; }
1044 alpha[ap++]=*p;
1045 break; // case 1
1046
1047 case 2:
1048 if (*p == '\"') state = 3;
1049 break; // case 2
1050
1051 case 3:
1052 if (*p == '\"') state = 4; else beta[bp++]=*p;
1053 break; // case 3
1054
1055 case 4:
1056 if (*p == ']') {
1057 if (game) {
1058 game->pgn.set(alpha,beta);
1059 if (!strcmp(alpha,"Variant")) {
1060 v=variantFromName(beta);
1061 game->Variant=v;
1062 }
1063 }
1064
1065 if (!indexonly)
1066 if (!strcmp(alpha,"FEN")) {
1067 Position g;
1068 g.setFEN(beta);
1069 gl.clear();
1070 gl.push_back(g);
1071 }
1072
1073 state = 0;
1074 }
1075 break; // case 4
1076
1077 // token
1078 case 6:
1079 if (strchr(whitespace,*p)) {
1080 Binder2.prepare(alpha);
1081
1082 #ifdef PGN_DEBUG
1083 cerr << "state 6 parsing token " << alpha << endl;
1084 #endif
1085
1086 // move number token (1. , 1...)
1087 if ( strchr(digits, alpha[0]) && strchr(alpha, '.') ) {
1088 state=8;
1089 // Account for PGNs where Black moves first
1090 if (strstr(alpha, "...") && !halfmove) {
1091 color = BLACK;
1092 halfmove = 1;
1093 }
1094 #ifdef PGN_DEBUG
1095 cerr << "v: movenumber\n";
1096 #endif
1097 break;
1098 }
1099
1100 // end game token (1-0, 0-1, 1/2-1/2, *)
1101 if (strchr(resultset, alpha[0]))
1102 if ((alpha[0]=='*') || Result.match() ) {
1103 #ifdef PGN_DEBUG
1104 cerr << "v: end of game\n";
1105 #endif
1106
1107 if (!strlen(omega)) strcpy(omega," ");
1108 if (alpha[0]=='*') r=UNDEF; else
1109 if (alpha[1]=='/') r=DRAW; else
1110 if (alpha[0]=='1') r=WHITE_WIN; else
1111 r=BLACK_WIN;
1112 if (game) {
1113 game->updateGame(gl);
1114 game->endGame(omega,r);
1115 game->guessInfoFromPGN();
1116 } else
1117 updatee->updateGame(gl);
1118
1119 if (game)
1120 global.GameList.push_back(game);
1121 state=9;
1122 break;
1123 }
1124
1125 if (alpha[0]=='$') {
1126 int i;
1127 t.set((char *) alpha);
1128 if (! gl.empty() ) {
1129 if (! global.annotator.isOpen() ) {
1130 i = t.tokenvalue("$");
1131 if (i < Glyphs.size()) {
1132 gl.back().addAnnotation( global.annotator.open() );
1133 global.annotator.append(* (Glyphs[i]));
1134 global.annotator.close();
1135 }
1136 } else {
1137 i = t.tokenvalue("$");
1138 if (i < Glyphs.size()) {
1139 global.annotator.append(* (Glyphs[i]));
1140 }
1141 }
1142 }
1143
1144 state = 8;
1145 break;
1146 }
1147
1148 #ifdef PGN_DEBUG
1149 cerr << "v: move text\n";
1150 #endif
1151 if (indexonly) { state=8; break; }
1152
1153 // move tokens
1154 if (gl.empty()) gl.push_back(Position());
1155
1156 {
1157 Position g;
1158 g = gl.back();
1159 g.clearAnnotation();
1160
1161 g.moveAnyNotation(alpha,color,game ? game->Variant : v);
1162 snprintf(beta,256,"%d%s %s",1+halfmove/2,
1163 (color==WHITE)?".":". ...",alpha);
1164 g.setLastMove(beta);
1165 gl.push_back(g);
1166 }
1167
1168 color^=COLOR_MASK;
1169 halfmove++;
1170 state=8;
1171
1172 } else { // non whitespace char
1173
1174 // ignore move number in PGNs like 1.e4 (no space between . and move)
1175 if ( ap && alpha[ap-1] == '.' )
1176 if (strchr(token,*p)) {
1177 memset(alpha,0,256);
1178 ap=0;
1179 }
1180
1181 alpha[ap++]=*p;
1182
1183 if (game)
1184 if ( (*p == '@') && (game->Variant == REGULAR) ) {
1185 f.seekg(pstart);
1186 gl.clear();
1187
1188 delete game;
1189 game=0;
1190 return(ParsePgnGame(f,filename,indexonly,gameid,CRAZYHOUSE));
1191 }
1192
1193 }
1194 break; // case 6
1195
1196 // { comments
1197 case 7:
1198 if (*p == '}') {
1199 global.annotator.close();
1200 state=8;
1201 break;
1202 }
1203
1204 if (! gl.empty() ) {
1205 if (! global.annotator.isOpen() )
1206 gl.back().addAnnotation( global.annotator.open() );
1207 global.annotator.append(*p >= ' ' ? *p : ' ');
1208 }
1209
1210 break; // case 7
1211
1212 case 9: // game over, waiting end of line
1213 if (*p == '\n') {
1214 gl.clear();
1215 return true;
1216 }
1217 break; // case 9
1218
1219 case 11: // Temporarily ignore alternate moves
1220 if (*p == ')')
1221 if (!(--altmoves))
1222 state = 8;
1223 if (*p == '(')
1224 altmoves++;
1225 break;
1226
1227 } // switch state
1228
1229 } // for p
1230
1231 } // while getline
1232
1233 // avoid issues with files whose last char belongs to a token
1234 if (state == 9) {
1235 gl.clear();
1236 return true;
1237 }
1238
1239 if (!gl.empty())
1240 gl.clear();
1241
1242 if (game) delete game;
1243 return false;
1244 } // method
1245
loadMoves()1246 bool ChessGame::loadMoves() {
1247 char z[512];
1248 if (Loaded) return true;
1249
1250 zifstream f(PGNSource);
1251
1252 if (!f) {
1253 snprintf(z,512,_("can't load PGN move text from %s (error opening file)"),PGNSource);
1254 global.output->append(z,0xc0ff00);
1255 return false;
1256 }
1257
1258 if (!f.seekg(SourceOffset)) {
1259 snprintf(z,512,_("can't seek to offset %lu of %s"),SourceOffset,PGNSource);
1260 global.output->append(z,0xc0ff00);
1261 f.close();
1262 return false;
1263 }
1264
1265 if (!ParsePgnGame(f,PGNSource,false,-1,Variant,this)) {
1266 g_strlcpy(z,_("error parsing PGN data"),512);
1267 global.output->append(z,0xc0ff00);
1268 f.close();
1269 return false;
1270 }
1271 f.close();
1272
1273 Loaded = true;
1274 return true;
1275 }
1276
LoadPGN(char * filename)1277 void ChessGame::LoadPGN(char *filename) {
1278 UnboundedProgressWindow *pw;
1279 int nextid,count=0;
1280
1281 vector<int> g_ids;
1282 vector<int>::iterator ii;
1283 list<ChessGame *>::iterator gi;
1284 bool id_space_empty=false;
1285
1286 zifstream f(filename);
1287 if (!f)
1288 return;
1289
1290 // cerr << "reading " << filename << endl;
1291 pw=new UnboundedProgressWindow(_("%d games read"));
1292
1293 // grab a list of existent game ids
1294 for(gi=global.GameList.begin();gi!=global.GameList.end();gi++)
1295 g_ids.push_back( (*gi)->GameNumber );
1296
1297 sort( g_ids.begin(), g_ids.end() );
1298 nextid = 10000;
1299
1300 for(ii=g_ids.begin(); ii != g_ids.end(); ii++) {
1301 if ( *ii > nextid ) break;
1302 if ( *ii == nextid ) ++nextid;
1303 }
1304
1305 if ( ii == g_ids.end() )
1306 id_space_empty = true;
1307
1308 while(!f.eof()) {
1309
1310 if (ParsePgnGame(f,filename,true,nextid)) {
1311
1312 ++count;
1313 ++nextid;
1314 if (!id_space_empty) {
1315 for( ; ii != g_ids.end(); ii++) {
1316 if ( *ii > nextid ) break;
1317 if ( *ii == nextid ) ++nextid;
1318 }
1319 if ( ii == g_ids.end() ) id_space_empty = true;
1320 }
1321
1322 } else
1323 break;
1324
1325 if (! (count%5) ) pw->setProgress(count);
1326 }
1327
1328 f.close();
1329 g_ids.clear();
1330 delete pw;
1331 }
1332
getResult()1333 GameResult ChessGame::getResult() {
1334 return(over?result:UNDEF);
1335 }
1336
variantFromName(const char * p)1337 variant ChessGame::variantFromName(const char *p) {
1338 variant v=REGULAR;
1339 variant allv[10] = {CRAZYHOUSE, BUGHOUSE, WILD, SUICIDE, LOSERS,
1340 GIVEAWAY, ATOMIC, WILDFR, WILDCASTLE, WILDNOCASTLE};
1341 int i;
1342
1343 for(i=0;i<10;i++)
1344 if (!strcmp(p,variantName(allv[i])))
1345 v = allv[i];
1346
1347 return v;
1348 }
1349
variantName(variant v)1350 const char * ChessGame::variantName(variant v) {
1351 switch(v) {
1352 case REGULAR: return("chess");
1353 case CRAZYHOUSE: return("crazyhouse");
1354 case SUICIDE: return("suicide");
1355 case BUGHOUSE: return("bughouse");
1356 case WILD: return("wild");
1357 case LOSERS: return("losers");
1358 case GIVEAWAY: return("giveaway");
1359 case ATOMIC: return("atomic");
1360 case WILDFR: return("fischerandom");
1361 case WILDCASTLE: return("wildcastle");
1362 case WILDNOCASTLE: return("nocastle");
1363 default: return("unknown");
1364 }
1365 }
1366
getEndReason()1367 char *ChessGame::getEndReason() {
1368 if (strlen(ereason)>2)
1369 return(ereason);
1370 else
1371 return 0;
1372 }
1373
1374 // --------------- PGN classes
1375
PGNpair()1376 PGNpair::PGNpair() {
1377 name="none";
1378 value="empty";
1379 }
1380
PGNpair(const char * n,const char * v)1381 PGNpair::PGNpair(const char *n, const char *v) {
1382 name=n;
1383 value=v;
1384 }
1385
PGNpair(const char * n,string & v)1386 PGNpair::PGNpair(const char *n, string &v) {
1387 name=n;
1388 value=v;
1389 }
1390
~PGNheader()1391 PGNheader::~PGNheader() {
1392 header.clear();
1393 }
1394
set(const char * n,const char * v)1395 void PGNheader::set(const char *n,const char *v) {
1396 vector<PGNpair *>::iterator li;
1397 for(li=header.begin();li!=header.end();li++)
1398 if ( (*li)->name == n ) {
1399 (*li)->value = v;
1400 return;
1401 }
1402 header.push_back(new PGNpair(n,v));
1403 }
1404
set(const char * n,string & v)1405 void PGNheader::set(const char *n,string &v) {
1406 vector<PGNpair *>::iterator li;
1407 for(li=header.begin();li!=header.end();li++)
1408 if ( (*li)->name == n ) {
1409 (*li)->value = v;
1410 return;
1411 }
1412 header.push_back(new PGNpair(n,v));
1413 }
1414
setIfAbsent(const char * n,const char * v)1415 void PGNheader::setIfAbsent(const char *n,const char *v) {
1416 vector<PGNpair *>::iterator li;
1417 for(li=header.begin();li!=header.end();li++)
1418 if ( (*li)->name == n )
1419 return;
1420 header.push_back(new PGNpair(n,v));
1421 }
1422
remove(const char * n)1423 void PGNheader::remove(const char *n) {
1424 vector<PGNpair *>::iterator li;
1425 for(li=header.begin();li!=header.end();li++)
1426 if ( (*li)->name == n ) {
1427 delete(*li);
1428 header.erase(li);
1429 break;
1430 }
1431 }
1432
empty()1433 int PGNheader::empty() {
1434 return(header.empty());
1435 }
1436
size()1437 int PGNheader::size() {
1438 return(header.size());
1439 }
1440
get(int index)1441 PGNpair * PGNheader::get(int index) {
1442 return(header[index]);
1443 }
1444
get(const char * n)1445 const char * PGNheader::get(const char *n) {
1446 vector<PGNpair *>::iterator li;
1447
1448 for(li=header.begin();li!=header.end();li++) {
1449 if ( (*li)->name == n )
1450 return((*li)->value.c_str());
1451 }
1452 return 0;
1453 }
1454
1455 // -- PGN info edit dialog ------
1456
PGNEditInfoDialog(ChessGame * src)1457 PGNEditInfoDialog::PGNEditInfoDialog(ChessGame *src) :
1458 ModalDialog(N_("PGN Headers"))
1459 {
1460 GtkWidget *v,*sw,*hb,*hb2,*l[2],*setb,*hs,*bb,*closeb;
1461 obj=src;
1462
1463 gtk_window_set_default_size(GTK_WINDOW(widget),270,300);
1464 v=gtk_vbox_new(FALSE,4);
1465 gtk_container_add(GTK_CONTAINER(widget),v);
1466
1467 sw=gtk_scrolled_window_new(0,0);
1468 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
1469 GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
1470
1471 clist=gtk_clist_new(2);
1472 gtk_widget_set_style(clist, gtk_widget_get_default_style() );
1473 gtk_clist_set_column_min_width(GTK_CLIST(clist),0,64);
1474 gtk_clist_set_column_min_width(GTK_CLIST(clist),1,64);
1475 gtk_clist_set_shadow_type(GTK_CLIST(clist),GTK_SHADOW_IN);
1476 gtk_clist_set_selection_mode(GTK_CLIST(clist),GTK_SELECTION_SINGLE);
1477 gtk_clist_set_column_title(GTK_CLIST(clist),0,_("Key"));
1478 gtk_clist_set_column_title(GTK_CLIST(clist),1,_("Value"));
1479 gtk_clist_column_titles_passive(GTK_CLIST(clist));
1480 gtk_clist_column_titles_show(GTK_CLIST(clist));
1481
1482 gtk_box_pack_start(GTK_BOX(v),sw,TRUE,TRUE,0);
1483 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
1484 GTK_POLICY_AUTOMATIC,
1485 GTK_POLICY_AUTOMATIC);
1486 gtk_container_add(GTK_CONTAINER(sw),clist);
1487
1488 hb=gtk_hbox_new(FALSE,0);
1489 del=gtk_button_new_with_label(_(" Remove Field "));
1490 gtk_box_pack_end(GTK_BOX(hb),del,FALSE,FALSE,0);
1491 gtk_box_pack_start(GTK_BOX(v),hb,FALSE,FALSE,4);
1492
1493 hb2=gtk_hbox_new(FALSE,0);
1494 l[0]=gtk_label_new(_("Key:"));
1495 l[1]=gtk_label_new(_("Value:"));
1496 en[0]=gtk_entry_new();
1497 en[1]=gtk_entry_new();
1498
1499 setb=gtk_button_new_with_label(_(" Set "));
1500
1501 gtk_box_pack_start(GTK_BOX(hb2),l[0],FALSE,FALSE,4);
1502 gtk_box_pack_start(GTK_BOX(hb2),en[0],TRUE,TRUE,4);
1503 gtk_box_pack_start(GTK_BOX(hb2),l[1],FALSE,FALSE,4);
1504 gtk_box_pack_start(GTK_BOX(hb2),en[1],TRUE,TRUE,4);
1505 gtk_box_pack_start(GTK_BOX(hb2),setb,FALSE,FALSE,4);
1506 gtk_box_pack_start(GTK_BOX(v),hb2,FALSE,FALSE,4);
1507
1508 hs=gtk_hseparator_new();
1509 gtk_box_pack_start(GTK_BOX(v),hs,FALSE,FALSE,4);
1510
1511 bb=gtk_hbutton_box_new();
1512 gtk_button_box_set_layout(GTK_BUTTON_BOX(bb), GTK_BUTTONBOX_END);
1513 gtk_button_box_set_spacing(GTK_BUTTON_BOX(bb), 5);
1514 gtk_box_pack_start(GTK_BOX(v),bb,FALSE,FALSE,2);
1515
1516 closeb=gtk_button_new_with_label(_("Close"));
1517 GTK_WIDGET_SET_FLAGS(closeb,GTK_CAN_DEFAULT);
1518 gtk_box_pack_start(GTK_BOX(bb),closeb,TRUE,TRUE,0);
1519 gtk_widget_grab_default(closeb);
1520
1521 Gtk::show(closeb,bb,hs,l[0],l[1],en[0],en[1],setb,hb2,
1522 hb,del,sw,clist,v,NULL);
1523 setDismiss(GTK_OBJECT(closeb),"clicked");
1524
1525 populate();
1526
1527 gtk_widget_set_sensitive(del,FALSE);
1528 Selection=-1;
1529
1530 gtk_signal_connect(GTK_OBJECT(setb),"clicked",
1531 GTK_SIGNAL_FUNC(pgnedit_set),(gpointer)this);
1532 gtk_signal_connect(GTK_OBJECT(del),"clicked",
1533 GTK_SIGNAL_FUNC(pgnedit_del),(gpointer)this);
1534 gtk_signal_connect(GTK_OBJECT(clist),"select_row",
1535 GTK_SIGNAL_FUNC(pgnedit_rowsel),(gpointer)this);
1536 gtk_signal_connect(GTK_OBJECT(clist),"unselect_row",
1537 GTK_SIGNAL_FUNC(pgnedit_rowunsel),(gpointer)this);
1538 }
1539
populate()1540 void PGNEditInfoDialog::populate() {
1541 int i,j;
1542 const char *p[2];
1543 PGNpair *pp;
1544
1545 gtk_clist_freeze(GTK_CLIST(clist));
1546 gtk_clist_clear(GTK_CLIST(clist));
1547
1548 j=obj->pgn.size();
1549 for(i=0;i<j;i++) {
1550 pp=obj->pgn.get(i);
1551 p[0]=pp->name.c_str();
1552 p[1]=pp->value.c_str();
1553 gtk_clist_append(GTK_CLIST(clist),(gchar **)p);
1554 }
1555 gtk_clist_thaw(GTK_CLIST(clist));
1556 Selection=-1;
1557 gtk_widget_set_sensitive(del,FALSE);
1558 }
1559
pgnedit_set(GtkWidget * w,gpointer data)1560 void pgnedit_set(GtkWidget *w, gpointer data) {
1561 PGNEditInfoDialog *me;
1562 me=(PGNEditInfoDialog *)data;
1563 char a[64],b[64];
1564 g_strlcpy(a,gtk_entry_get_text(GTK_ENTRY(me->en[0])),64);
1565 g_strlcpy(b,gtk_entry_get_text(GTK_ENTRY(me->en[1])),64);
1566 if (strlen(a) && strlen(b)) {
1567 me->obj->pgn.set((const char *)a,b);
1568 me->populate();
1569 }
1570 }
1571
pgnedit_del(GtkWidget * w,gpointer data)1572 void pgnedit_del(GtkWidget *w, gpointer data) {
1573 PGNEditInfoDialog *me;
1574 const char *k;
1575 me=(PGNEditInfoDialog *)data;
1576 if (me->Selection >= 0) {
1577 k=(me->obj->pgn.get(me->Selection))->name.c_str();
1578 me->obj->pgn.remove(k);
1579 me->populate();
1580 }
1581 }
1582
pgnedit_rowsel(GtkCList * w,gint row,gint col,GdkEventButton * eb,gpointer data)1583 void pgnedit_rowsel(GtkCList *w, gint row, gint col,
1584 GdkEventButton *eb,gpointer data) {
1585 PGNEditInfoDialog *me;
1586 PGNpair *pp;
1587 me=(PGNEditInfoDialog *)data;
1588 me->Selection=row;
1589 gtk_widget_set_sensitive(me->del,TRUE);
1590
1591 pp=me->obj->pgn.get(row);
1592 if (pp) {
1593 gtk_entry_set_text(GTK_ENTRY(me->en[0]),pp->name.c_str());
1594 gtk_entry_set_text(GTK_ENTRY(me->en[1]),pp->value.c_str());
1595 }
1596 }
1597
pgnedit_rowunsel(GtkCList * w,gint row,gint col,GdkEventButton * eb,gpointer data)1598 void pgnedit_rowunsel(GtkCList *w, gint row, gint col,
1599 GdkEventButton *eb,gpointer data) {
1600 PGNEditInfoDialog *me;
1601 me=(PGNEditInfoDialog *)data;
1602 me->Selection=-1;
1603 gtk_widget_set_sensitive(me->del,FALSE);
1604 }
1605
initGlyphs()1606 void ChessGame::initGlyphs() {
1607 if (GlyphsInited)
1608 return;
1609
1610 EboardFileFinder eff;
1611 string nag, nagpath;
1612 char line[512];
1613 int i;
1614
1615 // fix to look for NLS versions
1616 nag = "NAG.en.txt";
1617
1618 if (!eff.find(nag, nagpath)) {
1619 failGlyphs();
1620 return;
1621 }
1622
1623 ifstream nagf(nagpath.c_str());
1624
1625 if (!nagf) {
1626 failGlyphs();
1627 return;
1628 }
1629
1630 memset(line,0,512);
1631 while(nagf.getline(line,511,'\n')) {
1632 if (!strlen(line))
1633 break;
1634 Glyphs.push_back(new string(line));
1635 }
1636
1637 nagf.close();
1638 GlyphsInited = true;
1639 }
1640
failGlyphs()1641 void ChessGame::failGlyphs() {
1642 int i;
1643 string *oops;
1644 oops = new string("PGN NAG file missing");
1645 for(i=0;i<140;i++)
1646 Glyphs.push_back(oops);
1647 GlyphsInited = true;
1648 }
1649