src/main.c

changeset 130
3fc6b1d6cbe9
parent 129
189c7c77aaab
equal deleted inserted replaced
129:189c7c77aaab 130:3fc6b1d6cbe9
42 #include <signal.h> 42 #include <signal.h>
43 #include <errno.h> 43 #include <errno.h>
44 #include <unistd.h> 44 #include <unistd.h>
45 45
46 typedef struct { 46 typedef struct {
47 GameInfo gameinfo;
48 /** 47 /**
49 * Server host address. 48 * Server host address.
50 * TCP: server address or \c NULL when we are the server 49 * TCP: server address or \c NULL when we are the server
51 * Domain Socket: the path to the domain socket 50 * Domain Socket: the path to the domain socket
52 */ 51 */
53 char* serverhost; 52 char* serverhost;
54 char* continuepgn; 53 char* continuepgn;
54 unsigned short time;
55 unsigned short addtime;
56 unsigned short delay;
55 short port; 57 short port;
58 uint8_t servercolor;
59 bool timecontrol;
56 bool ishost; 60 bool ishost;
57 bool usedomainsocket; 61 bool usedomainsocket;
58 bool singlemachine; 62 bool singlemachine;
59 bool disableflip; 63 bool disableflip;
60 bool unicode; 64 bool unicode;
61 } Settings; 65 } Settings;
62 66
63 int get_settings(int argc, char **argv, Settings *settings) { 67 static Settings settings;
68
69 static void init_settings(void) {
70 memset(&settings, 0, sizeof(settings));
71 settings.servercolor = WHITE;
72 settings.port = 27015;
73 settings.unicode = !!setlocale(LC_CTYPE, "C.UTF-8");
74 }
75
76 static void cleanup() {
77 endwin();
78 if (settings.usedomainsocket && settings.ishost) {
79 remove(settings.serverhost);
80 }
81 }
82
83 static void settings_apply(GameState *gamestate) {
84 gamestate->info.servercolor = settings.servercolor;
85 gamestate->info.timecontrol = settings.timecontrol;
86 gamestate->info.time = settings.time;
87 gamestate->info.addtime = settings.addtime;
88 gamestate->info.delay = settings.delay;
89 }
90
91 static int get_settings(int argc, char **argv) {
64 char *valid; 92 char *valid;
65 unsigned long int time, port; 93 unsigned long int time, port;
66 uint8_t timeunit = 60; 94 uint8_t timeunit = 60;
67 size_t len; 95 size_t len;
68 bool port_set = false; 96 bool port_set = false;
69 97
70 for (int opt ; (opt = getopt(argc, argv, "a:bc:Fhp:rsS:t:uUv")) != -1 ;) { 98 for (int opt ; (opt = getopt(argc, argv, "a:bc:d:Fhp:rsS:t:uUv")) != -1 ;) {
71 switch (opt) { 99 switch (opt) {
72 case 'c': 100 case 'c':
73 settings->continuepgn = optarg; 101 settings.continuepgn = optarg;
74 break; 102 break;
75 case 'b': 103 case 'b':
76 settings->gameinfo.servercolor = BLACK; 104 settings.servercolor = BLACK;
77 break; 105 break;
78 case 'r': 106 case 'r':
79 settings->gameinfo.servercolor = rand() & 1 ? WHITE : BLACK; 107 settings.servercolor = rand() & 1 ? WHITE : BLACK;
80 break; 108 break;
81 case 's': 109 case 's':
82 settings->singlemachine = true; 110 settings.singlemachine = true;
83 break; 111 break;
84 case 'F': 112 case 'F':
85 settings->disableflip = true; 113 settings.disableflip = true;
86 break; 114 break;
87 case 'u': 115 case 'u':
88 if (port_set) { 116 if (port_set) {
89 fprintf(stderr, "Cannot use Unix domain sockets " 117 fprintf(stderr, "Cannot use Unix domain sockets "
90 "when a TCP port was specified.\n"); 118 "when a TCP port was specified.\n");
91 return 1; 119 return 1;
92 } 120 }
93 settings->usedomainsocket = true; 121 settings.usedomainsocket = true;
94 break; 122 break;
95 case 'U': 123 case 'U':
96 settings->unicode = false; 124 settings.unicode = false;
97 break; 125 break;
98 case 't': 126 case 't':
99 case 'a': 127 case 'a':
128 case 'd':
100 len = strlen(optarg); 129 len = strlen(optarg);
101 if (optarg[len-1] == 's') { 130 if (optarg[len-1] == 's') {
102 optarg[len-1] = '\0'; 131 optarg[len-1] = '\0';
103 timeunit = 1; 132 timeunit = 1;
104 } 133 }
107 || *valid != '\0') { 136 || *valid != '\0') {
108 fprintf(stderr, "Specified time is invalid (%s)" 137 fprintf(stderr, "Specified time is invalid (%s)"
109 "- Maximum: 65535 seconds (1092 minutes)\n", optarg); 138 "- Maximum: 65535 seconds (1092 minutes)\n", optarg);
110 return 1; 139 return 1;
111 } else { 140 } else {
112 settings->gameinfo.timecontrol = 1; 141 settings.timecontrol = 1;
113 if (opt=='t') { 142 if (opt=='t') {
114 settings->gameinfo.time = timeunit * time; 143 settings.time = timeunit * time;
144 } else if (opt=='a') {
145 settings.addtime = time;
115 } else { 146 } else {
116 settings->gameinfo.addtime = time; 147 settings.delay = time;
117 } 148 }
118 } 149 }
119 break; 150 break;
120 case 'p': 151 case 'p':
121 if (port_set) { 152 if (port_set) {
122 fprintf(stderr, "Cannot use -p twice.\n"); 153 fprintf(stderr, "Cannot use -p twice.\n");
123 return 1; 154 return 1;
124 } 155 }
125 if (settings->usedomainsocket) { 156 if (settings.usedomainsocket) {
126 fprintf(stderr, "Cannot specify TCP port " 157 fprintf(stderr, "Cannot specify TCP port "
127 "when using Unix domain sockets.\n"); 158 "when using Unix domain sockets.\n");
128 return 1; 159 return 1;
129 } 160 }
130 port = strtol(optarg, &valid, 10); 161 port = strtol(optarg, &valid, 10);
133 "Invalid port number (%s) - choose a number between " 164 "Invalid port number (%s) - choose a number between "
134 "1025 and 65535\n", 165 "1025 and 65535\n",
135 optarg); 166 optarg);
136 return 1; 167 return 1;
137 } else { 168 } else {
138 settings->port = (short) port; 169 settings.port = (short) port;
139 port_set = true; 170 port_set = true;
140 } 171 }
141 break; 172 break;
142 case 'v': 173 case 'v':
143 printf("terminal-chess : Version %s (Netcode Version %d)\n", 174 printf("terminal-chess : Version %s (Netcode Version %d)\n",
153 " -h This help page\n" 184 " -h This help page\n"
154 " -p TCP port to use (default: 27015)\n" 185 " -p TCP port to use (default: 27015)\n"
155 " -u Use Unix domain socket instead of TCP\n" 186 " -u Use Unix domain socket instead of TCP\n"
156 " -U Disables unicode pieces\n" 187 " -U Disables unicode pieces\n"
157 " -v Print version information and exits\n" 188 " -v Print version information and exits\n"
189 "\nTime control (default: disabled)\n"
190 " -t <time> Time limit in minutes (or seconds when used with 's' suffix)\n"
191 " -a <time> The time in seconds to add after each move\n"
192 " -d <time> A delay in seconds before the clock starts ticking each move\n"
158 "\nServer options\n" 193 "\nServer options\n"
159 " -a <time> Specifies the time to add after each move\n"
160 " -b Server plays black pieces (default: white)\n" 194 " -b Server plays black pieces (default: white)\n"
161 " -r Distribute color randomly\n" 195 " -r Distribute color randomly\n"
162 " -t <time> Specifies time limit (default: no limit)\n"
163 "\nHot seat\n" 196 "\nHot seat\n"
164 " -s Play a hot seat game (network options are ignored)\n" 197 " -s Play a hot seat game (network options are ignored)\n"
165 " -F Do not automatically flip the board in hot seat games\n" 198 " -F Do not automatically flip the board in hot seat games\n"
166 "\nNotes\n" 199 "\nNotes\n"
167 "The time unit for -a is seconds and for -t minutes by default. To "
168 "specify\nseconds for the -t option, use the s suffix.\n"
169 "Example: -t 150s\n\n"
170 "Use '-' for PGN files to read PGN data from standard input\n\n" 200 "Use '-' for PGN files to read PGN data from standard input\n\n"
171 "When playing over Unix domain socket, the HOST denotes the socket path.\n" 201 "When playing over Unix domain socket, the HOST denotes the socket path.\n"
172 "When the path doest not exist, a game is created. Otherwise, the program\n" 202 "When the path doest not exist, a game is created. Otherwise, the program\n"
173 "joins the existing game. When HOST is omitted, /tmp/chess.sock is used.\n" 203 "joins the existing game. When HOST is omitted, /tmp/chess.sock is used.\n"
174 ); 204 );
175 exit(0); 205 exit(0);
176 } 206 }
177 } 207 }
178 208
179 if (optind == argc - 1) { 209 if (optind == argc - 1) {
180 settings->serverhost = argv[optind]; 210 settings.serverhost = argv[optind];
181 } else if (optind < argc - 1) { 211 } else if (optind < argc - 1) {
182 fprintf(stderr, "Too many arguments\n"); 212 fprintf(stderr, "Too many arguments\n");
183 return 1; 213 return 1;
184 } 214 }
185 215
186 216
187 if (settings->continuepgn) { 217 if (settings.continuepgn) {
188 if (settings->serverhost) { 218 if (settings.serverhost) {
189 fprintf(stderr, "Can't continue a game when joining a server.\n"); 219 fprintf(stderr, "Can't continue a game when joining a server.\n");
190 return 1; 220 return 1;
191 } 221 }
192 } 222 }
193 223
194 if (settings->usedomainsocket) { 224 if (settings.usedomainsocket) {
195 if (!settings->serverhost) { 225 if (!settings.serverhost) {
196 settings->serverhost = "/tmp/chess.sock"; 226 settings.serverhost = "/tmp/chess.sock";
197 } 227 }
198 struct stat st; 228 struct stat st;
199 if (stat(settings->serverhost, &st) == 0) { 229 if (stat(settings.serverhost, &st) == 0) {
200 if (S_ISSOCK(st.st_mode)) { 230 if (S_ISSOCK(st.st_mode)) {
201 settings->ishost = false; 231 settings.ishost = false;
202 } else { 232 } else {
203 fprintf(stderr, "%s is not a Unix domain socket.\n", 233 fprintf(stderr, "%s is not a Unix domain socket.\n",
204 settings->serverhost); 234 settings.serverhost);
205 return 1; 235 return 1;
206 } 236 }
207 } else { 237 } else {
208 settings->ishost = true; 238 settings.ishost = true;
209 } 239 }
210 } else { 240 } else {
211 settings->ishost = !settings->serverhost; 241 settings.ishost = !settings.serverhost;
212 } 242 }
213 243
214 return 0; 244 return 0;
215 } 245 }
216 246
217 static Settings settings;
218
219 static void init_settings(void) {
220 memset(&settings, 0, sizeof(settings));
221 settings.gameinfo.servercolor = WHITE;
222 settings.port = 27015;
223 settings.unicode = !!setlocale(LC_CTYPE, "C.UTF-8");
224 }
225
226 static void cleanup() {
227 endwin();
228 if (settings.usedomainsocket && settings.ishost) {
229 remove(settings.serverhost);
230 }
231 }
232
233 static const uint8_t boardx = 4, boardy = 10; 247 static const uint8_t boardx = 4, boardy = 10;
234 static int inputy = 21; /* should be overridden on game startup */ 248 static int inputy = 21; /* should be overridden on game startup */
235 249
236 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) { 250 static int timecontrol(GameState *gamestate) {
237 if (gameinfo->timecontrol) { 251 if (gamestate->info.timecontrol) {
238 uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE); 252 uint16_t white = remaining_movetime(gamestate, WHITE);
239 uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK); 253 uint16_t black = remaining_movetime(gamestate, BLACK);
240 char clkstr[16]; 254 char clkstr[16];
241 bool always_hours = gameinfo->time >= 3600; 255 bool always_hours = gamestate->info.time >= 3600;
242 print_clk(white, clkstr, always_hours); 256 print_clk(white, clkstr, always_hours);
243 mvprintw(boardy+4, boardx-1, "White time: %s", clkstr); 257 mvprintw(boardy+4, boardx-1, "White time: %s", clkstr);
244 print_clk(black, clkstr, always_hours); 258 print_clk(black, clkstr, always_hours);
245 mvprintw(boardy+5, boardx-1, "Black time: %s", clkstr); 259 mvprintw(boardy+5, boardx-1, "Black time: %s", clkstr);
246 260
261 } 275 }
262 276
263 return 0; 277 return 0;
264 } 278 }
265 279
266 static void draw_board(GameState *gamestate, 280 static void draw_board(GameState *gamestate, uint8_t perspective) {
267 uint8_t perspective,
268 bool unicode) {
269 char fen[90]; 281 char fen[90];
270 compute_fen(fen, gamestate); 282 compute_fen(fen, gamestate);
271 mvaddstr(0, 0, fen); 283 mvaddstr(0, 0, fen);
272 284
273 for (uint8_t y = 0 ; y < 8 ; y++) { 285 for (uint8_t y = 0 ; y < 8 ; y++) {
274 for (uint8_t x = 0 ; x < 8 ; x++) { 286 for (uint8_t x = 0 ; x < 8 ; x++) {
275 uint8_t col = gamestate->board[y][x] & COLOR_MASK; 287 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
276 uint8_t piece = gamestate->board[y][x]; 288 uint8_t piece = gamestate->board[y][x];
277 char piecestr[5]; 289 char piecestr[5];
278 if (piece) { 290 if (piece) {
279 if (unicode) { 291 if (settings.unicode) {
280 char* uc = getpieceunicode(piece); 292 char* uc = getpieceunicode(piece);
281 strncpy(piecestr, uc, 5); 293 strncpy(piecestr, uc, 5);
282 } else { 294 } else {
283 piecestr[0] = (piece & PIECE_MASK) == PAWN 295 piecestr[0] = (piece & PIECE_MASK) == PAWN
284 ? 'P' : getpiecechr(piece); 296 ? 'P' : getpiecechr(piece);
369 default: 381 default:
370 printw("Unknown move parser error."); 382 printw("Unknown move parser error.");
371 } 383 }
372 } 384 }
373 385
374 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) { 386 static void save_pgn(GameState *gamestate) {
375 int y = getcury(stdscr); 387 int y = getcury(stdscr);
376 388
377 /* ask for player names */ 389 /* ask for player names */
378 { 390 {
379 char pname[PLAYER_NAME_BUFLEN]; 391 char pname[PLAYER_NAME_BUFLEN];
399 char filename[64]; 411 char filename[64];
400 if (getnstr(filename, 64) == OK && filename[0] != '\0') { 412 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
401 move(y, 0); 413 move(y, 0);
402 FILE *file = fopen(filename, "w"); 414 FILE *file = fopen(filename, "w");
403 if (file) { 415 if (file) {
404 write_pgn(file, gamestate, gameinfo, export_comments); 416 write_pgn(file, gamestate, export_comments);
405 fclose(file); 417 fclose(file);
406 printw("File saved."); 418 printw("File saved.");
407 } else { 419 } else {
408 printw("Can't write to file (%s).", strerror(errno)); 420 printw("Can't write to file (%s).", strerror(errno));
409 } 421 }
410 clrtoeol(); 422 clrtoeol();
411 } 423 }
412 } 424 }
413 425
414 #define MOVESTR_BUFLEN 10 426 #define MOVESTR_BUFLEN 10
415 static int domove_singlemachine(GameState *gamestate, 427 static int domove_singlemachine(GameState *gamestate, uint8_t curcolor) {
416 GameInfo *gameinfo, uint8_t curcolor) {
417
418
419 size_t bufpos = 0; 428 size_t bufpos = 0;
420 char movestr[MOVESTR_BUFLEN]; 429 char movestr[MOVESTR_BUFLEN];
421 430
422 flushinp(); 431 flushinp();
423 while (1) { 432 while (1) {
424 const char *curcolorstr = curcolor == WHITE ? "White" : "Black"; 433 const char *curcolorstr = curcolor == WHITE ? "White" : "Black";
425 if (timecontrol(gamestate, gameinfo)) { 434 if (timecontrol(gamestate)) {
426 return 1; 435 return 1;
427 } 436 }
428 move(inputy, 0); 437 move(inputy, 0);
429 printw( 438 printw(
430 "Use chess notation to enter your move.\n" 439 "Use chess notation to enter your move.\n"
442 return 1; 451 return 1;
443 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { 452 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
444 gamestate->remis = true; 453 gamestate->remis = true;
445 return 1; 454 return 1;
446 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { 455 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
447 save_pgn(gamestate, gameinfo); 456 save_pgn(gamestate);
448 } else if (movestr[0] == 0) { 457 } else if (movestr[0] == 0) {
449 /* ignore empty move strings and ask again */ 458 /* ignore empty move strings and ask again */
450 } else { 459 } else {
451 Move move; 460 Move move;
452 int result = eval_move(gamestate, movestr, &move, curcolor); 461 int result = eval_move(gamestate, movestr, &move, curcolor);
471 } 480 }
472 } 481 }
473 } 482 }
474 } 483 }
475 484
476 static int sendmove(GameState *gamestate, GameInfo *gameinfo, 485 static int sendmove(GameState *gamestate, int opponent, uint8_t mycolor) {
477 int opponent, uint8_t mycolor) {
478 486
479 size_t bufpos = 0; 487 size_t bufpos = 0;
480 char movestr[MOVESTR_BUFLEN]; 488 char movestr[MOVESTR_BUFLEN];
481 bool remis_rejected = false; 489 bool remis_rejected = false;
482 bool remis_suggested = false; 490 bool remis_suggested = false;
492 memset(gamestate->premove, 0, mlen); 500 memset(gamestate->premove, 0, mlen);
493 } 501 }
494 502
495 flushinp(); 503 flushinp();
496 while (1) { 504 while (1) {
497 if (timecontrol(gamestate, gameinfo)) { 505 if (timecontrol(gamestate)) {
498 net_send_code(opponent, NETCODE_TIMEOVER); 506 net_send_code(opponent, NETCODE_TIMEOVER);
499 return 1; 507 return 1;
500 } 508 }
501 509
502 move(inputy, 0); 510 move(inputy, 0);
558 gamestate->bresign = true; 566 gamestate->bresign = true;
559 } 567 }
560 net_send_code(opponent, NETCODE_RESIGN); 568 net_send_code(opponent, NETCODE_RESIGN);
561 return 1; 569 return 1;
562 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { 570 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
563 save_pgn(gamestate, gameinfo); 571 save_pgn(gamestate);
564 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { 572 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
565 if (remis_suggested) { 573 if (remis_suggested) {
566 net_send_code(opponent, NETCODE_REMIS); 574 net_send_code(opponent, NETCODE_REMIS);
567 gamestate->remis = true; 575 gamestate->remis = true;
568 return 1; 576 return 1;
627 } 635 }
628 } 636 }
629 } 637 }
630 } 638 }
631 639
632 static int recvmove(GameState *gamestate, GameInfo *gameinfo, 640 static int recvmove(GameState *gamestate, int opponent, uint8_t mycolor) {
633 int opponent, uint8_t mycolor) {
634 memset(gamestate->premove, 0, sizeof(gamestate->premove)); 641 memset(gamestate->premove, 0, sizeof(gamestate->premove));
635 642
636 size_t bufpos = 0; 643 size_t bufpos = 0;
637 char movestr[MOVESTR_BUFLEN]; 644 char movestr[MOVESTR_BUFLEN];
638 bool remis_suggested = false, resign_suggested = false; 645 bool remis_suggested = false, resign_suggested = false;
639 while (1) { 646 while (1) {
640 timecontrol(gamestate, gameinfo); 647 timecontrol(gamestate);
641 648
642 move(inputy, 0); 649 move(inputy, 0);
643 printw("Waiting for opponent. Use chess notation to prepare a move.\n"); 650 printw("Waiting for opponent. Use chess notation to prepare a move.\n");
644 if (*gamestate->premove) { 651 if (*gamestate->premove) {
645 printw("Current pre-move: %s \n\n", 652 printw("Current pre-move: %s \n\n",
674 net_send_code(opponent, NETCODE_TAUNT); 681 net_send_code(opponent, NETCODE_TAUNT);
675 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { 682 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
676 remis_suggested = true; 683 remis_suggested = true;
677 net_send_code(opponent, NETCODE_REMIS); 684 net_send_code(opponent, NETCODE_REMIS);
678 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { 685 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
679 save_pgn(gamestate, gameinfo); 686 save_pgn(gamestate);
680 } else if (movestr[0] == 0) { 687 } else if (movestr[0] == 0) {
681 memset(gamestate->premove, 0, sizeof(gamestate->premove)); 688 memset(gamestate->premove, 0, sizeof(gamestate->premove));
682 } else { 689 } else {
683 int res = check_move(movestr, mycolor); 690 int res = check_move(movestr, mycolor);
684 if (res == VALID_MOVE_SYNTAX) { 691 if (res == VALID_MOVE_SYNTAX) {
695 /* read opponent's move */ 702 /* read opponent's move */
696 uint8_t code = net_recieve_code_async(opponent); 703 uint8_t code = net_recieve_code_async(opponent);
697 switch (code) { 704 switch (code) {
698 case NETCODE_TIMEOVER: 705 case NETCODE_TIMEOVER:
699 /* redraw the time control */ 706 /* redraw the time control */
700 timecontrol(gamestate, gameinfo); 707 timecontrol(gamestate);
701 return 1; 708 return 1;
702 case NETCODE_RESIGN: 709 case NETCODE_RESIGN:
703 if (mycolor == WHITE) { 710 if (mycolor == WHITE) {
704 gamestate->bresign = true; 711 gamestate->bresign = true;
705 } else { 712 } else {
760 printw("\nInvalid network request."); 767 printw("\nInvalid network request.");
761 } 768 }
762 } 769 }
763 } 770 }
764 771
765 static void game_review(Settings* settings, GameState *gamestate) { 772 static void game_review(GameState *gamestate) {
766 const unsigned page_moves = 10; 773 const unsigned page_moves = 10;
767 GameInfo *gameinfo = &(settings->gameinfo);
768 GameState viewedstate = {0}; 774 GameState viewedstate = {0};
769 unsigned viewedmove = gamestate->movecount; 775 unsigned viewedmove = gamestate->movecount;
770 bool redraw = true; 776 bool redraw = true;
771 777
772 noecho(); 778 noecho();
775 if (redraw) { 781 if (redraw) {
776 gamestate_cleanup(&viewedstate); 782 gamestate_cleanup(&viewedstate);
777 gamestate_at_move(gamestate, viewedmove, &viewedstate); 783 gamestate_at_move(gamestate, viewedmove, &viewedstate);
778 784
779 erase(); /* don't use clear() to avoid flickering */ 785 erase(); /* don't use clear() to avoid flickering */
780 draw_board(&viewedstate, WHITE, settings->unicode); 786 draw_board(&viewedstate, WHITE);
781 timecontrol(&viewedstate, gameinfo); 787 timecontrol(&viewedstate);
782 788
783 move(getmaxy(stdscr)-5, 0); 789 move(getmaxy(stdscr)-5, 0);
784 if (gamestate->wresign) { 790 if (gamestate->wresign) {
785 addstr("White resigned.\n"); 791 addstr("White resigned.\n");
786 } else if (gamestate->bresign) { 792 } else if (gamestate->bresign) {
802 } 808 }
803 c = getch(); 809 c = getch();
804 if (c == 's') { 810 if (c == 's') {
805 addch('\r'); 811 addch('\r');
806 echo(); 812 echo();
807 save_pgn(&viewedstate, gameinfo); 813 save_pgn(&viewedstate);
808 noecho(); 814 noecho();
809 redraw = true; 815 redraw = true;
810 } else if (c == KEY_UP || c == KEY_LEFT) { 816 } else if (c == KEY_UP || c == KEY_LEFT) {
811 if (viewedmove > 0) { 817 if (viewedmove > 0) {
812 viewedmove--; 818 viewedmove--;
840 } while (c != 'q'); 846 } while (c != 'q');
841 echo(); 847 echo();
842 gamestate_cleanup(&viewedstate); 848 gamestate_cleanup(&viewedstate);
843 } 849 }
844 850
845 static void game_play_singlemachine(Settings *settings) { 851 static void game_play_singlemachine(void) {
846 inputy = getmaxy(stdscr) - 6; 852 inputy = getmaxy(stdscr) - 6;
847 853
848 GameState gamestate; 854 GameState gamestate;
849 gamestate_init(&gamestate); 855 gamestate_init(&gamestate);
856 settings_apply(&gamestate);
850 uint8_t curcol = WHITE; 857 uint8_t curcol = WHITE;
851 858
852 if (settings->continuepgn) { 859 if (settings.continuepgn) {
853 FILE *pgnfile = fopen(settings->continuepgn, "r"); 860 FILE *pgnfile = fopen(settings.continuepgn, "r");
854 if (pgnfile) { 861 if (pgnfile) {
855 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo)); 862 int result = read_pgn(pgnfile, &gamestate);
856 long position = ftell(pgnfile); 863 long position = ftell(pgnfile);
857 fclose(pgnfile); 864 fclose(pgnfile);
858 if (result) { 865 if (result) {
859 printw("Invalid PGN file content at position %ld:\n%s\n", 866 printw("Invalid PGN file content at position %ld:\n%s\n",
860 position, pgn_error_str(result)); 867 position, pgn_error_str(result));
872 } 879 }
873 880
874 bool running; 881 bool running;
875 do { 882 do {
876 clear(); 883 clear();
877 uint8_t perspective = settings->disableflip ? WHITE : curcol; 884 uint8_t perspective = settings.disableflip ? WHITE : curcol;
878 draw_board(&gamestate, perspective, settings->unicode); 885 draw_board(&gamestate, perspective);
879 running = !domove_singlemachine(&gamestate, 886 running = !domove_singlemachine(&gamestate, curcol);
880 &(settings->gameinfo), curcol);
881 curcol = opponent_color(curcol); 887 curcol = opponent_color(curcol);
882 } while (running); 888 } while (running);
883 889
884 game_review(settings, &gamestate); 890 game_review(&gamestate);
885 gamestate_cleanup(&gamestate); 891 gamestate_cleanup(&gamestate);
886 } 892 }
887 893
888 static void game_play(Settings *settings, GameState *gamestate, int opponent) { 894 static void game_play(GameState *gamestate, int opponent, bool as_client) {
889 inputy = getmaxy(stdscr) - 6; 895 inputy = getmaxy(stdscr) - 6;
890 896
891 uint8_t mycolor = settings->gameinfo.servercolor; 897 uint8_t mycolor = gamestate->info.servercolor;
892 if (!settings->ishost) { 898 if (as_client) {
893 mycolor = opponent_color(mycolor); 899 mycolor = opponent_color(mycolor);
894 } 900 }
895 901
896 bool myturn = (gamestate->movecount > 0 ? 902 bool myturn = (gamestate->movecount > 0 ?
897 (last_move(gamestate).piece & COLOR_MASK) : BLACK) != mycolor; 903 (last_move(gamestate).piece & COLOR_MASK) : BLACK) != mycolor;
898 904
899 bool running; 905 bool running;
900 do { 906 do {
901 clear(); 907 clear();
902 draw_board(gamestate, mycolor, settings->unicode); 908 draw_board(gamestate, mycolor);
903 if (myturn) { 909 if (myturn) {
904 running = !sendmove(gamestate, &(settings->gameinfo), 910 running = !sendmove(gamestate, opponent, mycolor);
905 opponent, mycolor);
906 } else { 911 } else {
907 running = !recvmove(gamestate, &(settings->gameinfo), 912 running = !recvmove(gamestate, opponent, mycolor);
908 opponent, mycolor);
909 } 913 }
910 myturn ^= true; 914 myturn ^= true;
911 } while (running); 915 } while (running);
912 } 916 }
913 917
914 static void dump_gameinfo(GameInfo *gameinfo) { 918 static void dump_gameinfo(GameState *gamestate) {
919 GameInfo *gameinfo = &gamestate->info;
915 int serverwhite = gameinfo->servercolor == WHITE; 920 int serverwhite = gameinfo->servercolor == WHITE;
916 attron(A_UNDERLINE); 921 attron(A_UNDERLINE);
917 printw("Game details\n"); 922 printw("Game details\n");
918 attroff(A_UNDERLINE); 923 attroff(A_UNDERLINE);
919 printw(" Server: %s\n Client: %s\n", 924 printw(" Server: %s\n Client: %s\n",
920 serverwhite?"White":"Black", serverwhite?"Black":"White" 925 serverwhite?"White":"Black", serverwhite?"Black":"White"
921 ); 926 );
922 if (gameinfo->timecontrol) { 927 if (gameinfo->timecontrol) {
923 if (gameinfo->time % 60) { 928 if (gameinfo->time % 60) {
924 printw(" Time limit: %ds + %ds\n", 929 printw(" Time limit: %us + %us",
925 gameinfo->time, gameinfo->addtime); 930 gameinfo->time, gameinfo->addtime);
926 } else { 931 } else {
927 printw(" Time limit: %dm + %ds\n", 932 printw(" Time limit: %um + %us",
928 gameinfo->time/60, gameinfo->addtime); 933 gameinfo->time/60, gameinfo->addtime);
929 } 934 }
935 if (gameinfo->delay) {
936 printw(" (with %us delay)", gameinfo->delay);
937 }
938 addch('\n');
930 } else { 939 } else {
931 printw(" No time limit\n"); 940 printw(" No time limit\n");
932 } 941 }
933 refresh(); 942 addch('\n');
934 }
935
936 static void dump_moveinfo(GameState *gamestate) {
937 for (unsigned i = 0 ; i < gamestate->movecount ; i++) { 943 for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
938 if (i % 2 == 0) { 944 if (i % 2 == 0) {
939 printw("%d. %s", 1+i/2, gamestate->moves[i].string); 945 printw("%d. %s", 1+i/2, gamestate->moves[i].string);
940 } else { 946 } else {
941 printw("%s", gamestate->moves[i].string); 947 printw("%s", gamestate->moves[i].string);
956 // this interrupts 962 // this interrupts
957 close(server_fd); 963 close(server_fd);
958 } 964 }
959 } 965 }
960 966
961 static int server_open(Server *server, Settings *settings) { 967 static int server_open(Server *server) {
962 printw("\nListening for client...\n"); 968 printw("\nListening for client...\n");
963 refresh(); 969 refresh();
964 if (settings->usedomainsocket 970 if (settings.usedomainsocket
965 ? net_create_sock(server, settings->serverhost) 971 ? net_create_sock(server, settings.serverhost)
966 : net_create_tcp(server, settings->port)) { 972 : net_create_tcp(server, settings.port)) {
967 addstr("Server creation failed"); 973 addstr("Server creation failed");
968 return 1; 974 return 1;
969 } 975 }
970 976
971 // allow Ctrl+C to interrupt the listening process 977 // allow Ctrl+C to interrupt the listening process
994 refresh(); 1000 refresh();
995 1001
996 return 0; 1002 return 0;
997 } 1003 }
998 1004
999 static int server_run(Settings *settings) { 1005 static int server_run(void) {
1000 Server server; 1006 Server server;
1001 1007
1002 dump_gameinfo(&(settings->gameinfo));
1003 GameState gamestate; 1008 GameState gamestate;
1004 gamestate_init(&gamestate); 1009 gamestate_init(&gamestate);
1005 if (settings->continuepgn) { 1010 settings_apply(&gamestate);
1011 if (settings.continuepgn) {
1006 /* preload PGN data before handshake */ 1012 /* preload PGN data before handshake */
1007 FILE *pgnfile = fopen(settings->continuepgn, "r"); 1013 FILE *pgnfile = fopen(settings.continuepgn, "r");
1008 if (pgnfile) { 1014 if (pgnfile) {
1009 int result = read_pgn(pgnfile, &gamestate, 1015 int result = read_pgn(pgnfile, &gamestate);
1010 &(settings->gameinfo));
1011 long position = ftell(pgnfile); 1016 long position = ftell(pgnfile);
1012 fclose(pgnfile); 1017 fclose(pgnfile);
1013 if (result) { 1018 if (result) {
1014 printw("Invalid PGN file content at position %ld:\n%s\n", 1019 printw("Invalid PGN file content at position %ld:\n%s\n",
1015 position, pgn_error_str(result)); 1020 position, pgn_error_str(result));
1017 } 1022 }
1018 if (!is_game_running(&gamestate)) { 1023 if (!is_game_running(&gamestate)) {
1019 addstr("Game has ended. Use -s to analyze it locally.\n"); 1024 addstr("Game has ended. Use -s to analyze it locally.\n");
1020 return 1; 1025 return 1;
1021 } 1026 }
1022 addch('\n');
1023 dump_moveinfo(&gamestate);
1024 addch('\n');
1025 } else { 1027 } else {
1026 printw("Can't read PGN file (%s)\n", strerror(errno)); 1028 printw("Can't read PGN file (%s)\n", strerror(errno));
1027 return 1; 1029 return 1;
1028 } 1030 }
1029 } 1031 }
1030 1032 dump_gameinfo(&gamestate);
1031 if (server_open(&server, settings)) { 1033
1034 if (server_open(&server)) {
1032 net_destroy(&server); 1035 net_destroy(&server);
1033 return 1; 1036 return 1;
1034 } 1037 }
1035 1038
1036 if (server_handshake(server.client)) { 1039 if (server_handshake(server.client)) {
1037 net_destroy(&server); 1040 net_destroy(&server);
1038 return 1; 1041 return 1;
1039 } 1042 }
1040 1043
1041 int fd = server.client->fd; 1044 int fd = server.client->fd;
1042 if (settings->continuepgn) { 1045 if (settings.continuepgn) {
1043 /* Continue game, send PGN data */ 1046 /* Continue game, send PGN data */
1044 uint16_t mc = gamestate.movecount; 1047 uint16_t mc = gamestate.movecount;
1045 size_t pgndata_size = sizeof(GameInfo)+sizeof(mc)+mc*sizeof(Move); 1048 size_t pgndata_size = sizeof(GameInfo)+sizeof(mc)+mc*sizeof(Move);
1046 char *pgndata = malloc(pgndata_size); 1049 char *pgndata = malloc(pgndata_size);
1047 memcpy(pgndata, &(settings->gameinfo), sizeof(GameInfo)); 1050 memcpy(pgndata, &(gamestate.info), sizeof(GameInfo));
1048 unsigned offset = sizeof(GameInfo); 1051 unsigned offset = sizeof(GameInfo);
1049 memcpy(pgndata+offset, &mc, sizeof(mc)); 1052 memcpy(pgndata+offset, &mc, sizeof(mc));
1050 offset += sizeof(mc); 1053 offset += sizeof(mc);
1051 memcpy(pgndata+offset, gamestate.moves, mc*sizeof(Move)); 1054 memcpy(pgndata+offset, gamestate.moves, mc*sizeof(Move));
1052 net_send_data(fd, NETCODE_PGNDATA, pgndata, pgndata_size); 1055 net_send_data(fd, NETCODE_PGNDATA, pgndata, pgndata_size);
1053 free(pgndata); 1056 free(pgndata);
1054 } else { 1057 } else {
1055 /* Start new game */ 1058 /* Start new game */
1056 net_send_data(fd, NETCODE_GAMEINFO, 1059 net_send_data(fd, NETCODE_GAMEINFO,
1057 &(settings->gameinfo), sizeof(GameInfo)); 1060 &(gamestate.info), sizeof(GameInfo));
1058 } 1061 }
1059 addstr("\rClient connected - awaiting challenge acceptance..."); 1062 addstr("\rClient connected - awaiting challenge acceptance...");
1060 refresh(); 1063 refresh();
1061 int code = net_recieve_code(fd); 1064 int code = net_recieve_code(fd);
1062 int exitcode = 0; 1065 int exitcode = 0;
1063 if (code == NETCODE_ACCEPT) { 1066 if (code == NETCODE_ACCEPT) {
1064 addstr("\rClient connected - challenge accepted."); 1067 addstr("\rClient connected - challenge accepted.");
1065 clrtoeol(); 1068 clrtoeol();
1066 game_play(settings, &gamestate, fd); 1069 game_play(&gamestate, fd, false);
1067 net_destroy(&server); 1070 net_destroy(&server);
1068 game_review(settings, &gamestate); 1071 game_review(&gamestate);
1069 } else if (code == NETCODE_DECLINE) { 1072 } else if (code == NETCODE_DECLINE) {
1070 addstr("\rClient connected - challenge declined."); 1073 addstr("\rClient connected - challenge declined.");
1071 clrtoeol(); 1074 clrtoeol();
1072 net_destroy(&server); 1075 net_destroy(&server);
1073 } else if (code == NETCODE_CONNLOST) { 1076 } else if (code == NETCODE_CONNLOST) {
1084 gamestate_cleanup(&gamestate); 1087 gamestate_cleanup(&gamestate);
1085 return exitcode; 1088 return exitcode;
1086 } 1089 }
1087 1090
1088 1091
1089 static int client_connect(Server *server, Settings *settings) { 1092 static int client_connect(Server *server) {
1090 if (settings->usedomainsocket 1093 if (settings.usedomainsocket
1091 ? net_find_sock(server, settings->serverhost) 1094 ? net_find_sock(server, settings.serverhost)
1092 : net_find_tcp(server, settings->serverhost, settings->port)) { 1095 : net_find_tcp(server, settings.serverhost, settings.port)) {
1093 addstr("Can't find server"); 1096 addstr("Can't find server");
1094 return 1; 1097 return 1;
1095 } 1098 }
1096 1099
1097 if (net_connect(server)) { 1100 if (net_connect(server)) {
1114 refresh(); 1117 refresh();
1115 1118
1116 return 0; 1119 return 0;
1117 } 1120 }
1118 1121
1119 static int client_run(Settings *settings) { 1122 static int client_run(void) {
1120 Server server; 1123 Server server;
1121 1124
1122 if (client_connect(&server, settings)) { 1125 if (client_connect(&server)) {
1123 net_destroy(&server); 1126 net_destroy(&server);
1124 return 1; 1127 return 1;
1125 } 1128 }
1126 1129
1127 if (client_handshake(&server)) { 1130 if (client_handshake(&server)) {
1133 GameState gamestate; 1136 GameState gamestate;
1134 gamestate_init(&gamestate); 1137 gamestate_init(&gamestate);
1135 bool played = false; 1138 bool played = false;
1136 if (code == NETCODE_GAMEINFO) { 1139 if (code == NETCODE_GAMEINFO) {
1137 /* Start new game */ 1140 /* Start new game */
1138 net_recieve_data(server.fd, &(settings->gameinfo), sizeof(GameInfo)); 1141 net_recieve_data(server.fd, &(gamestate.info), sizeof(GameInfo));
1139 dump_gameinfo(&(settings->gameinfo)); 1142 dump_gameinfo(&gamestate);
1140 if (prompt_yesno("Accept challenge")) { 1143 if (prompt_yesno("Accept challenge")) {
1141 net_send_code(server.fd, NETCODE_ACCEPT); 1144 net_send_code(server.fd, NETCODE_ACCEPT);
1142 game_play(settings, &gamestate, server.fd); 1145 game_play(&gamestate, server.fd, true);
1143 played = true; 1146 played = true;
1144 } else { 1147 } else {
1145 net_send_code(server.fd, NETCODE_DECLINE); 1148 net_send_code(server.fd, NETCODE_DECLINE);
1146 } 1149 }
1147 } else if (code == NETCODE_PGNDATA) { 1150 } else if (code == NETCODE_PGNDATA) {
1148 net_recieve_data(server.fd, &(settings->gameinfo), sizeof(GameInfo)); 1151 net_recieve_data(server.fd, &(gamestate.info), sizeof(GameInfo));
1149 dump_gameinfo(&(settings->gameinfo));
1150 uint16_t mc; 1152 uint16_t mc;
1151 net_recieve_data(server.fd, &mc, sizeof(mc)); 1153 net_recieve_data(server.fd, &mc, sizeof(mc));
1152 Move *moves = calloc(mc, sizeof(Move)); 1154 Move *moves = calloc(mc, sizeof(Move));
1153 net_recieve_data(server.fd, moves, mc*sizeof(Move)); 1155 net_recieve_data(server.fd, moves, mc*sizeof(Move));
1154 for (size_t i = 0 ; i < mc ; i++) { 1156 for (size_t i = 0 ; i < mc ; i++) {
1155 apply_move(&gamestate, &(moves[i])); 1157 apply_move(&gamestate, &(moves[i]));
1156 } 1158 }
1157 free(moves); 1159 free(moves);
1158 addch('\n'); 1160 dump_gameinfo(&gamestate);
1159 dump_moveinfo(&gamestate);
1160 if (prompt_yesno( 1161 if (prompt_yesno(
1161 "\n\nServer wants to continue a game. Accept challenge")) { 1162 "\n\nServer wants to continue a game. Accept challenge")) {
1162 net_send_code(server.fd, NETCODE_ACCEPT); 1163 net_send_code(server.fd, NETCODE_ACCEPT);
1163 game_play(settings, &gamestate, server.fd); 1164 game_play(&gamestate, server.fd, true);
1164 played = true; 1165 played = true;
1165 } else { 1166 } else {
1166 net_send_code(server.fd, NETCODE_DECLINE); 1167 net_send_code(server.fd, NETCODE_DECLINE);
1167 } 1168 }
1168 } else { 1169 } else {
1170 net_destroy(&server); 1171 net_destroy(&server);
1171 return 1; 1172 return 1;
1172 } 1173 }
1173 1174
1174 if (played) { 1175 if (played) {
1175 game_review(settings, &gamestate); 1176 game_review(&gamestate);
1176 } 1177 }
1177 gamestate_cleanup(&gamestate); 1178 gamestate_cleanup(&gamestate);
1178 1179
1179 net_destroy(&server); 1180 net_destroy(&server);
1180 return 0; 1181 return 0;
1182 1183
1183 int main(int argc, char **argv) { 1184 int main(int argc, char **argv) {
1184 srand(time(NULL)); 1185 srand(time(NULL));
1185 1186
1186 init_settings(); 1187 init_settings();
1187 if (get_settings(argc, argv, &settings)) { 1188 if (get_settings(argc, argv)) {
1188 return 1; 1189 return 1;
1189 } 1190 }
1190 1191
1191 initscr(); 1192 initscr();
1192 halfdelay(1); 1193 halfdelay(1);
1202 } 1203 }
1203 atexit(cleanup); 1204 atexit(cleanup);
1204 1205
1205 int exitcode; 1206 int exitcode;
1206 if (settings.singlemachine) { 1207 if (settings.singlemachine) {
1207 game_play_singlemachine(&settings); 1208 game_play_singlemachine();
1208 exitcode = EXIT_SUCCESS; 1209 exitcode = EXIT_SUCCESS;
1209 } else { 1210 } else {
1210 exitcode = settings.ishost ? 1211 exitcode = settings.ishost ? server_run() : client_run();
1211 server_run(&settings) : client_run(&settings);
1212 } 1212 }
1213 1213
1214 mvaddstr(getmaxy(stdscr)-1, 0, 1214 mvaddstr(getmaxy(stdscr)-1, 0,
1215 "Game has ended. Press any key to leave..."); 1215 "Game has ended. Press any key to leave...");
1216 clrtoeol(); 1216 clrtoeol();

mercurial