| 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); |
| 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 |
| 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" |
| 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) { |
| 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); |
| 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) { |
| 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 { |