acpc_dealer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/README.md +33 -0
  2. data/Rakefile +20 -0
  3. data/acpc_dealer.gemspec +29 -0
  4. data/bin/acpc_dealer +168 -0
  5. data/ext/hand_evaluator/extconf.rb +5 -0
  6. data/ext/hand_evaluator/hand_evaluator.c +38 -0
  7. data/lib/acpc_dealer/2p.limit.h5.r0.logs/2p.limit.h5.r0.actions.log +5 -0
  8. data/lib/acpc_dealer/2p.limit.h5.r0.logs/2p.limit.h5.r0.log +0 -0
  9. data/lib/acpc_dealer/dealer_runner.rb +72 -0
  10. data/lib/acpc_dealer/version.rb +3 -0
  11. data/lib/acpc_dealer.rb +33 -0
  12. data/lib/hand_evaluator.so +0 -0
  13. data/spec/coverage/assets/0.5.3/app.js +88 -0
  14. data/spec/coverage/assets/0.5.3/fancybox/blank.gif +0 -0
  15. data/spec/coverage/assets/0.5.3/fancybox/fancy_close.png +0 -0
  16. data/spec/coverage/assets/0.5.3/fancybox/fancy_loading.png +0 -0
  17. data/spec/coverage/assets/0.5.3/fancybox/fancy_nav_left.png +0 -0
  18. data/spec/coverage/assets/0.5.3/fancybox/fancy_nav_right.png +0 -0
  19. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_e.png +0 -0
  20. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_n.png +0 -0
  21. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_ne.png +0 -0
  22. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_nw.png +0 -0
  23. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_s.png +0 -0
  24. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_se.png +0 -0
  25. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_sw.png +0 -0
  26. data/spec/coverage/assets/0.5.3/fancybox/fancy_shadow_w.png +0 -0
  27. data/spec/coverage/assets/0.5.3/fancybox/fancy_title_left.png +0 -0
  28. data/spec/coverage/assets/0.5.3/fancybox/fancy_title_main.png +0 -0
  29. data/spec/coverage/assets/0.5.3/fancybox/fancy_title_over.png +0 -0
  30. data/spec/coverage/assets/0.5.3/fancybox/fancy_title_right.png +0 -0
  31. data/spec/coverage/assets/0.5.3/fancybox/fancybox-x.png +0 -0
  32. data/spec/coverage/assets/0.5.3/fancybox/fancybox-y.png +0 -0
  33. data/spec/coverage/assets/0.5.3/fancybox/fancybox.png +0 -0
  34. data/spec/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.css +363 -0
  35. data/spec/coverage/assets/0.5.3/fancybox/jquery.fancybox-1.3.1.pack.js +44 -0
  36. data/spec/coverage/assets/0.5.3/favicon_green.png +0 -0
  37. data/spec/coverage/assets/0.5.3/favicon_red.png +0 -0
  38. data/spec/coverage/assets/0.5.3/favicon_yellow.png +0 -0
  39. data/spec/coverage/assets/0.5.3/highlight.css +129 -0
  40. data/spec/coverage/assets/0.5.3/highlight.pack.js +1 -0
  41. data/spec/coverage/assets/0.5.3/jquery-1.6.2.min.js +18 -0
  42. data/spec/coverage/assets/0.5.3/jquery.dataTables.min.js +152 -0
  43. data/spec/coverage/assets/0.5.3/jquery.timeago.js +141 -0
  44. data/spec/coverage/assets/0.5.3/jquery.url.js +174 -0
  45. data/spec/coverage/assets/0.5.3/loading.gif +0 -0
  46. data/spec/coverage/assets/0.5.3/magnify.png +0 -0
  47. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  48. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  49. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  50. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  51. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  52. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  53. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  54. data/spec/coverage/assets/0.5.3/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  55. data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_222222_256x240.png +0 -0
  56. data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  57. data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_454545_256x240.png +0 -0
  58. data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_888888_256x240.png +0 -0
  59. data/spec/coverage/assets/0.5.3/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  60. data/spec/coverage/assets/0.5.3/smoothness/jquery-ui-1.8.4.custom.css +295 -0
  61. data/spec/coverage/assets/0.5.3/stylesheet.css +383 -0
  62. data/spec/coverage/index.html +81 -0
  63. data/spec/dealer_runner_spec.rb +83 -0
  64. data/spec/support/spec_helper.rb +33 -0
  65. data/vendor/project_acpc_server/LICENCE +23 -0
  66. data/vendor/project_acpc_server/Makefile +22 -0
  67. data/vendor/project_acpc_server/README +113 -0
  68. data/vendor/project_acpc_server/README.submission +42 -0
  69. data/vendor/project_acpc_server/acpc_play_match.pl +101 -0
  70. data/vendor/project_acpc_server/bm_server +0 -0
  71. data/vendor/project_acpc_server/bm_server.c +1557 -0
  72. data/vendor/project_acpc_server/bm_server.config +78 -0
  73. data/vendor/project_acpc_server/bm_widget +0 -0
  74. data/vendor/project_acpc_server/bm_widget.c +186 -0
  75. data/vendor/project_acpc_server/dealer +0 -0
  76. data/vendor/project_acpc_server/dealer.c +1278 -0
  77. data/vendor/project_acpc_server/evalHandTables +4269 -0
  78. data/vendor/project_acpc_server/example_player +0 -0
  79. data/vendor/project_acpc_server/example_player.c +204 -0
  80. data/vendor/project_acpc_server/example_player.limit.2p.sh +2 -0
  81. data/vendor/project_acpc_server/example_player.limit.3p.sh +2 -0
  82. data/vendor/project_acpc_server/example_player.nolimit.2p.sh +2 -0
  83. data/vendor/project_acpc_server/example_player.nolimit.3p.sh +2 -0
  84. data/vendor/project_acpc_server/game.c +1793 -0
  85. data/vendor/project_acpc_server/game.h +253 -0
  86. data/vendor/project_acpc_server/holdem.limit.2p.reverse_blinds.game +13 -0
  87. data/vendor/project_acpc_server/holdem.limit.3p.game +13 -0
  88. data/vendor/project_acpc_server/holdem.nolimit.2p.reverse_blinds.game +12 -0
  89. data/vendor/project_acpc_server/holdem.nolimit.3p.game +12 -0
  90. data/vendor/project_acpc_server/net.c +218 -0
  91. data/vendor/project_acpc_server/net.h +61 -0
  92. data/vendor/project_acpc_server/play_match.pl +99 -0
  93. data/vendor/project_acpc_server/protocol.odt +0 -0
  94. data/vendor/project_acpc_server/protocol.pdf +0 -0
  95. data/vendor/project_acpc_server/rng.c +138 -0
  96. data/vendor/project_acpc_server/rng.h +63 -0
  97. data/vendor/project_acpc_server/test.log +11 -0
  98. data/vendor/project_acpc_server/validate_submission.pl +546 -0
  99. metadata +315 -0
@@ -0,0 +1,1557 @@
1
+ /*
2
+ Copyright (C) 2011 by the Computer Poker Research Group, University of Alberta
3
+ */
4
+
5
+ #include <stdlib.h>
6
+ #include <stdio.h>
7
+ #define __STDC_FORMAT_MACROS
8
+ #include <inttypes.h>
9
+ #include <assert.h>
10
+ #include <string.h>
11
+ #include <strings.h>
12
+ #include <ctype.h>
13
+ #include <unistd.h>
14
+ #include <netdb.h>
15
+ #include <sys/socket.h>
16
+ #include <sys/types.h>
17
+ #include <arpa/inet.h>
18
+ #include <netinet/in.h>
19
+ #include <netinet/tcp.h>
20
+ #include <sys/wait.h>
21
+ #include <sys/time.h>
22
+ #include <time.h>
23
+ #include <signal.h>
24
+ #include <fcntl.h>
25
+ #include "game.h"
26
+ #include "net.h"
27
+ #include "rng.h"
28
+
29
+
30
+ #define STATUS_CLOSED 0
31
+ #define STATUS_UNVALIDATED 1
32
+ #define STATUS_OKAY 2
33
+
34
+ #define BM_DEALER "dealer"
35
+ #define BM_LOGDIR "logs"
36
+ #define BM_DEALER_WAIT_SECS 5
37
+ #define BM_MAX_IOWAIT_SECS 1
38
+
39
+
40
+ typedef struct LLPoolEntry_struct {
41
+ struct LLPoolEntry_struct *next;
42
+ struct LLPoolEntry_struct *prev;
43
+ char data[ 0 ];
44
+ } LLPoolEntry;
45
+
46
+ typedef struct {
47
+ LLPoolEntry *head;
48
+ LLPoolEntry *free;
49
+ int dataSize;
50
+ int numEntries;
51
+ } LLPool;
52
+
53
+ /* structure giving the specification for a local bot */
54
+ typedef struct {
55
+ char *name;
56
+ char *command;
57
+ } BotSpec;
58
+
59
+ /* structure giving the specification for a user */
60
+ typedef struct {
61
+ char *name;
62
+ char *passwd;
63
+ struct timeval waitStart;
64
+ } UserSpec;
65
+
66
+ typedef struct {
67
+ uint16_t maxMatchRuns; /* maximum number of runs for a match */
68
+ uint16_t maxRunningJobs; /* maximum simultaneous jobs at a time for game
69
+ 0 disables the check */
70
+ uint32_t matchHands; /* number of hands in a match */
71
+ Game *game;
72
+ char *gameFile;
73
+ LLPool *bots;
74
+
75
+ int curRunningJobs;
76
+ } GameConfig;
77
+
78
+ typedef struct {
79
+ uint16_t port;
80
+ uint16_t maxRunningBots; /* maximum simultaneous bots at a time
81
+ 0 disables the check */
82
+ uint16_t startupTimeoutSecs; /* maximum time to wait for clients to connect
83
+ 0 disables the timer */
84
+ uint16_t responseTimeoutSecs; /* maximum time to wait for clients to respond
85
+ with an action */
86
+ uint16_t handTimeoutSecs; /* maximum time to allowed per hand of play */
87
+ uint16_t avgHandTimeSecs; /* average time per hand allowed for the match */
88
+
89
+ LLPool *games;
90
+ LLPool *users;
91
+ } Config;
92
+
93
+ typedef struct {
94
+ int status;
95
+ UserSpec *user; /* NULL when status is STATUS_UNVALIDATED */
96
+ ReadBuf *connBuf;
97
+ } Connection;
98
+
99
+ typedef struct {
100
+ GameConfig *gameConf;
101
+ UserSpec *user;
102
+ int numRuns;
103
+ rng_state_t rng;
104
+ char *tag;
105
+ struct timeval queueTime;
106
+ struct {
107
+ int isNetworkPlayer;
108
+ LLPoolEntry *entry; /* connection if network player, bot otherwise */
109
+ } players[ MAX_PLAYERS ];
110
+ int isRunning;
111
+ } Match;
112
+
113
+ typedef struct {
114
+ pid_t dealerPID;
115
+ pid_t botPID[ MAX_PLAYERS ];
116
+ LLPoolEntry *matchEntry;
117
+ char *tag; /* based on tag from the match for this job */
118
+ uint16_t ports[ MAX_PLAYERS ];
119
+ } MatchJob;
120
+
121
+ typedef struct {
122
+ int listenSocket;
123
+ LLPool *conns;
124
+ LLPool *matches;
125
+ LLPool *jobs;
126
+
127
+ rng_state_t rng;
128
+
129
+ char *hostname;
130
+
131
+ int devnullfd;
132
+ } ServerState;
133
+
134
+
135
+ LLPool *newLLPool( const int dataSize )
136
+ {
137
+ LLPool *pool;
138
+
139
+ pool = (LLPool*)malloc( sizeof( LLPool ) );
140
+ assert( pool != 0 );
141
+ pool->head = NULL;
142
+ pool->free = NULL;
143
+ pool->dataSize = dataSize;
144
+ pool->numEntries = 0;
145
+ return pool;
146
+ }
147
+
148
+ int entryInList( LLPoolEntry *list, LLPoolEntry *entry )
149
+ {
150
+ while( list ) {
151
+
152
+ if( entry == list ) {
153
+
154
+ return 1;
155
+ }
156
+ if( list->next ) {
157
+
158
+ assert( list->next->prev == list );
159
+ }
160
+ list = list->next;
161
+ }
162
+ return 0;
163
+ }
164
+
165
+ /* add an object to the pool. data must have a size of pool->dataSize */
166
+ LLPoolEntry *LLPoolAddItem( LLPool *pool, void *item )
167
+ {
168
+ LLPoolEntry *entry;
169
+
170
+ if( pool->free ) {
171
+
172
+ entry = pool->free;
173
+ pool->free = entry->next;
174
+ } else {
175
+
176
+ entry = (LLPoolEntry*)malloc( sizeof( LLPoolEntry ) + pool->dataSize );
177
+ assert( entry != 0 );
178
+ }
179
+
180
+ assert( !entryInList( pool->head, entry ) );
181
+ entry->next = pool->head;
182
+ entry->prev = NULL;
183
+ memcpy( entry->data, item, pool->dataSize );
184
+ if( pool->head ) {
185
+
186
+ pool->head->prev = entry;
187
+ }
188
+ pool->head = entry;
189
+
190
+ ++pool->numEntries;
191
+
192
+ return entry;
193
+ }
194
+
195
+ /* remove an item from the pool, placing it in the free list.
196
+ entry must have been generated by LLPoolAddItem( pool, ... )
197
+ (that is, calling LLPoolRemoveEntry on an entry from another pool
198
+ is potentially a very bad idea...) */
199
+ void LLPoolRemoveEntry( LLPool *pool, LLPoolEntry *entry )
200
+ {
201
+ if( entry->prev ) {
202
+
203
+ assert( entry->prev->next == entry );
204
+ entry->prev->next = entry->next;
205
+ } else {
206
+
207
+ assert( pool->head == entry );
208
+ pool->head = entry->next;
209
+ }
210
+ if( entry->next ) {
211
+
212
+ assert( entry->next->prev == entry );
213
+ entry->next->prev = entry->prev;
214
+ }
215
+
216
+ assert( !entryInList( pool->free, entry ) );
217
+ if( pool->free ) {
218
+
219
+ pool->free->prev = entry;
220
+ }
221
+ entry->next = pool->free;
222
+ pool->free = entry;
223
+
224
+ --pool->numEntries;
225
+ }
226
+
227
+ /* LLPool iterator start */
228
+ LLPoolEntry *LLPoolFirstEntry( LLPool *pool )
229
+ {
230
+ return pool->head;
231
+ }
232
+
233
+ /* removing entries while iterating through the list is fine, as
234
+ long as cur is not the entry being removed. */
235
+ LLPoolEntry *LLPoolNextEntry( LLPoolEntry *cur )
236
+ {
237
+ if( cur ) {
238
+
239
+ return cur->next;
240
+ }
241
+ return NULL;
242
+ }
243
+
244
+ void *LLPoolGetItem( LLPoolEntry *entry )
245
+ {
246
+ return &entry->data;
247
+ }
248
+
249
+
250
+ void printUsage( FILE *file )
251
+ {
252
+ fprintf( file, "usage: bm_server config_file\n" );
253
+ }
254
+
255
+ void setGameDefaults( GameConfig *gameConf )
256
+ {
257
+ gameConf->maxMatchRuns = 10;
258
+ gameConf->maxRunningJobs = 1;
259
+ gameConf->matchHands = 5000;
260
+ gameConf->game = NULL;
261
+ gameConf->gameFile = NULL;
262
+ gameConf->bots = newLLPool( sizeof( BotSpec ) );
263
+
264
+ gameConf->curRunningJobs = 0;
265
+ }
266
+
267
+ void setDefaults( Config *conf )
268
+ {
269
+ conf->port = 54000;
270
+ conf->maxRunningBots = 0;
271
+ conf->startupTimeoutSecs = 60;
272
+ conf->responseTimeoutSecs = 600; /* Value from 2011 ACPC */
273
+ conf->handTimeoutSecs = 3000 * 7; /* Not enforced for 2011 ACPC */
274
+ conf->avgHandTimeSecs = 7; /* Value from 2011 ACPC */
275
+ conf->games = newLLPool( sizeof( GameConfig ) );
276
+ conf->users = newLLPool( sizeof( UserSpec ) );
277
+ }
278
+
279
+ /* returns entry for bot on success, NULL on failure */
280
+ LLPoolEntry *findBot( const GameConfig *game, const char *name )
281
+ {
282
+ LLPoolEntry *cur;
283
+
284
+ for( cur = LLPoolFirstEntry( game->bots );
285
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
286
+
287
+ if( !strcmp( ( (BotSpec *)LLPoolGetItem( cur ) )->name, name ) ) {
288
+
289
+ return cur;
290
+ }
291
+ }
292
+
293
+ return NULL;
294
+ }
295
+
296
+ void addBot( GameConfig *gameConf, const char *spec )
297
+ {
298
+ BotSpec bot;
299
+ char name[ READBUF_LEN ];
300
+ char command[ READBUF_LEN ];
301
+
302
+ /* split the line into name and command */
303
+ if( sscanf( spec, " %s %s", name, command ) < 2 ) {
304
+
305
+ fprintf( stderr, "BM_ERROR: could not get bot name and command from: %s",
306
+ spec );
307
+ exit( EXIT_FAILURE );
308
+ }
309
+
310
+ /* make sure there are no duplicates */
311
+ if( !strcmp( name, "LOCAL" ) ) {
312
+
313
+ fprintf( stderr, "BM_ERROR: LOCAL is a reserved bot name\n" );
314
+ exit( EXIT_FAILURE );
315
+ }
316
+ if( findBot( gameConf, name ) ) {
317
+
318
+ fprintf( stderr, "BM_ERROR: duplicate bot %s\n", name );
319
+ exit( EXIT_FAILURE );
320
+ }
321
+
322
+ /* add the bot */
323
+ bot.name = strdup( name );
324
+ bot.command = strdup( command );
325
+ LLPoolAddItem( gameConf->bots, &bot );
326
+ }
327
+
328
+ /* returns entry for user on success, NULL on failure */
329
+ LLPoolEntry *findUser( const Config *conf, const char *name )
330
+ {
331
+ LLPoolEntry *cur;
332
+
333
+ for( cur = LLPoolFirstEntry( conf->users );
334
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
335
+
336
+ if( !strcmp( ( (UserSpec *)LLPoolGetItem( cur ) )->name, name ) ) {
337
+
338
+ return cur;
339
+ }
340
+ }
341
+
342
+ return NULL;
343
+ }
344
+
345
+ void addUser( Config *conf, const char *spec )
346
+ {
347
+ UserSpec user;
348
+ char name[ READBUF_LEN ];
349
+ char passwd[ READBUF_LEN ];
350
+
351
+ /* split the line into name and password */
352
+ if( sscanf( spec, " %s %s", name, passwd ) < 2 ) {
353
+
354
+ fprintf( stderr, "BM_ERROR: could not get user name and password from: %s",
355
+ spec );
356
+ exit( EXIT_FAILURE );
357
+ }
358
+
359
+ /* make sure there are no duplicates */
360
+ if( findUser( conf, name ) ) {
361
+
362
+ fprintf( stderr, "BM_ERROR: duplicate user %s\n", name );
363
+ exit( EXIT_FAILURE );
364
+ }
365
+
366
+ /* add the user */
367
+ user.name = strdup( name );
368
+ user.passwd = strdup( passwd );
369
+ gettimeofday( &user.waitStart, NULL );
370
+ LLPoolAddItem( conf->users, &user );
371
+ }
372
+
373
+ /* returns entry for game on success, NULL on failure */
374
+ LLPoolEntry *findGame( const Config *conf, const char *name )
375
+ {
376
+ LLPoolEntry *cur;
377
+
378
+ for( cur = LLPoolFirstEntry( conf->games );
379
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
380
+
381
+ if( !strcmp( ( (GameConfig *)LLPoolGetItem( cur ) )->gameFile, name ) ) {
382
+
383
+ return cur;
384
+ }
385
+ }
386
+
387
+ return NULL;
388
+ }
389
+
390
+ /* validate a logon request
391
+ returns user on success, or NULL on failure */
392
+ UserSpec *validateLogon( const Config *conf, const char *line )
393
+ {
394
+ LLPoolEntry *cur;
395
+ char name[ READBUF_LEN ];
396
+ char passwd[ READBUF_LEN ];
397
+
398
+ if( sscanf( line, " %s %s", name, passwd ) < 2 ) {
399
+
400
+ return NULL;
401
+ }
402
+
403
+ for( cur = LLPoolFirstEntry( conf->users );
404
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
405
+ UserSpec *user = (UserSpec *)LLPoolGetItem( cur );
406
+
407
+ if( !strcmp( user->name, name ) ) {
408
+
409
+ if( !strcmp( user->passwd, passwd ) ) {
410
+
411
+ return user;
412
+ }
413
+ return NULL;
414
+ }
415
+ }
416
+
417
+ return NULL;
418
+ }
419
+
420
+ void readConfig( const char *filename, Config *conf )
421
+ {
422
+ int start;
423
+ FILE *file;
424
+ GameConfig *gameConf;
425
+ char *line, lineBuf[ READBUF_LEN ];
426
+
427
+ file = fopen( filename, "r" );
428
+ if( file == NULL ) {
429
+
430
+ fprintf( stderr, "BM_ERROR: could not open configuration file %s\n",
431
+ filename );
432
+ exit( EXIT_FAILURE );
433
+ }
434
+
435
+ gameConf = NULL;
436
+ while( fgets( lineBuf, READBUF_LEN, file ) ) {
437
+
438
+ /* skip past white space at start of line */
439
+ start = 0; while( isspace( lineBuf[ start ] ) ) { ++start; }
440
+ line = &lineBuf[ start ];
441
+
442
+ /* ignore comments or empty lines */
443
+ if( line[ 0 ] == '#' || line[ 0 ] == ';'
444
+ || line[ 0 ] == '\n' || line[ 0 ] == 0 ) {
445
+ continue;
446
+ }
447
+
448
+ if( strncasecmp( line, "port", 4 ) == 0 ) {
449
+
450
+ if( gameConf != NULL ) {
451
+
452
+ fprintf( stderr, "BM_ERROR: server port must be defined outside of game blocks\n" );
453
+ exit( EXIT_FAILURE );
454
+ }
455
+ if( sscanf( &line[ 4 ], "%"SCNu16, &conf->port ) < 1 ) {
456
+
457
+ fprintf( stderr, "BM_ERROR: could not get port from: %s", line );
458
+ exit( EXIT_FAILURE );
459
+ }
460
+ } else if( strncasecmp( line, "game", 4 ) == 0 ) {
461
+ FILE *file;
462
+ GameConfig gc;
463
+ char game[ READBUF_LEN ];
464
+
465
+ if( gameConf != NULL ) {
466
+
467
+ fprintf( stderr, "BM_ERROR: can't define a game within another game block\n" );
468
+ exit( EXIT_FAILURE );
469
+ }
470
+ if( sscanf( &line[ 4 ], " %s", game ) < 1 ) {
471
+
472
+ fprintf( stderr, "BM_ERROR: could not get game name from: %s", line );
473
+ exit( EXIT_FAILURE );
474
+ }
475
+ if( findGame( conf, game ) ) {
476
+
477
+ fprintf( stderr, "BM_ERROR: game %s has already been used\n", game );
478
+ exit( EXIT_FAILURE );
479
+ }
480
+
481
+ setGameDefaults( &gc );
482
+ gc.gameFile = strdup( game );
483
+
484
+ file = fopen( gc.gameFile, "r" );
485
+ if( file == NULL ) {
486
+
487
+ fprintf( stderr, "BM_ERROR: could not open game file %s\n", gc.gameFile );
488
+ exit( EXIT_FAILURE );
489
+ }
490
+ gc.game = readGame( file );
491
+ fclose( file );
492
+
493
+ if( gc.game == NULL ) {
494
+
495
+ fprintf( stderr, "BM_ERROR: could not read game %s", gc.gameFile );
496
+ exit( EXIT_FAILURE );
497
+ }
498
+ gameConf
499
+ = (GameConfig *)LLPoolGetItem( LLPoolAddItem( conf->games, &gc ) );
500
+ } else if( strncmp( line, "}", 1 ) == 0 ) {
501
+ /* finished game definition */
502
+
503
+ gameConf = NULL;
504
+ } else if( strncasecmp( line, "maxRunningBots", 14 ) == 0 ) {
505
+
506
+ if( gameConf != NULL ) {
507
+
508
+ fprintf( stderr, "BM_ERROR: maxRunningBots must be defined outside of game blocks\n" );
509
+ exit( EXIT_FAILURE );
510
+ }
511
+ if( sscanf( &line[ 14 ], "%"SCNu16, &conf->maxRunningBots ) < 1 ) {
512
+
513
+ fprintf( stderr, "BM_ERROR: could not get maximum number of bots running from: %s", line );
514
+ exit( EXIT_FAILURE );
515
+ }
516
+ } else if( strncasecmp( line, "startupTimeoutSecs", 18 ) == 0 ) {
517
+
518
+ if( gameConf != NULL ) {
519
+
520
+ fprintf( stderr, "BM_ERROR: startupTimeoutSecs must be defined outside of game blocks\n" );
521
+ exit( EXIT_FAILURE );
522
+ }
523
+ if( sscanf( &line[ 18 ], "%"SCNu16, &conf->startupTimeoutSecs ) < 1 ) {
524
+
525
+ fprintf( stderr, "BM_ERROR: could not get maximum dealer startup timeout: %s", line );
526
+ exit( EXIT_FAILURE );
527
+ }
528
+ } else if( strncasecmp( line, "responseTimeoutSecs", 19 ) == 0 ) {
529
+
530
+ if( gameConf != NULL ) {
531
+
532
+ fprintf( stderr, "BM_ERROR: responseTimeoutSecs must be defined outside of game blocks\n" );
533
+ exit( EXIT_FAILURE );
534
+ }
535
+ if( sscanf( &line[ 19 ], "%"SCNu16, &conf->responseTimeoutSecs ) < 1 ) {
536
+
537
+ fprintf( stderr, "BM_ERROR: could not get maximum dealer action timeout: %s", line );
538
+ exit( EXIT_FAILURE );
539
+ }
540
+ } else if( strncasecmp( line, "handTimeoutSecs", 15 ) == 0 ) {
541
+
542
+ if( gameConf != NULL ) {
543
+
544
+ fprintf( stderr, "BM_ERROR: handTimeoutSecs must be defined outside of game blocks\n" );
545
+ exit( EXIT_FAILURE );
546
+ }
547
+ if( sscanf( &line[ 15 ], "%"SCNu16, &conf->handTimeoutSecs ) < 1 ) {
548
+
549
+ fprintf( stderr, "BM_ERROR: could not get maximum dealer hand timeout: %s", line );
550
+ exit( EXIT_FAILURE );
551
+ }
552
+ } else if( strncasecmp( line, "avgHandTimeSecs", 15 ) == 0 ) {
553
+
554
+ if( gameConf != NULL ) {
555
+
556
+ fprintf( stderr, "BM_ERROR: avgHandTimeSecs must be defined outside of game blocks\n" );
557
+ exit( EXIT_FAILURE );
558
+ }
559
+ if( sscanf( &line[ 15 ], "%"SCNu16, &conf->avgHandTimeSecs ) < 1 ) {
560
+
561
+ fprintf( stderr, "BM_ERROR: could not get dealer average hand time: %s", line );
562
+ exit( EXIT_FAILURE );
563
+ }
564
+ } else if( strncasecmp( line, "maxMatchRuns", 12 ) == 0 ) {
565
+
566
+ if( gameConf == NULL ) {
567
+
568
+ fprintf( stderr, "BM_ERROR: maxMatchRuns must be defined within a game block\n" );
569
+ exit( EXIT_FAILURE );
570
+ }
571
+ if( sscanf( &line[ 12 ], "%"SCNu16, &gameConf->maxMatchRuns ) < 1 ) {
572
+
573
+ fprintf( stderr, "BM_ERROR: could not get maximum number of runs in a match from: %s", line );
574
+ exit( EXIT_FAILURE );
575
+ }
576
+ } else if( strncasecmp( line, "maxRunningJobs", 14 ) == 0 ) {
577
+
578
+ if( gameConf == NULL ) {
579
+
580
+ fprintf( stderr, "BM_ERROR: maxRunningJobs must be defined within a game block\n" );
581
+ exit( EXIT_FAILURE );
582
+ }
583
+ if( sscanf( &line[ 14 ], "%"SCNu16, &gameConf->maxRunningJobs ) < 1 ) {
584
+
585
+ fprintf( stderr, "BM_ERROR: could not get maximum number of running jobs from: %s", line );
586
+ exit( EXIT_FAILURE );
587
+ }
588
+ } else if( strncasecmp( line, "matchHands", 10 ) == 0 ) {
589
+
590
+ if( gameConf == NULL ) {
591
+
592
+ fprintf( stderr, "BM_ERROR: matchHands must be defined within a game block\n" );
593
+ exit( EXIT_FAILURE );
594
+ }
595
+ if( sscanf( &line[ 10 ], "%"SCNu32, &gameConf->matchHands ) < 1 ) {
596
+
597
+ fprintf( stderr, "BM_ERROR: could not get number of hands in a match from: %s", line );
598
+ exit( EXIT_FAILURE );
599
+ }
600
+ } else if( strncasecmp( line, "bot", 3 ) == 0 ) {
601
+
602
+ if( gameConf == NULL ) {
603
+
604
+ fprintf( stderr, "BM_ERROR: matchHands must be defined within a game block\n" );
605
+ exit( EXIT_FAILURE );
606
+ }
607
+ addBot( gameConf, &line[ 3 ] );
608
+ } else if( strncasecmp( line, "user", 4 ) == 0 ) {
609
+
610
+ if( gameConf != NULL ) {
611
+
612
+ fprintf( stderr,
613
+ "BM_ERROR: users must be defined outside of game blocks\n" );
614
+ exit( EXIT_FAILURE );
615
+ }
616
+ addUser( conf, &line[ 4 ] );
617
+ } else {
618
+
619
+ fprintf( stderr, "BM_ERROR: unknown configuration option %s", line );
620
+ exit( EXIT_FAILURE );
621
+ }
622
+ }
623
+
624
+ fclose( file );
625
+ }
626
+
627
+ void addConnection( ServerState *serv, const int sock )
628
+ {
629
+ Connection conn;
630
+
631
+ /* add the connection */
632
+ conn.status = STATUS_UNVALIDATED;
633
+ conn.user = NULL;
634
+ conn.connBuf = createReadBuf( sock );
635
+ if( conn.connBuf == 0 ) {
636
+
637
+ fprintf( stderr, "BM_ERROR: could not create read buffer for socket\n" );
638
+ exit( EXIT_FAILURE );
639
+ }
640
+ LLPoolAddItem( serv->conns, &conn );
641
+ }
642
+
643
+ int matchUsesConnection( const Match *match, const LLPoolEntry *connEntry )
644
+ {
645
+ int p;
646
+
647
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
648
+
649
+ if( match->players[ p ].isNetworkPlayer
650
+ && match->players[ p ].entry == connEntry ) {
651
+
652
+ return 1;
653
+ }
654
+ }
655
+
656
+ return 0;
657
+ }
658
+
659
+ void closeConnection( ServerState *serv, LLPoolEntry *connEntry )
660
+ {
661
+ Connection *conn = (Connection*)LLPoolGetItem( connEntry );
662
+ LLPoolEntry *cur, *next;
663
+
664
+ destroyReadBuf( conn->connBuf );
665
+ conn->status = STATUS_CLOSED;
666
+
667
+ /* remove any pending matches which relied on the connection */
668
+ for( cur = LLPoolFirstEntry( serv->matches ); cur != NULL; cur = next ) {
669
+ next = LLPoolNextEntry( cur );
670
+ Match *match = (Match *)LLPoolGetItem( cur );
671
+
672
+ if( matchUsesConnection( match, connEntry ) ) {
673
+
674
+ match->numRuns = 0;
675
+ }
676
+ }
677
+ }
678
+
679
+ void handleListenSocket( const Config *conf, ServerState *serv )
680
+ {
681
+ int sock;
682
+ struct sockaddr_in addr;
683
+ socklen_t addrLen;
684
+
685
+ addrLen = sizeof( addr );
686
+ sock = accept( serv->listenSocket, (struct sockaddr *)&addr, &addrLen );
687
+ if( sock < 0 ) {
688
+
689
+ fprintf( stderr, "WARNING: failed to accept incoming connection\n" );
690
+ return;
691
+ }
692
+
693
+ addConnection( serv, sock );
694
+ }
695
+
696
+ /* -1 on failure, 0 on success */
697
+ int parseMatchSpec( const Config *conf,
698
+ ServerState *serv,
699
+ const char *spec,
700
+ LLPoolEntry *connEntry,
701
+ Match *match )
702
+ {
703
+ uint32_t rngSeed;
704
+ int pos, t, p;
705
+ LLPoolEntry *entry;
706
+ char tag[ READBUF_LEN ];
707
+ char name[ READBUF_LEN ];
708
+
709
+ pos = 0;
710
+
711
+ if( sscanf( &spec[ pos ], " %s%n", name, &t ) < 1 ) {
712
+
713
+ return -1;
714
+ }
715
+ pos += t;
716
+
717
+ entry = findGame( conf, name );
718
+ if( entry == NULL ) {
719
+
720
+ return -1;
721
+ }
722
+ match->gameConf = (GameConfig *)LLPoolGetItem( entry );
723
+
724
+ if( sscanf( &spec[ pos ],
725
+ " %d %s %"SCNu32" %n",
726
+ &match->numRuns,
727
+ tag,
728
+ &rngSeed,
729
+ &t ) < 3 ) {
730
+
731
+ return -1;
732
+ }
733
+ pos += t;
734
+ if( match->numRuns < 0 || match->numRuns > match->gameConf->maxMatchRuns ) {
735
+
736
+ return -1;
737
+ }
738
+
739
+ /* make sure tag has no characters in it */
740
+ if( strchr( tag, '/' ) != NULL ) {
741
+
742
+ return -1;
743
+ }
744
+
745
+ /* get bots */
746
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
747
+
748
+ /* get the name */
749
+ if( sscanf( &spec[ pos ], " %s%n", name, &t ) < 1 ) {
750
+
751
+ return -1;
752
+ }
753
+ pos += t;
754
+
755
+ /* translate the name into an index */
756
+ if( !strcmp( name, "LOCAL" ) ) {
757
+
758
+ match->players[ p ].isNetworkPlayer = 1;
759
+ match->players[ p ].entry = connEntry;
760
+ } else {
761
+
762
+ match->players[ p ].isNetworkPlayer = 0;
763
+ match->players[ p ].entry = findBot( match->gameConf, name );
764
+ if( match->players[ p ].entry == NULL ) {
765
+
766
+ return -1;
767
+ }
768
+ }
769
+ }
770
+
771
+ match->tag = strdup( tag );
772
+ if( rngSeed ) {
773
+
774
+ init_genrand( &match->rng, rngSeed );
775
+ } else {
776
+
777
+ init_genrand( &match->rng, genrand_int32( &serv->rng ) );
778
+ }
779
+
780
+ return 0;
781
+ }
782
+
783
+ void writeHelpMessage( int fd )
784
+ {
785
+ int r;
786
+
787
+ r = write( fd, "HELP - this message\n", 20 );
788
+ r = write( fd, "GAMES - list available games and players\n", 41 );
789
+ r = write( fd, "QSTAT - show the current queue\n", 31 );
790
+ r = write( fd, "RUNMATCHES game #runs tag rngSeed player ... - submit match request\n", 68 );
791
+ r = write( fd, " - Player order decides match seating\n", 39 );
792
+ r = write( fd, " - \"LOCAL\" player runs the bm_widget agent (bot_command)\n", 60 );
793
+ }
794
+
795
+ void writeGameList( const Config *conf, int fd )
796
+ {
797
+ int r;
798
+ LLPoolEntry *cur, *botCur;
799
+ char line[ READBUF_LEN ];
800
+
801
+ for( cur = LLPoolFirstEntry( conf->games );
802
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
803
+ GameConfig *game = (GameConfig *)LLPoolGetItem( cur );
804
+
805
+ r = sprintf( line, "\n%s\n", game->gameFile );
806
+ assert( r > 0 );
807
+ r = write( fd, line, r );
808
+
809
+ for( botCur = LLPoolFirstEntry( game->bots );
810
+ botCur != NULL; botCur = LLPoolNextEntry( botCur ) ) {
811
+ BotSpec *bot = (BotSpec *)LLPoolGetItem( botCur );
812
+
813
+ r = sprintf( line, " %s\n", bot->name );
814
+ assert( r > 0 );
815
+ r = write( fd, line, r );
816
+ }
817
+ }
818
+ }
819
+
820
+ void writeQueueStatus( const Config *conf, const ServerState *serv, int fd )
821
+ {
822
+ int r;
823
+ LLPoolEntry *cur;
824
+ char line[ READBUF_LEN * 4 ];
825
+
826
+ if( serv->matches->numEntries == 0 ) {
827
+ r = write( fd, "Queue empty\n", 12 );
828
+ }
829
+
830
+ for( cur = LLPoolFirstEntry( serv->matches );
831
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
832
+ Match *match = (Match *)LLPoolGetItem( cur );
833
+
834
+ r = sprintf( line,
835
+ "%s %s %s * %d %s\n",
836
+ match->user->name,
837
+ match->tag,
838
+ match->gameConf->gameFile,
839
+ match->numRuns,
840
+ match->isRunning ? "R" : "Q" );
841
+ assert( r > 0 );
842
+ r = write( fd, line, r );
843
+ }
844
+ }
845
+
846
+ void handleConnection( Config *conf, ServerState *serv,
847
+ LLPoolEntry *connEntry )
848
+ {
849
+ int r;
850
+ Connection *conn = (Connection *)LLPoolGetItem( connEntry );
851
+ char line[ READBUF_LEN ];
852
+
853
+ while( ( r = getLine( conn->connBuf, READBUF_LEN, line, 0 ) ) >= 0 ) {
854
+
855
+ if( r == 0 ) {
856
+
857
+ closeConnection( serv, connEntry );
858
+ return;
859
+ }
860
+
861
+ if( conn->status == STATUS_UNVALIDATED ) {
862
+ UserSpec *user;
863
+
864
+ user = validateLogon( conf, line );
865
+ if( user == NULL ) {
866
+ /* couldn't authenticate */
867
+
868
+ r = write( conn->connBuf->fd, "BAD LOGON\n", 10 );
869
+ fprintf( stderr, "BM_ERROR: connection failed to log in\n" );
870
+ closeConnection( serv, connEntry );
871
+ return;
872
+ }
873
+
874
+ /* send an okay message */
875
+ r = write( conn->connBuf->fd, "LOGON OKAY - type help for commands\n", 36 );
876
+
877
+ /* connection status is now okay */
878
+ conn->user = user;
879
+ conn->status = STATUS_OKAY;
880
+ return;
881
+ }
882
+
883
+ if( !strncasecmp( line, "HELP", 4 ) ) {
884
+
885
+ writeHelpMessage( conn->connBuf->fd );
886
+ } else if( !strncasecmp( line, "GAMES", 5 ) ) {
887
+
888
+ writeGameList( conf, conn->connBuf->fd );
889
+ } else if( !strncasecmp( line, "QSTAT", 5 ) ) {
890
+
891
+ writeQueueStatus( conf, serv, conn->connBuf->fd );
892
+ } else if( !strncasecmp( line, "RUNMATCHES", 10 ) ) {
893
+ Match match;
894
+
895
+ if( parseMatchSpec( conf, serv, &line[ 10 ], connEntry, &match ) < 0 ) {
896
+
897
+ fprintf( stderr, "BM_ERROR: bad RUNMATCHES command: %s", line );
898
+ r = write( conn->connBuf->fd, "BAD RUNMATCHES COMMAND\n", 23 );
899
+ return;
900
+ }
901
+ match.user = ( (Connection *)LLPoolGetItem( connEntry ) )->user;
902
+ match.isRunning = 0;
903
+ gettimeofday( &match.queueTime, NULL );
904
+ LLPoolAddItem( serv->matches, &match );
905
+ return;
906
+ } else {
907
+
908
+ r = write( conn->connBuf->fd, "UNKNOWN\n", 8 );
909
+ return;
910
+ }
911
+ }
912
+ }
913
+
914
+ int timeIsEarlier( struct timeval *a, struct timeval *b )
915
+ {
916
+ if( a->tv_sec < b->tv_usec ) {
917
+ return 1;
918
+ } else if( a->tv_sec == b->tv_sec
919
+ && a->tv_usec < b->tv_usec ) {
920
+ return 1;
921
+ }
922
+ return 0;
923
+ }
924
+
925
+ /* how many bots will match start? */
926
+ int botsInMatch( const Match *match )
927
+ {
928
+ int p, num;
929
+
930
+ num = 0;
931
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
932
+
933
+ if( !match->players[ p ].isNetworkPlayer ) {
934
+
935
+ ++num;
936
+ }
937
+ }
938
+
939
+ return num;
940
+ }
941
+
942
+ void startDealer( const Config *conf, const Match *match, MatchJob *job )
943
+ {
944
+ int stdoutPipe[ 2 ], p, arg;
945
+ char handsString[ 16 ], rngString[ 16 ];
946
+ char startupTimeoutString[ 16 ], responseTimeoutString[ 16 ];
947
+ char handTimeoutString[ 16 ], avgHandTimeString[ 16 ];
948
+ char *argv[ MAX_PLAYERS + 64 ];
949
+
950
+ if( pipe( stdoutPipe ) < 0 ) {
951
+
952
+ fprintf( stderr, "BM_ERROR: could not create pipe for new dealer\n" );
953
+ exit( EXIT_FAILURE );
954
+ }
955
+
956
+ job->dealerPID = fork();
957
+ if( job->dealerPID < 0 ) {
958
+
959
+ fprintf( stderr, "BM_ERROR: fork() failed\n" );
960
+ exit( EXIT_FAILURE );
961
+ }
962
+ if( !job->dealerPID ) {
963
+ /* child runs the dealer command */
964
+ int stderrfd;
965
+ char tag[ READBUF_LEN ];
966
+
967
+ sprintf( tag, "%s/%s.stderr", BM_LOGDIR, job->tag );
968
+ stderrfd = open( tag, O_WRONLY | O_APPEND | O_CREAT, 0644 );
969
+ if( stderrfd < 0 ) {
970
+
971
+ fprintf( stderr,
972
+ "BM_ERROR: could not create error log %s\n",
973
+ tag );
974
+ exit( EXIT_FAILURE );
975
+ }
976
+ dup2( stderrfd, 2 );
977
+
978
+ /* change stdout to be the write end of the pipe */
979
+ close( stdoutPipe[ 0 ] );
980
+ dup2( stdoutPipe[ 1 ], 1 );
981
+
982
+ arg = 0;
983
+
984
+ argv[ arg ] = BM_DEALER;
985
+ ++arg;
986
+
987
+ sprintf( tag, "%s/%s", BM_LOGDIR, job->tag );
988
+ argv[ arg ] = tag;
989
+ ++arg;
990
+
991
+ argv[ arg ] = match->gameConf->gameFile;
992
+ ++arg;
993
+
994
+ sprintf( handsString, "%"PRIu32, match->gameConf->matchHands );
995
+ argv[ arg ] = handsString;
996
+ ++arg;
997
+
998
+ sprintf( rngString, "%"PRIu32,
999
+ genrand_int32( &( (Match *)LLPoolGetItem( job->matchEntry ) )
1000
+ ->rng ) );
1001
+ argv[ arg ] = rngString;
1002
+ ++arg;
1003
+
1004
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
1005
+
1006
+ if( match->players[ p ].isNetworkPlayer ) {
1007
+
1008
+ argv[ arg ]
1009
+ = ( (Connection *)LLPoolGetItem( match->players[ p ].entry ) )
1010
+ ->user->name;
1011
+ } else {
1012
+
1013
+ argv[ arg ]
1014
+ = ( (BotSpec *)LLPoolGetItem( match->players[ p ].entry ) )->name;
1015
+ }
1016
+ ++arg;
1017
+ }
1018
+
1019
+ if( conf->startupTimeoutSecs ) {
1020
+
1021
+ argv[ arg ] = "--start_timeout";
1022
+ ++arg;
1023
+
1024
+ sprintf( startupTimeoutString,
1025
+ "%d", (int)conf->startupTimeoutSecs * 1000 );
1026
+ argv[ arg ] = startupTimeoutString;
1027
+ ++arg;
1028
+ }
1029
+
1030
+ /* Add maximum per action timeout argument */
1031
+ argv[ arg ] = "--t_response";
1032
+ ++arg;
1033
+
1034
+ sprintf( responseTimeoutString,
1035
+ "%d", (int)conf->responseTimeoutSecs * 1000 );
1036
+ argv[ arg ] = responseTimeoutString;
1037
+ ++arg;
1038
+
1039
+ /* Add maximum per hand timeout argument */
1040
+ argv[ arg ] = "--t_hand";
1041
+ ++arg;
1042
+
1043
+ sprintf( handTimeoutString, "%d", (int)conf->handTimeoutSecs * 1000 );
1044
+ argv[ arg ] = handTimeoutString;
1045
+ ++arg;
1046
+
1047
+ /* Add average per hand time argument */
1048
+ argv[ arg ] = "--t_per_hand";
1049
+ ++arg;
1050
+
1051
+ sprintf( avgHandTimeString, "%d", (int)conf->avgHandTimeSecs * 1000 );
1052
+ argv[ arg ] = avgHandTimeString;
1053
+ ++arg;
1054
+
1055
+
1056
+ argv[ arg ] = "-q";
1057
+ ++arg;
1058
+
1059
+ argv[ arg ] = NULL;
1060
+
1061
+ execv( BM_DEALER, argv );
1062
+
1063
+ fprintf( stderr, "BM_ERROR: could not start dealer\n" );
1064
+ exit( EXIT_FAILURE );
1065
+ }
1066
+
1067
+ /* parent has to talk to child to get ports */
1068
+ ssize_t r;
1069
+ int pos, t;
1070
+ fd_set readfds;
1071
+ struct timeval timeout;
1072
+ char portString[ READBUF_LEN ];
1073
+
1074
+ close( stdoutPipe[ 1 ] );
1075
+ timeout.tv_sec = BM_DEALER_WAIT_SECS;
1076
+ timeout.tv_usec = 0;
1077
+ FD_ZERO( &readfds );
1078
+ FD_SET( stdoutPipe[ 0 ], &readfds );
1079
+ if( select( stdoutPipe[ 0 ] + 1, &readfds, NULL, NULL, &timeout ) < 1 ) {
1080
+
1081
+ fprintf( stderr,
1082
+ "BM_ERROR: timed out waiting for port string from dealer\n" );
1083
+ exit( EXIT_FAILURE );
1084
+ }
1085
+ r = read( stdoutPipe[ 0 ], portString, READBUF_LEN );
1086
+ if( r <= 0 || portString[ r - 1 ] != '\n' ) {
1087
+
1088
+ fprintf( stderr, "BM_ERROR: could not read port string from dealer\n" );
1089
+ exit( EXIT_FAILURE );
1090
+ }
1091
+ portString[ r ] = 0;
1092
+
1093
+ /* parse the port string */
1094
+ pos = 0;
1095
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
1096
+
1097
+ if( sscanf( &portString[ pos ],
1098
+ " %"SCNu16"%n",
1099
+ &job->ports[ p ],
1100
+ &t ) < 1 ) {
1101
+
1102
+ fprintf( stderr,
1103
+ "BM_ERROR: could not get port for player %d from dealer\n",
1104
+ p + 1 );
1105
+ exit( EXIT_FAILURE );
1106
+ }
1107
+ pos += t;
1108
+ }
1109
+ }
1110
+
1111
+ pid_t startBot( const ServerState *serv,
1112
+ const BotSpec *bot,
1113
+ const uint16_t port,
1114
+ const int botPosition )
1115
+ {
1116
+ pid_t pid;
1117
+
1118
+ pid = fork();
1119
+ if( pid < 0 ) {
1120
+
1121
+ fprintf( stderr, "BM_ERROR: fork() failed\n" );
1122
+ exit( EXIT_FAILURE );
1123
+ }
1124
+ if( !pid ) {
1125
+ /* child runs the bot command */
1126
+ char portString[ 8 ];
1127
+ char posString[ 16 ];
1128
+
1129
+ sprintf( portString, "%"PRIu16, port );
1130
+ sprintf( posString, "%d", botPosition );
1131
+
1132
+ /* throw away bot output */
1133
+ dup2( serv->devnullfd, 1 );
1134
+ dup2( serv->devnullfd, 2 );
1135
+
1136
+ execl( bot->command,
1137
+ bot->command,
1138
+ serv->hostname,
1139
+ portString,
1140
+ posString,
1141
+ NULL );
1142
+
1143
+ fprintf( stderr, "BM_ERROR: could not start bot %s\n", bot->command );
1144
+ exit( EXIT_FAILURE );
1145
+ }
1146
+
1147
+ return pid;
1148
+ }
1149
+
1150
+ int sendStartMessage( const ServerState *serv,
1151
+ const Connection *conn,
1152
+ const uint16_t port )
1153
+ {
1154
+ int len;
1155
+ char msg[ strlen( serv->hostname ) + 8 ];
1156
+
1157
+ len = sprintf( msg, "RUN %s %"PRIu16"\n", serv->hostname, port );
1158
+ assert( len > 0 );
1159
+
1160
+ if( write( conn->connBuf->fd, msg, len ) < len ) {
1161
+
1162
+ fprintf( stderr, "BM_ERROR: short write to connection\n" );
1163
+ return -1;
1164
+ }
1165
+
1166
+ return 0;
1167
+ }
1168
+
1169
+ MatchJob runMatchJob( const Config *conf,
1170
+ const ServerState *serv,
1171
+ LLPoolEntry *matchEntry,
1172
+ const uint32_t rngSeed )
1173
+ {
1174
+ int p, botPosition;
1175
+ MatchJob job;
1176
+ Match *match = (Match *)LLPoolGetItem( matchEntry );
1177
+ char tag[ READBUF_LEN ];
1178
+
1179
+ job.matchEntry = matchEntry;
1180
+
1181
+ /* make the tag from the match tag */
1182
+ sprintf( tag, "%s.%s", match->user->name, match->tag );
1183
+ job.tag = strdup( tag );
1184
+
1185
+ /* initialise all PIDs to 0 */
1186
+ job.dealerPID = 0;
1187
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
1188
+
1189
+ job.botPID[ p ] = 0;
1190
+ }
1191
+
1192
+ /* start the dealer */
1193
+ startDealer( conf, match, &job );
1194
+
1195
+ /* deal with all the players */
1196
+ botPosition = 0;
1197
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
1198
+
1199
+ if( match->players[ p ].isNetworkPlayer ) {
1200
+ /* send message with port to network player to start up */
1201
+ Connection *conn = (Connection*)LLPoolGetItem( match->players[ p ].entry );
1202
+
1203
+ if( sendStartMessage( serv, conn, job.ports[ p ] ) < 0 ) {
1204
+ /* abort the job... */
1205
+
1206
+ fprintf( stderr, "BM_ERROR: aborting job\n" );
1207
+
1208
+ kill( job.dealerPID, SIGTERM );
1209
+ while( p > 0 ) {
1210
+ --p;
1211
+
1212
+ if( job.botPID[ p ] ) {
1213
+
1214
+ kill( job.botPID[ p ], SIGTERM );
1215
+ }
1216
+ }
1217
+
1218
+ return job;
1219
+ }
1220
+ } else {
1221
+ /* start up bot */
1222
+
1223
+ job.botPID[ p ]
1224
+ = startBot( serv,
1225
+ (BotSpec *)LLPoolGetItem( match->players[ p ].entry ),
1226
+ job.ports[ p ],
1227
+ botPosition );
1228
+ ++botPosition;
1229
+ }
1230
+ }
1231
+
1232
+ return job;
1233
+ }
1234
+
1235
+ int startMatchJob( const Config *conf, ServerState *serv )
1236
+ {
1237
+ int running;
1238
+ LLPoolEntry *cur, *next, *best;
1239
+ Match *curMatch, *bestMatch;
1240
+ MatchJob job;
1241
+
1242
+ /* automatically done adding things if we've got no more matches */
1243
+ if( serv->matches->numEntries == 0 ) {
1244
+
1245
+ return 0;
1246
+ }
1247
+
1248
+ /* how many bots are currently running */
1249
+ running = 0;
1250
+ for( cur = LLPoolFirstEntry( serv->jobs );
1251
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
1252
+ MatchJob *job = (MatchJob *)LLPoolGetItem( cur );
1253
+
1254
+ running += botsInMatch( (Match*)LLPoolGetItem( job->matchEntry ) );
1255
+ }
1256
+
1257
+ /* pick the best match to start */
1258
+ best = 0;
1259
+ bestMatch = 0;
1260
+ for( cur = LLPoolFirstEntry( serv->matches ); cur != NULL; cur = next ) {
1261
+ next = LLPoolNextEntry( cur );
1262
+ curMatch = (Match *)LLPoolGetItem( cur );
1263
+
1264
+ if( curMatch->isRunning ) {
1265
+
1266
+ continue;
1267
+ }
1268
+
1269
+ if( curMatch->numRuns <= 0 ) {
1270
+ /* match is finished - clean it up */
1271
+
1272
+ free( curMatch->tag );
1273
+ LLPoolRemoveEntry( serv->matches, cur );
1274
+ continue;
1275
+ }
1276
+
1277
+ if( curMatch->gameConf->maxRunningJobs
1278
+ && curMatch->gameConf->curRunningJobs
1279
+ >= curMatch->gameConf->maxRunningJobs ) {
1280
+ /* cur refers to a match in a game which is currently too busy */
1281
+
1282
+ continue;
1283
+ }
1284
+
1285
+ if( best == 0
1286
+ || timeIsEarlier( &curMatch->user->waitStart,
1287
+ &bestMatch->user->waitStart )
1288
+ || ( !timeIsEarlier( &bestMatch->user->waitStart,
1289
+ &curMatch->user->waitStart )
1290
+ && timeIsEarlier( &curMatch->queueTime,
1291
+ &bestMatch->queueTime ) ) ) {
1292
+
1293
+ best = cur;
1294
+ bestMatch = curMatch;
1295
+ }
1296
+ }
1297
+
1298
+ /* return failure if we couldn't find a runnable job */
1299
+ if( best == NULL ) {
1300
+
1301
+ return 0;
1302
+ }
1303
+
1304
+ /* check if we have the space to run the bots */
1305
+ if( conf->maxRunningBots
1306
+ && botsInMatch( bestMatch ) + running > conf->maxRunningBots ) {
1307
+
1308
+ return 0;
1309
+ }
1310
+
1311
+ /* create the job */
1312
+ job = runMatchJob( conf, serv, best, genrand_int32( &bestMatch->rng ) );
1313
+ assert( job.dealerPID );
1314
+ LLPoolAddItem( serv->jobs, &job );
1315
+
1316
+ /* update status about running jobs */
1317
+ ++( bestMatch->gameConf->curRunningJobs );
1318
+ bestMatch->isRunning = 1;
1319
+
1320
+ /* update the user */
1321
+ gettimeofday( &bestMatch->user->waitStart, NULL );
1322
+
1323
+ /* update the match */
1324
+ --bestMatch->numRuns;
1325
+ gettimeofday( &bestMatch->queueTime, NULL );
1326
+
1327
+ return 1;
1328
+ }
1329
+
1330
+ void initServerState( const Config *conf, ServerState *serv )
1331
+ {
1332
+ struct addrinfo hints, *info;
1333
+ uint16_t port;
1334
+ int hnm, r;
1335
+ char *hn;
1336
+ char ipstr[ INET6_ADDRSTRLEN ];
1337
+
1338
+ serv->conns = newLLPool( sizeof( Connection ) );
1339
+ serv->matches = newLLPool( sizeof( Match ) );
1340
+ serv->jobs = newLLPool( sizeof( MatchJob ) );
1341
+
1342
+ /* create the socket clients will connect to */
1343
+ port = conf->port;
1344
+ serv->listenSocket = getListenSocket( &port );
1345
+ if( serv->listenSocket < 0 ) {
1346
+
1347
+ fprintf( stderr, "BM_ERROR: could not open socket for listening\n" );
1348
+ exit( EXIT_FAILURE );
1349
+ }
1350
+ printf( "starting server on port %"PRIu16"\n", conf->port );
1351
+
1352
+ init_genrand( &serv->rng, time( NULL ) );
1353
+
1354
+ hnm = sysconf( _SC_HOST_NAME_MAX );
1355
+ hn = (char*)malloc( hnm );
1356
+ assert( hn != 0 );
1357
+ if( gethostname( hn, hnm + 1 ) < 0 ) {
1358
+
1359
+ fprintf( stderr, "BM_ERROR: could not get hostname\n" );
1360
+ exit( EXIT_FAILURE );
1361
+ }
1362
+
1363
+ memset( &hints, 0, sizeof( hints ) );
1364
+ hints.ai_family = AF_INET;
1365
+ hints.ai_socktype = SOCK_STREAM;
1366
+ if( ( r = getaddrinfo( hn, NULL, &hints, &info ) ) != 0 ) {
1367
+
1368
+ fprintf( stderr,
1369
+ "BM_ERROR: could not get address info for host %s\n",
1370
+ hn );
1371
+ exit( 1 );
1372
+ }
1373
+ free( hn );
1374
+
1375
+ /* Get an address for the server */
1376
+ void *addr;
1377
+
1378
+ /* get the pointer to the address itself,
1379
+ * different fields in IPv4 and IPv6: */
1380
+ if ( info->ai_family == AF_INET ) {
1381
+ /* IPv4 */
1382
+ struct sockaddr_in *ipv4 = ( struct sockaddr_in * ) info->ai_addr;
1383
+ addr = &( ipv4->sin_addr );
1384
+ } else {
1385
+ /* IPv6 */
1386
+ struct sockaddr_in6 *ipv6 = ( struct sockaddr_in6 * ) info->ai_addr;
1387
+ addr = &( ipv6->sin6_addr );
1388
+ }
1389
+
1390
+ /* convert the IP to a string and store it:*/
1391
+ inet_ntop( info->ai_family, addr, ipstr, sizeof( ipstr ) );
1392
+ serv->hostname = strdup( ipstr );
1393
+
1394
+ freeaddrinfo( info ); /* free the linked list */
1395
+
1396
+ serv->devnullfd = open( "/dev/null", O_WRONLY );
1397
+ if( serv->devnullfd < 0 ) {
1398
+
1399
+ fprintf( stderr, "BM_ERROR: could not open /dev/null\n" );
1400
+ exit( EXIT_FAILURE );
1401
+ }
1402
+ }
1403
+
1404
+ int checkIfJobFinished( MatchJob *job )
1405
+ {
1406
+ int status, r, p, allDone;
1407
+ Match *match = (Match *)LLPoolGetItem( job->matchEntry );
1408
+
1409
+ allDone = 1;
1410
+
1411
+ if( job->dealerPID ) {
1412
+
1413
+ r = waitpid( job->dealerPID, &status, WNOHANG );
1414
+ if( r < 0 ) {
1415
+
1416
+ fprintf( stderr, "BM_ERROR: could not wait on child\n" );
1417
+ exit( EXIT_FAILURE );
1418
+ }
1419
+ if( r == job->dealerPID ) {
1420
+
1421
+ job->dealerPID = 0;
1422
+ } else {
1423
+
1424
+ allDone = 0;
1425
+ }
1426
+ }
1427
+
1428
+ for( p = 0; p < match->gameConf->game->numPlayers; ++p ) {
1429
+ if( job->botPID[ p ] == 0 ) {
1430
+ continue;
1431
+ }
1432
+
1433
+ r = waitpid( job->botPID[ p ], &status, WNOHANG );
1434
+ if( r < 0 ) {
1435
+
1436
+ fprintf( stderr, "BM_ERROR: could not wait on child\n" );
1437
+ exit( EXIT_FAILURE );
1438
+ }
1439
+ if( r == job->botPID[ p ] ) {
1440
+
1441
+ job->botPID[ p ] = 0;
1442
+ } else {
1443
+
1444
+ allDone = 0;
1445
+ }
1446
+ }
1447
+
1448
+ return allDone;
1449
+ }
1450
+
1451
+ void finishedJob( ServerState *serv, LLPoolEntry *jobEntry )
1452
+ {
1453
+ MatchJob *job = (MatchJob *)LLPoolGetItem( jobEntry );
1454
+ Match *match = (Match *)LLPoolGetItem( job->matchEntry );
1455
+
1456
+ free( job->tag );
1457
+ --( match->gameConf->curRunningJobs );
1458
+ match->isRunning = 0;
1459
+ LLPoolRemoveEntry( serv->jobs, jobEntry );
1460
+ }
1461
+
1462
+ int main( int argc, char **argv )
1463
+ {
1464
+ Config conf;
1465
+ ServerState serv;
1466
+ int maxfd;
1467
+ fd_set readfds;
1468
+ LLPoolEntry *cur, *next;
1469
+ struct timeval tv;
1470
+
1471
+ if( argc < 2 ) {
1472
+
1473
+ printUsage( stderr );
1474
+ exit( EXIT_FAILURE );
1475
+ }
1476
+
1477
+ /* Ignore SIGPIPE. It seems that SIGPIPE can be raised when the underlying
1478
+ * IO fails with a SIGPIPE. Unfortunately this causes the entire benchmark
1479
+ * server to crash and jobs are lost. Ignore the signal to avoid death */
1480
+ /* ???: May also need to catch SIGCHLD */
1481
+ signal( SIGPIPE, SIG_IGN );
1482
+
1483
+ /* use the config file */
1484
+ setDefaults( &conf );
1485
+ readConfig( argv[ 1 ], &conf );
1486
+
1487
+ /* initialise server state */
1488
+ initServerState( &conf, &serv );
1489
+
1490
+ /* main I/O loop */
1491
+ while( 1 ) {
1492
+
1493
+ /* clean up any finished jobs */
1494
+ for( cur = LLPoolFirstEntry( serv.jobs ); cur != NULL; cur = next ) {
1495
+ next = LLPoolNextEntry( cur );
1496
+ MatchJob *job = (MatchJob *)LLPoolGetItem( cur );
1497
+
1498
+ if( checkIfJobFinished( job ) ) {
1499
+
1500
+ finishedJob( &serv, cur );
1501
+ }
1502
+ }
1503
+
1504
+ /* clean up any closed connections */
1505
+ for( cur = LLPoolFirstEntry( serv.conns ); cur != NULL; cur = next ) {
1506
+ next = LLPoolNextEntry( cur );
1507
+
1508
+ if( ( (Connection *)LLPoolGetItem( cur ) )->status == STATUS_CLOSED ) {
1509
+
1510
+ LLPoolRemoveEntry( serv.conns, cur );
1511
+ }
1512
+ }
1513
+
1514
+ /* start jobs, up to the maximum */
1515
+ while( startMatchJob( &conf, &serv ) );
1516
+
1517
+ /* wait for input */
1518
+ FD_ZERO( &readfds );
1519
+ FD_SET( serv.listenSocket, &readfds );
1520
+ maxfd = serv.listenSocket;
1521
+ tv.tv_sec = BM_MAX_IOWAIT_SECS;
1522
+ tv.tv_usec = 0;
1523
+ for( cur = LLPoolFirstEntry( serv.conns );
1524
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
1525
+ Connection *conn = (Connection *)LLPoolGetItem( cur );
1526
+
1527
+ FD_SET( conn->connBuf->fd, &readfds );
1528
+ if( conn->connBuf->fd > maxfd ) {
1529
+
1530
+ maxfd = conn->connBuf->fd;
1531
+ }
1532
+ }
1533
+ if( select( maxfd + 1, &readfds, NULL, NULL, &tv ) < 0 ) {
1534
+
1535
+ fprintf( stderr, "BM_ERROR: select failed\n" );
1536
+ exit( -1 );
1537
+ }
1538
+
1539
+ /* process anything that's happened */
1540
+ if( FD_ISSET( serv.listenSocket, &readfds ) ) {
1541
+
1542
+ handleListenSocket( &conf, &serv );
1543
+ }
1544
+ for( cur = LLPoolFirstEntry( serv.conns );
1545
+ cur != NULL; cur = LLPoolNextEntry( cur ) ) {
1546
+ Connection *conn = (Connection *)LLPoolGetItem( cur );
1547
+
1548
+ if( FD_ISSET( conn->connBuf->fd, &readfds ) ) {
1549
+
1550
+ handleConnection( &conf, &serv, cur );
1551
+ }
1552
+ }
1553
+ }
1554
+
1555
+ close( serv.listenSocket );
1556
+ return EXIT_SUCCESS;
1557
+ }