// network.cpp
#include "common.h"
// prototypes of local functions
static void StartThread_ListenToServer (void *thread_parms);
static void EvaluateServerReply (player_t *player);
bool PlayerNetwork_Init (player_t *player)
{
// this function initializes the network layer and connects to the chess server
WSADATA wsaData;
struct hostent *hostinfo;
struct sockaddr_in service;
unsigned long nonblocking_mode;
char ascii_hostname[256];
onlineplayers = NULL; // ensure the online players array is empty
onlineplayer_count = 0; // and reset the online players count
onlineplayers_updated = false;
soughtgames = NULL; // ensure the sought games array is empty
soughtgame_count = 0; // and reset the sought games count
soughtgames_updated = false;
lastsought_time = 0;
chatterchannels = NULL; // ensure the chatter channels array is empty
chatterchannel_count = 0; // and reset the chatter channels count
chatterchannels_updated = false;
selected_chatterchannel = NULL;
PlayerCards_Init (); // initialize the player cards array
Challenges_Init (); // initialize the challenges array
Interlocutors_Init (); // initialize the interlocutors array
server_motd[0] = 0; // reset the server MOTD
player->is_connected = false; // remember we aren't connected yet
player->is_logged_in = false; // and not logged in either
player->is_in_game = false; // we aren't in game either
player->game_number = 0;
player->remaining_seconds = 0;
player->our_socket = INVALID_SOCKET; // and that we have no socket yet
player->name[0] = 0; // player name undefined
// initialize WinSock
if (WSAStartup (MAKEWORD (2, 2), &wsaData) != 0)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
messagebox.flags = MB_ICONWARNING | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (false);
}
// get the chess server's IP address from the host name
ConvertTo7BitASCII (ascii_hostname, sizeof (ascii_hostname), options.network.server_address);
hostinfo = gethostbyname (ascii_hostname);
if (hostinfo == NULL)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
messagebox.flags = MB_ICONWARNING | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (false);
}
// fill in the sockaddr server structure with the server hostinfo data
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr (inet_ntoa (*(struct in_addr *) hostinfo->h_addr_list[0]));
service.sin_port = htons (options.network.server_port);
// create our socket
if ((player->our_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
messagebox.flags = MB_ICONWARNING | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (false);
}
// connect the game to the chess server using that socket
if (connect (player->our_socket, (struct sockaddr *) &service, sizeof (service)) == -1)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
messagebox.flags = MB_ICONWARNING | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (false);
}
// set it to be non-blocking (but only AFTER the connection is made)
nonblocking_mode = 1;
if (ioctlsocket (player->our_socket, FIONBIO, &nonblocking_mode) == SOCKET_ERROR)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
messagebox.flags = MB_ICONWARNING | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (false);
}
// initialize the debug log file
Debug_Init (L"Chess server output.txt");
player->is_connected = true; // remember we are connected
return (true); // finished, we have a valid, connected socket
}
void PlayerNetwork_Shutdown (player_t *player)
{
// this function shutdowns the network layer
int chatterchannel_index;
// shutdown the socket if it is open. This will disconnect us from the server.
if (player->our_socket != INVALID_SOCKET)
closesocket (player->our_socket); // close the network socket
player->our_socket = INVALID_SOCKET;
player->is_connected = false; // remember we are no longer connected
player->is_logged_in = false; // and not logged in either
player->is_in_game = false; // we aren't in game either
player->game_number = 0;
player->remaining_seconds = 0;
player->name[0] = 0; // player name undefined
server_motd[0] = 0; // reset the server MOTD
// cycle through all chatter channels we know...
for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)
{
SAFE_free ((void **) &chatterchannels[chatterchannel_index].members); // free their member list
chatterchannels[chatterchannel_index].member_count = 0; // reset their members count
}
SAFE_free ((void **) &chatterchannels); // free the chatter channels array
chatterchannel_count = 0; // and reset the chatter channels count
chatterchannels_updated = false;
selected_chatterchannel = NULL;
SAFE_free ((void **) &soughtgames); // free the sought games array
soughtgame_count = 0; // and reset the sought games count
soughtgames_updated = false;
SAFE_free ((void **) &onlineplayers); // free the online players array
onlineplayer_count = 0; // and reset the online players count
onlineplayers_updated = false;
Interlocutors_Shutdown (); // shutdown the interlocutors
Challenges_Shutdown (); // shutdown the challenges
PlayerCards_Shutdown (); // shutdown the player cards
// shutdown all possible opened UI windows
if (IsWindow (hChatterChannelsWnd))
DestroyWindow (hChatterChannelsWnd);
hChatterChannelsWnd = NULL;
if (IsWindow (hGamesWnd))
DestroyWindow (hGamesWnd);
hGamesWnd = NULL;
if (IsWindow (hMOTDWnd))
DestroyWindow (hMOTDWnd);
hMOTDWnd = NULL;
if (IsWindow (hOpponentsWnd))
DestroyWindow (hOpponentsWnd);
hOpponentsWnd = NULL;
if (IsWindow (hSoughtWnd))
DestroyWindow (hSoughtWnd);
hSoughtWnd = NULL;
// shutdown WinSock
WSACleanup ();
return; // finished
}
bool PlayerNetwork_Think (player_t *player)
{
// this function is called once per game tick to listen to the network and react to what the server tells us. Returns TRUE
// if we need to update the scene.
player_t *local_player;
char *block_start;
char *block_end;
char *ascii_sendbuffer; // mallocated
wchar_t *widechar_buffer; // mallocated
wchar_t *widechar_line;
int send_retval;
int recv_retval;
int byte_index;
int rewrite_index;
int length;
bool do_update;
// are we NOT connected yet ?
if (!player->is_connected)
return (false); // consistency check: if we aren't connected, don't do anything
// are we NOT logged in yet AND is our socket valid AND is there nothing in the center of the screen yet ?
if (!player->is_logged_in && (player->our_socket != INVALID_SOCKET) && !the_scene.gui.central_text.is_displayed)
{
Scene_SetText (&the_scene.gui.central_text, 50.0f, 40.0f, -1, ALIGN_CENTER, ALIGN_CENTER, ALIGN_CENTER, centermsg_fontindex, RGBA_TO_RGBACOLOR (255, 255, 255, 191),
999999.0f, true, LOCALIZE (L"Connecting")); // display "connecting" in the middle of the screen
the_scene.gui.want_spinwheel = true; // start spinning wheel
}
do_update = false; // assume we don't need to update the scene until told otherwise
////////////////////////////
// BEGIN listening to server
// proceed in filling the ascii buffer with what's coming
// get a hand on the end of the player's recv buffer and compute its remaining size
length = strlen (player->ascii_recvbuffer);
// if the server sent us something, append it to what we've received
recv_retval = recv (player->our_socket, &player->ascii_recvbuffer[length], player->recvbuffer_size - length, 0);
if (recv_retval > 0)
{
// parse all received data and eradicate all carriage returns. Also convert %'s to a safer glyph.
rewrite_index = 0;
for (byte_index = 0; byte_index < recv_retval; byte_index++)
if (player->ascii_recvbuffer[length + byte_index] != '\r')
{
if (player->ascii_recvbuffer[length + byte_index] == '%')
player->ascii_recvbuffer[length + rewrite_index] = '¤';
else
player->ascii_recvbuffer[length + rewrite_index] = player->ascii_recvbuffer[length + byte_index];
rewrite_index++;
}
player->ascii_recvbuffer[length + rewrite_index] = 0; // terminate the buffer ourselves
length = strlen (player->ascii_recvbuffer); // and update the new recvbuffer's length
}
else if (recv_retval == SOCKET_ERROR)
{
recv_retval = WSAGetLastError (); // ask Windows to be more specific about the error
if (recv_retval == WSAEWOULDBLOCK)
; // it's okay. We're a non-blocking socket.
else if (recv_retval == WSAETIMEDOUT)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_ConnectionToChessServerLost"));
messagebox.flags = MB_ICONWARNING | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (false);
}
}
// END listening to server
//////////////////////////
//////////////////////////////////
// BEGIN evaluating server replies
// are we NOT logged in yet ?
if (!player->is_logged_in)
{
// is it a login prompt ?
if ((length > 7) && (strcmp (&player->ascii_recvbuffer[length - 7], "login: ") == 0))
{
Player_SendBuffer_Add (player, 1000, L"%s\n", options.network.login); // send the login string
player->ascii_recvbuffer[0] = 0; // and discard recvbuffer as we've processed this message
}
// else is it a password prompt ?
else if (((length > 10) && (strcmp (&player->ascii_recvbuffer[length - 10], "password: ") == 0))
|| ((length > 3) && (strcmp (&player->ascii_recvbuffer[length - 3], "\":\n") == 0)))
{
Player_SendBuffer_Add (player, 1000, L"%s\n", options.network.password); // send the password string
player->ascii_recvbuffer[0] = 0; // and discard recvbuffer as we've processed this message
}
// else is it a confirmation that we've successfully logged in ?
else if (((block_start = strstr (player->ascii_recvbuffer, "\n**** Starting FICS session as ")) != NULL)
&& (block_end = strstr (player->ascii_recvbuffer, " ****\n")))
{
player->is_logged_in = true; // remember we are logged in
the_scene.gui.central_text.disappear_time = current_time + 1.0f; // fade the "connecting" phrase out now (FIXME: ugly)
the_scene.gui.want_spinwheel = false; // stop spinning wheel
do_update = true; // and update the scene
block_start += 31; // skip the "\n**** Starting FICS session as " substring
local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the local player
// copy out local player's login name, as reported by server, and convert it to wchar_t
for (byte_index = 0; (byte_index < WCHAR_SIZEOF (local_player->name)) && isalpha (block_start[byte_index]); byte_index++)
local_player->name[byte_index] = (wchar_t) block_start[byte_index]; // copy nickname one character after the other. Very dirty conversion.
if (byte_index < WCHAR_SIZEOF (local_player->name))
local_player->name[byte_index] = 0; // finish the string ourselves
else
local_player->name[WCHAR_SIZEOF (local_player->name) - 1] = 0; // truncate it if neeeded
server_motd[0] = 0; // remember we haven't read the MOTD yet
the_board.reevaluate = true; // reeevaluate the board (to update the title bar)
// see if there's more data to parse after this message, in which case move it in place
length = strlen (block_end + 6);
if (length > 0)
memmove (player->ascii_recvbuffer, block_end + 6, length + 1);
else
player->ascii_recvbuffer[0] = 0; // else discard recvbuffer as we've processed this message
}
}
// else we are logged in
else
{
// see if we have a complete reply block (remember: percent sign was replaced with '¤')
block_end = strstr (player->ascii_recvbuffer, "\nfics¤ ");
// as long as we can find some...
while (block_end != NULL)
{
*block_end = 0; // break the string here
// convert the block to wide char and evaluate it, taking action if needed
ConvertToWideChar (player->recvbuffer, player->recvbuffer_size, player->ascii_recvbuffer);
Debug_Log (L"RECEIVED:[%s]\n", player->recvbuffer); // log what we've received
EvaluateServerReply (player); // and evaluate it
// see if there's more data to parse after this message, in which case move it in place
length = strlen (block_end + 7);
if (length > 0)
memmove (player->ascii_recvbuffer, block_end + 7, length + 1);
else
{
player->ascii_recvbuffer[0] = 0;
break; // it was the last prompt, so discard recvbuffer as we've processed this message
}
block_end = strstr (player->ascii_recvbuffer, "\nfics¤ "); // remember: percent sign was replaced with '¤'
}
}
// END evaluating server replies
////////////////////////////////
// have we been notified that the current player just changed, has at least one move been played AND is it the remote player's turn now ?
if (the_board.has_playerchanged && (the_board.move_count > 1) && (Board_ColorToMove (&the_board) == player->color))
{
Debug_Log (L"===Local player just played, sending the chosen move to chess server===\n");
Player_SendBuffer_Add (player, 1000, L"%s\n", the_board.moves[the_board.move_count - 1].pgntext); // send the move string to the chess server
}
// has the user entered some chatter text ?
if (!the_scene.gui.is_entering_text && (the_scene.gui.entered_ccreply.text != NULL))
{
// is player logged in AND has a chatter channel been selected ? if so, send chat string to server
if (player->is_logged_in && (selected_chatterchannel != NULL))
Player_SendBuffer_Add (player, 1000, L"+channel %d\ntell %d %s\n", selected_chatterchannel->id, selected_chatterchannel->id, the_scene.gui.entered_ccreply.text);
// reset the entered text buffer
SAFE_free ((void **) &the_scene.gui.entered_ccreply.text); // reset the entered text buffer
the_scene.gui.entered_ccreply.text_length = 0; // and set its length to zero
}
// does the local player want to cancel its last move ? this can be either HIS move or HIS OPPONENTS's move...
local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the local player
if ((local_player != NULL) && local_player->wants_cancel)
{
Player_SendBuffer_Add (player, 1000, L"takeback\n"); // send the takeback request to our opponent
Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRequestSent"), player->name); // send a notification to its chat window
the_board.game_state = STATE_PLAYING; // remember the game is now playing (in case we wanted to cancel the closing move of a finished game, this opens the game again)
local_player->wants_cancel = false; // don't do this all day long
}
if (player->wants_cancel)
{
Player_SendBuffer_Add (player, 1000, L"takeback\n"); // send the takeback request to our opponent
Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRequestSent"), player->name); // send a notification to its chat window
the_board.game_state = STATE_PLAYING; // remember the game is now playing (in case we wanted to cancel the closing move of a finished game, this opens the game again)
player->wants_cancel = false; // don't do this all day long
}
// if we have something to send, do it
if (!player->sendbuffer_locked && (player->sendbuffer[0] != 0) && (animation_endtime + 1.0f < current_time))
{
// log what we're sending
Debug_Log (L"SENDING:[%s]\n", player->sendbuffer);
player->sendbuffer_locked = true; // lock the buffer
// now read line per line (mallocate a line buffer as large as necessary)
length = wcslen (player->sendbuffer) + 1 + 1; // +1 for \n, +1 for null terminator
ascii_sendbuffer = (char *) SAFE_malloc (length, sizeof (char), false);
widechar_buffer = (wchar_t *) SAFE_malloc (length, sizeof (wchar_t), false);
widechar_line = player->sendbuffer; // start at the first character
while ((widechar_line = wcsgets (widechar_buffer, length, widechar_line)) != NULL)
{
wcscat_s (widechar_buffer, length, L"\n"); // put the carriage return back
ConvertTo7BitASCII (ascii_sendbuffer, length, widechar_buffer); // convert to ASCII
send_retval = send (player->our_socket, ascii_sendbuffer, strlen (ascii_sendbuffer), 0); // send data
}
SAFE_free ((void **) &widechar_buffer); // we no longer need the line buffer, so free it
SAFE_free ((void **) &ascii_sendbuffer);
player->sendbuffer[0] = 0; // what we had to send has been sent, reset the send buffer
player->sendbuffer_locked = false; // and unlock it
// did send() report an error ?
if (send_retval == SOCKET_ERROR)
{
messagebox.hWndParent = hMainWnd;
wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_ConnectionToChessServerLost"));
messagebox.flags = MB_ICONERROR | MB_OK;
DialogBox_Message (&messagebox); // display a modeless error message box
PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
return (true); // on error, cancel
}
}
return (do_update); // finished
}
static void EvaluateServerReply (player_t *player)
{
// this function parses a network reply and evaluates it, deciding what to do
//////////////////////
// OUT OF GAME REPLIES
// the MOTD is the first reply to be evaluated
EvaluateServerReply_MOTD (player);
// evaluate dangerous replies first, i.e. those that contain user-settable text
EvaluateServerReply_Announcement (player);
EvaluateServerReply_ChannelMessage (player);
EvaluateServerReply_PrivateMessage (player);
EvaluateServerReply_Finger (player);
// the less dangerous replies can now be evaluated without ambiguity
EvaluateServerReply_Seek (player);
EvaluateServerReply_Challenge (player);
EvaluateServerReply_ChallengeAccepted (player);
EvaluateServerReply_ChallengeDeclined (player);
EvaluateServerReply_Takeback (player);
EvaluateServerReply_TakebackDeclinedByOther (player);
EvaluateServerReply_TakebackDeclinedByYou (player);
EvaluateServerReply_PlayNotAllowed (player);
EvaluateServerReply_PlayUnexistent (player);
EvaluateServerReply_PlayWrongRating (player);
EvaluateServerReply_ChannelsAndMembers (player);
EvaluateServerReply_SoughtList (player);
EvaluateServerReply_PlayersList (player);
//////////////////
// IN GAME REPLIES
EvaluateServerReply_GameStarting (player);
EvaluateServerReply_GameState (player);
EvaluateServerReply_GameResults (player);
return; // finished evaluating this reply
}