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 | } |