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