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 <stdlib.h>
27 #include <string.h>
28 #include <gtk/gtk.h>
29 
30 #include "config.h"
31 
32 #ifdef HAVE_STRINGS_H
33 #include <strings.h>
34 #endif
35 
36 #include <ctype.h>
37 #include "protocol.h"
38 #include "global.h"
39 #include "chess.h"
40 #include "status.h"
41 #include "seekgraph.h"
42 #include "eboard.h"
43 
44 // <b1> game 45 white [PPPPPNBBR] black [PPNN]
45 
FicsProtocol()46 FicsProtocol::FicsProtocol() {
47   SetPat *sp;
48 
49   global.debug("FicsProtocol","FicsProtocol");
50 
51   GActions.push_back(new string("All Observers"));
52   GActions.push_back(new string("Game Info"));
53 
54   PActions.push_back(new string("Finger"));
55   PActions.push_back(new string("Stats"));
56   PActions.push_back(new string("History"));
57   PActions.push_back(new string("Ping"));
58   PActions.push_back(new string("Log"));
59   PActions.push_back(new string("Variables"));
60 
61   /* parsing state */
62 
63   LinesReceived      = 0;
64 
65   AuthGone           = false;
66   InitSent           = false;
67   LecBotSpecial      = false;
68   LastWasStyle12     = false;
69   LastWasPrompt      = false;
70   LastColor          = global.Colors.TextBright;
71   LastChannel        = -1;
72   UseMsecs           = false;
73 
74   PendingStatus      = 0;
75   RetrievingMoves    = 0;
76   RetrievingGameList = false;
77   RetrievingAdList   = false;
78 
79   lastGLC=0;
80   lastALC=0;
81 
82   PartnerGame = -1;
83   UserPlayingWithWhite = true;
84 
85   xxnext=0;
86   xxwhen=0;
87   xxplayer[0][0]=0;
88   xxplayer[1][0]=0;
89   xxrating[0][0]=0;
90   xxrating[1][0]=0;
91 
92   Login.set   ("*login:*");
93   Password.set("*password:*");
94   Prompt.set("*ics%%*");
95 
96   WelcomeMessage.append(new ExactStringPat("**** Starting "));
97   WelcomeMessage.append(new KleeneStarPat());
98   WelcomeMessage.append(new ExactStringPat("session as "));
99   WelcomeMessage.append(sp=new PercSSetPat());
100   WelcomeMessage.append(new KleeneStarPat());
101 
102   CreateGame0.set("Creating: %S (*) %S (*)*");
103   CreateGame.set("{Game %n (%S vs. %S) Creating %s %r *}*");
104   ContinueGame.set("{Game %n (%S vs. %S) Continuing %s %r *}*");
105 
106   BeginObserve.set("You are now observing game %n.");
107   EndObserve.set("*Removing game %n from observation list*");
108   ObsInfoLine.set("Game %n: %S (*) %S (*) %s %r *");
109 
110   EndExamine.set("You are no longer examining game %n*");
111   MsSet.set("ms set.*");
112 
113   Style12.set("<12> *");
114   Style12House.set("<b1> game %n white [*] black [*]*");
115   /* As of 2007 FICS is sending this before moves even in crazyhouse games */
116   Style12BadHouse.set("<b1> game %n white [*] black [*]%b<-*");
117 
118   WhiteWon.set    ("{Game %n (%S vs. %S) *} 1-0*");
119   BlackWon.set    ("{Game %n (%S vs. %S) *} 0-1*");
120   Drawn.set       ("{Game %n (%S vs. %S) *} 1/2-1/2*");
121   Interrupted.set ("{Game %n (%S vs. %S) *} %**");
122 
123   DrawOffer.set("%S offers you a draw.*");
124 
125   PrivateTell.set ("%A tells you:*");
126   //Index of new news items
127   News.set("Index of new *news items:*");
128 
129   // Ns: 0=game 1=rat.white 2=rat.black
130   // Ss: 0=whitep 1=blackp
131   // *s: 1=game string
132   GameStatus.set("*%n%b%n%b%S%b%n%b%S%b[*]*");
133 
134   // 2 (Exam. 2413 Species     1234 Pulga     ) [ br  3   1] W:  1
135   // amazingly enough, the indexes are the same as in GameStatus
136   ExaGameStatus.set("*%n (Exam.%b%n %S%b%n %S%b)%b[*]*");
137 
138   GameListEntry.set("*%n *[*]*:*");
139   EndOfGameList.set("*%n games displayed.*");
140 
141   AdListEntry.set("*%n%b%n%b%r%b%n%b%n%b%r*");
142   EndOfAdList.set("*%n ad* displayed.*");
143 
144   //IceBox (1156) vs. damilasa (UNR) --- Sun Apr 22, 09:07 ??? 2001
145   MovesHeader.set("%S (*) vs. %S (*) --- *");
146   //Unrated blitz match, initial time: 2 minutes, increment: 12 seconds.
147   MovesHeader2.set("%s %r match, initial time: *");
148   //Move  IceBox             damilasa
149   MovesHeader3.set("Move%b%S%b%S*");
150   //----  ----------------   ----------------
151   MovesHeader4.set("----  ---*");
152   //  1.  d4      (0:00)     d5      (0:00)
153   // n0   r0      r1         r2      r3
154   MovesBody.set("*%n.%b%r%b%r%b%r%b%r*");
155   // same thing, line without black move
156   MovesBody2.set("*%n.%b%r%b%r*");
157   //      {Still in progress} *
158   MovesEnd.set("%b{*}*");
159 
160   Kibitz.set  ("%A*[%n]%bkibitzes:*");
161   Whisper.set ("%A*[%n]%bwhispers:*");
162   Say.set     ("%A[%n] says:*");
163   Say2.set     ("%A says:*");
164   ChannelTell.set("*(%n):*");
165   Seek.set("*(*) seeking*(*to respond)");
166   Shout.set ("%A shouts: *");
167   cShout.set ("%A c-shouts: *");
168   It.set ("-->*");
169   Notify.set("Notification:*");
170 
171   SgClear.set("<sc>*");
172   SgRemove.set("<sr>*");
173   SgRemove2.set("Ads removed:*");
174 
175   // <s> 8 w=visar ti=02 rt=2194  t=4 i=0 r=r tp=suicide c=? rr=0-9999 a=t f=f
176   //    n0   s0       r0    n1  *0  n2  n3  s1    r1       r2    r3      s2  s3
177   SgAdd.set("<s>%b%n%bw=%S%bti=%r%brt=%n*%bt=%n%bi=%n%br=%s%btp=%r%bc=%r%brr=%r%ba=%s%bf=%s*");
178   SgAdd2.set("%b<s>%b%n%bw=%S%bti=%r%brt=%n*%bt=%n%bi=%n%br=%s%btp=%r%bc=%r%brr=%r%ba=%s%bf=%s*");
179 
180   // bughouse stuff
181   PTell.set("%A (your partner) tells you: *");
182 
183   // sample output: Your partner is playing game 75 (aglup vs. cglup).
184   GotPartnerGame.set("Your partner is playing game %n (*");
185 
186   // challenge
187   Challenge.set("Challenge: *");
188 
189   // FICS LectureBot fake channel tells
190   LectureBotXTell.set(":LectureBot(TD)(67):*");
191   LectureBotXTellContinued.set(":   *");
192 
193   // AllOb parsing
194   AllObNone.set("No one is observing game %n.*");
195 
196   // Observing 18 [Fullmer vs. knighttour]: #boffo #HummerESS TieFighter (3 users)
197   // Observing 62 [VABORIS vs. Fudpucker]: BillJr(TM) CrouchingTiger(U) #Firefly
198   AllObFirstLine.set("Observing %n [*]: *");
199   AllObMidLine.set("\\*");
200   AllObState = 0;
201   memset(AllObAcc,0,1024);
202 
203   memset(style12table,0,256*sizeof(piece));
204   style12table['-']=EMPTY;
205   style12table['P']=PAWN|WHITE;
206   style12table['R']=ROOK|WHITE;
207   style12table['N']=KNIGHT|WHITE;
208   style12table['B']=BISHOP|WHITE;
209   style12table['Q']=QUEEN|WHITE;
210   style12table['K']=KING|WHITE;
211   style12table['p']=PAWN|BLACK;
212   style12table['r']=ROOK|BLACK;
213   style12table['n']=KNIGHT|BLACK;
214   style12table['b']=BISHOP|BLACK;
215   style12table['q']=QUEEN|BLACK;
216   style12table['k']=KING|BLACK;
217 
218   Binder.add(&WelcomeMessage,&UserName,&Login,&Password,&Prompt,
219 	     &CreateGame,&CreateGame0,&ContinueGame,&BeginObserve,
220 	     &EndObserve,&NotObserve,&EndExamine,&GameStatus,
221 	     &ExaGameStatus,&GameListEntry,&EndOfGameList,
222 	     &AdListEntry,&EndOfAdList,&Style12,&Style12House,
223 	     &WhiteWon,&BlackWon,&Drawn,&Interrupted,&DrawOffer,
224 	     &PrivateTell,&News,&MovesHeader,&MovesHeader2,
225 	     &MovesHeader3,&MovesHeader4,&MovesBody,&MovesBody2,
226 	     &MovesEnd,NULL);
227   Binder.add(&Kibitz,&Whisper,&Say,&Shout,&cShout,&It,&ChannelTell,
228 	     &Seek,&Notify,&SgClear,&SgAdd,&SgAdd2,&SgRemove,
229 	     &PTell,&Say2,&Challenge,&LectureBotXTell,
230 	     &LectureBotXTellContinued,&GotPartnerGame,
231 	     &AllObNone,&AllObFirstLine,&AllObMidLine,
232 	     &Style12BadHouse,&ObsInfoLine,&SgRemove2,&MsSet,NULL);
233 }
234 
~FicsProtocol()235 FicsProtocol::~FicsProtocol() {
236   global.debug("FicsProtocol","~FicsProtocol");
237   clearMoveBuffer();
238 }
239 
finalize()240 void FicsProtocol::finalize() {
241   global.debug("FicsProtocol","finalize");
242   if (global.network)
243     global.network->writeLine("quit");
244   if (global.IcsSeekGraph && global.skgraph2)
245     global.skgraph2->clear();
246 }
247 
receiveString(char * netstring)248 void FicsProtocol::receiveString(char *netstring) {
249   ++LinesReceived;
250   parser1(netstring);
251 }
252 
253 /*
254    -- eats blank lines after style-12 lines
255    -- ensures initialization is sent on non-FICS servers
256    -- ensures password mode is off on non-FICS servers
257    -- removes the prompt from the line
258    -- ignore empty lines
259    -- calls the next parser
260 */
parser1(char * T)261 bool FicsProtocol::parser1(char *T) {
262   char pstring[1024];
263 
264   /* ignore blank lines from timeseal */
265   if (T[0]==0 && LastWasStyle12) {
266     LastWasStyle12 = false;
267     return true;
268   }
269 
270   LastWasStyle12 = false;
271   Binder.prepare(T);
272 
273   /* remove prompts */
274   while(Prompt.match()) {
275     /* avoid fake prompts in help files */
276     if (strlen(Prompt.getStarToken(0)) > 9)
277       break;
278 
279     /* send initialization to servers that don't tell us our name */
280     if (!InitSent) {
281       UserName.reset();
282       UserName.append(new CIExactStringPat("i n v a l i d"));
283       strcpy(nick,"i n v a l i d");
284       sendInitialization();
285       InitSent = true;
286     }
287 
288     /* exit auth mode on old ICS code */
289     if (!AuthGone) {
290       AuthGone = true;
291       global.input->setPasswordMode(0);
292     }
293 
294     /* remove prompt, ignore prompt-only lines */
295     if (strlen(Prompt.getStarToken(1))>3) {
296       memset(pstring,0,1024);
297       g_strlcpy(pstring,Prompt.getStarToken(1),1024);
298       T = pstring;
299       if (T[0]==' ') T++;
300       Binder.prepare(T);
301     } else {
302       LastWasPrompt = true;
303       return true;
304     }
305 
306   } /* while Prompt.match */
307 
308   /* ignore empty lines after prompts (??) */
309   if (T[0]==0 && LastWasPrompt)
310     return false;
311 
312   LastWasPrompt = false;
313 
314   /* call next parser */
315   return(parser2(T));
316 }
317 
318 
319 /*
320   -- check for login-time patterns, perform console output
321      and control password obfuscation
322   -- if login is gone, calls next parser
323 */
parser2(char * T)324 bool FicsProtocol::parser2(char *T) {
325 
326   if (!AuthGone) {
327     Binder.prepare(T);
328 
329     if (Password.match())
330       global.input->setPasswordMode(1);
331 
332     if (Login.match())
333       global.input->setPasswordMode(0);
334 
335     if (WelcomeMessage.match()) {
336       global.input->setPasswordMode(0);
337       AuthGone = true;
338       g_strlcpy(nick,WelcomeMessage.getToken(3),64);
339       UserName.reset();
340       UserName.append(new KleeneStarPat());
341       UserName.append(new CIExactStringPat(nick));
342       UserName.append(new KleeneStarPat());
343 
344       if (!InitSent) {
345 	sendInitialization();
346 	InitSent = true;
347       }
348     }
349     return(doOutput(T,-1,false,global.Colors.TextBright));
350   }
351 
352   return(parser3(T));
353 }
354 
355 /*
356   -- parse style-12 and zhouse stock lines, end with no output
357   -- parse seek lines, output them depending on user settings, end
358   -- parse seekinfo lines and update the seek table accordingly,
359      end with no output
360   -- call next parser
361 */
parser3(char * T)362 bool FicsProtocol::parser3(char *T) {
363 
364   Binder.prepare(T);
365 
366   /* style 12 strings are parsed, end with no output */
367   if (Style12.match()) {
368     parseStyle12(T);
369     LastWasStyle12 = true;
370     return true;
371   }
372 
373   /* bad (early) zhouse stock lines - ignore with no output unless
374      we are playing bughouse */
375   if (Style12BadHouse.match()) {
376     attachHouseStock(Style12BadHouse,true);
377     return true;
378   }
379 
380   /* zhouse stock line, parse, end with no output */
381   if (Style12House.match()) {
382     attachHouseStock(Style12House,false);
383     return true;
384   }
385 
386   /* human-readable seek line */
387   if (Seek.match()) {
388 
389     if (global.HideSeeks && global.IcsSeekGraph)
390       return true;
391 
392     return(doOutput(T, -1, false, global.Colors.Seeks));
393   }
394 
395   /* seek table/graph lines */
396   if (global.IcsSeekGraph) {
397 
398     if (SgClear.match()) {
399       ensureSeekGraph();
400       global.skgraph2->clear();
401       return true;
402     }
403 
404     if (SgAdd.match()) {
405       seekAdd(SgAdd);
406       return true;
407     }
408 
409     if (SgAdd2.match()) {
410       seekAdd(SgAdd2);
411       return true;
412     }
413 
414     if (SgRemove.match()) {
415       seekRemove(SgRemove);
416       return true;
417     }
418   } else {
419 
420     if (SgClear.match()) return true;
421     if (SgAdd.match()) return true;
422     if (SgAdd2.match()) return true;
423     if (SgRemove.match()) return true;
424 
425   }
426 
427   // Ads Removed: msg
428   if (SgRemove2.match()) return true;
429 
430   return(parser4(T));
431 }
432 
433 /*
434   -- treat "Ads on Server" retrieval, end with no output
435   -- treat  "Games on Server" retrieval, end with no output
436   -- treat move list retrieval, end with no output
437   -- call next parser
438 */
parser4(char * T)439 bool FicsProtocol::parser4(char *T) {
440 
441   Binder.prepare(T);
442 
443   /* ad list retrieval for Windows -> Ads on Server */
444   if (RetrievingAdList) {
445 
446     if (AdListEntry.match()) {
447       sendAdListEntry(T);
448       return true;
449     }
450 
451     if (EndOfAdList.match()) {
452       RetrievingAdList = false;
453       lastALC->endOfList();
454       lastALC = 0;
455       return true;
456     }
457   }
458 
459   /* game list retrieval for Windows -> Games on Server */
460   if (RetrievingGameList) {
461 
462     if (GameListEntry.match()) {
463       sendGameListEntry();
464       return true;
465     }
466 
467     if (EndOfGameList.match()) {
468       RetrievingGameList = false;
469       lastGLC->endOfList();
470       lastGLC = 0;
471       return true;
472     }
473 
474   }
475 
476   /* move list retrieval */
477   switch(RetrievingMoves) {
478   case 0:
479     break;
480   case 1:
481     if (MovesHeader.match()) {
482       ++RetrievingMoves;
483       g_strlcpy(xxplayer[xxnext], MovesHeader.getSToken(0), 64);
484       g_strlcpy(xxrating[xxnext], MovesHeader.getStarToken(0), 64);
485       xxnext=(++xxnext)%2;
486       g_strlcpy(xxplayer[xxnext], MovesHeader.getSToken(1), 64);
487       g_strlcpy(xxrating[xxnext], MovesHeader.getStarToken(1), 64);
488       xxnext=(++xxnext)%2;
489       xxwhen = LinesReceived;
490       return true;
491     }
492     break;
493   case 2:
494     if (MovesHeader2.match()) {
495       ++RetrievingMoves;
496       return true;
497     }
498     break;
499   case 3:
500     if (MovesHeader3.match()) {
501       ++RetrievingMoves;
502       return true;
503     }
504     break;
505   case 4:
506     if (MovesHeader4.match()) {
507       ++RetrievingMoves;
508       clearMoveBuffer();
509       return true;
510     }
511     break;
512   case 5:
513     if (MovesBody.match())  { retrieve1(MovesBody);  return true; }
514     if (MovesBody2.match()) { retrieve2(MovesBody2); return true; }
515     if (MovesEnd.match()) {
516       retrievef();
517 
518       if (MoveRetrievals.empty())
519 	RetrievingMoves = 0;
520       else
521 	RetrievingMoves = 1;
522 
523       return true;
524     }
525     break;
526 
527   } /* switch RetrievingMoves */
528 
529   return(parser5(T));
530 }
531 
532 /*
533   -- treat game creation (1), echo line to console, end
534   -- treat game creation (2), end with no output
535   -- treat adjournment resume, end with no output
536   -- treat observation/examination start/end, end with no output
537   -- treat game status retrieval, end with output
538   -- treat game information line (wild/fr, wild/[012] issue)
539   -- call next parser
540 */
parser5(char * T)541 bool FicsProtocol::parser5(char *T) {
542 
543   Binder.prepare(T);
544 
545   if (CreateGame0.match()) {
546     //printf("c0 match\n");
547     g_strlcpy(xxplayer[xxnext],CreateGame0.getSToken(0),64);
548     g_strlcpy(xxrating[xxnext],CreateGame0.getStarToken(0),64);
549     xxnext=(++xxnext)%2;
550     g_strlcpy(xxplayer[xxnext],CreateGame0.getSToken(1),64);
551     g_strlcpy(xxrating[xxnext],CreateGame0.getStarToken(1),64);
552     xxnext=(++xxnext)%2;
553     xxwhen = LinesReceived;
554     return(doOutput(T,-1,false,global.Colors.TextBright));
555   }
556 
557   if (CreateGame.match()) {
558     createGame(CreateGame);
559     return true;
560   }
561 
562   if (ContinueGame.match()) {
563     createGame(ContinueGame);
564     /* fics sends 2 moves lists (!?), we ignore one of them */
565     retrieveMoves(ContinueGame);
566     return true;
567   }
568 
569   if (BeginObserve.match()) {
570     addGame(BeginObserve);
571     return true;
572   }
573 
574   if (EndObserve.match()) {
575     int gn;
576     ChessGame *cg;
577     gn = atoi(EndObserve.getNToken(0));
578     cg = global.getGame(gn);
579     if (cg!=NULL)
580       if (! cg->isOver())
581 	removeGame(EndObserve); // if not over, user typed unob himself
582     return true;
583   }
584 
585   if (EndExamine.match()) {
586     removeGame(EndExamine);
587     return true;
588   }
589 
590   if (PendingStatus>0 && ExaGameStatus.match()) {
591     updateGame(ExaGameStatus);
592     --PendingStatus;
593     return(doOutput(T,-1,false,global.Colors.TextBright));
594   }
595 
596   if (PendingStatus>0 && GameStatus.match()) {
597     updateGame(GameStatus);
598     --PendingStatus;
599 
600   }
601 
602   if (ObsInfoLine.match()) {
603     int gm;
604     ChessGame *cg;
605     gm = atoi(ObsInfoLine.getNToken(0));
606     cg = global.getGame(gm);
607     if (cg!=NULL) {
608       char *p;
609       p = ObsInfoLine.getRToken(0);
610       if (!strcmp(p,"wild/fr")) {
611 	cg->Variant = WILDFR;
612       } else if (!strcmp(p,"wild/2")) {
613 	cg->Variant = WILDNOCASTLE;
614       } else if ((!strcmp(p,"wild/0"))||(!strcmp(p,"wild/1"))) {
615 	cg->Variant = WILDCASTLE;
616       }
617     }
618     return(doOutput(T,-1,false,global.Colors.TextBright));
619   }
620 
621   return(parser6(T));
622 }
623 
624 /*
625   -- treat game termination, end with output
626   -- call next parser
627 */
parser6(char * T)628 bool FicsProtocol::parser6(char *T) {
629   int tb = global.Colors.TextBright;
630 
631   Binder.prepare(T);
632 
633   if (WhiteWon.match()) {
634     gameOver(WhiteWon, WHITE_WIN);
635     return(doOutput(T,-1,false,tb));
636   }
637 
638   if (BlackWon.match()) {
639     gameOver(BlackWon, BLACK_WIN);
640     return(doOutput(T,-1,false,tb));
641   }
642 
643   if (Drawn.match()) {
644     gameOver(Drawn, DRAW);
645     return(doOutput(T,-1,false,tb));
646   }
647 
648   if (Interrupted.match()) {
649     gameOver(Interrupted, UNDEF);
650     return(doOutput(T,-1,false,tb));
651   }
652 
653   return(parser7(T));
654 }
655 
656 /*
657   -- treat start of partner game in bughouse, end with output
658   -- treat the hidden auto-allob feature
659   -- call the next parser
660 */
parser7(char * T)661 bool FicsProtocol::parser7(char *T) {
662 
663   Binder.prepare(T);
664 
665   if (GotPartnerGame.match()) {
666     prepareBugPane();
667     return(doOutput(T,-1,false,global.Colors.TextBright));
668   }
669 
670   if (!AllObReqs.empty() || AllObState!=0) {
671     switch(AllObState) {
672     case 0:
673       if (AllObNone.match()) {
674 	if (atoi(AllObNone.getNToken(0)) == AllObReqs.back()) {
675 	  snprintf(AllObAcc,1024,"Game %d: no observers.",AllObReqs.back());
676 	  AllObReqs.pop_back();
677 	  global.status->setText(AllObAcc,20);
678 	  return true;
679 	} else {
680 	  return(doOutput(T,-1,false,global.Colors.TextBright));
681 	}
682       }
683       if (AllObFirstLine.match())
684 	if (doAllOb1())
685 	  return true;
686 	else
687 	  return(doOutput(T,-1,false,global.Colors.TextBright));
688       break; /* case 0 */
689     case 1:
690       if (AllObMidLine.match()) {
691 	doAllOb2();
692 	return true;
693       }
694       break; /* case 1 */
695     case 2:
696       if (strstr(T, "user")!=0)
697 	return true;
698       if (T[0] == 0) {
699 	AllObState = 3;
700 	return true;
701       }
702     case 3:
703       if (strstr(T,"1 game displayed")!=0) {
704 	AllObState = 0;
705 	return true;
706       }
707       break;
708     }
709   } /* allob treatment */
710 
711   return(parser8(T));
712 }
713 
714 /*
715   -- colorize and output chat lines
716   -- treat ms ivar
717 */
parser8(char * T)718 bool FicsProtocol::parser8(char *T) {
719   int msgcolor  = global.Colors.TextDefault;
720   bool personal = false;
721   ExtPatternMatcher *tm=0;
722 
723   Binder.prepare(T);
724 
725   if (UserName.match()) {
726     msgcolor = global.Colors.PrivateTell;
727     personal = true;
728   }
729 
730   /* normal continuations */
731   if (T[0]=='\\')
732     return(doOutput(T,LastChannel,personal,personal?msgcolor:LastColor));
733 
734   /* lecturebot's stupid fake channel tells */
735   if (LecBotSpecial)
736     if (LectureBotXTellContinued.match())
737       return(doOutput(T,LastChannel,false,LastColor));
738     else
739       LecBotSpecial = false;
740 
741   if (Challenge.match()) global.challenged();
742 
743   if (PrivateTell.match()) tm = &PrivateTell;
744   if (Say.match())         tm = &Say;
745   if (Say2.match())        tm = &Say2;
746 
747   /* update chat mode command after a direct tell */
748   if (tm != 0) {
749     string x;
750     char xn[64], *xp;
751     x = "t ";
752     g_strlcpy(xn, tm->getAToken(0), 64);
753     xp = strchr(xn,'('); if (xp) *xp = 0;
754     xp = strchr(xn,'['); if (xp) *xp = 0;
755     x += xn;
756     global.ConsoleReply = x;
757     global.input->updatePrefix();
758     global.privatelyTold();
759   }
760 
761   if (UserName.match()) {
762     msgcolor = global.Colors.PrivateTell;
763     personal = true;
764 
765     if ( (!ChannelTell.match()) &&
766 	 (!LectureBotXTell.match()) )
767       return(doOutput(T,-1,personal,msgcolor));
768   }
769 
770   if (LectureBotXTell.match()) {
771     LecBotSpecial = true;
772     if (!personal) msgcolor = global.Colors.ChannelTell;
773     return(doOutput(T,67,personal,msgcolor));
774   }
775 
776   if (PTell.match()) {
777     global.privatelyTold();
778     global.bugpane->addBugText(PTell.getStarToken(0));
779     return(doOutput(T,-1,true,global.Colors.PrivateTell));
780   }
781 
782   if (PrivateTell.match() || Say.match() || Say2.match()) {
783     return(doOutput(T,-1,true,global.Colors.PrivateTell));
784   }
785 
786   if (DrawOffer.match()) {
787     global.drawOffered();
788     return(doOutput(T,-1,true,global.Colors.PrivateTell));
789   }
790 
791   if (News.match() || Notify.match())
792     return(doOutput(T,-1,false,global.Colors.NewsNotify));
793 
794   if (Shout.match() || cShout.match() || It.match())
795     return(doOutput(T,-1,false,global.Colors.Shouts));
796 
797   if (Kibitz.match() || Whisper.match())
798     return(doOutput(T,-1,false,global.Colors.KibitzWhisper));
799 
800   if (ChannelTell.match()) {
801     if (strchr(ChannelTell.getStarToken(0),' ')==0) {
802       int ch;
803       ch=atoi(ChannelTell.getNToken(0));
804       if (!personal) msgcolor = global.Colors.ChannelTell;
805       return(doOutput(T,ch,personal,msgcolor));
806     }
807   }
808 
809   if (T[0]==':')
810     return(doOutput(T,-1,personal,global.Colors.Mamer));
811 
812   if (MsSet.match()) {
813     UseMsecs = true;
814     return(doOutput(T,-1,personal,msgcolor));
815   }
816 
817   /* nothing matched, so much CPU for nothing ;-) */
818 
819   return(doOutput(T,-1,personal,msgcolor));
820 }
821 
doOutput(char * msg,int channel,bool personal,int msgcolor)822 bool FicsProtocol::doOutput(char *msg, int channel,
823 			    bool personal, int msgcolor)
824 {
825   LastChannel = channel;
826   LastColor   = msgcolor;
827 
828   if (channel<0 || !global.SplitChannels) {
829     global.output->append(msg, msgcolor,
830 			  (personal)?IM_PERSONAL:IM_NORMAL);
831     return true;
832   }
833 
834   global.appendToChannel(channel, msg, msgcolor,
835 			 (personal)?IM_PERSONAL:IM_NORMAL);
836 
837   if (global.ChannelsToConsoleToo)
838     global.output->append(msg, msgcolor,
839 			  (personal)?IM_PERSONAL:IM_NORMAL);
840 
841   return true;
842 }
843 
createExaminedGame(int gameid,char * whitep,char * blackp)844 void FicsProtocol::createExaminedGame(int gameid,char *whitep,char *blackp) {
845   ChessGame *cg;
846   Board *b;
847   char z[64];
848 
849   global.debug("FicsProtocol","createExaminedGame");
850 
851   cg=new ChessGame();
852   cg->clock_regressive=1;
853   cg->GameNumber=gameid;
854   cg->StopClock=1;
855   cg->source = GS_ICS;
856   g_strlcpy(cg->PlayerName[0],whitep,64);
857   g_strlcpy(cg->PlayerName[1],blackp,64);
858   cg->MyColor=WHITE|BLACK;
859   cg->protodata[0]=258;
860 
861   global.prependGame(cg);
862   b=new Board();
863   b->reset();
864   cg->setBoard(b);
865   b->setGame(cg);
866   b->setCanMove(true);
867   b->setSensitive(true);
868 
869   snprintf(z,64,_("Exam.Game #%d"),cg->GameNumber);
870   global.ebook->addPage(b->widget,z,cg->GameNumber);
871   b->setNotebook(global.ebook,cg->GameNumber);
872 
873   if (global.PopupSecondaryGames) {
874     b->pop();
875     b->repaint();
876   }
877 
878   cg->acknowledgeInfo();
879 
880   snprintf(z,64,"game %d",cg->GameNumber);
881   global.network->writeLine(z);
882   PendingStatus++;
883 }
884 
sendInitialization()885 void FicsProtocol::sendInitialization() {
886   char z[128];
887   if (!InitSent) {
888 
889     snprintf(z,128,"set interface eboard %s/%s\niset startpos 1\niset defprompt 1\niset ms 1",
890 	     global.Version,global.SystemType);
891     global.network->writeLine(z);
892     if (global.Premove)
893       global.network->writeLine("iset premove 1\n");
894     else
895       global.network->writeLine("iset premove 0\n");
896     if (global.IcsSeekGraph)
897       global.network->writeLine("iset seekinfo 1\niset seekremove 1");
898     else
899       global.network->writeLine("iset seekremove 1");
900     global.network->writeLine("iset lock 1\nstyle 12");
901     InitSent = true;
902   }
903 }
904 
905 // creates a non-moveable, non-editable position for sposition
createScratchPos(int gameid,char * whitep,char * blackp)906 void FicsProtocol::createScratchPos(int gameid,char *whitep,char *blackp)
907 {
908   ChessGame *cg;
909   Board *b;
910   char z[64];
911 
912   global.debug("FicsProtocol","createScratchPos");
913 
914   cg=new ChessGame();
915   cg->clock_regressive=1;
916   cg->GameNumber=gameid;
917   cg->StopClock=1;
918   cg->source=GS_ICS;
919   g_strlcpy(cg->PlayerName[0],whitep,64);
920   g_strlcpy(cg->PlayerName[1],blackp,64);
921   cg->MyColor=WHITE|BLACK;
922   cg->protodata[0]=259;
923 
924   global.prependGame(cg);
925   b=new Board();
926   b->reset();
927   cg->setBoard(b);
928   b->setGame(cg);
929   b->setCanMove(false);
930   b->setSensitive(false);
931 
932   snprintf(z,64,_("Pos: %s vs. %s"),whitep,blackp);
933   global.ebook->addPage(b->widget,z,cg->GameNumber);
934   b->setNotebook(global.ebook,cg->GameNumber);
935 
936   cg->acknowledgeInfo();
937 }
938 
createGame(ExtPatternMatcher & pm)939 void FicsProtocol::createGame(ExtPatternMatcher &pm) {
940   ChessGame *cg,*rep;
941   char *p;
942 
943   global.debug("FicsProtocol","createGame");
944 
945   // sanity check to ignore gin lines
946   {
947     char p1[64],p2[64];
948 
949     if (LinesReceived - xxwhen > 4) {
950       //printf("gin ignore 1, LR=%d xxwhen=%d\n",LinesReceived,xxwhen);
951       return;
952     }
953     memset(p1,0,64);
954     memset(p2,0,64);
955     g_strlcpy(p1,pm.getSToken(0),63);
956     g_strlcpy(p2,pm.getSToken(1),63);
957 
958     if (strcmp(p1,xxplayer[0])!=0 || strcmp(p2,xxplayer[1])!=0) {
959       //printf("gin ignore 2\n");
960       return;
961     }
962   }
963 
964   cg=new ChessGame();
965 
966   cg->source=GS_ICS;
967   cg->clock_regressive=1;
968   cg->GameNumber=atoi(pm.getNToken(0));
969   g_strlcpy(cg->PlayerName[0],pm.getSToken(0),64);
970   g_strlcpy(cg->PlayerName[1],pm.getSToken(1),64);
971 
972   // FICS sends two "Creating" strings when resuming an adjourned
973   // game. Weird.
974   rep=global.getGame(cg->GameNumber);
975   if (rep) {
976     if ( (!strcmp(cg->PlayerName[0],rep->PlayerName[0]))&&
977 	 (!strcmp(cg->PlayerName[1],rep->PlayerName[1]))&&
978 	 (rep->isFresh()) ) {
979       delete cg;
980       return;
981     }
982   }
983 
984   p=knowRating(cg->PlayerName[0]);
985   if (p) g_strlcpy(cg->Rating[0],p,32);
986   p=knowRating(cg->PlayerName[1]);
987   if (p) g_strlcpy(cg->Rating[1],p,32);
988   clearRatingBuffer();
989 
990   if (!strncmp(cg->PlayerName[0],nick,strlen(cg->PlayerName[0]))) {
991     cg->MyColor=WHITE;
992     UserPlayingWithWhite = true;
993   }
994   if (!strncmp(cg->PlayerName[1],nick,strlen(cg->PlayerName[1]))) {
995     cg->MyColor=BLACK;
996     UserPlayingWithWhite = false;
997   }
998 
999   if (!strcmp(pm.getSToken(2),"rated"))
1000     cg->Rated=1;
1001   else
1002     cg->Rated=0;
1003 
1004   cg->Variant=REGULAR;
1005   if (!strcmp(pm.getRToken(0),"crazyhouse"))    cg->Variant=CRAZYHOUSE;
1006   else if (!strcmp(pm.getRToken(0),"bughouse")) cg->Variant=BUGHOUSE;
1007   else if (!strcmp(pm.getRToken(0),"suicide"))  cg->Variant=SUICIDE;
1008   else if (!strcmp(pm.getRToken(0),"losers"))   cg->Variant=LOSERS;
1009   else if (!strcmp(pm.getRToken(0),"atomic"))   cg->Variant=ATOMIC;
1010   else if (!strncmp(pm.getRToken(0),"wild",4)) {
1011     cg->Variant=WILD;
1012     if (!strncmp(pm.getRToken(0),"wild/0",6))  cg->Variant=WILDCASTLE;
1013     if (!strncmp(pm.getRToken(0),"wild/1",6))  cg->Variant=WILDCASTLE;
1014     if (!strncmp(pm.getRToken(0),"wild/2",6))  cg->Variant=WILDNOCASTLE;
1015     if (!strncmp(pm.getRToken(0),"wild/fr",7)) cg->Variant=WILDFR;
1016   }
1017 
1018   global.prependGame(cg);
1019   global.BoardList.front()->reset();
1020 
1021   cg->setBoard(global.BoardList.front());
1022   global.BoardList.front()->setGame(cg);
1023   global.BoardList.front()->pop();
1024   global.BoardList.front()->setCanMove(true);
1025   global.BoardList.front()->repaint();
1026 
1027   cg->acknowledgeInfo();
1028   global.status->setText(_("Game started!"),10);
1029   global.gameStarted();
1030 }
1031 
addGame(ExtPatternMatcher & pm)1032 void FicsProtocol::addGame(ExtPatternMatcher &pm) {
1033   ChessGame *cg;
1034   Board *b;
1035   bool is_partner_game=false;
1036   char tab[96];
1037 
1038   global.debug("FicsProtocol","addGame");
1039 
1040   cg=new ChessGame();
1041 
1042   cg->clock_regressive=1;
1043   cg->GameNumber=atoi(pm.getNToken(0));
1044   cg->Rated=0;
1045   cg->Variant=REGULAR;
1046   cg->source = GS_ICS;
1047 
1048   global.prependGame(cg);
1049   b=new Board();
1050   b->reset();
1051   cg->setBoard(b);
1052   b->setGame(cg);
1053   b->setCanMove(false);
1054   b->setSensitive(false);
1055 
1056   if (cg->GameNumber == PartnerGame) {
1057     is_partner_game=true;
1058     PartnerGame = -1;
1059     cg->protodata[2] = 1;
1060   }
1061 
1062   snprintf(tab,96,_("Game #%d"),cg->GameNumber);
1063   global.ebook->addPage(b->widget,tab,cg->GameNumber);
1064   b->setNotebook(global.ebook,cg->GameNumber);
1065 
1066   if (!is_partner_game)
1067     if (global.PopupSecondaryGames) {
1068       b->pop();
1069       b->repaint();
1070     }
1071 
1072   cg->acknowledgeInfo();
1073 
1074   // to gather info about time, variant, player names...
1075   snprintf(tab,96,"game %d",cg->GameNumber);
1076   global.network->writeLine(tab);
1077   PendingStatus++;
1078 }
1079 
discardGame(int gameid)1080 void FicsProtocol::discardGame(int gameid) {
1081   ChessGame *cg;
1082   Board *b;
1083   char z[64];
1084 
1085   global.debug("FicsProtocol","discardGame");
1086 
1087   cg=global.getGame(gameid);
1088   if (!cg)
1089     return;
1090 
1091   switch(cg->protodata[0]) {
1092   case 257: // observed
1093     snprintf(z,64,"unobserve %d",cg->GameNumber);
1094     break;
1095   case 258: // examined
1096     strcpy(z,"unexamine");
1097     break;
1098   case 259: // isolated position
1099     z[0]=0;
1100     break;
1101   default:
1102     cerr << "protodata for game " << gameid << " is " << cg->protodata[0] << endl;
1103     return;
1104   }
1105 
1106   if (! cg->isOver())
1107     if (strlen(z))
1108       global.network->writeLine(z);
1109 
1110   b=cg->getBoard();
1111   if (b==0) return;
1112   global.ebook->removePage(cg->GameNumber);
1113   global.removeBoard(b);
1114   delete(b);
1115   cg->GameNumber=global.nextFreeGameId(10000);
1116   cg->setBoard(0);
1117 
1118 }
1119 
removeGame(ExtPatternMatcher & pm)1120 void FicsProtocol::removeGame(ExtPatternMatcher &pm) {
1121   int gm;
1122   global.debug("FicsProtocol","removeGame");
1123 
1124   gm=atoi(pm.getNToken(0));
1125 
1126   if (global.SmartDiscard && global.ebook->isNaughty(gm))
1127     return;
1128 
1129   innerRemoveGame(gm);
1130 }
1131 
innerRemoveGame(int gameid)1132 void FicsProtocol::innerRemoveGame(int gameid) {
1133   Board *b;
1134   ChessGame *cg;
1135 
1136   cg=global.getGame(gameid);
1137   if (cg==NULL)
1138     return;
1139 
1140   // bughouse partner game ?
1141   if (cg->protodata[2])
1142     global.bugpane->stopClock();
1143 
1144   b=cg->getBoard();
1145   if (b==NULL) return;
1146   global.ebook->removePage(gameid);
1147   global.removeBoard(b);
1148   delete(b);
1149   cg->GameNumber=global.nextFreeGameId(10000);
1150   cg->setBoard(0);
1151 }
1152 
sendDrop(piece p,int x,int y)1153 void FicsProtocol::sendDrop(piece p,int x,int y) {
1154   piece k;
1155   char drop[5];
1156 
1157   global.debug("FicsProtocol","sendDrop");
1158 
1159   k=p&PIECE_MASK;
1160   drop[4]=0;
1161   switch(k) {
1162   case PAWN:   drop[0]='P'; break;
1163   case ROOK:   drop[0]='R'; break;
1164   case KNIGHT: drop[0]='N'; break;
1165   case BISHOP: drop[0]='B'; break;
1166   case QUEEN:  drop[0]='Q'; break;
1167   default: return;
1168   }
1169   drop[1]='@';
1170   drop[2]='a'+x;
1171   drop[3]='1'+y;
1172   global.network->writeLine(drop);
1173 }
1174 
sendMove(int x1,int y1,int x2,int y2,int prom)1175 void FicsProtocol::sendMove(int x1,int y1,int x2,int y2,int prom) {
1176   char move[7];
1177   piece pp;
1178 
1179   global.debug("FicsProtocol","sendMove");
1180 
1181   move[4]=0;
1182   move[5]=0;
1183   move[6]=0;
1184   move[0]='a'+x1;
1185   move[1]='1'+y1;
1186   move[2]='a'+x2;
1187   move[3]='1'+y2;
1188 
1189   if (prom) {
1190     pp=global.promotion->getPiece();
1191     move[4]='=';
1192     switch(pp) {
1193     case QUEEN:   move[5]='Q'; break;
1194     case ROOK:    move[5]='R'; break;
1195     case BISHOP:  move[5]='B'; break;
1196     case KNIGHT:  move[5]='N'; break;
1197     case KING:    move[5]='K'; break;
1198     }
1199   }
1200   global.network->writeLine(move);
1201 }
1202 
1203 // attach stock info to bug/zhouse games
attachHouseStock(ExtPatternMatcher & pm,bool OnlyForBughouse)1204 void FicsProtocol::attachHouseStock(ExtPatternMatcher &pm, bool OnlyForBughouse) {
1205   ChessGame *cg;
1206   int gid;
1207   char *p;
1208   Position *pos;
1209 
1210   global.debug("FicsProtocol","attachHouseStock");
1211 
1212   // Style12House or Style12BadHouse
1213   //  <b1> game %n white [*] black [*]*
1214   //  <b1> game %n white [*] black [*]%b<-*
1215 
1216   gid=atoi(pm.getNToken(0));
1217 
1218   cg=global.getGame(gid);
1219   if (!cg)
1220     return;
1221 
1222   if (OnlyForBughouse && cg->Variant != BUGHOUSE)
1223     return;
1224 
1225   pos=&(cg->getLastPosition());
1226 
1227   pos->clearStock();
1228   p=pm.getStarToken(0);
1229 
1230   //  cerr << "[" << p << "]\n";
1231 
1232   for(;*p;p++)
1233     pos->incStockCount(style12table[*p]);
1234 
1235   p=pm.getStarToken(1);
1236 
1237   // cerr << "[" << p << "]\n";
1238 
1239   for(;*p;p++)
1240     pos->incStockCount(style12table[tolower(*p)]);
1241 
1242   cg->updateStock();
1243 }
1244 
parseStyle12(char * data)1245 void FicsProtocol::parseStyle12(char *data) {
1246   static const char *sep=" \t\n";
1247   tstring t;
1248   int i,j;
1249   string *sp;
1250   Position pos;
1251   int blacktomove;
1252   int game;
1253   int rel;
1254   int itime,inc,flip;
1255   int str[2],movenum,clk[2];
1256   ChessGame *refgame;
1257   char stra[64],strb[64],strc[64];
1258   char plyr[2][64];
1259   int ackp=0;
1260   int stopclock = -1; /* -1: keep as it is, 0: stop 1: moving */
1261   bool spawnExamination;
1262   bool someSound;
1263 
1264   Position prevpos;
1265 
1266   global.debug("FicsProtocol","parseStyle12");
1267 
1268   t.set(data);
1269   t.token(sep); // <12>
1270 
1271   // the position itself
1272   for(i=7;i>=0;i--) {
1273     sp=t.token(sep);
1274     for(j=0;j<8;j++)
1275       pos.setPiece(j,i,style12table[sp->at(j)]);
1276   }
1277 
1278   sp=t.token(sep); // B / W (color to move)
1279   blacktomove=(sp->at(0)=='B');
1280 
1281   pos.ep[0]=t.tokenvalue(sep); // double pawn push
1282   if (blacktomove)
1283     pos.ep[1]=2;
1284   else
1285     pos.ep[1]=5;
1286 
1287   // white castle short
1288   pos.maycastle[0]=t.tokenvalue(sep)?true:false;
1289 
1290   // white castle long
1291   pos.maycastle[2]=t.tokenvalue(sep)?true:false;
1292 
1293   // black castle short
1294   pos.maycastle[1]=t.tokenvalue(sep)?true:false;
1295 
1296   // black castle long
1297   pos.maycastle[3]=t.tokenvalue(sep)?true:false;
1298 
1299   t.token(sep); // mv count for draw
1300 
1301   game=t.tokenvalue(sep); // game number
1302 
1303   memset(plyr[0],0,64);
1304   memset(plyr[1],0,64);
1305   t.token(sep)->copy(plyr[0],63); // white's name
1306   t.token(sep)->copy(plyr[1],63); // black's name
1307 
1308   rel=t.tokenvalue(sep); // relationship
1309 
1310   switch(rel) {
1311   case 2: // I am the examiner of this game
1312     spawnExamination=false;
1313     refgame=global.getGame(game);
1314 
1315     if (refgame==NULL)
1316       spawnExamination=true;
1317     else // v_examine=1 on FICS: played game still on main board but inactive
1318       if ( (refgame->protodata[0]!=258) && (refgame->isOver()) )
1319 	spawnExamination=true;
1320 
1321     if (spawnExamination)
1322       createExaminedGame(game,plyr[0],plyr[1]);
1323     break;
1324 
1325   case -3: // isolated position (e.g.: sposition command)
1326     game=global.nextFreeGameId(10000);
1327     createScratchPos(game,plyr[0],plyr[1]);
1328     break;
1329   case -4: // initial position of wild game
1330     WildStartPos = pos;
1331     return;
1332   case -2: // observing examined game
1333   case -1: // I am playing, it's my opponent's turn
1334   case 1: // I am playing, it's my turn
1335   case 0: // live observation
1336     break;
1337   }
1338 
1339   itime=t.tokenvalue(sep); // initial time (minutes)
1340   inc=t.tokenvalue(sep);   // increment (secs)
1341 
1342   str[0]=t.tokenvalue(sep);  // white strength
1343   str[1]=t.tokenvalue(sep);  // black strength
1344 
1345   clk[0]=t.tokenvalue(sep);  // white rem time
1346   clk[1]=t.tokenvalue(sep);  // black rem time
1347 
1348   if (!UseMsecs) {
1349     clk[0] *= 1000;
1350     clk[1] *= 1000;
1351   }
1352 
1353   movenum=t.tokenvalue(sep); // move # to be made
1354 
1355   t.token(sep);               // verbose for previous move
1356 
1357   memset(stra,0,64);
1358   memset(strb,0,64);
1359   t.token(sep)->copy(stra,63); // time taken for previous move
1360   t.token(sep)->copy(strb,63); // pretty print for previous move
1361 
1362   flip=t.tokenvalue(sep);    // flip board ?
1363 
1364   t.setFail(-1);
1365   stopclock = t.tokenvalue(sep); // undocumented pause/unpause clock flag
1366   if (stopclock != -1)
1367     stopclock = 1-stopclock;
1368 
1369   refgame=global.getGame(game);
1370   if (refgame==NULL) {
1371     cerr << "no such game: " << game << endl;
1372     return;
1373   }
1374 
1375   switch(rel) {
1376   case  0: // observed
1377   case -2: // observing examined game
1378     refgame->protodata[0]=257;
1379     refgame->AmPlaying=false;
1380     break;
1381   case 1:
1382   case -1:
1383     refgame->protodata[0]=0;
1384     refgame->MyColor=(flip)?BLACK:WHITE;
1385     refgame->AmPlaying=true;
1386     if ((global.IcsSeekGraph)&&(global.skgraph2))
1387       global.skgraph2->clear();
1388     break;
1389   case 2: // examiner mode
1390     refgame->protodata[0]=258;
1391     refgame->setFree();
1392     refgame->AmPlaying=false;
1393     break;
1394   case -3: // isolated position
1395     refgame->protodata[0]=259;
1396     refgame->AmPlaying=false;
1397     break;
1398   }
1399 
1400   if (strcmp(refgame->PlayerName[0],plyr[0])) {
1401     strcpy(refgame->PlayerName[0],plyr[0]);
1402     ackp=1;
1403   }
1404 
1405   if (strcmp(refgame->PlayerName[1],plyr[1])) {
1406     strcpy(refgame->PlayerName[1],plyr[1]);
1407     ackp=1;
1408   }
1409 
1410   if ( (refgame->timecontrol.value[0] != itime*60)||(refgame->timecontrol.value[1] != inc) ) {
1411     refgame->timecontrol.setIcs(itime*60,inc);
1412     ackp=1;
1413   }
1414 
1415   if (ackp)
1416     refgame->acknowledgeInfo();
1417 
1418   g_strlcat(strb," ",64);
1419   g_strlcat(strb,stra,64);
1420   snprintf(strc,64,"%d.%s%s",
1421 	   (blacktomove?movenum:movenum-1),
1422 	   (blacktomove?" ":" ... "),
1423 	  strb);
1424   if (strc[0]=='0')
1425     strcpy(strc,"0. startpos");
1426 
1427   if ((rel==-1)&&(global.AnimateMoves))
1428     refgame->getBoard()->supressNextAnimation();
1429 
1430   if (rel==-2) // don't let it tick when observing examined games
1431     refgame->StopClock=1;
1432 
1433   someSound = (rel==1 || rel==0 || rel==2 || rel==-2) && (movenum!=1 || blacktomove);
1434 
1435   if (stopclock >= 0)
1436     refgame->StopClock = stopclock;
1437 
1438   prevpos = refgame->getLastPosition();
1439 
1440   refgame->updatePosition2(pos, (movenum-(blacktomove?0:1)), blacktomove,
1441 			   clk[0], clk[1], strc, someSound);
1442 
1443   /*
1444   if ( ((rel==-1)&&(blacktomove)) || ((rel==1)&&(!blacktomove)) ) {
1445     if (flip) cerr << "I'm white and <12> told me to flip!\n";
1446   }
1447 
1448   if ( ((rel==1)&&(blacktomove)) || ((rel==-1)&&(!blacktomove)) ) {
1449     if (!flip) cerr << "I'm black and <12> told me not to flip!\n";
1450   }
1451   */
1452 
1453   if (rel==1 && (movenum!=1 || blacktomove) )
1454     global.opponentMoved();
1455 
1456   if (rel==0 || rel==-2 || rel==2 && (movenum!=1 || blacktomove))
1457     if (prevpos != pos)
1458       global.moveMade();
1459 
1460   if (rel!=0) refgame->flipHint(flip);
1461   if (rel==1) refgame->enableMoving(true);
1462   if (rel==-1) refgame->enableMoving(false);
1463 
1464   if (global.IcsAllObPlayed && (rel==1 || rel==-1) )
1465     if (refgame->protodata[3] == 0) {
1466       refgame->protodata[3] = gtk_timeout_add(30*1000, fics_allob, (void *) refgame);
1467       fics_allob((gpointer) refgame);
1468     }
1469 
1470   if (global.IcsAllObObserved && (rel==0) )
1471     if (refgame->protodata[3] == 0) {
1472       refgame->protodata[3] = gtk_timeout_add(30*1000, fics_allob, (void *) refgame);
1473       fics_allob((gpointer) refgame);
1474     }
1475 
1476 }
1477 
updateGame(ExtPatternMatcher & pm)1478 void FicsProtocol::updateGame(ExtPatternMatcher &pm) {
1479   ChessGame *refgame;
1480   char *p,z[32];
1481   int gm;
1482 
1483   global.debug("FicsProtocol","updateGame");
1484 
1485   // Ns: 0=game 1=rat.white 2=rat.black
1486   // Ss: 0=whitep 1=blackp
1487   // *s: 1=game string
1488   //  GameStatus.set("*%n%b%n%b%S%b%n%b%S%b[*]*");
1489   //  ExaGameStatus.set("*%n (Exam.%b%n %S%b%n %S%b)%b[*]*");
1490 
1491   gm=atoi(pm.getNToken(0));
1492 
1493   refgame=global.getGame(gm);
1494   if (refgame==NULL) {
1495     PendingStatus++;
1496     return;
1497   }
1498 
1499   g_strlcpy(refgame->PlayerName[0],pm.getSToken(0),64);
1500   g_strlcpy(refgame->PlayerName[1],pm.getSToken(1),64);
1501 
1502   p=pm.getStarToken(1);
1503 
1504   switch(p[1]) {
1505   case 's':
1506   case 'b':
1507   case 'l':
1508     refgame->Variant=REGULAR;
1509     break;
1510   case 'z':
1511     refgame->Variant=CRAZYHOUSE;
1512     break;
1513   case 'S':
1514     refgame->Variant=SUICIDE;
1515     break;
1516   case 'w':
1517     if (IS_NOT_WILD(refgame->Variant)) refgame->Variant=WILD;
1518     break;
1519   case 'B':
1520     refgame->Variant=BUGHOUSE;
1521     break;
1522   case 'L':
1523     refgame->Variant=LOSERS;
1524     break;
1525   case 'x':
1526     refgame->Variant=ATOMIC;
1527     break;
1528   }
1529 
1530   refgame->Rated=(p[2]=='r');
1531 
1532   memset(z,0,8);
1533   memcpy(z,&p[3],3);
1534 
1535   refgame->timecontrol.setIcs(60*atoi(z),atoi(&p[7]));
1536   refgame->acknowledgeInfo();
1537 
1538   // bughouse partner game, set up names in the bugpane
1539   if (refgame->protodata[2]!=0) {
1540     global.bugpane->setPlayerNames(refgame->PlayerName[0],
1541 				   refgame->PlayerName[1]);
1542   }
1543 
1544   if (refgame->protodata[0]!=258) {
1545     MoveRetrievals.push_front(gm);
1546     snprintf(z,32,"moves %d",gm);
1547     global.network->writeLine(z);
1548     RetrievingMoves=1;
1549   }
1550 
1551 }
1552 
gameOver(ExtPatternMatcher & pm,GameResult result)1553 void FicsProtocol::gameOver(ExtPatternMatcher &pm, GameResult result) {
1554   int game;
1555   char reason[64];
1556   ChessGame *refgame;
1557 
1558   global.debug("FicsProtocol","gameOver");
1559 
1560   game=atoi(pm.getNToken(0));
1561   reason[63]=0;
1562   g_strlcpy(reason,pm.getStarToken(0),64);
1563 
1564   refgame=global.getGame(game);
1565   if (refgame==NULL) return;
1566 
1567   // allob timeout
1568   if (refgame->protodata[3]!=0) {
1569     gtk_timeout_remove(refgame->protodata[3]);
1570     refgame->protodata[3] = 0;
1571   }
1572 
1573   if (refgame->protodata[0]==0) {
1574     if ( (   UserPlayingWithWhite  && result==WHITE_WIN) ||
1575 	 ( (!UserPlayingWithWhite) && result==BLACK_WIN) )
1576       global.gameWon();
1577 
1578     if ( (   UserPlayingWithWhite  && result==BLACK_WIN) ||
1579 	 ( (!UserPlayingWithWhite) && result==WHITE_WIN) )
1580       global.gameLost();
1581   }
1582 
1583   if (refgame->protodata[0]==257) {
1584     global.gameFinished();
1585   }
1586 
1587   refgame->endGame(reason,result);
1588 
1589   if (((refgame->protodata[0]==0)&&(global.AppendPlayed))
1590       ||
1591       ((refgame->protodata[0]==257)&&(global.AppendObserved)))
1592     {
1593       if  (refgame->savePGN(global.AppendFile,true)) {
1594 	char z[128];
1595 	snprintf(z,128,_("Game appended to %s"),global.AppendFile);
1596 	global.status->setText(z,5);
1597       }
1598     }
1599 
1600   // refresh seek graph
1601   if ((global.IcsSeekGraph)&&(refgame->protodata[0]==0))
1602 	refreshSeeks(true);
1603 
1604   // remove board ?
1605   // if game relation is observation and user has the Smart Discard setting on:
1606   //  discard now if user is not looking to the board or schedule removal to
1607   //  next pane switch if user _is_ looking to the board
1608   if ((refgame->protodata[0]==257)&&(global.SmartDiscard)) {
1609     if ( global.ebook->getCurrentPageId() == refgame->GameNumber )
1610       global.ebook->setNaughty(refgame->GameNumber,true);
1611     else
1612       innerRemoveGame(refgame->GameNumber);
1613   }
1614 }
1615 
refreshSeeks(bool create)1616 void FicsProtocol::refreshSeeks(bool create) {
1617   if (create) ensureSeekGraph();
1618   if ((global.IcsSeekGraph)&&(global.skgraph2)) {
1619     global.skgraph2->clear();
1620     global.network->writeLine("iset seekinfo 1");
1621   }
1622 }
1623 
hasAuthenticationPrompts()1624 int  FicsProtocol::hasAuthenticationPrompts() {
1625   return 1;
1626 }
1627 
resign()1628 void FicsProtocol::resign() {
1629   global.network->writeLine("resign");
1630   global.status->setText(_("Resigned."),10);
1631 }
1632 
draw()1633 void FicsProtocol::draw() {
1634   global.network->writeLine("draw");
1635   global.status->setText(_("<Fics Protocol> draw request sent"),30);
1636 }
1637 
adjourn()1638 void FicsProtocol::adjourn() {
1639   global.network->writeLine("adjourn");
1640   global.status->setText(_("<Fics Protocol> adjourn request sent"),30);
1641 }
1642 
abort()1643 void FicsProtocol::abort() {
1644   global.network->writeLine("abort");
1645   global.status->setText(_("<Fics Protocol> abort request sent"),30);
1646 }
1647 
clearMoveBuffer()1648 void FicsProtocol::clearMoveBuffer() {
1649   global.debug("FicsProtocol","clearMoveBuffer");
1650   MoveBuf.clear();
1651 }
1652 
retrieve1(ExtPatternMatcher & pm)1653 void FicsProtocol::retrieve1(ExtPatternMatcher &pm) {
1654   char z[32],mv[32];
1655   char tmp[2][32];
1656   int itmp;
1657   Position p,q;
1658   int gm;
1659   ChessGame *cg;
1660   variant Vr=REGULAR;
1661 
1662   global.debug("FicsProtocol","retrieve1");
1663 
1664   gm=MoveRetrievals.back();
1665   cg=global.getGame(gm);
1666   if (cg!=NULL) Vr=cg->Variant;
1667 
1668   if (MoveBuf.empty()) {
1669     if (IS_WILD(Vr))
1670       MoveBuf.push_back(WildStartPos);
1671     else
1672       MoveBuf.push_back(Position());
1673   }
1674   p = MoveBuf.back();
1675 
1676 
1677   itmp=atoi(pm.getNToken(0));
1678   g_strlcpy(tmp[0],pm.getRToken(0),32);
1679   g_strlcpy(tmp[1],pm.getRToken(1),32);
1680   snprintf(z,32,"%.2d. %s %s",itmp,tmp[0],tmp[1]);
1681   g_strlcpy(mv,tmp[0],32);
1682 
1683   p.moveStdNotation(mv,WHITE,Vr);
1684   p.setLastMove(z);
1685 
1686   MoveBuf.push_back(p);
1687 
1688   q=p;
1689 
1690   itmp=atoi(pm.getNToken(0));
1691   g_strlcpy(tmp[0],pm.getRToken(2),32);
1692   g_strlcpy(tmp[1],pm.getRToken(3),32);
1693   snprintf(z,32,"%.2d. ... %s %s",itmp,tmp[0],tmp[1]);
1694   g_strlcpy(mv,tmp[0],32);
1695 
1696   q.moveStdNotation(mv,BLACK,Vr);
1697   q.setLastMove(z);
1698 
1699   MoveBuf.push_back(q);
1700 }
1701 
retrieve2(ExtPatternMatcher & pm)1702 void FicsProtocol::retrieve2(ExtPatternMatcher &pm) {
1703   Position p;
1704   char z[32],mv[32];
1705   char tmp[2][32];
1706   int itmp, gm;
1707   ChessGame *cg;
1708   variant Vr=REGULAR;
1709 
1710   global.debug("FicsProtocol","retrieve2");
1711 
1712   gm=MoveRetrievals.back();
1713   cg=global.getGame(gm);
1714   if (cg!=NULL) Vr=cg->Variant;
1715 
1716   if (MoveBuf.empty()) {
1717     if (IS_WILD(Vr))
1718       MoveBuf.push_back(WildStartPos);
1719     else
1720       MoveBuf.push_back(Position());
1721   }
1722   p = MoveBuf.back();
1723 
1724   itmp=atoi(pm.getNToken(0));
1725   g_strlcpy(tmp[0],pm.getRToken(0),32);
1726   g_strlcpy(tmp[1],pm.getRToken(1),32);
1727   snprintf(z,32,"%.2d. %s %s",itmp,tmp[0],tmp[1]);
1728   g_strlcpy(mv,tmp[0],32);
1729 
1730   p.moveStdNotation(mv,WHITE,Vr);
1731   p.setLastMove(z);
1732 
1733   MoveBuf.push_back(p);
1734 }
1735 
retrievef()1736 void FicsProtocol::retrievef() {
1737   int gm;
1738   ChessGame *cg;
1739   char *p;
1740 
1741   global.debug("FicsProtocol","retrievef");
1742 
1743   if (MoveRetrievals.empty())
1744     return;
1745 
1746   gm=MoveRetrievals.back();
1747 
1748   cg=global.getGame(gm);
1749   if (cg==NULL) {
1750     cerr << _("no such game: ") << gm << endl;
1751     return;
1752   }
1753   MoveRetrievals.pop_back();
1754 
1755   p=knowRating(cg->PlayerName[0]);
1756   if (p) g_strlcpy(cg->Rating[0],p,32);
1757   p=knowRating(cg->PlayerName[1]);
1758   if (p) g_strlcpy(cg->Rating[1],p,32);
1759   clearRatingBuffer();
1760 
1761   cg->updateGame(MoveBuf);
1762   cg->acknowledgeInfo();
1763   clearMoveBuffer();
1764   WildStartPos.setStartPos();
1765 }
1766 
queryGameList(GameListConsumer * glc)1767 void FicsProtocol::queryGameList(GameListConsumer *glc) {
1768   if (RetrievingGameList) {
1769     if (glc!=lastGLC)
1770       glc->endOfList();
1771     return;
1772   }
1773 
1774   RetrievingGameList=1;
1775   lastGLC=glc;
1776   global.network->writeLine("games");
1777 }
1778 
queryAdList(GameListConsumer * glc)1779 void FicsProtocol::queryAdList(GameListConsumer *glc) {
1780   if (RetrievingAdList) {
1781     if (glc!=lastALC)
1782       glc->endOfList();
1783     return;
1784   }
1785   RetrievingAdList=1;
1786   lastALC=glc;
1787   global.network->writeLine("sough");
1788 }
1789 
sendGameListEntry()1790 void FicsProtocol::sendGameListEntry() {
1791   char frank[256];
1792   int gn;
1793   //  GameListEntry.set("*%n *[*]*:*");
1794 
1795   gn=atoi(GameListEntry.getNToken(0));
1796 
1797   g_strlcpy(frank,GameListEntry.getStarToken(1),256);
1798   g_strlcat(frank,"[",256);
1799   g_strlcat(frank,GameListEntry.getStarToken(2),256);
1800   g_strlcat(frank,"]",256);
1801   g_strlcat(frank,GameListEntry.getStarToken(3),256);
1802   g_strlcat(frank,":",256);
1803   g_strlcat(frank,GameListEntry.getStarToken(4),256);
1804 
1805   lastGLC->appendGame(gn,frank);
1806 }
1807 
sendAdListEntry(char * pat)1808 void FicsProtocol::sendAdListEntry(char *pat) {
1809   ExtPatternMatcher rpat;
1810   int gn;
1811 
1812   rpat.set("*%n*");
1813   if (!rpat.match(pat))
1814     return;
1815 
1816   gn=atoi(rpat.getNToken(0));
1817   lastALC->appendGame(gn,rpat.getStarToken(1));
1818 }
1819 
observe(int gameid)1820 void FicsProtocol::observe(int gameid) {
1821   char z[64];
1822   snprintf(z,64,"observe %d",gameid);
1823   global.network->writeLine(z);
1824 }
1825 
answerAd(int adid)1826 void FicsProtocol::answerAd(int adid) {
1827   char z[64];
1828   snprintf(z,64,"play %d",adid);
1829   global.network->writeLine(z);
1830 }
1831 
knowRating(char * player)1832 char * FicsProtocol::knowRating(char *player) {
1833   int i;
1834   for(i=0;i<2;i++)
1835     if (strlen(xxplayer[i]))
1836       if ( !strncmp(xxplayer[i],player,strlen(player)) )
1837 	return(xxrating[i]);
1838   return 0;
1839 }
1840 
clearRatingBuffer()1841 void FicsProtocol::clearRatingBuffer() {
1842   xxplayer[0][0]=0;
1843   xxplayer[1][0]=0;
1844 }
1845 
exaForward(int n)1846 void FicsProtocol::exaForward(int n) {
1847   char z[64];
1848   snprintf(z,64,"forward %d",n);
1849   global.network->writeLine(z);
1850 }
1851 
exaBackward(int n)1852 void FicsProtocol::exaBackward(int n) {
1853   char z[64];
1854   snprintf(z,64,"backward %d",n);
1855   global.network->writeLine(z);
1856 }
1857 
retrieveMoves(ExtPatternMatcher & pm)1858 void FicsProtocol::retrieveMoves(ExtPatternMatcher &pm) {
1859   ChessGame *refgame;
1860   int gm;
1861   char z[64];
1862   global.debug("FicsProtocol","retrieveMoves");
1863 
1864   gm=atoi(pm.getNToken(0));
1865   refgame=global.getGame(gm);
1866   if (refgame==NULL)
1867     return;
1868 
1869   if (refgame->protodata[0]!=258) {
1870     MoveRetrievals.push_front(gm);
1871     snprintf(z,64,"moves %d",gm);
1872     global.network->writeLine(z);
1873     RetrievingMoves=1;
1874   }
1875 }
1876 
1877 // --- seek graph
1878 
ensureSeekGraph()1879 void FicsProtocol::ensureSeekGraph() {
1880   if (global.skgraph2==NULL) {
1881     global.skgraph2=new SeekGraph2();
1882     global.skgraph2->show();
1883     global.ebook->addPage(global.skgraph2->widget,_("Seek Graph"),-3);
1884     global.skgraph2->setNotebook(global.ebook,-3);
1885   }
1886 }
1887 
seekAdd(ExtPatternMatcher & pm)1888 void FicsProtocol::seekAdd(ExtPatternMatcher &pm) {
1889   SeekAd *ad;
1890   char *p;
1891   int flags;
1892 
1893   global.debug("FicsProtocol","seekAdd");
1894 
1895   // <s> 8 w=visar ti=02 rt=2194  t=4 i=0 r=r tp=suicide c=? rr=0-9999 a=t f=f
1896   //    n0   s0       r0    n1  *0  n2  n3  s1    r1       r2    r3      s2  s3
1897 
1898   ensureSeekGraph();
1899   ad=new SeekAd();
1900 
1901   // %n
1902   ad->id      = atoi(pm.getNToken(0));
1903   ad->rating  = pm.getNToken(1);
1904   ad->clock   = atoi(pm.getNToken(2));
1905   ad->incr    = atoi(pm.getNToken(3));
1906 
1907   p=pm.getStarToken(0);
1908   if ((p[0]=='P')&&(atoi(ad->rating.c_str())==0)) ad->rating="++++";
1909 
1910   // %s
1911   ad->player  = pm.getSToken(0);
1912 
1913   p=pm.getSToken(1);
1914   if (p[0]=='r') ad->rated=true;
1915 
1916   ad->kind    = pm.getRToken(1);
1917 
1918   // %r
1919   flags = strtol(pm.getRToken(0),NULL,16);
1920 
1921   ad->flags=" ";
1922 
1923   if (flags&0x01) ad->flags += "(U)";
1924   if (flags&0x02) ad->flags += "(C)";
1925   if (flags&0x04) ad->flags += "(GM)";
1926   if (flags&0x08) ad->flags += "(IM)";
1927   if (flags&0x10) ad->flags += "(FM)";
1928   if (flags&0x20) ad->flags += "(WGM)";
1929   if (flags&0x40) ad->flags += "(WIM)";
1930   if (flags&0x80) ad->flags += "(WFM)";
1931 
1932   p=pm.getRToken(2);
1933   switch(p[0]) {
1934   case '?': ad->color=" "; break;
1935   case 'W': ad->color=_("white"); break;
1936   case 'B': ad->color=_("black"); break;
1937   }
1938 
1939   ad->range=pm.getRToken(3);
1940 
1941   p=pm.getSToken(2); if (p[0]=='t') ad->automatic=true;
1942   p=pm.getSToken(3); if (p[0]=='t') ad->formula=true;
1943 
1944   global.skgraph2->add(ad);
1945 }
1946 
seekRemove(ExtPatternMatcher & pm)1947 void FicsProtocol::seekRemove(ExtPatternMatcher &pm) {
1948   tstring t;
1949   int i;
1950   t.setFail(-1);
1951   t.set(pm.getStarToken(0));
1952   ensureSeekGraph();
1953   while( (i=t.tokenvalue(" \t\n")) >= 0 ) {
1954     global.skgraph2->remove(i);
1955   }
1956 }
1957 
getPlayerActions()1958 vector<string *> * FicsProtocol::getPlayerActions() {
1959   return(&PActions);
1960 }
1961 
getGameActions()1962 vector<string *> * FicsProtocol::getGameActions() {
1963   return(&GActions);
1964 }
1965 
callPlayerAction(char * player,string * action)1966 void FicsProtocol::callPlayerAction(char *player, string *action) {
1967   for(unsigned int i=0;i<PActions.size();i++)
1968     if ( (*PActions[i]) == (*action) ) { doPlayerAction(player, i); break; }
1969 }
1970 
callGameAction(int gameid,string * action)1971 void FicsProtocol::callGameAction(int gameid, string *action) {
1972   for(unsigned int i=0;i<GActions.size();i++)
1973     if ( (*GActions[i]) == (*action) ) { doGameAction(gameid, i); break; }
1974 }
1975 
doPlayerAction(char * player,int id)1976 void FicsProtocol::doPlayerAction(char *player, int id) {
1977   char z[256],w[256];
1978   bool ok=true;
1979 
1980   switch(id) {
1981   case 0: snprintf(z,256,"finger %s",player); break;
1982   case 1: snprintf(z,256,"stat %s",player); break;
1983   case 2: snprintf(z,256,"history %s",player); break;
1984   case 3: snprintf(z,256,"ping %s",player); break;
1985   case 4: snprintf(z,256,"log %s",player); break;
1986   case 5: snprintf(z,256,"var %s",player); break;
1987   default: ok=false;
1988   }
1989 
1990   if (ok) {
1991     snprintf(w,256,_("> [issued from menu] %s"),z);
1992     global.output->append(w,global.SelfInputColor);
1993     global.network->writeLine(z);
1994   }
1995 }
1996 
doGameAction(int gameid,int id)1997 void FicsProtocol::doGameAction(int gameid, int id) {
1998   char z[256], w[256];
1999   bool ok=true;
2000 
2001   switch(id) {
2002   case 0: snprintf(z,256,"allob %d",gameid); break;
2003   case 1: snprintf(z,256,"ginfo %d",gameid); break;
2004   default: ok=false;
2005   }
2006 
2007   if (ok) {
2008     snprintf(w,256,_("> [issued from menu] %s"),z);
2009     global.output->append(w,global.SelfInputColor);
2010     global.network->writeLine(z);
2011   }
2012 }
2013 
prepareBugPane()2014 void FicsProtocol::prepareBugPane() {
2015   char z[64];
2016   PartnerGame = atoi(GotPartnerGame.getNToken(0));
2017   snprintf(z,64,"observe %d",PartnerGame);
2018   global.network->writeLine(z);
2019 }
2020 
updateVar(ProtocolVar pv)2021 void FicsProtocol::updateVar(ProtocolVar pv) {
2022   char z[256];
2023   switch(pv) {
2024   case PV_premove:
2025     snprintf(z,256,"iset premove %d\n",global.Premove ? 1 : 0);
2026     global.network->writeLine(z);
2027     break;
2028   default:
2029     break;
2030   }
2031 }
2032 
fics_allob(gpointer data)2033 gboolean fics_allob(gpointer data) {
2034   ChessGame *cg = (ChessGame *) data;
2035   FicsProtocol *proto;
2036   char z[64];
2037 
2038   if (global.protocol == 0)  return FALSE;
2039   if (cg == 0)               return FALSE;
2040   if (cg->protodata[3] == 0) return FALSE;
2041   if (cg->isOver())          return FALSE;
2042   if (cg->source != GS_ICS)  return FALSE;
2043   if (global.network == 0)   return FALSE;
2044 
2045   proto = (FicsProtocol *) (global.protocol);
2046   proto->AllObReqs.push_front(cg->GameNumber);
2047 
2048   snprintf(z,64,"allob %d",cg->GameNumber);
2049   global.network->writeLine(z);
2050 
2051   return TRUE;
2052 }
2053 
2054 // AllObFirstLine Matched
doAllOb1()2055 bool FicsProtocol::doAllOb1() {
2056   int n,i;
2057   tstring t;
2058   string *p;
2059   bool islast = false;
2060 
2061   //  cerr << "allob2\n";
2062 
2063   n = atoi(AllObFirstLine.getNToken(0));
2064 
2065   // cerr << "n0=[" << AllObFirstLine.getNToken(0) << "]\n";
2066   // cerr << "n=" << n << ", back=" << AllObReqs.back() << endl;
2067   if (n!=AllObReqs.back())
2068     return false;
2069 
2070   t.set(AllObFirstLine.getStarToken(1));
2071 
2072   snprintf(AllObAcc,1024,"Observing game %d: ",n);
2073   i=0;
2074   while((p=t.token(" "))!=0) {
2075 
2076     if ( (*p)[0] == '(' ) {
2077       islast = true;
2078       break;
2079     }
2080 
2081     if(i!=0) strcat(AllObAcc,", ");
2082     g_strlcat(AllObAcc,p->c_str(),1024);
2083     ++i;
2084 
2085     //    cerr << "partial: [" << AllObAcc << "]\n";
2086 
2087     if (strlen(AllObAcc) > 128) {
2088       g_strlcat(AllObAcc,"...",1024);
2089       break;
2090     }
2091   }
2092 
2093   if (islast) {
2094     AllObState = 2;
2095     global.status->setText(AllObAcc,28);
2096     AllObReqs.pop_back();
2097   } else
2098     AllObState = 1;
2099 
2100   return true;
2101 }
2102 
2103 // AllObMidLine matched
doAllOb2()2104 void FicsProtocol::doAllOb2() {
2105   int i;
2106   bool islast = false;
2107   tstring t;
2108   string *p;
2109 
2110   t.set(AllObFirstLine.getStarToken(0));
2111 
2112   while((p=t.token(" "))!=0) {
2113 
2114     if ( (*p)[0] == '(' ) {
2115       islast = true;
2116       break;
2117     }
2118     if (strlen(AllObAcc) > 128)
2119       continue;
2120 
2121     g_strlcat(AllObAcc,", ",1024);
2122     g_strlcat(AllObAcc,p->c_str(),1024);
2123     ++i;
2124     if (strlen(AllObAcc) > 128) {
2125       g_strlcat(AllObAcc,"...",1024);
2126       break;
2127     }
2128   }
2129 
2130   if (islast) {
2131     AllObState = 2;
2132     global.status->setText(AllObAcc,28);
2133     AllObReqs.pop_back();
2134   } else
2135     AllObState = 1;
2136 
2137 }
2138