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