src/main.c

changeset 129
189c7c77aaab
parent 94
864f59271974
child 130
3fc6b1d6cbe9
equal deleted inserted replaced
128:ce38ee9bc3af 129:189c7c77aaab
27 * 27 *
28 */ 28 */
29 29
30 #define PROGRAM_VERSION "1.0 alpha" 30 #define PROGRAM_VERSION "1.0 alpha"
31 31
32 #include "game.h" 32 #include "chess/rules.h"
33 #include "chess/pgn.h"
33 #include "input.h" 34 #include "input.h"
34 #include "network.h" 35 #include "network.h"
35 #include "colors.h" 36 #include "colors.h"
36 #include <string.h> 37 #include <string.h>
37 #include <time.h> 38 #include <time.h>
38 #include <getopt.h> 39 #include <getopt.h>
39 #include <locale.h> 40 #include <locale.h>
40 #include <sys/stat.h> 41 #include <sys/stat.h>
42 #include <signal.h>
43 #include <errno.h>
44 #include <unistd.h>
45
46 typedef struct {
47 GameInfo gameinfo;
48 /**
49 * Server host address.
50 * TCP: server address or \c NULL when we are the server
51 * Domain Socket: the path to the domain socket
52 */
53 char* serverhost;
54 char* continuepgn;
55 short port;
56 bool ishost;
57 bool usedomainsocket;
58 bool singlemachine;
59 bool disableflip;
60 bool unicode;
61 } Settings;
41 62
42 int get_settings(int argc, char **argv, Settings *settings) { 63 int get_settings(int argc, char **argv, Settings *settings) {
43 char *valid; 64 char *valid;
44 unsigned long int time, port; 65 unsigned long int time, port;
45 uint8_t timeunit = 60; 66 uint8_t timeunit = 60;
207 if (settings.usedomainsocket && settings.ishost) { 228 if (settings.usedomainsocket && settings.ishost) {
208 remove(settings.serverhost); 229 remove(settings.serverhost);
209 } 230 }
210 } 231 }
211 232
233 static const uint8_t boardx = 4, boardy = 10;
234 static int inputy = 21; /* should be overridden on game startup */
235
236 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
237 if (gameinfo->timecontrol) {
238 uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
239 uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
240 char clkstr[16];
241 bool always_hours = gameinfo->time >= 3600;
242 print_clk(white, clkstr, always_hours);
243 mvprintw(boardy+4, boardx-1, "White time: %s", clkstr);
244 print_clk(black, clkstr, always_hours);
245 mvprintw(boardy+5, boardx-1, "Black time: %s", clkstr);
246
247 if (white == 0) {
248 move(inputy, 0);
249 printw("Time is over - Black wins!");
250 clrtobot();
251 refresh();
252 return 1;
253 }
254 if (black == 0) {
255 move(inputy, 0);
256 printw("Time is over - White wins!");
257 clrtobot();
258 refresh();
259 return 1;
260 }
261 }
262
263 return 0;
264 }
265
266 static void draw_board(GameState *gamestate,
267 uint8_t perspective,
268 bool unicode) {
269 char fen[90];
270 compute_fen(fen, gamestate);
271 mvaddstr(0, 0, fen);
272
273 for (uint8_t y = 0 ; y < 8 ; y++) {
274 for (uint8_t x = 0 ; x < 8 ; x++) {
275 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
276 uint8_t piece = gamestate->board[y][x];
277 char piecestr[5];
278 if (piece) {
279 if (unicode) {
280 char* uc = getpieceunicode(piece);
281 strncpy(piecestr, uc, 5);
282 } else {
283 piecestr[0] = (piece & PIECE_MASK) == PAWN
284 ? 'P' : getpiecechr(piece);
285 piecestr[1] = '\0';
286 }
287 } else {
288 piecestr[0] = ' ';
289 piecestr[1] = '\0';
290 }
291
292 bool boardblack = (y&1)==(x&1);
293 attrset((col==WHITE ? A_BOLD : A_DIM)|
294 COLOR_PAIR(col == WHITE ?
295 (boardblack ? COL_WB : COL_WW) :
296 (boardblack ? COL_BB : COL_BW)
297 )
298 );
299
300 int cy = perspective == WHITE ? boardy-y : boardy-7+y;
301 int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
302 mvprintw(cy, cx, " %s ", piecestr);
303 }
304 }
305
306 attrset(A_NORMAL);
307 for (uint8_t i = 0 ; i < 8 ; i++) {
308 int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
309 int y = perspective == WHITE ? boardy-i : boardy-7+i;
310 mvaddch(boardy+1, x, 'a'+i);
311 mvaddch(y, boardx-2, '1'+i);
312 }
313
314 /* move log */
315 uint8_t logy = 2;
316 const uint8_t logx = boardx + 28;
317 move(logy, logx);
318
319 /* count full moves */
320 unsigned int logi = 0;
321
322 /* wrap log after 45 moves */
323 while (gamestate->movecount/6-logi/3 >= 15) {
324 logi++;
325 }
326
327 for (unsigned mi = logi*2 ; mi < gamestate->movecount ; mi++) {
328 bool iswhite = mi % 2 == 0;
329 if (iswhite) {
330 logi++;
331 printw("%d. ", logi);
332 }
333
334 addstr(gamestate->moves[mi].string);
335 if (!iswhite && logi%3 == 0) {
336 move(++logy, logx);
337 } else {
338 addch(' ');
339 }
340 }
341 }
342
343 static void eval_move_failed_msg(int code) {
344 switch (code) {
345 case AMBIGUOUS_MOVE:
346 printw("Ambiguous move - please specify the piece to move.");
347 break;
348 case INVALID_POSITION:
349 printw("No piece can be moved this way.");
350 break;
351 case NEED_PROMOTION:
352 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
353 break;
354 case KING_IN_CHECK:
355 printw("Your king is in check!");
356 break;
357 case PIECE_PINNED:
358 printw("This piece is pinned!");
359 break;
360 case INVALID_MOVE_SYNTAX:
361 printw("Can't interpret move - please use algebraic notation.");
362 break;
363 case RULES_VIOLATED:
364 printw("Move does not comply chess rules.");
365 break;
366 case KING_MOVES_INTO_CHECK:
367 printw("Can't move the king into a check position.");
368 break;
369 default:
370 printw("Unknown move parser error.");
371 }
372 }
373
374 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
375 int y = getcury(stdscr);
376
377 /* ask for player names */
378 {
379 char pname[PLAYER_NAME_BUFLEN];
380 printw("\rWhite's name (%s): ", pgn_player_name(gamestate, WHITE));
381 clrtoeol();
382 if (getnstr(pname, PLAYER_NAME_BUFLEN) == OK && pname[0] != '\0') {
383 strncpy(gamestate->wname, pname, PLAYER_NAME_BUFLEN);
384 }
385 move(y, 0);
386 printw("\rBlack's name (%s): ", pgn_player_name(gamestate, BLACK));
387 clrtoeol();
388 if (getnstr(pname, PLAYER_NAME_BUFLEN) == OK && pname[0] != '\0') {
389 strncpy(gamestate->bname, pname, PLAYER_NAME_BUFLEN);
390 }
391 move(y, 0);
392 }
393
394 bool export_comments = prompt_yesno("Export with comments");
395
396 printw("\rFilename: ");
397 clrtoeol();
398
399 char filename[64];
400 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
401 move(y, 0);
402 FILE *file = fopen(filename, "w");
403 if (file) {
404 write_pgn(file, gamestate, gameinfo, export_comments);
405 fclose(file);
406 printw("File saved.");
407 } else {
408 printw("Can't write to file (%s).", strerror(errno));
409 }
410 clrtoeol();
411 }
412 }
413
414 #define MOVESTR_BUFLEN 10
415 static int domove_singlemachine(GameState *gamestate,
416 GameInfo *gameinfo, uint8_t curcolor) {
417
418
419 size_t bufpos = 0;
420 char movestr[MOVESTR_BUFLEN];
421
422 flushinp();
423 while (1) {
424 const char *curcolorstr = curcolor == WHITE ? "White" : "Black";
425 if (timecontrol(gamestate, gameinfo)) {
426 return 1;
427 }
428 move(inputy, 0);
429 printw(
430 "Use chess notation to enter your move.\n"
431 "Or use a command: remis, resign, savepgn\n\n"
432 "%s to move: ", curcolorstr);
433 clrtoeol();
434
435 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
436 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
437 if (curcolor == WHITE) {
438 gamestate->wresign = true;
439 } else {
440 gamestate->bresign = true;
441 }
442 return 1;
443 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
444 gamestate->remis = true;
445 return 1;
446 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
447 save_pgn(gamestate, gameinfo);
448 } else if (movestr[0] == 0) {
449 /* ignore empty move strings and ask again */
450 } else {
451 Move move;
452 int result = eval_move(gamestate, movestr, &move, curcolor);
453 if (result == VALID_MOVE_SYNTAX) {
454 result = validate_move(gamestate, &move);
455 if (result == VALID_MOVE_SEMANTICS) {
456 apply_move(gamestate, &move);
457 if (gamestate->checkmate) {
458 return 1;
459 } else if (gamestate->stalemate) {
460 return 1;
461 } else {
462 return 0;
463 }
464 } else {
465 eval_move_failed_msg(result);
466 }
467 } else {
468 eval_move_failed_msg(result);
469 }
470 clrtoeol();
471 }
472 }
473 }
474 }
475
476 static int sendmove(GameState *gamestate, GameInfo *gameinfo,
477 int opponent, uint8_t mycolor) {
478
479 size_t bufpos = 0;
480 char movestr[MOVESTR_BUFLEN];
481 bool remis_rejected = false;
482 bool remis_suggested = false;
483 bool resign_suggested = false;
484 bool use_premove = false;
485 uint8_t code;
486
487 if (*gamestate->premove) {
488 use_premove = true;
489 const unsigned mlen = sizeof(gamestate->premove);
490 strncpy(movestr, gamestate->premove, mlen);
491 movestr[mlen] = '\0';
492 memset(gamestate->premove, 0, mlen);
493 }
494
495 flushinp();
496 while (1) {
497 if (timecontrol(gamestate, gameinfo)) {
498 net_send_code(opponent, NETCODE_TIMEOVER);
499 return 1;
500 }
501
502 move(inputy, 0);
503 printw("Use chess notation to enter your move.\n");
504 if (resign_suggested) {
505 if (remis_suggested) {
506 printw("The opponent asks you to resign or accept remis. \n\n");
507 } else {
508 printw("The opponent asks you to resign. \n\n");
509 }
510 } else if (remis_suggested) {
511 printw("The opponent offers remis. Type remis to accept. \n\n");
512 } else if (remis_rejected) {
513 printw("Remis offer rejected. \n\n");
514 } else {
515 printw("Or use a command: remis, resign, savepgn \n\n");
516 }
517 printw("Type your move: ");
518 clrtoeol();
519
520 /* check if the opponent sent us something */
521 code = net_recieve_code_async(opponent);
522 switch (code) {
523 case NETCODE_REMIS:
524 remis_suggested = true;
525 break;
526 case NETCODE_TAUNT:
527 resign_suggested = true;
528 break;
529 case NETCODE_RESIGN:
530 if (mycolor == WHITE) {
531 gamestate->bresign = true;
532 } else {
533 gamestate->wresign = true;
534 }
535 return 1;
536 case NETCODE_CONNLOST:
537 gamestate->ragequit = true;
538 return 1;
539 case NETCODE_ERROR:
540 printw("\rCannot perform asynchronous network IO");
541 cbreak(); getch();
542 exit(EXIT_FAILURE);
543 case NETCODE_AGAIN:
544 /* try again */
545 break;
546 default:
547 printw("\nThe opponent sent an invalid network pacakge.");
548 }
549
550 /* read move */
551 if (use_premove || asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
552 bool was_premove = use_premove;
553 use_premove = false;
554 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
555 if (mycolor == WHITE) {
556 gamestate->wresign = true;
557 } else {
558 gamestate->bresign = true;
559 }
560 net_send_code(opponent, NETCODE_RESIGN);
561 return 1;
562 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
563 save_pgn(gamestate, gameinfo);
564 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
565 if (remis_suggested) {
566 net_send_code(opponent, NETCODE_REMIS);
567 gamestate->remis = true;
568 return 1;
569 } if (!remis_rejected) {
570 net_send_code(opponent, NETCODE_REMIS);
571 printw("Remis offer sent - waiting for acceptance...");
572 refresh();
573 code = net_recieve_code(opponent);
574 if (code == NETCODE_ACCEPT) {
575 gamestate->remis = true;
576 return 1;
577 } else if (code == NETCODE_CONNLOST) {
578 gamestate->ragequit = true;
579 return 1;
580 } else {
581 remis_rejected = true;
582 }
583 }
584 } else if (movestr[0] == 0) {
585 /* ignore empty move strings and ask again */
586 } else {
587 Move move;
588 int eval_result = eval_move(gamestate, movestr, &move, mycolor);
589 switch (eval_result) {
590 case VALID_MOVE_SYNTAX:
591 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
592 code = net_recieve_code(opponent);
593 move.check = code == NETCODE_CHECK ||
594 code == NETCODE_CHECKMATE;
595 gamestate->checkmate = code == NETCODE_CHECKMATE;
596 gamestate->stalemate = code == NETCODE_STALEMATE;
597 if (code == NETCODE_DECLINE) {
598 uint32_t reason;
599 net_recieve_data(opponent, &reason, sizeof(uint32_t));
600 reason = ntohl(reason);
601 eval_move_failed_msg(reason);
602 } else if (code == NETCODE_ACCEPT
603 || code == NETCODE_CHECK
604 || code == NETCODE_CHECKMATE
605 || code == NETCODE_STALEMATE) {
606 apply_move(gamestate, &move);
607 if (gamestate->checkmate || gamestate->stalemate) {
608 return 1;
609 } else {
610 return 0;
611 }
612 } else if (code == NETCODE_CONNLOST) {
613 printw("Your opponent left the game.");
614 return 1;
615 } else {
616 printw("Invalid network response.");
617 }
618 break;
619 default:
620 if (was_premove) {
621 printw("\nThe prepared move could not be executed.");
622 } else {
623 eval_move_failed_msg(eval_result);
624 }
625 }
626 clrtoeol();
627 }
628 }
629 }
630 }
631
632 static int recvmove(GameState *gamestate, GameInfo *gameinfo,
633 int opponent, uint8_t mycolor) {
634 memset(gamestate->premove, 0, sizeof(gamestate->premove));
635
636 size_t bufpos = 0;
637 char movestr[MOVESTR_BUFLEN];
638 bool remis_suggested = false, resign_suggested = false;
639 while (1) {
640 timecontrol(gamestate, gameinfo);
641
642 move(inputy, 0);
643 printw("Waiting for opponent. Use chess notation to prepare a move.\n");
644 if (*gamestate->premove) {
645 printw("Current pre-move: %s \n\n",
646 gamestate->premove);
647 } else if (remis_suggested && !resign_suggested) {
648 printw("Suggested remis. \n\n");
649 } else if (resign_suggested) {
650 if (remis_suggested) {
651 printw("Suggested to resign or at least to accept remis. \n\n");
652 } else {
653 printw("Suggested to resign. \n\n");
654 }
655 } else {
656 printw("Or use a command: remis, resign, taunt, savepgn \n\n");
657 }
658 printw("Prepare your next move: ");
659 clrtoeol();
660 refresh();
661
662 /* allow the player to prepare a move */
663 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
664 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
665 if (mycolor == WHITE) {
666 gamestate->wresign = true;
667 } else {
668 gamestate->bresign = true;
669 }
670 net_send_code(opponent, NETCODE_RESIGN);
671 return 1;
672 } else if (strncmp(movestr, "taunt", MOVESTR_BUFLEN) == 0) {
673 resign_suggested = true;
674 net_send_code(opponent, NETCODE_TAUNT);
675 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
676 remis_suggested = true;
677 net_send_code(opponent, NETCODE_REMIS);
678 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
679 save_pgn(gamestate, gameinfo);
680 } else if (movestr[0] == 0) {
681 memset(gamestate->premove, 0, sizeof(gamestate->premove));
682 } else {
683 int res = check_move(movestr, mycolor);
684 if (res == VALID_MOVE_SYNTAX) {
685 strncpy(gamestate->premove, movestr, 8);
686 memset(movestr, 0, MOVESTR_BUFLEN);
687 bufpos = 0;
688 clrtobot();
689 } else {
690 eval_move_failed_msg(res);
691 }
692 }
693 }
694
695 /* read opponent's move */
696 uint8_t code = net_recieve_code_async(opponent);
697 switch (code) {
698 case NETCODE_TIMEOVER:
699 /* redraw the time control */
700 timecontrol(gamestate, gameinfo);
701 return 1;
702 case NETCODE_RESIGN:
703 if (mycolor == WHITE) {
704 gamestate->bresign = true;
705 } else {
706 gamestate->wresign = true;
707 }
708 return 1;
709 case NETCODE_CONNLOST:
710 gamestate->ragequit = true;
711 return 1;
712 case NETCODE_REMIS:
713 if (remis_suggested) {
714 gamestate->remis = true;
715 return 1;
716 } else {
717 if (prompt_yesno(
718 "\rYour opponent offers remis - do you accept")) {
719 gamestate->remis = true;
720 net_send_code(opponent, NETCODE_ACCEPT);
721 return 1;
722 } else {
723 net_send_code(opponent, NETCODE_DECLINE);
724 }
725 }
726 break;
727 case NETCODE_MOVE: {
728 Move move;
729 net_recieve_data(opponent, &move, sizeof(Move));
730 code = validate_move(gamestate, &move);
731 if (code == VALID_MOVE_SEMANTICS) {
732 apply_move(gamestate, &move);
733 if (gamestate->checkmate) {
734 net_send_code(opponent, NETCODE_CHECKMATE);
735 return 1;
736 } else if (gamestate->stalemate) {
737 net_send_code(opponent, NETCODE_STALEMATE);
738 return 1;
739 } else if (move.check) {
740 net_send_code(opponent, NETCODE_CHECK);
741 } else {
742 net_send_code(opponent, NETCODE_ACCEPT);
743 }
744 return 0;
745 } else {
746 uint32_t reason = htonl(code);
747 net_send_data(opponent, NETCODE_DECLINE,
748 &reason, sizeof(uint32_t));
749 }
750 break;
751 }
752 case NETCODE_ERROR:
753 printw("\rCannot perform asynchronous network IO");
754 cbreak(); getch();
755 exit(EXIT_FAILURE);
756 case NETCODE_AGAIN:
757 /* try again */
758 break;
759 default:
760 printw("\nInvalid network request.");
761 }
762 }
763 }
764
765 static void game_review(Settings* settings, GameState *gamestate) {
766 const unsigned page_moves = 10;
767 GameInfo *gameinfo = &(settings->gameinfo);
768 GameState viewedstate = {0};
769 unsigned viewedmove = gamestate->movecount;
770 bool redraw = true;
771
772 noecho();
773 int c;
774 do {
775 if (redraw) {
776 gamestate_cleanup(&viewedstate);
777 gamestate_at_move(gamestate, viewedmove, &viewedstate);
778
779 erase(); /* don't use clear() to avoid flickering */
780 draw_board(&viewedstate, WHITE, settings->unicode);
781 timecontrol(&viewedstate, gameinfo);
782
783 move(getmaxy(stdscr)-5, 0);
784 if (gamestate->wresign) {
785 addstr("White resigned.\n");
786 } else if (gamestate->bresign) {
787 addstr("Black resigned.\n");
788 } else if (gamestate->remis) {
789 addstr("The game ended remis.\n");
790 } else if (gamestate->stalemate) {
791 addstr("The game ended in a stalemate.\n");
792 } else if (gamestate->checkmate) {
793 printw("%s was checkmated.\n",
794 gamestate->movecount % 2 == 0 ? "White" : "Black");
795 } else if (gamestate->ragequit) {
796 printw("Your opponent disconnected.\n");
797 }
798 addstr("\nPress 'q' to quit, 's' to save the position as PGN, or\n"
799 "arrow keys, home/end, page up/down to review the game.\n");
800 flushinp();
801 redraw = false;
802 }
803 c = getch();
804 if (c == 's') {
805 addch('\r');
806 echo();
807 save_pgn(&viewedstate, gameinfo);
808 noecho();
809 redraw = true;
810 } else if (c == KEY_UP || c == KEY_LEFT) {
811 if (viewedmove > 0) {
812 viewedmove--;
813 redraw = true;
814 }
815 } else if (c == KEY_DOWN || c == KEY_RIGHT) {
816 if (viewedmove < gamestate->movecount) {
817 viewedmove++;
818 redraw = true;
819 }
820 } else if (c == KEY_HOME) {
821 viewedmove = 0;
822 redraw = true;
823 } else if (c == KEY_END) {
824 viewedmove = gamestate->movecount;
825 redraw = true;
826 } else if (c == KEY_PPAGE) {
827 if (viewedmove > page_moves) {
828 viewedmove -= page_moves;
829 } else {
830 viewedmove = 0;
831 }
832 redraw = true;
833 } else if (c == KEY_NPAGE) {
834 viewedmove += page_moves;
835 if (viewedmove > gamestate->movecount) {
836 viewedmove = gamestate->movecount;
837 }
838 redraw = true;
839 }
840 } while (c != 'q');
841 echo();
842 gamestate_cleanup(&viewedstate);
843 }
844
845 static void game_play_singlemachine(Settings *settings) {
846 inputy = getmaxy(stdscr) - 6;
847
848 GameState gamestate;
849 gamestate_init(&gamestate);
850 uint8_t curcol = WHITE;
851
852 if (settings->continuepgn) {
853 FILE *pgnfile = fopen(settings->continuepgn, "r");
854 if (pgnfile) {
855 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
856 long position = ftell(pgnfile);
857 fclose(pgnfile);
858 if (result) {
859 printw("Invalid PGN file content at position %ld:\n%s\n",
860 position, pgn_error_str(result));
861 return;
862 }
863 if (!is_game_running(&gamestate)) {
864 addstr("Game has ended. Use -S to analyze it.\n");
865 return;
866 }
867 curcol = opponent_color(last_move(&gamestate).piece&COLOR_MASK);
868 } else {
869 printw("Can't read PGN file (%s)\n", strerror(errno));
870 return;
871 }
872 }
873
874 bool running;
875 do {
876 clear();
877 uint8_t perspective = settings->disableflip ? WHITE : curcol;
878 draw_board(&gamestate, perspective, settings->unicode);
879 running = !domove_singlemachine(&gamestate,
880 &(settings->gameinfo), curcol);
881 curcol = opponent_color(curcol);
882 } while (running);
883
884 game_review(settings, &gamestate);
885 gamestate_cleanup(&gamestate);
886 }
887
888 static void game_play(Settings *settings, GameState *gamestate, int opponent) {
889 inputy = getmaxy(stdscr) - 6;
890
891 uint8_t mycolor = settings->gameinfo.servercolor;
892 if (!settings->ishost) {
893 mycolor = opponent_color(mycolor);
894 }
895
896 bool myturn = (gamestate->movecount > 0 ?
897 (last_move(gamestate).piece & COLOR_MASK) : BLACK) != mycolor;
898
899 bool running;
900 do {
901 clear();
902 draw_board(gamestate, mycolor, settings->unicode);
903 if (myturn) {
904 running = !sendmove(gamestate, &(settings->gameinfo),
905 opponent, mycolor);
906 } else {
907 running = !recvmove(gamestate, &(settings->gameinfo),
908 opponent, mycolor);
909 }
910 myturn ^= true;
911 } while (running);
912 }
913
914 static void dump_gameinfo(GameInfo *gameinfo) {
915 int serverwhite = gameinfo->servercolor == WHITE;
916 attron(A_UNDERLINE);
917 printw("Game details\n");
918 attroff(A_UNDERLINE);
919 printw(" Server: %s\n Client: %s\n",
920 serverwhite?"White":"Black", serverwhite?"Black":"White"
921 );
922 if (gameinfo->timecontrol) {
923 if (gameinfo->time % 60) {
924 printw(" Time limit: %ds + %ds\n",
925 gameinfo->time, gameinfo->addtime);
926 } else {
927 printw(" Time limit: %dm + %ds\n",
928 gameinfo->time/60, gameinfo->addtime);
929 }
930 } else {
931 printw(" No time limit\n");
932 }
933 refresh();
934 }
935
936 static void dump_moveinfo(GameState *gamestate) {
937 for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
938 if (i % 2 == 0) {
939 printw("%d. %s", 1+i/2, gamestate->moves[i].string);
940 } else {
941 printw("%s", gamestate->moves[i].string);
942 }
943 // only five moves reliably fit into one screen row
944 if ((i+1) % 10) {
945 addch(' ');
946 } else {
947 addch('\n');
948 }
949 }
950 refresh();
951 }
952
953 static int server_fd = -1;
954 static void interrupt_listen(int sig) {
955 if (server_fd > -1) {
956 // this interrupts
957 close(server_fd);
958 }
959 }
960
961 static int server_open(Server *server, Settings *settings) {
962 printw("\nListening for client...\n");
963 refresh();
964 if (settings->usedomainsocket
965 ? net_create_sock(server, settings->serverhost)
966 : net_create_tcp(server, settings->port)) {
967 addstr("Server creation failed");
968 return 1;
969 }
970
971 // allow Ctrl+C to interrupt the listening process
972 server_fd = server->fd;
973 signal(SIGINT, interrupt_listen);
974
975 if (net_listen(server)) {
976 addstr("Listening for client failed or interrupted");
977 return 1;
978 }
979
980 // restore default action
981 signal(SIGINT, SIG_DFL);
982
983 return 0;
984 }
985
986 static int server_handshake(Client *client) {
987 net_send_code(client->fd, NETCODE_VERSION);
988 if (net_recieve_code(client->fd) != NETCODE_VERSION) {
989 addstr("Client uses an incompatible software version.");
990 return 1;
991 }
992
993 addstr("Client connected - transmitting gameinfo...");
994 refresh();
995
996 return 0;
997 }
998
999 static int server_run(Settings *settings) {
1000 Server server;
1001
1002 dump_gameinfo(&(settings->gameinfo));
1003 GameState gamestate;
1004 gamestate_init(&gamestate);
1005 if (settings->continuepgn) {
1006 /* preload PGN data before handshake */
1007 FILE *pgnfile = fopen(settings->continuepgn, "r");
1008 if (pgnfile) {
1009 int result = read_pgn(pgnfile, &gamestate,
1010 &(settings->gameinfo));
1011 long position = ftell(pgnfile);
1012 fclose(pgnfile);
1013 if (result) {
1014 printw("Invalid PGN file content at position %ld:\n%s\n",
1015 position, pgn_error_str(result));
1016 return 1;
1017 }
1018 if (!is_game_running(&gamestate)) {
1019 addstr("Game has ended. Use -s to analyze it locally.\n");
1020 return 1;
1021 }
1022 addch('\n');
1023 dump_moveinfo(&gamestate);
1024 addch('\n');
1025 } else {
1026 printw("Can't read PGN file (%s)\n", strerror(errno));
1027 return 1;
1028 }
1029 }
1030
1031 if (server_open(&server, settings)) {
1032 net_destroy(&server);
1033 return 1;
1034 }
1035
1036 if (server_handshake(server.client)) {
1037 net_destroy(&server);
1038 return 1;
1039 }
1040
1041 int fd = server.client->fd;
1042 if (settings->continuepgn) {
1043 /* Continue game, send PGN data */
1044 uint16_t mc = gamestate.movecount;
1045 size_t pgndata_size = sizeof(GameInfo)+sizeof(mc)+mc*sizeof(Move);
1046 char *pgndata = malloc(pgndata_size);
1047 memcpy(pgndata, &(settings->gameinfo), sizeof(GameInfo));
1048 unsigned offset = sizeof(GameInfo);
1049 memcpy(pgndata+offset, &mc, sizeof(mc));
1050 offset += sizeof(mc);
1051 memcpy(pgndata+offset, gamestate.moves, mc*sizeof(Move));
1052 net_send_data(fd, NETCODE_PGNDATA, pgndata, pgndata_size);
1053 free(pgndata);
1054 } else {
1055 /* Start new game */
1056 net_send_data(fd, NETCODE_GAMEINFO,
1057 &(settings->gameinfo), sizeof(GameInfo));
1058 }
1059 addstr("\rClient connected - awaiting challenge acceptance...");
1060 refresh();
1061 int code = net_recieve_code(fd);
1062 int exitcode = 0;
1063 if (code == NETCODE_ACCEPT) {
1064 addstr("\rClient connected - challenge accepted.");
1065 clrtoeol();
1066 game_play(settings, &gamestate, fd);
1067 net_destroy(&server);
1068 game_review(settings, &gamestate);
1069 } else if (code == NETCODE_DECLINE) {
1070 addstr("\rClient connected - challenge declined.");
1071 clrtoeol();
1072 net_destroy(&server);
1073 } else if (code == NETCODE_CONNLOST) {
1074 addstr("\rClient connected - but gave no response.");
1075 clrtoeol();
1076 net_destroy(&server);
1077 } else {
1078 addstr("\rInvalid client response");
1079 clrtoeol();
1080
1081 net_destroy(&server);
1082 exitcode = 1;
1083 }
1084 gamestate_cleanup(&gamestate);
1085 return exitcode;
1086 }
1087
1088
1089 static int client_connect(Server *server, Settings *settings) {
1090 if (settings->usedomainsocket
1091 ? net_find_sock(server, settings->serverhost)
1092 : net_find_tcp(server, settings->serverhost, settings->port)) {
1093 addstr("Can't find server");
1094 return 1;
1095 }
1096
1097 if (net_connect(server)) {
1098 addstr("Can't connect to server");
1099 return 1;
1100 }
1101
1102 return 0;
1103 }
1104
1105 static int client_handshake(Server *server) {
1106 if (net_recieve_code(server->fd) != NETCODE_VERSION) {
1107 addstr("Server uses an incompatible software version.");
1108 return 1;
1109 } else {
1110 net_send_code(server->fd, NETCODE_VERSION);
1111 }
1112
1113 printw("Connection established!\n\n");
1114 refresh();
1115
1116 return 0;
1117 }
1118
1119 static int client_run(Settings *settings) {
1120 Server server;
1121
1122 if (client_connect(&server, settings)) {
1123 net_destroy(&server);
1124 return 1;
1125 }
1126
1127 if (client_handshake(&server)) {
1128 net_destroy(&server);
1129 return 1;
1130 }
1131
1132 uint8_t code = net_recieve_code(server.fd);
1133 GameState gamestate;
1134 gamestate_init(&gamestate);
1135 bool played = false;
1136 if (code == NETCODE_GAMEINFO) {
1137 /* Start new game */
1138 net_recieve_data(server.fd, &(settings->gameinfo), sizeof(GameInfo));
1139 dump_gameinfo(&(settings->gameinfo));
1140 if (prompt_yesno("Accept challenge")) {
1141 net_send_code(server.fd, NETCODE_ACCEPT);
1142 game_play(settings, &gamestate, server.fd);
1143 played = true;
1144 } else {
1145 net_send_code(server.fd, NETCODE_DECLINE);
1146 }
1147 } else if (code == NETCODE_PGNDATA) {
1148 net_recieve_data(server.fd, &(settings->gameinfo), sizeof(GameInfo));
1149 dump_gameinfo(&(settings->gameinfo));
1150 uint16_t mc;
1151 net_recieve_data(server.fd, &mc, sizeof(mc));
1152 Move *moves = calloc(mc, sizeof(Move));
1153 net_recieve_data(server.fd, moves, mc*sizeof(Move));
1154 for (size_t i = 0 ; i < mc ; i++) {
1155 apply_move(&gamestate, &(moves[i]));
1156 }
1157 free(moves);
1158 addch('\n');
1159 dump_moveinfo(&gamestate);
1160 if (prompt_yesno(
1161 "\n\nServer wants to continue a game. Accept challenge")) {
1162 net_send_code(server.fd, NETCODE_ACCEPT);
1163 game_play(settings, &gamestate, server.fd);
1164 played = true;
1165 } else {
1166 net_send_code(server.fd, NETCODE_DECLINE);
1167 }
1168 } else {
1169 addstr("Server sent invalid gameinfo.");
1170 net_destroy(&server);
1171 return 1;
1172 }
1173
1174 if (played) {
1175 game_review(settings, &gamestate);
1176 }
1177 gamestate_cleanup(&gamestate);
1178
1179 net_destroy(&server);
1180 return 0;
1181 }
1182
212 int main(int argc, char **argv) { 1183 int main(int argc, char **argv) {
213 srand(time(NULL)); 1184 srand(time(NULL));
214 1185
215 init_settings(); 1186 init_settings();
216 if (get_settings(argc, argv, &settings)) { 1187 if (get_settings(argc, argv, &settings)) {

mercurial