Subversion Repositories Games.Chess Giants

Rev

Rev 177 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
// network.cpp
2
 
3
#include "common.h"
4
 
5
 
6
// handy definitions
7
#define REACH_NEXT_FIELD_ELSE(field,action) \
8
{ \
9
   while (*(field) && !iswspace (*(field))) (field)++; if (*(field) == 0) action; \
10
   while (*(field) && iswspace (*(field))) (field)++; if (*(field) == 0) action; \
11
}
12
#define LOCATE_REPLY_END_FROM_START(end,start) \
13
{ \
14
   (end) = wcschr ((start), L'\n'); \
15
   while (((end) != NULL) && ((end)[1] == L'\\')) (end) = wcschr (&(end)[1], L'\n'); /* find the first line feed that is NOT followed by a backslash */ \
16
   if ((end) != NULL) (end)++; /* skip it */ \
17
   else (end) = (start) + wcslen (start); \
18
}
19
#define ERASE_FROM_TO(beginning,end) \
20
{ \
21
   while ((((end) != NULL) && ((beginning) < (end))) || (*(beginning) != 0)) \
22
      *(beginning) = L' ', (beginning)++; /* replace all the area with spaces */ \
23
}
24
#define IS_FIELD_PRESENT(field_var,string) \
25
   (((field_var) = wcsstr (player->recvbuffer, (string))) != NULL) /* this text is present */
26
#define IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE(field_var,string) \
27
   ((((field_var) = wcsstr (player->recvbuffer, (string))) != NULL) /* this text is present... */ \
28
    && (ReachBeginningOfCurrentLine (player->recvbuffer, (field_var)) == (field_var))) /* ... at the beginning of a line */
29
 
30
 
31
 
32
// prototypes of local functions
33
static void ReadNickname (wchar_t *nickname, size_t nickname_size, wchar_t *from_string);
34
static void ReadGamename (wchar_t *gamename, size_t gamename_size, wchar_t *from_string);
35
static void ReadSpannedLine (wchar_t *outstring, size_t outstring_size, wchar_t *multiline_string);
36
 
37
 
38
void EvaluateServerReply_MOTD (player_t *player)
39
{
40
   // this function parses a network reply and evaluates it, deciding what to do
41
 
42
   // is the MOTD already filled ?
43
   if (server_motd[0] != 0)
44
      return; // if so, this reply can't be a MOTD so just return
45
 
46
   // read all received text as part of the MOTD
47
   wcscpy_s (server_motd, WCHAR_SIZEOF (server_motd), player->recvbuffer);
48
 
49
   // do we want the MOTD to be displayed ?
50
   if (options.network.want_servermessages && options.network.want_motdonconnect)
51
      Window_MOTD (); // display MOTD window if required
52
 
53
   Player_SendBuffer_Add (player, 1000, L"style 12\n"); // set the style 12 (computer-friendly board display)
54
   Player_SendBuffer_Add (player, 1000, L"who\n"); // send the players update request
55
   Player_SendBuffer_Add (player, 1000, L"sought all\n"); // send the sought games update request
56
   if (options.network.want_publicchat)
57
      Player_SendBuffer_Add (player, 1000, L"inchannel\n"); // send the chatter channels update request
58
 
59
   return; // finished evaluating the MOTD
60
}
61
 
62
 
63
void EvaluateServerReply_Announcement (player_t *player)
64
{
65
   // this function parses a network reply and evaluates it, deciding what to do
66
 
67
   // Announcement format:
68
   //RECEIVED:[
69
   //
70
   //    **ANNOUNCEMENT** from relay: FICS is relaying the 11th Delhi International
71
   //\   Open Category A 2013 - Round 3, the 24th Villa de Roquetas International 
72
   //\   Open 2013 - Round 8 and the 18th Bosnjaci International Open 2013 - Round 
73
   //\   5. To find more about Relay type "tell relay help"
74
   //]
75
 
76
   static wchar_t announcement_text[1024];
77
 
78
   wchar_t *announcement_start;
79
   wchar_t *announcement_end;
80
   wchar_t *field_start;
81
 
82
   // are both of the possible announcement headers NOT present ?
83
   if (!IS_FIELD_PRESENT (announcement_start, L"\n\n    **ANNOUNCEMENT** from ")
84
       && !IS_FIELD_PRESENT (announcement_start, L"\n\n    **UNREG ANNOUNCEMENT** from "))
85
      return; // if so, this reply can't be an announcement notification so just return
86
 
87
   // this reply is indeed an announcement ; find where it ends
88
   LOCATE_REPLY_END_FROM_START (announcement_end, announcement_start + 2);
89
 
90
   // are we concerned about server messages ?
91
   if (options.network.want_servermessages)
92
   {
93
      field_start = wcsstr (announcement_start, L": "); // reach the first colon+space, that's where the announcement starts
94
      if (field_start != NULL)
95
      {
96
         field_start += 2; // skip colon and space
97
         ReadSpannedLine (announcement_text, WCHAR_SIZEOF (announcement_text), field_start); // now format the announcement text well
185 pmbaty 98
         Scene_AddCCAnnouncement (&the_scene, LOCALIZE (L"ImportantMessage"), announcement_text); // and put it in place
1 pmbaty 99
      }
100
   }
101
 
102
   // now erase the announcement from the recvbuffer, so that it cannot be misinterpreted by further parsing
103
   ERASE_FROM_TO (announcement_start, announcement_end);
104
 
105
   return; // finished evaluating this announcement
106
}
107
 
108
 
109
void EvaluateServerReply_ChannelMessage (player_t *player)
110
{
111
   // this function parses a network reply and evaluates it, deciding what to do
112
 
113
   static wchar_t channelmessage_text[1024];
114
 
115
   wchar_t nickname[32];
116
   wchar_t channelname[64];
117
   unsigned long rgbx_color;
118
   wchar_t *channelmessage_start;
119
   wchar_t *channelmessage_end;
120
   wchar_t *field_start;
121
   wchar_t *field_stop;
122
   int channel_index;
123
   int channel_number;
124
 
125
   // is the discriminatives bit for a channel message NOT present ?
126
   if (!IS_FIELD_PRESENT (field_stop, L"): "))
127
      return; // if so, this reply can't be a channel message so just return
128
 
129
   // reach beginning of current line
130
   channelmessage_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_stop);
131
 
132
   // locate the first space in line (i.e. where the message text is supposed to start)
133
   field_start = wcschr (channelmessage_start, L' ');
134
   if (field_start != NULL)
135
      field_start++; // skip it
136
 
137
   // is the first space BEFORE the discirminative bit ?
138
   if (field_start < field_stop)
139
      return; // if so, this reply can't be a channel message so just return
140
 
141
   while ((field_stop > player->recvbuffer) && (*field_stop != L'('))
142
      field_stop--; // parse the string backwards to find the channel index
143
   if (*field_stop != L'(')
144
      return; // drop bogus replies
145
   channel_number = _wtoi (&field_stop[1]); // read channel number
146
   if (channel_number == 0)
147
      return; // if what's between the parentheses is not a number, this reply can't be a channel message so just return
148
 
149
   // this reply is indeed a channel message ; find where it ends
150
   LOCATE_REPLY_END_FROM_START (channelmessage_end, channelmessage_start);
151
 
152
   // are we concerned about channel messages ?
153
   if (options.network.want_publicchat)
154
   {
155
      ReadNickname (nickname, WCHAR_SIZEOF (nickname), channelmessage_start); // get the nickname
156
      ReadSpannedLine (channelmessage_text, WCHAR_SIZEOF (channelmessage_text), field_start); // get the message
157
 
158
      channel_number = _wtoi (&field_stop[1]); // and read channel number
159
      for (channel_index = 0; channel_index < chatterchannel_count; channel_index++)
160
         if (chatterchannels[channel_index].id == channel_number)
161
         {
162
            if (chatterchannels[channel_index].theme[0] != 0)
163
            {
164
               wcscpy_s (channelname, WCHAR_SIZEOF (channelname), chatterchannels[channel_index].theme);
165
               rgbx_color = chatterchannels[channel_index].color;
166
            }
167
            break; // break as soon as we find the channel's name and copy it if it exists
168
         }
169
      if (channelname[0] == 0)
170
      {
171
         swprintf_s (channelname, WCHAR_SIZEOF (channelname), L"%s %d", LOCALIZE (L"ChatterChannels_ColumnChannelNumber"), channel_number); // if it hasn't been filled, use the number
172
         rgbx_color = RGBA_TO_RGBACOLOR (17, 181, 205, 0); // default channel color
173
      }
174
 
175
      // add CC reply
176
      Scene_AddCCReply (&the_scene, nickname, channelname, rgbx_color, channelmessage_text);
177
   }
178
 
179
   // now erase the channel message from the recvbuffer, so that it cannot be misinterpreted by further parsing
180
   ERASE_FROM_TO (channelmessage_start, channelmessage_end);
181
 
182
   return; // finished evaluating this channel message
183
}
184
 
185
 
186
void EvaluateServerReply_PrivateMessage (player_t *player)
187
{
188
   // this function parses a network reply and evaluates it, deciding what to do
189
 
190
   static wchar_t privatemessage_text[16384];
191
 
192
   wchar_t nickname[32];
193
   wchar_t *privatemessage_start;
194
   wchar_t *privatemessage_end;
195
   wchar_t *field_start;
196
   wchar_t *first_space;
197
   int player_index;
198
 
199
   // is the private message discriminative bit NOT present ?
200
   // private messages have " tells you: " just after the first space
201
   if (!IS_FIELD_PRESENT (field_start, L" tells you: "))
202
      return; // if so, this reply can't be a private message so just return
203
 
204
   // look up for the last line feed and the first space of the line
205
   privatemessage_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start);
206
   first_space = wcschr (privatemessage_start, L' ');
207
 
208
   // is the first space BEFORE the discirminative bit ?
209
   if ((first_space != NULL) && (first_space < field_start))
210
      return; // if so, this reply can't be a channel message so just return
211
 
212
   // this reply is indeed a private message ; find where it ends
213
   LOCATE_REPLY_END_FROM_START (privatemessage_end, privatemessage_start + 2);
214
 
215
   ReadNickname (nickname, WCHAR_SIZEOF (nickname), privatemessage_start); // get username
216
 
217
   // is it NOT RoboAdmin OR do we care about server messages ?
218
   if ((_wcsicmp (L"ROBOadmin", nickname) != 0) || options.network.want_servermessages)
219
   {
220
      // see if this nickname exists in the list of connected players ; if not, refresh list
221
      for (player_index = 0; player_index < onlineplayer_count; player_index++)
222
         if (wcscmp (nickname, onlineplayers[player_index].nickname) == 0)
223
            break; // break as soon as we find it
224
 
225
      // have we NOT found it ?
226
      if (player_index == onlineplayer_count)
227
         Player_SendBuffer_Add (player, 1000, L"who\n"); // if so, request a players list refresh
228
 
229
      field_start += 12; // skip string break and reach the next colon, that's where the PM starts
230
      ReadSpannedLine (privatemessage_text, WCHAR_SIZEOF (privatemessage_text), field_start); // now format the PM text well
231
 
232
      // find or create our interlocutor structure and append the chat text in it
233
      Interlocutor_Chat (Interlocutor_FindOrCreate (nickname), nickname, false, privatemessage_text);
234
   }
235
 
236
   // now erase the private message from the recvbuffer, so that it cannot be misinterpreted by further parsing
237
   ERASE_FROM_TO (privatemessage_start, privatemessage_end);
238
 
239
   return; // finished evaluating this private message
240
}
241
 
242
 
243
void EvaluateServerReply_Finger (player_t *player)
244
{
245
   // this function parses a network reply and evaluates it, deciding what to do
246
 
247
   wchar_t line_buffer[256];
248
   wchar_t month_str[5];
249
   wchar_t dayhrminsec[8];
250
   wchar_t nickname[32];
251
   gamestylerating_t gs;
252
   playercard_t *playercard;
253
   player_t *local_player;
254
   wchar_t *finger_start;
255
   wchar_t *finger_end;
256
   wchar_t *field_start;
257
   wchar_t *fingerdata_start;
258
   wchar_t *string_pointer;
259
   int onlineplayer_index;
260
   int days;
261
   int hours;
262
   int minutes;
263
   int number;
264
 
265
   // is it a NEGATIVE finger reply ? "'gue$tpm' is not a valid handle."
266
   if (IS_FIELD_PRESENT (field_start, L"' is not a valid handle."))
267
   {
268
      field_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // look up for the last line feed
269
      if (field_start[0] == L'\'') // first character must be an apostrophe
270
      {
271
         ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[1]); // read username
272
 
273
         // test again with the complete finger reply header. Are we SURE it is a finger reply ?
274
         if (wcscmp (&field_start[1 + wcslen (nickname)], L"' is not a valid handle.") == 0)
275
         {
276
            // find or create our player card structure
277
            playercard = PlayerCard_FindOrCreate (nickname);
278
            playercard->doesnt_exist = true; // mark it as non-existing
279
            playercard->update_dialog = true; // and tell the dialog to update itself
280
 
281
            return; // finished evaluating this finger reply
282
         }
283
      }
284
   }
285
 
286
   // is it a NEGATIVE finger reply ? "Ambiguous name guestp:"
287
   if (IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Ambiguous name "))
288
   {
289
      ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[15]); // read username
290
 
291
      // find or create our player card structure
292
      playercard = PlayerCard_FindOrCreate (nickname);
293
      playercard->doesnt_exist = true; // mark it as non-existing
294
      playercard->update_dialog = true; // and tell the dialog to update itself
295
 
296
      return; // finished evaluating this finger reply
297
   }
298
 
299
   // is it a NEGATIVE finger reply ? "There is no player matching the name guestpm."
300
   if (IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"There is no player matching the name "))
301
   {
302
      ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[37]); // read username
303
 
304
      // find or create our player card structure
305
      playercard = PlayerCard_FindOrCreate (nickname);
306
      playercard->doesnt_exist = true; // mark it as non-existing
307
      playercard->update_dialog = true; // and tell the dialog to update itself
308
 
309
      return; // finished evaluating this finger reply
310
   }
311
 
312
   // is it a POSITIVE finger reply ? "Finger of guestpm:"
313
   if (IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Finger of "))
314
   {
315
      ReadNickname (nickname, WCHAR_SIZEOF (nickname), &field_start[10]); // get username
316
 
317
      // find or create our player card structure
318
      playercard = PlayerCard_FindOrCreate (nickname);
319
      playercard->got_reply = true; // remember it has actual data
320
 
321
      // when we have it, update username
322
      wcscpy_s (playercard->nickname, WCHAR_SIZEOF (playercard->nickname), nickname);
323
 
324
      // find local player and see whether this player card is ours
325
      local_player = Player_FindByType (PLAYER_HUMAN);
326
      if ((local_player != NULL) && (_wcsicmp (nickname, local_player->name) == 0))
327
         playercard->is_own = true; // remember this player card is ours
328
 
329
      ///////////////////////////////////////////////////////////////////////
330
      // Finger: parse the connection data (On for: n days n hours n minutes)
331
 
332
      // reach the next double line-break, that's where connection data starts
333
      fingerdata_start = wcsstr (field_start, L"\n\n");
334
      if (fingerdata_start == NULL)
335
         return; // nothing in finger reply ; reply evaluated
336
 
337
      fingerdata_start += 2; // skip them
338
      wcsgets (line_buffer, WCHAR_SIZEOF (line_buffer), fingerdata_start); // copy that line into a buffer for easy parsing
339
 
340
      // is the player currently online ?
341
      if (wcsncmp (line_buffer, L"On for: ", 8) == 0)
342
      {
343
         field_start = &line_buffer[8]; // skip the "On for: " text
344
 
345
         // according to the data presentation, read and convert in the right form
346
         if (swscanf_s (field_start, L"%d %*s %d %*s %d %*s Idle: %d %s", &days, &hours, &minutes, &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 5)
347
         {
348
            playercard->minutes_online = (days * 60 * 24) + (hours * 60) + minutes; // days, hours, minutes
349
            playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));
350
         }
351
         else if (swscanf_s (field_start, L"%d %*s %d %*s Idle: %d %s", &hours, &minutes, &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 4)
352
         {
353
            playercard->minutes_online = (hours * 60) + minutes; // hours, minutes
354
            playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));
355
         }
356
         else if (swscanf_s (field_start, L"%*d secs Idle: %d %s", &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 2)
357
         {
358
            playercard->minutes_online = 1; // less than one minute, round to 1
359
            playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));
360
         }
361
         else if (swscanf_s (field_start, L"%d %*s Idle: %d %s", &minutes, &number, dayhrminsec, WCHAR_SIZEOF (dayhrminsec)) == 3)
362
         {
363
            playercard->minutes_online = minutes; // just minutes
364
            playercard->seconds_idle = number * (dayhrminsec[0] == L'd' ? 60 * 60 * 24 : (dayhrminsec[0] == L'h' ? 60 * 60 : (dayhrminsec[0] == L'm' ? 60 : 1)));
365
         }
366
 
367
         playercard->update_dialog = true; // remember to update dialog
368
      }
369
 
370
      // else has player already disconnected ?
371
      else if (wcsncmp (line_buffer, L"Last disconnected: ", 19) == 0)
372
      {
373
         field_start = &line_buffer[19]; // skip the "Last disconnected: " text
374
 
375
         // read and convert the data in the right form
81 pmbaty 376
         if (swscanf_s (field_start, L"%*s %s %d, %*d:%*d %*s %d", month_str, WCHAR_SIZEOF (month_str), &days, &number) == 3)
377
         {
378
            playercard->disconnection_day = days; // reuse the "days" variable, which is an int, and we need an int in swscanf_s()
1 pmbaty 379
            playercard->disconnection_month = MonthStringToNumber (month_str); // convert month from string to number
81 pmbaty 380
            playercard->disconnection_year = number; // reuse the "number" variable, which is an int, and we need an int in swscanf_s()
381
         }
1 pmbaty 382
 
383
         playercard->update_dialog = true; // remember to update dialog
384
      }
385
 
386
      // else player has never connected
387
      else
388
      {
389
         playercard->disconnection_day = 0; // 0 everywhere means the player has never connected
390
         playercard->disconnection_month = 0;
391
         playercard->disconnection_year = 0;
392
 
393
         playercard->update_dialog = true; // remember to update dialog
394
      }
395
 
396
      ////////////////////////////////////////////////////////////
397
      // Finger: parse the status data (playing game N:aaa vs bbb)
398
 
399
      // see if this player is currently playing a game
400
      field_start = wcsstr (fingerdata_start, L"\n(playing game ");
401
      if (field_start != NULL)
402
      {
403
         field_start += 15; // skip the "\n(playing game " substring
404
 
405
         // update player activity in opponents list
406
         for (onlineplayer_index = 0; onlineplayer_index < onlineplayer_count; onlineplayer_index++)
407
            if (wcscmp (onlineplayers[onlineplayer_index].nickname, playercard->nickname) == 0)
408
            {
409
               // if this player is NOT involved in a tournament...
410
               if (onlineplayers[onlineplayer_index].handlestatus != HANDLESTATUS_INTOURNAMENT)
411
               {
412
                  if (playercard->seconds_idle < 5 * 60)
413
                     onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_INGAME; // update status (playing)
414
                  else
415
                     onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_INACTIVEORBUSY; // update status (idle)
416
               }
417
               break; // stop searching as soon as player is found
418
            }
419
 
420
         // scan the game number and name
421
         swscanf_s (field_start, L"%d: %[^)]", &playercard->game_played, playercard->game_name, WCHAR_SIZEOF (playercard->game_name));
422
         playercard->update_dialog = true; // remember to update dialog
423
      }
424
 
425
      // see if this player is currently playing a game
426
      field_start = wcsstr (fingerdata_start, L"\n(examining game ");
427
      if (field_start != NULL)
428
      {
429
         field_start += 17; // skip the "\n(examining game " substring
430
 
431
         // update player activity in opponents list
432
         for (onlineplayer_index = 0; onlineplayer_index < onlineplayer_count; onlineplayer_index++)
433
            if (wcscmp (onlineplayers[onlineplayer_index].nickname, playercard->nickname) == 0)
434
            {
435
               // if this player is NOT involved in a tournament...
436
               if (onlineplayers[onlineplayer_index].handlestatus != HANDLESTATUS_INTOURNAMENT)
437
               {
438
                  if (playercards->seconds_idle < 5 * 60)
439
                     onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_EXAMININGAGAME; // update status (studying)
440
                  else
441
                     onlineplayers[onlineplayer_index].handlestatus = HANDLESTATUS_INACTIVEORBUSY; // update status (idle)
442
               }
443
               break; // stop searching as soon as player is found
444
            }
445
 
446
         // scan the game number and name
447
         swscanf_s (field_start, L"%d: %[^)]", &playercard->game_played, playercard->game_name, WCHAR_SIZEOF (playercard->game_name));
448
         playercard->update_dialog = true; // remember to update dialog
449
      }
450
 
451
      //////////////////////////////////////////
452
      // Finger: parse the game statistics array
453
 
454
      // see if this player has game statistics, find it and jump to the first line
455
      field_start = wcsstr (fingerdata_start, L"          rating     RD      win    loss    draw   total   best\n");
456
      if (field_start != NULL)
457
      {
458
         field_start += 64; // skip the rating table headers and its carriage return
459
 
460
         // game statistics start here. Read line per line...
461
         string_pointer = field_start; // start at the beginning of the line
462
         while ((string_pointer = wcsgets (line_buffer, WCHAR_SIZEOF (line_buffer), string_pointer)) != NULL)
463
         {
464
            if (line_buffer[0] == 0)
465
               continue; // skip empty lines
466
 
467
            memset (&gs, 0, sizeof (gs)); // reset all statistics we're about to read
468
 
469
            // does it look like a valid game statistics line ?
470
            if ((swscanf_s (line_buffer, L"%s %d %f %d %d %d %d", gs.name, WCHAR_SIZEOF (gs.name), &gs.rating, &gs.rd, &gs.win_count, &gs.loss_count, &gs.draw_count, &gs.total_matches) == 7)
471
                || (swscanf_s (line_buffer, L"%s ---- %f %d %d %d %d", gs.name, WCHAR_SIZEOF (gs.name), &gs.rd, &gs.win_count, &gs.loss_count, &gs.draw_count, &gs.total_matches) == 6))
472
            {
473
               // reallocate space to hold one game style rating more in this player card
474
               playercard->gamestyleratings = (gamestylerating_t *) SAFE_realloc (playercard->gamestyleratings, playercard->gamestylerating_count, playercard->gamestylerating_count + 1, sizeof (gamestylerating_t), false);
475
               memcpy (&playercard->gamestyleratings[playercard->gamestylerating_count], &gs, sizeof (gamestylerating_t)); // copy data
476
               playercard->gamestylerating_count++; // we know now one game style rating more for this player card
477
            }
478
         }
479
 
480
         playercard->update_dialog = true; // remember to update dialog
481
      }
482
 
483
      //////////////////////////////////////
484
      // Finger: parse the personal messages
485
 
486
      // see if this player has personal data, find it and jump to the first line
487
      field_start = wcsstr (fingerdata_start, L"\n\n 1:");
488
      if (field_start != NULL)
489
      {
490
         field_start += 2; // skip the two carriage returns
491
         finger_start = field_start;
492
 
493
         // personal data starts here. Read line per line...
494
         string_pointer = field_start; // start at the beginning of the line
495
         while ((string_pointer = wcsgets (line_buffer, WCHAR_SIZEOF (line_buffer), string_pointer)) != NULL)
496
         {
497
            if (line_buffer[0] == 0)
498
               continue; // skip empty lines
499
 
500
            // does it look like a valid personal finger data line ?
501
            if ((wcslen (line_buffer) > 4) && (wcsncmp (&line_buffer[2], L": ", 2) == 0))
502
               PlayerCard_AppendPersonalData (playercard, &line_buffer[4]); // if so, append it to player's personal data
503
            else if (wcsncmp (line_buffer, L"\\   ", 3) == 0)
504
            {
505
               if (playercard->fingertext_length > 1)
506
               {
507
                  playercard->fingertext[playercard->fingertext_length - 1] = 0; // chop off the last carriage return in finger text
508
                  playercard->fingertext_length--; // UGLY: now size no longer reflects allocated space
509
               }
510
               PlayerCard_AppendPersonalData (playercard, &line_buffer[3]); // ...and append it to player's personal data
511
            }
512
         }
513
 
514
         // now erase that text from the recvbuffer, so that it cannot be misinterpreted by further parsing
515
         LOCATE_REPLY_END_FROM_START (finger_end, finger_start);
516
         ERASE_FROM_TO (finger_start, finger_end);
517
 
518
         playercard->update_dialog = true; // remember to update dialog
519
      }
520
 
521
      return; // finished evaluating this finger reply
522
   }
523
 
524
   return; // this was not a reply we could be concerned about
525
}
526
 
527
 
528
void EvaluateServerReply_Seek (player_t *player)
529
{
530
   // this function parses a network reply and evaluates it, deciding what to do
531
 
532
   wchar_t *field_start;
533
 
534
   // are the two bits of the seek notification sentence not present ?
535
   if (!IS_FIELD_PRESENT (field_start, L") seeking ") || !IS_FIELD_PRESENT (field_start, L"\" to respond)"))
536
      return; // if so, this reply can't be a seek notification so just return
537
 
538
   // only refresh the sought games list if we're displaying it
539
   if (IsWindow (hSoughtWnd) && (lastsought_time + 5.0f < current_time))
540
      Player_SendBuffer_Add (player, 1000, L"sought all\n"); // send the sought games update request
541
 
542
   return; // finished evaluating this seek notification
543
}
544
 
545
 
546
void EvaluateServerReply_Challenge (player_t *player)
547
{
548
   // this function parses a network reply and evaluates it, deciding what to do
549
 
550
   challenge_t chal;
551
   wchar_t *field_start;
552
 
553
   // is the challenge line header NOT present ?
554
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Challenge: "))
555
      return; // if so, this reply can't be a challenge notification so just return
556
 
557
   // challenges can appear as
558
   // Challenge: IOEO (1370) pmbaty (----) unrated blitz 2 12.
559
   // Challenge: IOEO (1370) [black] pmbaty (----) unrated blitz 2 12.
560
 
561
   field_start += 11; // skip the "Challenge: " substring
562
   ReadNickname (chal.challenger, WCHAR_SIZEOF (chal.challenger), field_start); // read challenger nickname
563
   REACH_NEXT_FIELD_ELSE (field_start, return);
564
   if (*field_start == L'(')
565
      field_start++; // skip the leading parenthesis
566
   if ((*field_start != L'-') && (*field_start != L'+'))
567
      chal.challenger_level = _wtoi (field_start); // read player rating
568
   else
569
      chal.challenger_level = 0;
570
   REACH_NEXT_FIELD_ELSE (field_start, return);
571
   if (_wcsnicmp (field_start, L"[black]", 7) == 0)
572
   {
573
      chal.color = COLOR_BLACK; // opponent wants to play black
574
      REACH_NEXT_FIELD_ELSE (field_start, return);
575
   }
576
   else if (_wcsnicmp (field_start, L"[white]", 7) == 0)
577
   {
578
      chal.color = COLOR_WHITE; // opponent wants to play white
579
      REACH_NEXT_FIELD_ELSE (field_start, return);
580
   }
581
   else
582
      chal.color = COLOR_UNSPECIFIED; // opponent has no preference over which color he wants to play
583
   REACH_NEXT_FIELD_ELSE (field_start, return); // skip our name
584
   REACH_NEXT_FIELD_ELSE (field_start, return); // skip our rating   
585
   chal.is_rated = (*field_start == L'r' ? true : false); // read whether it is rated or not
586
   REACH_NEXT_FIELD_ELSE (field_start, return);
587
   ReadGamename (chal.game_type, WCHAR_SIZEOF (chal.game_type), field_start); // read game type
588
   REACH_NEXT_FIELD_ELSE (field_start, return);
589
   chal.initial_time = (float) _wtoi (field_start); // read initial time
590
   REACH_NEXT_FIELD_ELSE (field_start, return);
591
   chal.increment = (float) _wtoi (field_start); // read increment
592
 
593
   // is this variant unsupported ?
172 pmbaty 594
   if ((_wcsicmp (chal.game_type, L"untimed") != 0) && (_wcsicmp (chal.game_type, L"standard") != 0)
595
       && (_wcsicmp (chal.game_type, L"blitz") != 0) && (_wcsicmp (chal.game_type, L"lightning") != 0)
596
      /* && (_wcsicmp (chal.game_type, L"losers") != 0) && (_wcsicmp (chal.game_type, L"atomic") != 0)*/)
1 pmbaty 597
      Player_SendBuffer_Add (player, 1000, L"decline %s\n", chal.challenger); // automatically decline all unsupported games
598
   else
599
   {
600
      // supported variant. Display a message box for the user to choose whether to accept or decline.
601
 
602
      // request a player list update before displaying the dialog box
603
      if (lastonlineplayers_time + 5.0f < current_time)
604
         Player_SendBuffer_Add (player, 1000, L"who\n");
605
 
606
      // print a notification in this player's chat window
607
      Interlocutor_Notify (Interlocutor_FindOrCreate (chal.challenger), LOCALIZE (L"Chat_InvitationReceived"), chal.challenger);
608
 
609
      // find or create our challenge structure and update its data
610
      Challenge_UpdateData (Challenge_FindOrCreate (chal.challenger), &chal);
611
   }
612
 
613
   return; // finished evaluating this challenge notification
614
}
615
 
616
 
617
void EvaluateServerReply_ChallengeAccepted (player_t *player)
618
{
619
   // this function parses a network reply and evaluates it, deciding what to do
620
 
621
   wchar_t nickname[32];
622
   wchar_t *line_start;
623
   wchar_t *field_start;
624
   interlocutor_t *interlocutor;
625
   int player_index;
626
 
627
   // is the challenge accepted notification discriminative bit NOT present ?
628
   if (!IS_FIELD_PRESENT (field_start, L" accepts the match offer."))
629
      return; // if so, this reply can't be a challenge reply notification so just return
630
 
631
   line_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // look up for the last line feed
632
   ReadNickname (nickname, WCHAR_SIZEOF (nickname), line_start); // get username
633
 
634
   // see if this nickname exists in the list of connected players ; if not, refresh list
635
   for (player_index = 0; player_index < onlineplayer_count; player_index++)
636
      if (wcscmp (nickname, onlineplayers[player_index].nickname) == 0)
637
         break; // break as soon as we find it
638
 
639
   // have we NOT found it ?
640
   if (player_index == onlineplayer_count)
641
      Player_SendBuffer_Add (player, 1000, L"who\n"); // if so, request a players list refresh
642
 
643
   // send a notification to this player's chat window
644
   interlocutor = Interlocutor_FindOrCreate (nickname);
645
   Interlocutor_Notify (interlocutor, LOCALIZE (L"Chat_InvitationAcceptedByOther"), nickname);
646
   if (IsWindow (interlocutor->hWnd))
647
      ShowWindow (interlocutor->hWnd, SW_MINIMIZE); // minimize chat window immediately
648
 
172 pmbaty 649
   // remember the game rules
650
   wcscpy_s (the_board.game_rules, WCHAR_SIZEOF (the_board.game_rules), L"standard"); // TODO: support other game rules
651
 
1 pmbaty 652
   return; // finished evaluating this challenge reply notification
653
}
654
 
655
 
656
void EvaluateServerReply_ChallengeDeclined (player_t *player)
657
{
658
   // this function parses a network reply and evaluates it, deciding what to do
659
 
660
   wchar_t nickname[32];
661
   wchar_t *line_start;
662
   wchar_t *field_start;
663
   int player_index;
664
 
665
   // is the challenge declined notification discriminative bit NOT present ?
666
   if (!IS_FIELD_PRESENT (field_start, L" declines the match offer."))
667
      return; // if so, this reply can't be a challenge reply notification so just return
668
 
669
   line_start = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // look up for the last line feed
670
   ReadNickname (nickname, WCHAR_SIZEOF (nickname), line_start); // get username
671
 
672
   // see if this nickname exists in the list of connected players ; if not, refresh list
673
   for (player_index = 0; player_index < onlineplayer_count; player_index++)
674
      if (wcscmp (nickname, onlineplayers[player_index].nickname) == 0)
675
         break; // break as soon as we find it
676
 
677
   // have we NOT found it ?
678
   if (player_index == onlineplayer_count)
679
      Player_SendBuffer_Add (player, 1000, L"who\n"); // if so, request a players list refresh
680
 
681
   // send a notification to this player's chat window
682
   Interlocutor_Notify (Interlocutor_FindOrCreate (nickname), LOCALIZE (L"Chat_InvitationDeclinedByOther"), nickname);
683
 
684
   return; // finished evaluating this challenge reply notification
685
}
686
 
687
 
688
void EvaluateServerReply_Takeback (player_t *player)
689
{
690
   // this function parses a network reply and evaluates it, deciding what to do
691
 
692
   wchar_t *field_start;
693
   wchar_t *field_stop;
694
   int howmany_halfmoves;
695
 
696
   // is the challenge declined notification discriminative bit NOT present ?
697
   if (!IS_FIELD_PRESENT (field_start, L" would like to take back ") || !IS_FIELD_PRESENT (field_stop, L" half move(s)"))
698
      return; // if so, this reply can't be a challenge reply notification so just return
699
 
700
   // read the numbre of half moves the opponent reclaims
701
   swscanf_s (field_start, L" would like to take back %d ", &howmany_halfmoves);
702
 
703
   // send a notification to the local player's chat window
704
   Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRequestReceived"), player->name, howmany_halfmoves);
705
   DialogBox_Takeback (howmany_halfmoves); // and fire up a modal dialog box to ask confirmation to the local player
706
 
707
   return; // finished evaluating this challenge reply notification
708
}
709
 
710
 
711
void EvaluateServerReply_TakebackDeclinedByOther (player_t *player)
712
{
713
   // this function parses a network reply and evaluates it, deciding what to do
714
 
715
   wchar_t *field_start;
716
 
717
   // is the takeback declined notification discriminative bit NOT present ?
718
   if (!IS_FIELD_PRESENT (field_start, L" declines the takeback request."))
719
      return; // if so, this reply can't be a takeback reply notification so just return
720
 
721
   // send a notification to the local player's chat window
722
   Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRefused"), player->name);
723
 
724
   return; // finished evaluating this challenge reply notification
725
}
726
 
727
 
728
void EvaluateServerReply_TakebackDeclinedByYou (player_t *player)
729
{
730
   // this function parses a network reply and evaluates it, deciding what to do
731
 
732
   wchar_t *field_start;
733
 
734
   // is the takeback declined notification discriminative bit NOT present ?
735
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"You decline the takeback request from "))
736
      return; // if so, this reply can't be a takeback reply notification so just return
737
 
738
   // send a notification to the local player's chat window
739
   Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRefused"), player->name);
740
 
741
   return; // finished evaluating this challenge reply notification
742
}
743
 
744
 
745
void EvaluateServerReply_PlayNotAllowed (player_t *player)
746
{
747
   // this function parses a network reply and evaluates it, deciding what to do
748
 
749
   wchar_t *field_start;
750
 
751
   // is the play reply notification discriminative bit NOT present ?
752
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Only registered players can play rated games."))
753
      return; // if so, this reply can't be a play reply notification so just return
754
 
755
   // display a message box for the player to know that his opponent refuses to play
756
   messagebox.hWndParent = (IsWindow (hSoughtWnd) ? hSoughtWnd : hMainWnd);
757
   wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"PlayReply_ServerReply"));
758
   swprintf_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"PlayReply_OnlyRegisteredCanPlayRated"));
759
   messagebox.flags = MB_ICONINFORMATION | MB_OK;
760
   DialogBox_Message (&messagebox); // display a modeless error message box
761
 
762
   return; // finished evaluating this play reply notification
763
}
764
 
765
 
766
void EvaluateServerReply_PlayUnexistent (player_t *player)
767
{
768
   // this function parses a network reply and evaluates it, deciding what to do
769
 
770
   wchar_t *field_start;
771
 
772
   // is the play reply notification discriminative bit NOT present ?
773
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"That seek is not available."))
774
      return; // if so, this reply can't be a play reply so just return
775
 
776
   // display a message box for the player to know that his opponent refuses to play
777
   messagebox.hWndParent = (IsWindow (hSoughtWnd) ? hSoughtWnd : hMainWnd);
778
   wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"PlayReply_ServerReply"));
779
   swprintf_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"PlayReply_UnexistentSeek"));
780
   messagebox.flags = MB_ICONINFORMATION | MB_OK;
781
   DialogBox_Message (&messagebox); // display a modeless error message box
782
 
783
   return; // finished evaluating this play reply notification
784
}
785
 
786
 
787
void EvaluateServerReply_PlayWrongRating (player_t *player)
788
{
789
   // this function parses a network reply and evaluates it, deciding what to do
790
 
791
   wchar_t *field_start;
792
 
793
   // is the play reply notification discriminative bit NOT present ?
794
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Your rating does not qualify for this seek."))
795
      return; // if so, this reply can't be a play reply so just return
796
 
797
   // display a message box for the player to know that his opponent refuses to play
798
   messagebox.hWndParent = (IsWindow (hSoughtWnd) ? hSoughtWnd : hMainWnd);
799
   wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"PlayReply_ServerReply"));
800
   swprintf_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"PlayReply_WrongRating"));
801
   messagebox.flags = MB_ICONINFORMATION | MB_OK;
802
   DialogBox_Message (&messagebox); // display a modeless error message box
803
 
804
   return; // finished evaluating this play reply notification
805
}
806
 
807
 
808
void EvaluateServerReply_ChannelsAndMembers (player_t *player)
809
{
810
   // this function parses a network reply and evaluates it, deciding what to do
811
 
812
   chatterchannel_t cc;
813
   wchar_t *field_start;
814
   wchar_t *field_stop;
815
   wchar_t *string_pointer;
816
   wchar_t *big_buffer;
817
   player_t *local_player;
818
   int previouslyselected_channelid;
819
   int naturallanguagechannel_index;
820
   int chatterchannel_index;
821
   int cctheme_length;
822
   int char_index;
823
 
824
   // is the channels and members header bit NOT present ?
825
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Channel "))
826
      return; // if so, this reply can't be a channels and members list so just return
827
 
828
   local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the human player
829
 
830
   // is a chatter channel selected ?
831
   if (selected_chatterchannel != NULL)
832
      previouslyselected_channelid = selected_chatterchannel->id; // save its ID
833
   else
834
      previouslyselected_channelid = -1; // -1 will instruct us to select a default chatter channel
835
 
836
   // for each chatter channel we know already...
837
   for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)
838
   {
839
      SAFE_free ((void **) &chatterchannels[chatterchannel_index].members); // for each channel, free its members array
840
      chatterchannels[chatterchannel_index].member_count = 0;
841
   }
842
   SAFE_free ((void **) &chatterchannels); // free the chatter channel list we know
843
   chatterchannel_count = 0; // reset the chatter channel count
844
   naturallanguagechannel_index = -1; // reset the natural language channel index
845
 
846
   // linearize the string
847
   string_pointer = field_start;
848
   while (string_pointer[1] != 0)
849
   {
850
      if ((string_pointer[0] == L'\n') && (string_pointer[1] == L'\\'))
851
         string_pointer[0] = string_pointer[1] = L' '; // replace every newline followed by a backslash by two spaces
852
      string_pointer++;
853
   }
854
 
855
   // mallocate space for a big buffer
856
   big_buffer = (wchar_t *) SAFE_malloc (1024 * 1024, sizeof (wchar_t), false);
857
 
858
   // read line per line
859
   string_pointer = ReachBeginningOfCurrentLine (player->recvbuffer, field_start); // start at the first character
860
   while ((string_pointer = wcsgets (big_buffer, 1024 * 1024, string_pointer)) != NULL)
861
   {
862
      if (big_buffer[0] == 0)
863
         break; // if it's an empty line, then the channel list is finished
864
 
865
      // now parse the chatter channel data
866
      field_start = big_buffer;
867
      memset (&cc, 0, sizeof (cc));
868
 
869
      REACH_NEXT_FIELD_ELSE (field_start, continue);
870
      cc.id = _wtoi (field_start); // read chatter channel id
871
 
872
      REACH_NEXT_FIELD_ELSE (field_start, continue);
873
      if (*field_start == L'"')
874
      {
875
         field_start++; // skip the quote
876
         field_stop = field_start;
877
         while (*field_stop && (*field_stop != L'"'))
878
            field_stop++; // reach the next quote
879
         if (*field_stop == 0)
880
            continue; // discard bogus lines
881
         *field_stop = 0; // break the string here
882
         wcscpy_s (cc.theme, WCHAR_SIZEOF (cc.theme), field_start); // copy theme
883
         cctheme_length = wcslen (cc.theme);
884
         for (char_index = 0; char_index < cctheme_length; char_index++)
885
            if (cc.theme[char_index] == L'_')
886
               cc.theme[char_index] = L' '; // convert underscores to spaces
59 pmbaty 887
         if (_wcsicmp (cc.theme, languages[language_id].name) == 0)
1 pmbaty 888
            naturallanguagechannel_index = chatterchannel_count; // if this channel is the natural language one, remember it
889
         field_start = field_stop + 1; // and continue reading the string
890
         REACH_NEXT_FIELD_ELSE (field_start, continue);
891
      }
892
 
893
      // determine the channel color according to channel ID
894
      srand (1000 + cc.id);
895
      cc.color = RGBA_TO_RGBACOLOR (rand () % 256, rand () % 256, rand () % 256, 0xff);
896
 
897
      // now read the channel members
898
      cc.members = NULL;
899
      cc.member_count = 0;
900
      while (*field_start != 0)
901
      {
902
         cc.members = (chatterchannelmember_t *) SAFE_realloc (cc.members, cc.member_count, cc.member_count + 1, sizeof (chatterchannelmember_t), false);
903
         if (*field_start == '{')
904
         {
905
            ReadNickname (cc.members[cc.member_count].nickname, WCHAR_SIZEOF (cc.members[cc.member_count].nickname), &field_start[1]);
906
            cc.members[cc.member_count].is_silenced = true; // this player plays in silence
907
         }
908
         else
909
         {
910
            ReadNickname (cc.members[cc.member_count].nickname, WCHAR_SIZEOF (cc.members[cc.member_count].nickname), field_start);
911
            cc.members[cc.member_count].is_silenced = false; // this player allows us to talk to him
912
         }
913
         if (wcscmp (cc.members[cc.member_count].nickname, local_player->name) == 0)
914
            cc.is_open = true; // if we are on this channel, mark this channel as open
915
         cc.member_count++; // reallocate, read player nickname and increase chatter channel members array size
916
         REACH_NEXT_FIELD_ELSE (field_start, continue); // and advance one player more
917
      }
918
 
919
      // all parsing was OK, reallocate chatter channels list to have one channel more
920
      chatterchannels = (chatterchannel_t *) SAFE_realloc (chatterchannels, chatterchannel_count, chatterchannel_count + 1, sizeof (chatterchannel_t), true);
921
      memcpy (&chatterchannels[chatterchannel_count], &cc, sizeof (chatterchannel_t)); // now save data
922
      chatterchannel_count++; // we know now one sought game more
923
   }
924
 
925
   // free the big buffer space we used
926
   SAFE_free ((void **) &big_buffer);
927
 
928
   // now that the chatter channels are read, find again the one that was previously selected
929
 
930
   // if no chatter channel is selected yet, and we have a natural language channel exists and this channel is not open yet...
931
   if ((previouslyselected_channelid == -1) && (naturallanguagechannel_index != -1) && !chatterchannels[naturallanguagechannel_index].is_open)
932
      Player_SendBuffer_Add (player, 1000, L"+channel %d\n", chatterchannels[naturallanguagechannel_index].id); // open this channel
933
 
934
   // cycle through all the chatter channels we know...
935
   for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)
936
      if ((previouslyselected_channelid != -1) && (chatterchannels[chatterchannel_index].id == previouslyselected_channelid))
937
         break; // break as soon as we find it
938
 
939
   // have we found none ?
940
   if (chatterchannel_index == chatterchannel_count)
941
   {
942
      // cycle through all the chatter channels we know...
943
      for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)
944
         if (chatterchannels[chatterchannel_index].is_open && (wcsistr (chatterchannels[chatterchannel_index].theme, L"chat") != NULL))
945
            break; // break on the first open general chatter channel we find
946
 
947
      // have we found none ?
948
      if (chatterchannel_index == chatterchannel_count)
949
         chatterchannel_index = 0; // ultimate fallback, select the first channel
950
   }
951
 
952
   selected_chatterchannel = &chatterchannels[chatterchannel_index]; // in the end, select the channel that was previously selected
953
 
954
   chatterchannels_updated = true; // remember chatter channels list is to be updated
955
   return; // finished evaluating this channel list
956
}
957
 
958
 
959
void EvaluateServerReply_SoughtList (player_t *player)
960
{
961
   // this function parses a network reply and evaluates it, deciding what to do
962
 
963
   wchar_t line_buffer[256];
964
   soughtgame_t sg;
965
   wchar_t *field_start;
966
   wchar_t *string_pointer;
967
 
968
   // is the sought games footer bit NOT present ?
969
   if (!IS_FIELD_PRESENT (field_start, L" ads displayed.") && !IS_FIELD_PRESENT (field_start, L" ad displayed."))
970
      return; // if so, this reply can't be a sought games list so just return
971
 
972
   SAFE_free ((void **) &soughtgames); // free the sought games list we know
973
   soughtgame_count = 0; // reset the sought games count
974
 
975
   // now read line per line
976
   string_pointer = player->recvbuffer; // start at the first character
977
   while ((string_pointer = wcsgets (line_buffer, sizeof (line_buffer), string_pointer)) != NULL)
978
   {
979
      if (line_buffer[0] == L'\n')
980
         continue; // discard empty lines
981
      else if ((wcsstr (line_buffer, L" ads displayed.") != NULL) || (wcsstr (line_buffer, L" ad displayed.") != NULL))
982
         break; // if it's the end of the list, stop reading
983
 
984
      // now parse the sought games data
985
      field_start = line_buffer;
986
      memset (&sg, 0, sizeof (sg));
987
 
988
      while (*field_start && iswspace (*field_start))
989
         field_start++; // skip leading spaces
990
      if (*field_start == 0)
991
         continue; // discard bogus lines
992
 
993
      sg.id = _wtoi (field_start); // read sought game id
994
      REACH_NEXT_FIELD_ELSE (field_start, continue);
995
      if ((*field_start != L'-') && (*field_start != L'+'))
996
         sg.rating = _wtoi (field_start); // read player rating
997
      REACH_NEXT_FIELD_ELSE (field_start, continue);
998
      ReadNickname (sg.nickname, WCHAR_SIZEOF (sg.nickname), field_start); // read nickname
999
      REACH_NEXT_FIELD_ELSE (field_start, continue);
1000
      sg.initial_time = (float) _wtof (field_start); // read initial time
1001
      REACH_NEXT_FIELD_ELSE (field_start, continue);
1002
      sg.increment = (float) _wtof (field_start); // read Fischer increment
1003
      REACH_NEXT_FIELD_ELSE (field_start, continue);
1004
      sg.rating_type = (*field_start == L'r' ? GAMERATINGTYPE_SUPPORTEDRATED : GAMERATINGTYPE_SUPPORTEDUNRATED); // read whether it is rated or not
1005
      REACH_NEXT_FIELD_ELSE (field_start, continue);
1006
      ReadGamename (sg.game_type, WCHAR_SIZEOF (sg.game_type), field_start); // read game type
1007
      sg.game_type[0] = towupper (sg.game_type[0]); // capitalize first character
1008
      REACH_NEXT_FIELD_ELSE (field_start, continue);
1009
      sg.color = COLOR_UNSPECIFIED; // set unspecified color until told otherwise
1010
      if (*field_start == L'[')
1011
      {
1012
         if (field_start[1] == L'b') sg.color = COLOR_BLACK; // read specified color
1013
         else if (field_start[1] == L'w') sg.color = COLOR_WHITE;
1014
         REACH_NEXT_FIELD_ELSE (field_start, continue);
1015
      }
1016
      if (swscanf_s (field_start, L"%d-%d", &sg.lowest_accepted, &sg.highest_accepted) != 2)
1017
         continue; // read minimal and maximal accepted ELO, and discard bogus lines
1018
 
1019
      // read whether the game will start automatically and whether the player's filter formula will be checked
1020
      if (wcsstr (field_start, L" mf") != NULL)
1021
      {
1022
         sg.manual_start = true;
1023
         sg.formula_checked = true;
1024
      }
1025
      else if (wcsstr (field_start, L" m") != NULL)
1026
         sg.manual_start = true;
1027
      else if (wcsstr (field_start, L" f") != NULL)
1028
         sg.formula_checked = true;
1029
 
1030
      // is this variant unsupported ?
1031
      if ((wcscmp (sg.game_type, L"Untimed") != 0) && (wcscmp (sg.game_type, L"Standard") != 0)
1032
            && (wcscmp (sg.game_type, L"Blitz") != 0) && (wcscmp (sg.game_type, L"Lightning") != 0))
1033
      {
1034
         sg.rating = 0;
1035
         sg.initial_time = 0.0f;
1036
         sg.increment = 0.0f;
1037
         sg.rating_type = GAMERATINGTYPE_UNSUPPORTED; // if so, clear some values to clean up the display
1038
         sg.color = COLOR_UNSPECIFIED;
1039
         sg.lowest_accepted = 0;
1040
         sg.highest_accepted = 0;
1041
         sg.manual_start = true;
1042
         sg.formula_checked = false;
1043
      }
1044
 
1045
      // all parsing was OK, reallocate sought games list to have one sought game more
1046
      soughtgames = (soughtgame_t *) SAFE_realloc (soughtgames, soughtgame_count, soughtgame_count + 1, sizeof (soughtgame_t), true);
1047
      memcpy (&soughtgames[soughtgame_count], &sg, sizeof (soughtgame_t)); // now save data
1048
      soughtgame_count++; // we know now one sought game more
1049
   }
1050
 
1051
   soughtgames_updated = true; // remember sought games display is to be updated
1052
   lastsought_time = current_time; // remember when we were last updated
1053
   return; // finished evaluating this sought games list
1054
}
1055
 
1056
 
1057
void EvaluateServerReply_PlayersList (player_t *player)
1058
{
1059
   // this function parses a network reply and evaluates it, deciding what to do
1060
 
1061
   wchar_t line_buffer[256];
1062
   onlineplayer_t olp;
1063
   wchar_t *field_stop;
1064
   wchar_t *string_pointer;
1065
   int buffer_length;
1066
   int char_index;
1067
 
1068
   // is the players list footer bit NOT present ?
1069
   if (!IS_FIELD_PRESENT (field_stop, L"(*) indicates system administrator."))
1070
      return; // if so, this reply can't be a players list so just return
1071
 
1072
   SAFE_free ((void **) &onlineplayers); // free the online players list we know
1073
   onlineplayer_count = 0; // reset the players count
1074
 
1075
   // format the player list well. We slightly modify recvbuffer here.
1076
   // for each character in string...
1077
   buffer_length = wcslen (player->recvbuffer);
1078
   for (char_index = 0; char_index < buffer_length - 1; char_index++)
1079
   {
1080
      // is it a separator (two spaces) ?
1081
      if ((player->recvbuffer[char_index] == L' ') && (player->recvbuffer[char_index + 1] == L' '))
1082
      {
1083
         while (iswspace (player->recvbuffer[char_index]))
1084
         {
1085
            player->recvbuffer[char_index] = L'\n'; // replace all spaces by newlines
1086
            char_index++; // skip all spaces and reach the next non-space character
1087
         }
1088
      }
1089
   }
1090
 
1091
   // now read line per line
1092
   string_pointer = player->recvbuffer; // start at the first character
1093
   while ((string_pointer = wcsgets (line_buffer, sizeof (line_buffer), string_pointer)) != NULL)
1094
   {
1095
      if (line_buffer[0] == L'\n')
1096
         continue; // discard empty lines
1097
      else if (wcsstr (line_buffer, L"(*) indicates system administrator.") != NULL)
1098
         break; // if it's the end of the list, stop parsing
1099
 
1100
      // now parse the player data
1101
 
1102
      // parse the handle status
1103
      if (wcschr (line_buffer, L'^') != NULL) olp.handlestatus = HANDLESTATUS_INGAME; // player is in game
1104
      else if (wcschr (line_buffer, L'~') != NULL) olp.handlestatus = HANDLESTATUS_INSIMULATION; // player is in simulation
1105
      else if (wcschr (line_buffer, L'&') != NULL) olp.handlestatus = HANDLESTATUS_INTOURNAMENT; // player is in tournament
1106
      else if (wcschr (line_buffer, L'#') != NULL) olp.handlestatus = HANDLESTATUS_EXAMININGAGAME; // player is examining a game
1107
      else if (wcschr (line_buffer, L':') != NULL) olp.handlestatus = HANDLESTATUS_NOTOPENFORAMATCH; // player is not open for a match
1108
      else if (wcschr (line_buffer, L'.') != NULL) olp.handlestatus = HANDLESTATUS_INACTIVEORBUSY; // player is inactive or busy
1109
      else olp.handlestatus = HANDLESTATUS_AVAILABLE; // player is available
1110
 
1111
      // parse the handle codes
1112
      olp.handlecodes = 0;
1113
      if (wcsstr (line_buffer, L"(*)") != NULL) olp.handlecodes |= HANDLECODE_ADMINISTRATOR; // player is administrator
1114
      if (wcsstr (line_buffer, L"(B)") != NULL) olp.handlecodes |= HANDLECODE_BLINDFOLD; // player is blindfold
1115
      if (wcsstr (line_buffer, L"(C)") != NULL) olp.handlecodes |= HANDLECODE_COMPUTER; // player is a computer
1116
      if (wcsstr (line_buffer, L"(T)") != NULL) olp.handlecodes |= HANDLECODE_TEAM; // player is several persons
1117
      if (wcsstr (line_buffer, L"(U)") != NULL) olp.handlecodes |= HANDLECODE_UNREGISTERED; // player is unregistered
1118
      if (wcsstr (line_buffer, L"(CA)") != NULL) olp.handlecodes |= HANDLECODE_CHESSADVISOR; // player is a chess advisor
1119
      if (wcsstr (line_buffer, L"(SR)") != NULL) olp.handlecodes |= HANDLECODE_SERVICEREPRESENTATIVE; // player is a service representative
1120
      if (wcsstr (line_buffer, L"(TD)") != NULL) olp.handlecodes |= HANDLECODE_TOURNAMENTDIRECTOR; // player is a tournament director
1121
      if (wcsstr (line_buffer, L"(TM)") != NULL) olp.handlecodes |= HANDLECODE_MAMERMANAGER; // player is a mamer manager
1122
      if (wcsstr (line_buffer, L"(FM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEMASTER; // player is a FIDE master
1123
      if (wcsstr (line_buffer, L"(IM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEINTERNATIONALMASTER; // player is a FIDE international master
1124
      if (wcsstr (line_buffer, L"(GM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEGREATMASTER; // player is a FIDE grand master
1125
      if (wcsstr (line_buffer, L"(WIM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEWOMENSINTERNATIONALMASTER; // player is a FIDE woman international master
1126
      if (wcsstr (line_buffer, L"(WGM)") != NULL) olp.handlecodes |= HANDLECODE_FIDEWOMENSGREATMASTER; // player is a FIDE woman great master
1127
 
1128
      // get to the first non-numeric character
1129
      buffer_length = wcslen (line_buffer);
1130
      for (char_index = 0; char_index < buffer_length; char_index++)
1131
         if (!iswdigit (line_buffer[char_index]) && (line_buffer[char_index] != L'+') && (line_buffer[char_index] != L'-'))
1132
            break; // break as soon as we find it
1133
 
1134
      if (char_index >= buffer_length - 1)
1135
         continue; // consistency check: this player is bogus
1136
 
1137
      // is it a E ? else is it a P ?
1138
      if (line_buffer[char_index] == L'E') olp.ratingtype = OPPONENTRATINGTYPE_ESTIMATED; // this player's rating is estimated
1139
      else if (line_buffer[char_index] == L'P') olp.ratingtype = OPPONENTRATINGTYPE_PROVISIONAL; // this player's rating is provisional
1140
      else olp.ratingtype = OPPONENTRATINGTYPE_DEFAULT; // this player's rating is normal
1141
 
1142
      wcscpy_s (olp.nickname, WCHAR_SIZEOF (olp.nickname), &line_buffer[char_index + 1]); // copy nickname
1143
      if ((field_stop = wcschr (olp.nickname, L'(')) != NULL)
1144
         *field_stop = 0; // separate nickname from its handle flags
1145
 
1146
      // read opponent rating
1147
      olp.rating = _wtoi (line_buffer); // ++++ (unregistered) and ---- (no rating) will be translated as 0
1148
 
1149
      // all parsing was OK, reallocate online players list to have one player more
1150
      onlineplayers = (onlineplayer_t *) SAFE_realloc (onlineplayers, onlineplayer_count, onlineplayer_count + 1, sizeof (onlineplayer_t), true);
1151
      memcpy (&onlineplayers[onlineplayer_count], &olp, sizeof (onlineplayer_t)); // now save data
1152
      onlineplayer_count++; // we know now one player more
1153
   }
1154
 
1155
   onlineplayers_updated = true; // remember online player list is to be updated
1156
   lastonlineplayers_time = current_time; // remember when we were last updated
1157
   return; // finished evaluating this players list
1158
}
1159
 
1160
 
1161
void EvaluateServerReply_GameStarting (player_t *player)
1162
{
1163
   // this function parses a network reply and evaluates it, deciding what to do
1164
 
1165
   wchar_t white_name[32];
1166
   wchar_t black_name[32];
1167
   wchar_t *field_start;
1168
   player_t *local_player;
1169
 
1170
   // is the game starting notification header bit NOT present ?
1171
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"Creating: "))
1172
      return; // if so, this reply can't be a game starting notification so just return
1173
 
1174
   // read the interesting parameters, namely white player's name (so that we know which side to display)
1175
   if (swscanf_s (field_start, L"Creating: %s %*s %s %*s %*s %*s %*d %*d", white_name, WCHAR_SIZEOF (white_name), black_name, WCHAR_SIZEOF (black_name)) != 2)
1176
      Debug_Log (L"===WARNING: unable to parse game starting notification message!===\n%s\n======\n", field_start);
1177
 
1178
   local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the local player
1179
 
1180
   // is local player NOT the white color AND should be, OR is local player white color AND should NOT be ?
1181
   if (((local_player->color != COLOR_WHITE) && (wcscmp (white_name, local_player->name) == 0))
1182
       || ((local_player->color == COLOR_WHITE) && (wcscmp (white_name, local_player->name) != 0)))
1183
   {
1184
      Debug_Log (L"===Game starting and local player is not white and should be (or is white and should not be), swapping sides===\n");
1185
      the_board.want_playerswap = true; // swap sides
1186
   }
1187
 
1188
   // display the game starting message notification
140 pmbaty 1189
   Scene_UpdateText (&the_scene.gui.central_text, RGBA_TO_RGBACOLOR (255, 255, 255, 191), 5.0f, true, L"\n\n\n\n\n%s\n%s (%s) %s %s (%s)", LOCALIZE (L"NewGame_Title"), white_name, LOCALIZE (L"Games_White"), LOCALIZE (L"Versus"), black_name, LOCALIZE (L"Games_Black"));
1 pmbaty 1190
 
1191
   // close any possible singleton window
1192
   if (IsWindow (hChatterChannelsWnd))
1193
      DestroyWindow (hChatterChannelsWnd);
1194
   if (IsWindow (hGamesWnd))
1195
      DestroyWindow (hGamesWnd); // PGN games window (improbable)
1196
   if (IsWindow (hMOTDWnd))
1197
      DestroyWindow (hMOTDWnd);
1198
   if (IsWindow (hOpponentsWnd))
1199
      DestroyWindow (hOpponentsWnd);
1200
   if (IsWindow (hSoughtWnd))
1201
      DestroyWindow (hSoughtWnd);
1202
 
1203
   return; // finished evaluating the game starting notification
1204
}
1205
 
1206
 
1207
void EvaluateServerReply_GameState (player_t *player)
1208
{
1209
   // this function parses a network reply and evaluates it, deciding what to do
1210
 
1211
   // Format of style12 computer-parseable lines (ICC/FICS):
1212
   // 
1213
   // <12> rnbqkb-r pppppppp -----n-- -------- ----P--- -------- PPPPKPPP RNBQ-BNR B -1 0 0 1 1 0 7 Newton Einstein 1 2 12 39 39 119 122 2 K/e1-e2 (0:06) Ke2 0
1214
   // 
1215
   // This string always begins on a new line, and there are always exactly 31 non-empty fields separated by blanks. The fields are:
1216
   // 
1217
   // * the string "<12>" to identify this line.
1218
   // * eight fields representing the board position. First one is White's 8th rank (also Black's 1st rank), then White's 7th rank (also Black's 2nd), etc.
1219
   // * color whose turn it is to move ("B" or "W")
1220
   // * -1 if the previous move was NOT a double pawn push, otherwise the chess board file  (numbered 0--7 for a--h) in which the double push was made
1221
   // * can White still castle short? (0=no, 1=yes)
1222
   // * can White still castle long?
1223
   // * can Black still castle short?
1224
   // * can Black still castle long?
1225
   // * the number of moves made since the last irreversible move (0 if last move was irreversible. If value is >= 100, game can be declared a draw due to the 50 move rule.)
1226
   // * The game number
1227
   // * White's name
1228
   // * Black's name
1229
   // * my relation to this game:
1230
   //     -3 isolated position, such as for "ref 3" or the "sposition" command
1231
   //     -2 I am observing game being examined
1232
   //      2 I am the examiner of this game
1233
   //     -1 I am playing, it is my opponent's move
1234
   //      1 I am playing and it is my move
1235
   //      0 I am observing a game being played
1236
   // * initial time (in seconds) of the match
1237
   // * increment In seconds) of the match
1238
   // * White material strength
1239
   // * Black material strength
1240
   // * White's remaining time
1241
   // * Black's remaining time
1242
   // * the number of the move about to be made (standard chess numbering -- White's and Black's first moves are both 1, etc.)
1243
   // * verbose coordinate notation for the previous move ("none" if there were none) [note this used to be broken for examined games]
1244
   // * time taken to make previous move "(min:sec)".
1245
   // * pretty notation for the previous move ("none" if there is none)
1246
   // * flip field for board orientation: 1 = Black at bottom, 0 = White at bottom.
1247
   // 
1248
   // In the future, new fields may be added to the end of the data string, so programs should parse from left to right.
1249
   // 
1250
   // Special information for bughouse games
1251
   // --------------------------------------
1252
   // When showing positions from bughouse games, a second line showing piece holding is given, with "<b1>" at the beginning, for example:
1253
   //   <b1> game 6 white [PNBBB] black [PNB]
1254
   // Also, when pieces are "passed" during bughouse, a short data string -- not the entire board position -- is sent.  For example:
1255
   //   <b1> game 52 white [NB] black [N] <- BN
1256
   // The final two letters indicate the piece that was passed; in the above example, a knight (N) was passed to Black.
1257
   // A prompt may preceed the <b1> header.
1258
 
1259
   wchar_t positions[65];
1260
   wchar_t move_color_as_string[2];
1261
   int move_color;
1262
   int player_color;
1263
   int pawnrush_column;
1264
   int can_white_castle_short;
1265
   int can_white_castle_long;
1266
   int can_black_castle_short;
1267
   int can_black_castle_long;
1268
   int number_of_moves_since_last_irreversible_move;
1269
   int game_number;
1270
   wchar_t white_name[32];
1271
   wchar_t black_name[32];
1272
   int my_status;
1273
   int white_remaining_time_in_seconds;
1274
   int black_remaining_time_in_seconds;
1275
   int turn_number_1_based;
1276
   int move_index;
1277
   wchar_t pretty_movestring[8];
1278
   boardmove_t move;
1279
   wchar_t *field_start;
1280
   int recognized_fields;
1281
   boardmove_t *last_move;
1282
 
1283
   // is it NOT a style12 line reply ?
1284
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"<12> "))
1285
      return; // if so, this reply can't be a style12 notification so just return
1286
 
1287
   recognized_fields = swscanf_s (field_start, L"<12> " // style12 header
1288
                                               L"%s %s %s %s %s %s %s %s " // "rnbqkbnr pppppppp -------- -------- -------- -------- PPPPPPPP RNBQKBNR"
1289
                                               L"%s %d %d %d %d %d %d %d " // "W -1 1 1 1 1 0 2"
1290
                                               L"%s %s %d %*d %*d %*d %*d " // "guestpmtelnet GuestJXTJ -1 2 12 39 39"
1291
                                               L"%d %d %d %*s %*s %s %*d", // "120 120 1 none (0:00) none 1 0 0"
1292
                                               &positions[0 * 8], 9, &positions[1 * 8], 9, &positions[2 * 8], 9, &positions[3 * 8], 9, &positions[4 * 8], 9, &positions[5 * 8], 9, &positions[6 * 8], 9, &positions[7 * 8], 9,
1293
                                               move_color_as_string, WCHAR_SIZEOF (move_color_as_string), &pawnrush_column, &can_white_castle_short, &can_white_castle_long, &can_black_castle_short, &can_black_castle_long, &number_of_moves_since_last_irreversible_move, &game_number,
1294
                                               white_name, WCHAR_SIZEOF (white_name), black_name, WCHAR_SIZEOF (black_name), &my_status,
1295
                                               &white_remaining_time_in_seconds, &black_remaining_time_in_seconds, &turn_number_1_based, pretty_movestring, WCHAR_SIZEOF (pretty_movestring));
1296
   if (recognized_fields != 23)
1297
      return; // unparseable style12 reply
1298
 
1299
   // remember we are in game and which game it is
1300
   player->is_in_game = true;
1301
   player->game_number = game_number;
1302
 
1303
   // convert some data into easier formats first. The color which just moved is the opposite of the color to move now
1304
   move_color = (towupper (move_color_as_string[0]) == L'W' ? COLOR_BLACK : COLOR_WHITE);
1305
   move_index = (2 * turn_number_1_based) - 2 + (move_color == COLOR_WHITE ? 1 : 0);
1306
 
1307
   // save the unquestionable parameters (direct it to the opposite player if the board is going to be swapped)
1308
   player_color = the_board.want_playerswap ? COLOR_WHITE : COLOR_BLACK;
1309
   ReadNickname (the_board.players[player_color].name, WCHAR_SIZEOF (the_board.players[player_color].name), black_name);
1310
   the_board.players[player_color].remaining_seconds = black_remaining_time_in_seconds;
1311
 
1312
   player_color = the_board.want_playerswap ? COLOR_BLACK : COLOR_WHITE;
1313
   ReadNickname (the_board.players[player_color].name, WCHAR_SIZEOF (the_board.players[player_color].name), white_name);
1314
   the_board.players[player_color].remaining_seconds = white_remaining_time_in_seconds;
1315
 
1316
   // is it an initial board position ?
1317
   if (wcscmp (pretty_movestring, L"none") == 0)
1318
   {
1319
      Debug_Log (L"===Setting up board according to chess server's specifications and beginning new game===\n");
1320
 
1321
      // set up the board according to what the chess server tells us
1322
      memset (&move, 0, sizeof (move));
1323
      Move_SetupFromStyle12 (&move, positions, move_color, pawnrush_column,
1324
                              (can_white_castle_short != 0), (can_white_castle_long != 0), (can_black_castle_short != 0), (can_black_castle_long != 0), pretty_movestring);
1325
      Board_Reset (&the_board, move.fen_string);
1326
   }
1327
 
1328
   // else play the move and swap sides
1329
   else
1330
   {
1331
      Debug_Log (L"===Received board status update from chess server===\n");
1332
 
1333
      // are we appending a new move ?
1334
      if (((move_color == player->color) && (move_index == the_board.move_count)) // either a new move by our opponent
1335
          || ((move_color != player->color) && (move_index == the_board.move_count - 1))) // or a confirmation of a move already played by the local player
1336
      {
1337
         Debug_Log (L"===Appending new move %d===\n", move_index);
1338
 
1339
         // get a pointer to the previous move
1340
         last_move = &the_board.moves[the_board.move_count - 1];
1341
 
1342
         // is the server reporting the remote player's move ?
1343
         if (move_color == player->color)
1344
         {
1345
            // evaluate the move string
1346
            wcscpy_s (move.pgntext, WCHAR_SIZEOF (move.pgntext), pretty_movestring);
1347
            if (!Move_SetupFromSAN (last_move, &move, player->color))
1348
               Debug_Log (L"===WARNING: unable to interpret server's table state while evaluating its notification of remote player move!===\n%s\n======\n", pretty_movestring);
1349
 
1350
            // play the remote opponent's move
1351
            Board_AppendMove (&the_board, move.source[0], move.source[1], move.target[0], move.target[1], move.promotion_type, NULL);
1352
 
171 pmbaty 1353
            the_board.players[1 - player->color].should_wakeup = true; // tell the opposite player to wake up
1 pmbaty 1354
            animation_endtime = current_time + ANIMATION_DURATION; // play move animation now
1355
         }
1356
 
1357
         // else the server is acknowledging the local player's move
1358
         else
1359
         {
1360
            // evaluate the move string
1361
            wcscpy_s (last_move->pgntext, WCHAR_SIZEOF (last_move->pgntext), pretty_movestring);
1362
            if (!Move_SetupFromSAN (&the_board.moves[the_board.move_count - 2], last_move, last_move->color))
1363
               Debug_Log (L"===WARNING: unable to interpret server's table state while evaluating its reply after local player move!===\n%s\n======\n", pretty_movestring);
1364
         }
1365
 
1366
         // in case a new move was appended, update the last move pointer
1367
         last_move = &the_board.moves[the_board.move_count - 1];
1368
      }
1369
 
1370
      // else the server must be time-warping the game backwards
1371
      else
1372
      {
1373
         Debug_Log (L"===Backing up to move %d===\n", move_index);
1374
 
1375
         last_move = &the_board.moves[move_index]; // get a pointer to the move we're backing up to
1376
         the_board.move_count = move_index + 1; // update the board's move count
1377
         if (the_board.viewed_move > the_board.move_count - 1)
1378
            the_board.viewed_move = the_board.move_count - 1; // and the board's viewed move as well
1379
 
1380
         // send a notification to the player's chat window
161 pmbaty 1381
         Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackAccepted"), NULL);
1 pmbaty 1382
      }
1383
 
1384
      // make the server set it up correctly
1385
      Move_SetupFromStyle12 (last_move, positions, move_color, pawnrush_column,
1386
                              (can_white_castle_short != 0), (can_white_castle_long != 0), (can_black_castle_short != 0), (can_black_castle_long != 0), pretty_movestring);
177 pmbaty 1387
 
1388
      Debug_LogMove (&the_board.moves[the_board.move_count - 1], L"===Here is the updated board state as reported by the engine===\n");
1 pmbaty 1389
   }
1390
 
136 pmbaty 1391
   the_board.game_state = STATE_PLAYING; // remember that a game is currently playing (this may be overwritten later by EvaluateServerReply_GameResults)
1 pmbaty 1392
   the_board.reevaluate = true; // and reeevaluate the board
1393
   return; // finished evaluating this style12 notification line
1394
}
1395
 
1396
 
1397
void EvaluateServerReply_GameResults (player_t *player)
1398
{
1399
   // this function parses a network reply and evaluates it, deciding what to do
1400
 
1401
   wchar_t *field_start;
1402
   int game_number;
1403
 
1404
   // is the game results notification header bit NOT present ?
1405
   if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"{Game "))
1406
      return; // if so, this reply can't be a game results notification so just return
1407
 
1408
   // are we NOT in game yet OR is it a game creation message ?
1409
   if (!player->is_in_game || (wcsstr (field_start, L") Creating") != NULL))
1410
      return; // if so, this message is not a game results but a game creation message
1411
 
1412
   // verify it's for the game we're playing
1413
   if (swscanf_s (field_start, L"{Game %d ", &game_number) != 1)
1414
   {
1415
      Debug_Log (L"===WARNING: unable to parse game results notification message!===\n%s\n======\n", field_start);
1416
      return; // on error, drop a warning in the log file and return
1417
   }
1418
   if (game_number != player->game_number)
1419
   {
1420
      Debug_Log (L"===WARNING: received game results notification message with wrong game number! Ignoring.===\n");
1421
      return; // on error, drop a warning in the log file and return
1422
   }
1423
 
1424
   ////////////////////////////
1425
   // interpret the game result
1426
 
1427
   // do the white win ?
1428
   if (wcsstr (field_start, L"} 1-0") != NULL)
1429
   {
1430
      // is it a checkmate ?
1431
      if (wcsstr (field_start, L"checkmate") != NULL)
1432
      {
1433
         Debug_Log (L"===Server tells us that black is checkmate: white wins!===\n");
1434
         the_board.game_state = STATE_WHITEWIN_CHECKMATE; // remember game state
1435
      }
1436
 
1437
      // else it must be a resign, a forfeit or an adjudication
1438
      else
1439
      {
1440
         Debug_Log (L"===Server tells us that black [resigns|forfeits|loses adjudication]: white wins!===\n");
1441
         the_board.game_state = STATE_WHITEWIN_RESIGNORFORFEIT; // remember game state
1442
      }
1443
 
116 pmbaty 1444
      // if white player is human, play the victory sound, else, play defeat sound at the center of the board
185 pmbaty 1445
      Audio_PlaySoundAtCenter (the_board.players[COLOR_WHITE].type == PLAYER_HUMAN ? SOUNDTYPE_VICTORY : SOUNDTYPE_DEFEAT);
1 pmbaty 1446
   }
1447
 
1448
   // else do the black win ?
1449
   else if (wcsstr (field_start, L"} 0-1") != NULL)
1450
   {
1451
      // is it a checkmate ?
1452
      if (wcsstr (field_start, L" checkmate") != NULL)
1453
      {
1454
         Debug_Log (L"===Server tells us that white is checkmate: black wins!===\n");
1455
         the_board.game_state = STATE_BLACKWIN_CHECKMATE; // remember game state
1456
      }
1457
 
1458
      // else it must be a resign, a forfeit or an adjudication
1459
      else
1460
      {
1461
         Debug_Log (L"===Server tells us that white [resigns|forfeits|loses adjudication]: black wins!===\n");
1462
         the_board.game_state = STATE_BLACKWIN_RESIGNORFORFEIT; // remember game state
1463
      }
1464
 
116 pmbaty 1465
      // if black player is human, play the victory sound, else, play defeat sound at the center of the board
185 pmbaty 1466
      Audio_PlaySoundAtCenter (the_board.players[COLOR_BLACK].type == PLAYER_HUMAN ? SOUNDTYPE_VICTORY : SOUNDTYPE_DEFEAT);
1 pmbaty 1467
   }
1468
 
1469
   // else is it a draw ?
1470
   else if (wcsstr (field_start, L"} 1/2-1/2") != NULL)
1471
   {
1472
      // is it a stalemate ?
1473
      if (wcsstr (field_start, L" stalemate") != NULL)
1474
      {
1475
         Debug_Log (L"===Server tells us it's a stalemate: game is a draw===\n");
1476
         the_board.game_state = STATE_DRAW_STALEMATE; // remember game state
1477
      }
1478
 
1479
      // else is it a mutual agreement ?
1480
      else if (wcsstr (field_start, L" mutual agreement") != NULL)
1481
      {
1482
         Debug_Log (L"===Server tells us it's a draw by mutual agreement: game is a draw===\n");
1483
         the_board.game_state = STATE_DRAW_AGREEMENT; // remember game state
1484
      }
1485
 
1486
      // else it's another reason
1487
      else
1488
      {
1489
         Debug_Log (L"===Server tells us it's a draw for another reason: game is a draw===\n");
1490
         the_board.game_state = STATE_DRAW_OTHER; // remember game state
1491
      }
1492
 
116 pmbaty 1493
      // play a defeat sound at the center of the board
185 pmbaty 1494
      Audio_PlaySoundAtCenter (SOUNDTYPE_DEFEAT);
1 pmbaty 1495
   }
1496
 
1497
   // else is it an adjournment ?
1498
   else if (wcsstr (field_start, L"} *") != NULL)
1499
   {
1500
      Debug_Log (L"===Server tells the game is adjourned: game adjourned===\n");
1501
      the_board.game_state = STATE_ADJOURNED; // remember game state
1502
   }
1503
 
1504
   // else we can't interpret the game state
1505
   else
1506
   {
1507
      Debug_Log (L"===WARNING: unable to interpret game results notification message!===\n%s\n======\n", field_start);
1508
      return; // on error, drop a warning in the log file and return
1509
   }
1510
 
1511
   // remember player is no longer in game
1512
   player->is_in_game = false;
1513
   player->game_number = 0;
1514
 
1515
   // reevaluate the board and display the endgame dialog box
1516
   the_board.reevaluate = true;
1517
   DialogBox_EndGame ();
1518
 
1519
   return; // finished evaluating the game results notification
1520
}
1521
 
1522
 
1523
static void ReadNickname (wchar_t *nickname, size_t nickname_size, wchar_t *from_string)
1524
{
1525
   // helper function to read a nickname and strip it from its eventual flags
1526
 
1527
   unsigned int char_index;
1528
 
1529
   // as long as we don't read a forbidden character...
1530
   for (char_index = 0; char_index < nickname_size; char_index++)
1531
      if (iswalpha (from_string[char_index]))
1532
         nickname[char_index] = from_string[char_index]; // copy nickname one character after the other
1533
      else
1534
         break; // else stop copying immediately
1535
 
1536
   if (char_index < nickname_size)
1537
      nickname[char_index] = 0; // finish the string ourselves
1538
   else
1539
      nickname[nickname_size - 1] = 0; // truncate it if neeeded
1540
 
1541
   return; // finished
1542
}
1543
 
1544
 
1545
static void ReadGamename (wchar_t *gamename, size_t gamename_size, wchar_t *from_string)
1546
{
1547
   // helper function to read a game name
1548
 
1549
   unsigned int char_index;
1550
 
1551
   // as long as we don't read a forbidden character...
1552
   for (char_index = 0; char_index < gamename_size; char_index++)
1553
      if (iswgraph (from_string[char_index]))
1554
         gamename[char_index] = from_string[char_index]; // copy game name one character after the other
1555
      else
1556
         break; // else stop copying immediately
1557
 
1558
   if (char_index < gamename_size)
1559
      gamename[char_index] = 0; // finish the string ourselves
1560
   else
1561
      gamename[gamename_size - 1] = 0; // truncate it if neeeded
1562
 
1563
   return; // finished
1564
}
1565
 
1566
 
1567
static void ReadSpannedLine (wchar_t *outstring, size_t outstring_size, wchar_t *multiline_string)
1568
{
1569
   // this function linearizes a multiline string and takes out any special formatting character of it
1570
 
1571
   int length;
1572
   int read_index;
1573
   unsigned int write_index;
1574
 
1575
   length = wcslen (multiline_string); // get text length first
1576
 
1577
   // for each character in string...
1578
   write_index = 0;
1579
   for (read_index = 0; read_index < length; read_index++)
1580
   {
1581
      if (wcsncmp (&multiline_string[read_index], L"\\   ", 4) == 0)
1582
         read_index += 4; // if it's a new line indentation, skip it
1583
 
1584
      // are we NOT reading a newline followed by a backslash AND is it still room in the output string ?
1585
      if (!((read_index < length - 1) && (multiline_string[read_index] == L'\n') && (multiline_string[read_index + 1] == L'\\'))
1586
          && (write_index < outstring_size - 1))
1587
      {
1588
         if (multiline_string[read_index] == L'\n')
1589
            break; // if it's a newline (without backslash following), stop reading
1590
 
1591
         outstring[write_index] = multiline_string[read_index]; // else copy this character to output string
1592
         write_index++; // and advance in string
1593
      }
1594
   }
1595
 
1596
   outstring[write_index] = 0; // finish string
1597
   return; // and return
1598
}