Subversion Repositories Games.Chess Giants

Rev

Rev 136 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
// network.cpp
2
 
3
#include "common.h"
4
 
5
 
6
// prototypes of local functions
7
static void StartThread_ListenToServer (void *thread_parms);
8
static void EvaluateServerReply (player_t *player);
9
 
10
 
11
bool PlayerNetwork_Init (player_t *player)
12
{
13
   // this function initializes the network layer and connects to the chess server
14
 
15
   WSADATA wsaData;
16
   struct hostent *hostinfo;
17
   struct sockaddr_in service;
18
   unsigned long nonblocking_mode;
19
   char ascii_hostname[256];
20
 
21
   onlineplayers = NULL; // ensure the online players array is empty
22
   onlineplayer_count = 0; // and reset the online players count
23
   onlineplayers_updated = false;
24
 
25
   soughtgames = NULL; // ensure the sought games array is empty
26
   soughtgame_count = 0; // and reset the sought games count
27
   soughtgames_updated = false;
28
   lastsought_time = 0;
29
 
30
   chatterchannels = NULL; // ensure the chatter channels array is empty
31
   chatterchannel_count = 0; // and reset the chatter channels count
32
   chatterchannels_updated = false;
33
   selected_chatterchannel = NULL;
34
 
35
   PlayerCards_Init (); // initialize the player cards array
36
   Challenges_Init (); // initialize the challenges array
37
   Interlocutors_Init (); // initialize the interlocutors array
38
 
39
   server_motd[0] = 0; // reset the server MOTD
40
 
41
   player->is_connected = false; // remember we aren't connected yet
42
   player->is_logged_in = false; // and not logged in either
43
   player->is_in_game = false; // we aren't in game either
44
   player->game_number = 0;
45
   player->remaining_seconds = 0;
46
   player->our_socket = INVALID_SOCKET; // and that we have no socket yet
47
   player->name[0] = 0; // player name undefined
48
 
49
   // initialize WinSock
50
   if (WSAStartup (MAKEWORD (2, 2), &wsaData) != 0)
51
   {
52
      messagebox.hWndParent = hMainWnd;
53
      wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
54
      wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
55
      messagebox.flags = MB_ICONWARNING | MB_OK;
56
      DialogBox_Message (&messagebox); // display a modeless error message box
57
 
58
      PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
59
      return (false);
60
   }
61
 
62
   // get the chess server's IP address from the host name
63
   ConvertTo7BitASCII (ascii_hostname, sizeof (ascii_hostname), options.network.server_address);
64
   hostinfo = gethostbyname (ascii_hostname);
65
   if (hostinfo == NULL)
66
   {
67
      messagebox.hWndParent = hMainWnd;
68
      wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
69
      wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
70
      messagebox.flags = MB_ICONWARNING | MB_OK;
71
      DialogBox_Message (&messagebox); // display a modeless error message box
72
 
73
      PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
74
      return (false);
75
   }
76
 
77
   // fill in the sockaddr server structure with the server hostinfo data
78
   service.sin_family = AF_INET;
79
   service.sin_addr.s_addr = inet_addr (inet_ntoa (*(struct in_addr *) hostinfo->h_addr_list[0]));
80
   service.sin_port = htons (options.network.server_port);
81
 
82
   // create our socket
83
   if ((player->our_socket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
84
   {
85
      messagebox.hWndParent = hMainWnd;
86
      wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
87
      wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
88
      messagebox.flags = MB_ICONWARNING | MB_OK;
89
      DialogBox_Message (&messagebox); // display a modeless error message box
90
 
91
      PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
92
      return (false);
93
   }
94
 
95
   // connect the game to the chess server using that socket
96
   if (connect (player->our_socket, (struct sockaddr *) &service, sizeof (service)) == -1)
97
   {
98
      messagebox.hWndParent = hMainWnd;
99
      wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
100
      wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
101
      messagebox.flags = MB_ICONWARNING | MB_OK;
102
      DialogBox_Message (&messagebox); // display a modeless error message box
103
 
104
      PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
105
      return (false);
106
   }
107
 
108
   // set it to be non-blocking (but only AFTER the connection is made)
109
   nonblocking_mode = 1;
110
   if (ioctlsocket (player->our_socket, FIONBIO, &nonblocking_mode) == SOCKET_ERROR)
111
   {
112
      messagebox.hWndParent = hMainWnd;
113
      wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
114
      wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_NetworkInitializationFailed"));
115
      messagebox.flags = MB_ICONWARNING | MB_OK;
116
      DialogBox_Message (&messagebox); // display a modeless error message box
117
 
118
      PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
119
      return (false);
120
   }
121
 
122
   // initialize the debug log file
123
   Debug_Init (L"Chess server output.txt");
124
 
125
   player->is_connected = true; // remember we are connected
126
   return (true); // finished, we have a valid, connected socket
127
}
128
 
129
 
130
void PlayerNetwork_Shutdown (player_t *player)
131
{
132
   // this function shutdowns the network layer
133
 
134
   int chatterchannel_index;
135
 
136
   // shutdown the socket if it is open. This will disconnect us from the server.
137
   if (player->our_socket != INVALID_SOCKET)
138
      closesocket (player->our_socket); // close the network socket
139
   player->our_socket = INVALID_SOCKET;
140
 
141
   player->is_connected = false; // remember we are no longer connected
142
   player->is_logged_in = false; // and not logged in either
143
   player->is_in_game = false; // we aren't in game either
144
   player->game_number = 0;
145
   player->remaining_seconds = 0;
146
   player->name[0] = 0; // player name undefined
147
 
148
   server_motd[0] = 0; // reset the server MOTD
149
 
150
   // cycle through all chatter channels we know...
151
   for (chatterchannel_index = 0; chatterchannel_index < chatterchannel_count; chatterchannel_index++)
152
   {
153
      SAFE_free ((void **) &chatterchannels[chatterchannel_index].members); // free their member list
154
      chatterchannels[chatterchannel_index].member_count = 0; // reset their members count
155
   }
156
   SAFE_free ((void **) &chatterchannels); // free the chatter channels array
157
   chatterchannel_count = 0; // and reset the chatter channels count
158
   chatterchannels_updated = false;
159
   selected_chatterchannel = NULL;
160
 
161
   SAFE_free ((void **) &soughtgames); // free the sought games array
162
   soughtgame_count = 0; // and reset the sought games count
163
   soughtgames_updated = false;
164
 
165
   SAFE_free ((void **) &onlineplayers); // free the online players array
166
   onlineplayer_count = 0; // and reset the online players count
167
   onlineplayers_updated = false;
168
 
169
   Interlocutors_Shutdown (); // shutdown the interlocutors
170
   Challenges_Shutdown (); // shutdown the challenges
171
   PlayerCards_Shutdown (); // shutdown the player cards
172
 
173
   // shutdown all possible opened UI windows
174
   if (IsWindow (hChatterChannelsWnd))
175
      DestroyWindow (hChatterChannelsWnd);
176
   hChatterChannelsWnd = NULL;
177
   if (IsWindow (hGamesWnd))
178
      DestroyWindow (hGamesWnd);
179
   hGamesWnd = NULL;
180
   if (IsWindow (hMOTDWnd))
181
      DestroyWindow (hMOTDWnd);
182
   hMOTDWnd = NULL;
183
   if (IsWindow (hOpponentsWnd))
184
      DestroyWindow (hOpponentsWnd);
185
   hOpponentsWnd = NULL;
186
   if (IsWindow (hSoughtWnd))
187
      DestroyWindow (hSoughtWnd);
188
   hSoughtWnd = NULL;
189
 
190
   // shutdown WinSock
191
   WSACleanup ();
192
 
193
   return; // finished
194
}
195
 
196
 
197
bool PlayerNetwork_Think (player_t *player)
198
{
199
   // this function is called once per game tick to listen to the network and react to what the server tells us. Returns TRUE
200
   // if we need to update the scene.
201
 
202
   player_t *local_player;
203
   char *block_start;
204
   char *block_end;
205
   char *ascii_sendbuffer; // mallocated
206
   wchar_t *widechar_buffer; // mallocated
207
   wchar_t *widechar_line;
208
   int send_retval;
209
   int recv_retval;
210
   int byte_index;
211
   int rewrite_index;
212
   int length;
213
   bool do_update;
214
 
215
   // are we NOT connected yet ?
216
   if (!player->is_connected)
217
      return (false); // consistency check: if we aren't connected, don't do anything
218
 
219
   // are we NOT logged in yet AND is our socket valid AND is there nothing in the center of the screen yet ?
220
   if (!player->is_logged_in && (player->our_socket != INVALID_SOCKET) && !the_scene.gui.central_text.is_displayed)
221
   {
222
      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),
223
                     999999.0f, true, LOCALIZE (L"Connecting")); // display "connecting" in the middle of the screen
224
      the_scene.gui.want_spinwheel = true; // start spinning wheel
225
   }
226
 
227
   do_update = false; // assume we don't need to update the scene until told otherwise
228
 
229
   ////////////////////////////
230
   // BEGIN listening to server
231
 
232
   // proceed in filling the ascii buffer with what's coming
233
 
234
   // get a hand on the end of the player's recv buffer and compute its remaining size
235
   length = strlen (player->ascii_recvbuffer);
236
 
237
   // if the server sent us something, append it to what we've received
238
   recv_retval = recv (player->our_socket, &player->ascii_recvbuffer[length], player->recvbuffer_size - length, 0);
239
   if (recv_retval > 0)
240
   {
241
      // parse all received data and eradicate all carriage returns. Also convert %'s to a safer glyph.
242
      rewrite_index = 0;
243
      for (byte_index = 0; byte_index < recv_retval; byte_index++)
244
         if (player->ascii_recvbuffer[length + byte_index] != '\r')
245
         {
246
            if (player->ascii_recvbuffer[length + byte_index] == '%')
247
               player->ascii_recvbuffer[length + rewrite_index] = '¤';
248
            else
249
               player->ascii_recvbuffer[length + rewrite_index] = player->ascii_recvbuffer[length + byte_index];
250
            rewrite_index++;
251
         }
252
      player->ascii_recvbuffer[length + rewrite_index] = 0; // terminate the buffer ourselves
253
      length = strlen (player->ascii_recvbuffer); // and update the new recvbuffer's length
254
   }
255
   else if (recv_retval == SOCKET_ERROR)
256
   {
257
      recv_retval = WSAGetLastError (); // ask Windows to be more specific about the error
258
      if (recv_retval == WSAEWOULDBLOCK)
259
         ; // it's okay. We're a non-blocking socket.
260
      else if (recv_retval == WSAETIMEDOUT)
261
      {
262
         messagebox.hWndParent = hMainWnd;
263
         wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
264
         wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_ConnectionToChessServerLost"));
265
         messagebox.flags = MB_ICONWARNING | MB_OK;
266
         DialogBox_Message (&messagebox); // display a modeless error message box
267
 
268
         PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
269
         return (false);
270
      }
271
   }
272
 
273
   // END listening to server
274
   //////////////////////////
275
 
276
   //////////////////////////////////
277
   // BEGIN evaluating server replies
278
 
279
   // are we NOT logged in yet ?
280
   if (!player->is_logged_in)
281
   {
282
      // is it a login prompt ?
283
      if ((length > 7) && (strcmp (&player->ascii_recvbuffer[length - 7], "login: ") == 0))
284
      {
285
         Player_SendBuffer_Add (player, 1000, L"%s\n", options.network.login); // send the login string
286
         player->ascii_recvbuffer[0] = 0; // and discard recvbuffer as we've processed this message
287
      }
288
 
289
      // else is it a password prompt ?
290
      else if (((length > 10) && (strcmp (&player->ascii_recvbuffer[length - 10], "password: ") == 0))
291
               || ((length > 3) && (strcmp (&player->ascii_recvbuffer[length - 3], "\":\n") == 0)))
292
      {
293
         Player_SendBuffer_Add (player, 1000, L"%s\n", options.network.password); // send the password string
294
         player->ascii_recvbuffer[0] = 0; // and discard recvbuffer as we've processed this message
295
      }
296
 
297
      // else is it a confirmation that we've successfully logged in ?
298
      else if (((block_start = strstr (player->ascii_recvbuffer, "\n**** Starting FICS session as ")) != NULL)
299
               && (block_end = strstr (player->ascii_recvbuffer, " ****\n")))
300
      {
301
         player->is_logged_in = true; // remember we are logged in
302
         the_scene.gui.central_text.disappear_time = current_time + 1.0f; // fade the "connecting" phrase out now (FIXME: ugly)
303
         the_scene.gui.want_spinwheel = false; // stop spinning wheel
304
         do_update = true; // and update the scene
305
 
306
         block_start += 31; // skip the "\n**** Starting FICS session as " substring
307
         local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the local player
308
 
309
         // copy out local player's login name, as reported by server, and convert it to wchar_t
310
         for (byte_index = 0; (byte_index < WCHAR_SIZEOF (local_player->name)) && isalpha (block_start[byte_index]); byte_index++)
311
            local_player->name[byte_index] = (wchar_t) block_start[byte_index]; // copy nickname one character after the other. Very dirty conversion.
312
         if (byte_index < WCHAR_SIZEOF (local_player->name))
313
            local_player->name[byte_index] = 0; // finish the string ourselves
314
         else
315
            local_player->name[WCHAR_SIZEOF (local_player->name) - 1] = 0; // truncate it if neeeded
316
 
317
         server_motd[0] = 0; // remember we haven't read the MOTD yet
318
         the_board.reevaluate = true; // reeevaluate the board (to update the title bar)
319
 
320
         // see if there's more data to parse after this message, in which case move it in place
321
         length = strlen (block_end + 6);
322
         if (length > 0)
323
            memmove (player->ascii_recvbuffer, block_end + 6, length + 1);
324
         else
325
            player->ascii_recvbuffer[0] = 0; // else discard recvbuffer as we've processed this message
326
      }
327
   }
328
 
329
   // else we are logged in
330
   else
331
   {
332
      // see if we have a complete reply block (remember: percent sign was replaced with '¤')
333
      block_end = strstr (player->ascii_recvbuffer, "\nfics¤ ");
334
 
335
      // as long as we can find some...
336
      while (block_end != NULL)
337
      {
338
         *block_end = 0; // break the string here
339
 
340
         // convert the block to wide char and evaluate it, taking action if needed
341
         ConvertToWideChar (player->recvbuffer, player->recvbuffer_size, player->ascii_recvbuffer);
342
         Debug_Log (L"RECEIVED:[%s]\n", player->recvbuffer); // log what we've received
343
         EvaluateServerReply (player); // and evaluate it
344
 
345
         // see if there's more data to parse after this message, in which case move it in place
346
         length = strlen (block_end + 7);
347
         if (length > 0)
348
            memmove (player->ascii_recvbuffer, block_end + 7, length + 1);
349
         else
350
         {
351
            player->ascii_recvbuffer[0] = 0;
352
            break; // it was the last prompt, so discard recvbuffer as we've processed this message
353
         }
354
         block_end = strstr (player->ascii_recvbuffer, "\nfics¤ "); // remember: percent sign was replaced with '¤'
355
      }
356
   }
357
 
358
   // END evaluating server replies
359
   ////////////////////////////////
360
 
361
   // 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 ?
362
   if (the_board.has_playerchanged && (the_board.move_count > 1) && (Board_ColorToMove (&the_board) == player->color))
363
   {
364
      Debug_Log (L"===Local player just played, sending the chosen move to chess server===\n");
365
      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
366
   }
367
 
368
   // has the user entered some chatter text ?
369
   if (!the_scene.gui.is_entering_text && (the_scene.gui.entered_ccreply.text != NULL))
370
   {
371
      // is player logged in AND has a chatter channel been selected ? if so, send chat string to server
372
      if (player->is_logged_in && (selected_chatterchannel != NULL))
373
         Player_SendBuffer_Add (player, 1000, L"+channel %d\ntell %d %s\n", selected_chatterchannel->id, selected_chatterchannel->id, the_scene.gui.entered_ccreply.text);
374
 
375
      // reset the entered text buffer
376
      SAFE_free ((void **) &the_scene.gui.entered_ccreply.text); // reset the entered text buffer
377
      the_scene.gui.entered_ccreply.text_length = 0; // and set its length to zero
378
   }
379
 
380
   // does the local player want to cancel its last move ? this can be either HIS move or HIS OPPONENTS's move...
381
   local_player = Player_FindByType (PLAYER_HUMAN); // get a pointer to the local player
382
   if ((local_player != NULL) && local_player->wants_cancel)
383
   {
384
      Player_SendBuffer_Add (player, 1000, L"takeback\n"); // send the takeback request to our opponent
385
      Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRequestSent"), player->name); // send a notification to its chat window
386
      local_player->wants_cancel = false; // don't do this all day long
387
   }
388
   if (player->wants_cancel)
389
   {
390
      Player_SendBuffer_Add (player, 1000, L"takeback\n"); // send the takeback request to our opponent
391
      Interlocutor_Notify (Interlocutor_FindOrCreate (player->name), LOCALIZE (L"Chat_TakebackRequestSent"), player->name); // send a notification to its chat window
392
      player->wants_cancel = false; // don't do this all day long
393
   }
394
 
395
   // if we have something to send, do it
396
   if (!player->sendbuffer_locked && (player->sendbuffer[0] != 0) && (animation_endtime + 1.0f < current_time))
397
   {
398
      // log what we're sending
399
      Debug_Log (L"SENDING:[%s]\n", player->sendbuffer);
400
 
401
      player->sendbuffer_locked = true; // lock the buffer
402
 
403
      // now read line per line (mallocate a line buffer as large as necessary)
404
      length = wcslen (player->sendbuffer) + 1 + 1; // +1 for \n, +1 for null terminator
405
      ascii_sendbuffer = (char *) SAFE_malloc (length, sizeof (char), false);
406
      widechar_buffer = (wchar_t *) SAFE_malloc (length, sizeof (wchar_t), false);
407
      widechar_line = player->sendbuffer; // start at the first character
408
      while ((widechar_line = wcsgets (widechar_buffer, length, widechar_line)) != NULL)
409
      {
410
         wcscat_s (widechar_buffer, length, L"\n"); // put the carriage return back
411
         ConvertTo7BitASCII (ascii_sendbuffer, length, widechar_buffer); // convert to ASCII
412
         send_retval = send (player->our_socket, ascii_sendbuffer, strlen (ascii_sendbuffer), 0); // send data
413
      }
414
      SAFE_free ((void **) &widechar_buffer); // we no longer need the line buffer, so free it
415
      SAFE_free ((void **) &ascii_sendbuffer);
416
 
417
      player->sendbuffer[0] = 0; // what we had to send has been sent, reset the send buffer
418
      player->sendbuffer_locked = false; // and unlock it
419
 
420
      // did send() report an error ?
421
      if (send_retval == SOCKET_ERROR)
422
      {
423
         messagebox.hWndParent = hMainWnd;
424
         wcscpy_s (messagebox.title, WCHAR_SIZEOF (messagebox.title), LOCALIZE (L"ImportantMessage"));
425
         wcscpy_s (messagebox.text, WCHAR_SIZEOF (messagebox.text), LOCALIZE (L"Error_ConnectionToChessServerLost"));
426
         messagebox.flags = MB_ICONERROR | MB_OK;
427
         DialogBox_Message (&messagebox); // display a modeless error message box
428
 
429
         PlayerNetwork_Shutdown (player); // on error, shutdown the network and display an error message
430
         return (true); // on error, cancel
431
      }
432
   }
433
 
434
   return (do_update); // finished
435
}
436
 
437
 
438
static void EvaluateServerReply (player_t *player)
439
{
440
   // this function parses a network reply and evaluates it, deciding what to do
441
 
442
   //////////////////////
443
   // OUT OF GAME REPLIES
444
 
445
   // the MOTD is the first reply to be evaluated
446
   EvaluateServerReply_MOTD (player);
447
 
448
   // evaluate dangerous replies first, i.e. those that contain user-settable text
449
   EvaluateServerReply_Announcement (player);
450
   EvaluateServerReply_ChannelMessage (player);
451
   EvaluateServerReply_PrivateMessage (player);
452
   EvaluateServerReply_Finger (player);
453
 
454
   // the less dangerous replies can now be evaluated without ambiguity
455
   EvaluateServerReply_Seek (player);
456
   EvaluateServerReply_Challenge (player);
457
   EvaluateServerReply_ChallengeAccepted (player);
458
   EvaluateServerReply_ChallengeDeclined (player);
459
   EvaluateServerReply_Takeback (player);
460
   EvaluateServerReply_TakebackDeclinedByOther (player);
461
   EvaluateServerReply_TakebackDeclinedByYou (player);
462
   EvaluateServerReply_PlayNotAllowed (player);
463
   EvaluateServerReply_PlayUnexistent (player);
464
   EvaluateServerReply_PlayWrongRating (player);
465
   EvaluateServerReply_ChannelsAndMembers (player);
466
   EvaluateServerReply_SoughtList (player);
467
   EvaluateServerReply_PlayersList (player);
468
 
469
   //////////////////
470
   // IN GAME REPLIES
471
 
472
   EvaluateServerReply_GameStarting (player);
473
   EvaluateServerReply_GameState (player);
474
   EvaluateServerReply_GameResults (player);
475
 
476
   return; // finished evaluating this reply
477
}