Subversion Repositories Games.Chess Giants

Rev

Rev 171 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  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
  98.          Scene_AddAnnouncement (&the_scene, announcement_text); // and put it in place
  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
  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()
  379.             playercard->disconnection_month = MonthStringToNumber (month_str); // convert month from string to number
  380.             playercard->disconnection_year = number; // reuse the "number" variable, which is an int, and we need an int in swscanf_s()
  381.          }
  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 ?
  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)*/)
  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.  
  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.  
  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
  887.          if (_wcsicmp (cc.theme, languages[language_id].name) == 0)
  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
  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"));
  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.  
  1353.             the_board.players[1 - player->color].should_wakeup = true; // tell the opposite player to wake up
  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
  1381.          Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackAccepted"), NULL);
  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);
  1387.    }
  1388.  
  1389.    the_board.game_state = STATE_PLAYING; // remember that a game is currently playing (this may be overwritten later by EvaluateServerReply_GameResults)
  1390.    the_board.reevaluate = true; // and reeevaluate the board
  1391.    return; // finished evaluating this style12 notification line
  1392. }
  1393.  
  1394.  
  1395. void EvaluateServerReply_GameResults (player_t *player)
  1396. {
  1397.    // this function parses a network reply and evaluates it, deciding what to do
  1398.  
  1399.    wchar_t *field_start;
  1400.    int game_number;
  1401.  
  1402.    // is the game results notification header bit NOT present ?
  1403.    if (!IS_FIELD_PRESENT_AT_BEGINNING_OF_LINE (field_start, L"{Game "))
  1404.       return; // if so, this reply can't be a game results notification so just return
  1405.  
  1406.    // are we NOT in game yet OR is it a game creation message ?
  1407.    if (!player->is_in_game || (wcsstr (field_start, L") Creating") != NULL))
  1408.       return; // if so, this message is not a game results but a game creation message
  1409.  
  1410.    // verify it's for the game we're playing
  1411.    if (swscanf_s (field_start, L"{Game %d ", &game_number) != 1)
  1412.    {
  1413.       Debug_Log (L"===WARNING: unable to parse game results notification message!===\n%s\n======\n", field_start);
  1414.       return; // on error, drop a warning in the log file and return
  1415.    }
  1416.    if (game_number != player->game_number)
  1417.    {
  1418.       Debug_Log (L"===WARNING: received game results notification message with wrong game number! Ignoring.===\n");
  1419.       return; // on error, drop a warning in the log file and return
  1420.    }
  1421.  
  1422.    ////////////////////////////
  1423.    // interpret the game result
  1424.  
  1425.    // do the white win ?
  1426.    if (wcsstr (field_start, L"} 1-0") != NULL)
  1427.    {
  1428.       // is it a checkmate ?
  1429.       if (wcsstr (field_start, L"checkmate") != NULL)
  1430.       {
  1431.          Debug_Log (L"===Server tells us that black is checkmate: white wins!===\n");
  1432.          the_board.game_state = STATE_WHITEWIN_CHECKMATE; // remember game state
  1433.       }
  1434.  
  1435.       // else it must be a resign, a forfeit or an adjudication
  1436.       else
  1437.       {
  1438.          Debug_Log (L"===Server tells us that black [resigns|forfeits|loses adjudication]: white wins!===\n");
  1439.          the_board.game_state = STATE_WHITEWIN_RESIGNORFORFEIT; // remember game state
  1440.       }
  1441.  
  1442.       // if white player is human, play the victory sound, else, play defeat sound at the center of the board
  1443.       Audio_PlaySound (the_board.players[COLOR_WHITE].type == PLAYER_HUMAN ? SOUNDTYPE_VICTORY : SOUNDTYPE_DEFEAT, 0.0f, 0.0f, 0.04f);
  1444.    }
  1445.  
  1446.    // else do the black win ?
  1447.    else if (wcsstr (field_start, L"} 0-1") != NULL)
  1448.    {
  1449.       // is it a checkmate ?
  1450.       if (wcsstr (field_start, L" checkmate") != NULL)
  1451.       {
  1452.          Debug_Log (L"===Server tells us that white is checkmate: black wins!===\n");
  1453.          the_board.game_state = STATE_BLACKWIN_CHECKMATE; // remember game state
  1454.       }
  1455.  
  1456.       // else it must be a resign, a forfeit or an adjudication
  1457.       else
  1458.       {
  1459.          Debug_Log (L"===Server tells us that white [resigns|forfeits|loses adjudication]: black wins!===\n");
  1460.          the_board.game_state = STATE_BLACKWIN_RESIGNORFORFEIT; // remember game state
  1461.       }
  1462.  
  1463.       // if black player is human, play the victory sound, else, play defeat sound at the center of the board
  1464.       Audio_PlaySound (the_board.players[COLOR_BLACK].type == PLAYER_HUMAN ? SOUNDTYPE_VICTORY : SOUNDTYPE_DEFEAT, 0.0f, 0.0f, 0.04f);
  1465.    }
  1466.  
  1467.    // else is it a draw ?
  1468.    else if (wcsstr (field_start, L"} 1/2-1/2") != NULL)
  1469.    {
  1470.       // is it a stalemate ?
  1471.       if (wcsstr (field_start, L" stalemate") != NULL)
  1472.       {
  1473.          Debug_Log (L"===Server tells us it's a stalemate: game is a draw===\n");
  1474.          the_board.game_state = STATE_DRAW_STALEMATE; // remember game state
  1475.       }
  1476.  
  1477.       // else is it a mutual agreement ?
  1478.       else if (wcsstr (field_start, L" mutual agreement") != NULL)
  1479.       {
  1480.          Debug_Log (L"===Server tells us it's a draw by mutual agreement: game is a draw===\n");
  1481.          the_board.game_state = STATE_DRAW_AGREEMENT; // remember game state
  1482.       }
  1483.  
  1484.       // else it's another reason
  1485.       else
  1486.       {
  1487.          Debug_Log (L"===Server tells us it's a draw for another reason: game is a draw===\n");
  1488.          the_board.game_state = STATE_DRAW_OTHER; // remember game state
  1489.       }
  1490.  
  1491.       // play a defeat sound at the center of the board
  1492.       Audio_PlaySound (SOUNDTYPE_DEFEAT, 0.0f, 0.0f, 0.04f);
  1493.    }
  1494.  
  1495.    // else is it an adjournment ?
  1496.    else if (wcsstr (field_start, L"} *") != NULL)
  1497.    {
  1498.       Debug_Log (L"===Server tells the game is adjourned: game adjourned===\n");
  1499.       the_board.game_state = STATE_ADJOURNED; // remember game state
  1500.    }
  1501.  
  1502.    // else we can't interpret the game state
  1503.    else
  1504.    {
  1505.       Debug_Log (L"===WARNING: unable to interpret game results notification message!===\n%s\n======\n", field_start);
  1506.       return; // on error, drop a warning in the log file and return
  1507.    }
  1508.  
  1509.    // remember player is no longer in game
  1510.    player->is_in_game = false;
  1511.    player->game_number = 0;
  1512.  
  1513.    // reevaluate the board and display the endgame dialog box
  1514.    the_board.reevaluate = true;
  1515.    DialogBox_EndGame ();
  1516.  
  1517.    return; // finished evaluating the game results notification
  1518. }
  1519.  
  1520.  
  1521. static void ReadNickname (wchar_t *nickname, size_t nickname_size, wchar_t *from_string)
  1522. {
  1523.    // helper function to read a nickname and strip it from its eventual flags
  1524.  
  1525.    unsigned int char_index;
  1526.  
  1527.    // as long as we don't read a forbidden character...
  1528.    for (char_index = 0; char_index < nickname_size; char_index++)
  1529.       if (iswalpha (from_string[char_index]))
  1530.          nickname[char_index] = from_string[char_index]; // copy nickname one character after the other
  1531.       else
  1532.          break; // else stop copying immediately
  1533.  
  1534.    if (char_index < nickname_size)
  1535.       nickname[char_index] = 0; // finish the string ourselves
  1536.    else
  1537.       nickname[nickname_size - 1] = 0; // truncate it if neeeded
  1538.  
  1539.    return; // finished
  1540. }
  1541.  
  1542.  
  1543. static void ReadGamename (wchar_t *gamename, size_t gamename_size, wchar_t *from_string)
  1544. {
  1545.    // helper function to read a game name
  1546.  
  1547.    unsigned int char_index;
  1548.  
  1549.    // as long as we don't read a forbidden character...
  1550.    for (char_index = 0; char_index < gamename_size; char_index++)
  1551.       if (iswgraph (from_string[char_index]))
  1552.          gamename[char_index] = from_string[char_index]; // copy game name one character after the other
  1553.       else
  1554.          break; // else stop copying immediately
  1555.  
  1556.    if (char_index < gamename_size)
  1557.       gamename[char_index] = 0; // finish the string ourselves
  1558.    else
  1559.       gamename[gamename_size - 1] = 0; // truncate it if neeeded
  1560.  
  1561.    return; // finished
  1562. }
  1563.  
  1564.  
  1565. static void ReadSpannedLine (wchar_t *outstring, size_t outstring_size, wchar_t *multiline_string)
  1566. {
  1567.    // this function linearizes a multiline string and takes out any special formatting character of it
  1568.  
  1569.    int length;
  1570.    int read_index;
  1571.    unsigned int write_index;
  1572.  
  1573.    length = wcslen (multiline_string); // get text length first
  1574.  
  1575.    // for each character in string...
  1576.    write_index = 0;
  1577.    for (read_index = 0; read_index < length; read_index++)
  1578.    {
  1579.       if (wcsncmp (&multiline_string[read_index], L"\\   ", 4) == 0)
  1580.          read_index += 4; // if it's a new line indentation, skip it
  1581.  
  1582.       // are we NOT reading a newline followed by a backslash AND is it still room in the output string ?
  1583.       if (!((read_index < length - 1) && (multiline_string[read_index] == L'\n') && (multiline_string[read_index + 1] == L'\\'))
  1584.           && (write_index < outstring_size - 1))
  1585.       {
  1586.          if (multiline_string[read_index] == L'\n')
  1587.             break; // if it's a newline (without backslash following), stop reading
  1588.  
  1589.          outstring[write_index] = multiline_string[read_index]; // else copy this character to output string
  1590.          write_index++; // and advance in string
  1591.       }
  1592.    }
  1593.  
  1594.    outstring[write_index] = 0; // finish string
  1595.    return; // and return
  1596. }
  1597.