/* $Id$ */ #include "bbs.h" #include "chess.h" #include #define assert_not_reached() assert(!"Should never be here!!!") #define dim(x) (sizeof(x) / sizeof(x[0])) #define CHESS_HISTORY_INITIAL_BUFFER_SIZE 300 #define CHESS_HISTORY_BUFFER_INCREMENT 50 #define CHESS_DRAWING_SIDE_ROW 7 #define CHESS_DRAWING_REAL_TURN_ROW 8 #define CHESS_DRAWING_REAL_STEP_ROW 9 #define CHESS_DRAWING_REAL_TIME_ROW1 10 #define CHESS_DRAWING_REAL_TIME_ROW2 11 #define CHESS_DRAWING_REAL_WARN_ROW 13 #define CHESS_DRAWING_MYWIN_ROW 17 #define CHESS_DRAWING_HISWIN_ROW 18 #define CHESS_DRAWING_PHOTOED_STEP_ROW 18 #define CHESS_DRAWING_PHOTOED_TURN_ROW 19 #define CHESS_DRAWING_PHOTOED_TIME_ROW1 20 #define CHESS_DRAWING_PHOTOED_TIME_ROW2 21 #define CHESS_DRAWING_PHOTOED_WARN_ROW 22 #define CONNECT_PEER() add_io(info->sock, 0) #define IGNORE_PEER() add_io(0, 0) #define DO_WITHOUT_PEER(TIMEOUT,ACT,ELSE) \ do { \ void (*orig_alarm_handler)(int) = \ Signal(SIGALRM, &SigjmpEnv); \ IGNORE_PEER(); \ if(sigsetjmp(sigjmpEnv, 1)) \ ELSE; \ else { \ alarm(TIMEOUT); \ ACT; \ } \ CONNECT_PEER(); \ Signal(SIGALRM, orig_alarm_handler); \ } while(0) static const char * const ChessHintStr[] = { " q 認輸離開", " p 要求和棋", "方向鍵 移動遊標", "Enter 選擇/移動" }; struct ChessReplayMap_t { const char* name; int name_len; ChessInfo* (*func)(FILE* fp); }; static const struct ChessReplayMap_t ChessReplayMap[] = { { "gomoku", 6, &gomoku_replay }, { "chc", 3, &chc_replay }, { "go", 2, &gochess_replay }, { "reversi",7, &reversi_replay }, { NULL } }; static ChessInfo * CurrentPlayingGameInfo; static sigjmp_buf sigjmpEnv; /* XXX: This is a BAD way to pass information. * Fix this by handling chess request ourselves. */ static ChessTimeLimit * _current_time_limit; static void SigjmpEnv(int sig) { siglongjmp(sigjmpEnv, 1); } #define CHESS_HISTORY_ENTRY(INFO,N) \ ((INFO)->history.body + (N) * (INFO)->constants->step_entry_size) static void ChessHistoryInit(ChessHistory* history, int entry_size) { history->size = CHESS_HISTORY_INITIAL_BUFFER_SIZE; history->used = 0; history->body = calloc(CHESS_HISTORY_INITIAL_BUFFER_SIZE, entry_size); } const void* ChessHistoryRetrieve(ChessInfo* info, int n) { assert(n >= 0 && n < info->history.used); return CHESS_HISTORY_ENTRY(info, n); } void ChessHistoryAppend(ChessInfo* info, void* step) { if (info->history.used == info->history.size) info->history.body = realloc(info->history.body, (info->history.size += CHESS_HISTORY_BUFFER_INCREMENT) * info->constants->step_entry_size); memmove(CHESS_HISTORY_ENTRY(info, info->history.used), step, info->constants->step_entry_size); info->history.used++; } static void ChessBroadcastListInit(ChessBroadcastList* list) { list->head.next = NULL; } static void ChessBroadcastListClear(ChessBroadcastList* list) { ChessBroadcastListNode* p = list->head.next; while (p) { ChessBroadcastListNode* t = p->next; close(p->sock); free(p); p = t; } } static ChessBroadcastListNode* ChessBroadcastListInsert(ChessBroadcastList* list) { ChessBroadcastListNode* p = (ChessBroadcastListNode*) malloc(sizeof(ChessBroadcastListNode)); p->next = list->head.next; list->head.next = p; return p; } static void ChessDrawHelpLine(const ChessInfo* info) { const static char* const HelpStr[] = { /* CHESS_MODE_VERSUS, 對奕 */ ANSI_COLOR(1;33;42) " 下棋 " ANSI_COLOR(;31;47) " (←↑↓→)" ANSI_COLOR(30) " 移動 " ANSI_COLOR(31) "(空白鍵/ENTER)" ANSI_COLOR(30) " 下子 " ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "認輸 " ANSI_COLOR(31) "(p)" ANSI_COLOR(30) "虛手/和棋 " ANSI_COLOR(31) "(u)" ANSI_COLOR(30) "悔棋 " ANSI_RESET, /* CHESS_MODE_WATCH, 觀棋 */ ANSI_COLOR(1;33;42) " 觀棋 " ANSI_COLOR(;31;47) " (←→)" ANSI_COLOR(30) " 前後一步 " ANSI_COLOR(31) "(↑↓)" ANSI_COLOR(30) " 前後十步 " ANSI_COLOR(31) "(PGUP/PGDN)" ANSI_COLOR(30) " 最初/目前盤面 " ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "離開 " ANSI_RESET, /* CHESS_MODE_PERSONAL, 打譜 */ ANSI_COLOR(1;33;42) " 打譜 " ANSI_COLOR(;31;47) " (←↑↓→)" ANSI_COLOR(30) " 移動 " ANSI_COLOR(31) "(空白鍵/ENTER)" ANSI_COLOR(30) " 下子 " ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "離開 " ANSI_COLOR(31) "(u)" ANSI_COLOR(30) "悔棋 " ANSI_RESET, /* CHESS_MODE_REPLAY, 看譜 */ ANSI_COLOR(1;33;42) " 看譜 " ANSI_COLOR(;31;47) " (←→)" ANSI_COLOR(30) " 前後一步 " ANSI_COLOR(31) "(↑↓)" ANSI_COLOR(30) " 前後十步 " ANSI_COLOR(31) "(PGUP/PGDN)" ANSI_COLOR(30) " 最初/目前盤面 " ANSI_COLOR(31) "(q)" ANSI_COLOR(30) "離開 " ANSI_RESET, }; mvouts(b_lines, 0, HelpStr[info->mode]); info->actions->drawline(info, b_lines); } void ChessDrawLine(const ChessInfo* info, int line) { #define DRAWLINE(LINE) \ do { \ move((LINE), 0); \ clrtoeol(); \ info->actions->drawline(info, (LINE)); \ } while (0) if (line == b_lines) { ChessDrawHelpLine(info); return; } else if (line == CHESS_DRAWING_TURN_ROW) line = info->photo ? CHESS_DRAWING_PHOTOED_TURN_ROW : CHESS_DRAWING_REAL_TURN_ROW; else if (line == CHESS_DRAWING_TIME_ROW) { if(info->photo) { DRAWLINE(CHESS_DRAWING_PHOTOED_TIME_ROW1); DRAWLINE(CHESS_DRAWING_PHOTOED_TIME_ROW2); } else { DRAWLINE(CHESS_DRAWING_REAL_TIME_ROW1); DRAWLINE(CHESS_DRAWING_REAL_TIME_ROW2); } return; } else if (line == CHESS_DRAWING_WARN_ROW) line = info->photo ? CHESS_DRAWING_PHOTOED_WARN_ROW : CHESS_DRAWING_REAL_WARN_ROW; else if (line == CHESS_DRAWING_STEP_ROW) line = info->photo ? CHESS_DRAWING_PHOTOED_STEP_ROW : CHESS_DRAWING_REAL_STEP_ROW; DRAWLINE(line); #undef DRAWLINE } void ChessRedraw(const ChessInfo* info) { int i; clear(); for (i = 0; i <= b_lines; ++i) ChessDrawLine(info, i); } inline static int ChessTimeCountDownCalc(ChessInfo* info, int who, int length) { info->lefttime[who] -= length; if (!info->timelimit) /* traditional mode, only left time is considered */ return info->lefttime[who] < 0; if (info->lefttime[who] < 0) { /* only allowed when in free time */ if (info->lefthand[who]) return 1; info->lefttime[who] += info->timelimit->limit_time; info->lefthand[who] = info->timelimit->limit_hand; return (info->lefttime[who] < 0); } return 0; } int ChessTimeCountDown(ChessInfo* info, int who, int length) { int result = ChessTimeCountDownCalc(info, who, length); ChessDrawLine(info, CHESS_DRAWING_TIME_ROW); return result; } void ChessStepMade(ChessInfo* info, int who) { if (!info->timelimit) info->lefttime[who] = info->constants->traditional_timeout; else if ( (info->lefthand[who] && (--(info->lefthand[who]) == 0) && info->timelimit->time_mode == CHESS_TIMEMODE_COUNTING) || (info->lefthand[who] == 0 && info->lefttime[who] <= 0) ) { info->lefthand[who] = info->timelimit->limit_hand; info->lefttime[who] = info->timelimit->limit_time; } } /* * Start of the network communication function. */ inline static ChessStepType ChessRecvMove(ChessInfo* info, int sock, void *step) { if (read(sock, step, info->constants->step_entry_size) != info->constants->step_entry_size) return CHESS_STEP_FAILURE; return *(ChessStepType*) step; } inline static int ChessSendMove(ChessInfo* info, int sock, const void *step) { if (write(sock, step, info->constants->step_entry_size) != info->constants->step_entry_size) return 0; return 1; } inline static int ChessStepSendOpposite(ChessInfo* info, const void* step) { void (*orig_handler)(int); int result = 1; /* fd 0 is the socket to user, it means no oppisite available. * (Might be personal play) */ if (info->sock == 0) return 1; orig_handler = Signal(SIGPIPE, SIG_IGN); if (!ChessSendMove(info, info->sock, step)) result = 0; Signal(SIGPIPE, orig_handler); return result; } inline static void ChessStepBroadcast(ChessInfo* info, const void *step) { ChessBroadcastListNode *p = &(info->broadcast_list.head); void (*orig_handler)(int); orig_handler = Signal(SIGPIPE, SIG_IGN); while(p->next){ if (!ChessSendMove(info, p->next->sock, step)) { /* remove viewer */ ChessBroadcastListNode *tmp = p->next->next; free(p->next); p->next = tmp; } else p = p->next; } Signal(SIGPIPE, orig_handler); } int ChessStepSend(ChessInfo* info, const void* step) { /* send to opposite... */ if (!ChessStepSendOpposite(info, step)) return 0; /* and watchers */ ChessStepBroadcast(info, step); return 1; } int ChessMessageSend(ChessInfo* info, ChessStepType type) { return ChessStepSend(info, &type); } static inline int ChessCheckAlive(ChessInfo* info) { ChessStepType type = CHESS_STEP_NOP; return ChessStepSendOpposite(info, &type); } ChessStepType ChessStepReceive(ChessInfo* info, void* step) { ChessStepType result = ChessRecvMove(info, info->sock, step); /* automatical routing */ if (result != CHESS_STEP_FAILURE) ChessStepBroadcast(info, step); /* and logging */ if (result == CHESS_STEP_NORMAL || result == CHESS_STEP_PASS) ChessHistoryAppend(info, step); return result; } inline static void ChessReplayUntil(ChessInfo* info, int n) { const void* step; if (n <= info->current_step) return; while (info->current_step < n - 1) { info->actions->apply_step(info->board, ChessHistoryRetrieve(info, info->current_step)); info->current_step++; } /* spcial for last one to maintian information correct */ step = ChessHistoryRetrieve(info, info->current_step); if (info->mode == CHESS_MODE_WATCH || info->mode == CHESS_MODE_REPLAY) info->turn = info->current_step & 1; info->actions->prepare_step(info, step); info->actions->apply_step(info->board, step); info->current_step++; } static int ChessAnswerRequest(ChessInfo* info, const char* req_name) { char buf[4]; char msg[64]; snprintf(info->warnmsg, sizeof(info->warnmsg), ANSI_COLOR(1;31) "要求%s!" ANSI_RESET, req_name); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); bell(); snprintf(msg, sizeof(msg), "對方要求%s,是否接受?(y/N)", req_name); DO_WITHOUT_PEER(30, getdata(b_lines, 0, msg, buf, sizeof(buf), DOECHO), buf[0] = 'n'); ChessDrawHelpLine(info); info->warnmsg[0] = 0; ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); if (buf[0] == 'y' || buf[0] == 'Y') return 1; else return 0; } ChessGameResult ChessPlayFuncMy(ChessInfo* info) { int last_time = now; int endturn = 0; ChessGameResult game_result = CHESS_RESULT_CONTINUE; int ch; #ifdef DBCSAWARE int move_count = 0; #endif info->pass[(int) info->turn] = 0; bell(); while (!endturn) { ChessStepType result; ChessDrawLine(info, CHESS_DRAWING_TIME_ROW); info->actions->movecur(info->cursor.r, info->cursor.c); oflush(); ch = igetch(); if (ChessTimeCountDown(info, 0, now - last_time)) { /* ran out of time */ game_result = CHESS_RESULT_LOST; endturn = 1; break; } last_time = now; switch (ch) { case I_OTHERDATA: result = ChessStepReceive(info, &info->step_tmp); if (result == CHESS_STEP_FAILURE || result == CHESS_STEP_DROP) { game_result = CHESS_RESULT_WIN; endturn = 1; } else if (result == CHESS_STEP_TIE_ACC) { game_result = CHESS_RESULT_TIE; endturn = 1; } else if (result == CHESS_STEP_TIE_REJ) { strcpy(info->warnmsg, ANSI_COLOR(1;31) "求和被拒!" ANSI_RESET); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); } else if (result == CHESS_STEP_UNDO) { if (ChessAnswerRequest(info, "悔棋")) { ChessMessageSend(info, CHESS_STEP_UNDO_ACC); info->actions->init_board(info->board); info->current_step = 0; ChessReplayUntil(info, info->history.used - 1); info->history.used--; ChessRedraw(info); endturn = 1; } else ChessMessageSend(info, CHESS_STEP_UNDO_REJ); } else if (result == CHESS_STEP_NORMAL || result == CHESS_STEP_SPECIAL) { info->actions->prepare_step(info, &info->step_tmp); game_result = info->actions->apply_step(info->board, &info->step_tmp); info->actions->drawstep(info, &info->step_tmp); endturn = 1; ChessStepMade(info, 0); } break; case KEY_UP: info->cursor.r--; if (info->cursor.r < 0) info->cursor.r = info->constants->board_height - 1; break; case KEY_DOWN: info->cursor.r++; if (info->cursor.r >= info->constants->board_height) info->cursor.r = 0; break; case KEY_LEFT: #ifdef DBCSAWARE if (!ISDBCSAWARE()) { if (++move_count >= 2) move_count = 0; else break; } #endif /* defined(DBCSAWARE) */ info->cursor.c--; if (info->cursor.c < 0) info->cursor.c = info->constants->board_width - 1; break; case KEY_RIGHT: #ifdef DBCSAWARE if (!ISDBCSAWARE()) { if (++move_count >= 2) move_count = 0; else break; } #endif /* defined(DBCSAWARE) */ info->cursor.c++; if (info->cursor.c >= info->constants->board_width) info->cursor.c = 0; break; case 'q': { char buf[4]; DO_WITHOUT_PEER(30, getdata(b_lines, 0, info->mode == CHESS_MODE_PERSONAL ? "是否真的要離開?(y/N)" : "是否真的要認輸?(y/N)", buf, sizeof(buf), DOECHO), buf[0] = 'n'); ChessDrawHelpLine(info); if (buf[0] == 'y' || buf[0] == 'Y') { game_result = CHESS_RESULT_LOST; endturn = 1; } } break; case 'p': if (info->constants->pass_is_step) { ChessStepType type = CHESS_STEP_PASS; ChessHistoryAppend(info, &type); strcpy(info->last_movestr, "虛手"); info->pass[(int) info->turn] = 1; ChessMessageSend(info, CHESS_STEP_PASS); endturn = 1; } else if (info->mode != CHESS_MODE_PERSONAL) { char buf[4]; DO_WITHOUT_PEER(30, getdata(b_lines, 0, "是否真的要和棋?(y/N)", buf, sizeof(buf), DOECHO), buf[0] = 'n'); ChessDrawHelpLine(info); if (buf[0] == 'y' || buf[1] == 'Y') { ChessMessageSend(info, CHESS_STEP_TIE); strlcpy(info->warnmsg, ANSI_COLOR(1;33) "要求和棋!" ANSI_RESET, sizeof(info->warnmsg)); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); bell(); } } break; case 'u': if (info->mode == CHESS_MODE_PERSONAL && info->history.used > 0) { ChessMessageSend(info, CHESS_STEP_UNDO_ACC); info->actions->init_board(info->board); info->current_step = 0; ChessReplayUntil(info, info->history.used - 1); info->history.used--; ChessRedraw(info); endturn = 1; } break; case KEY_ENTER: case ' ': endturn = info->actions->select(info, info->cursor, &game_result); break; case I_TIMEOUT: break; case KEY_UNKNOWN: break; default: if (info->actions->process_key) { DO_WITHOUT_PEER(30, endturn = info->actions->process_key(info, ch, &game_result), ); } } } ChessTimeCountDown(info, 0, now - last_time); ChessStepMade(info, 0); ChessDrawLine(info, CHESS_DRAWING_TIME_ROW); ChessDrawLine(info, CHESS_DRAWING_STEP_ROW); return game_result; } static ChessGameResult ChessPlayFuncHis(ChessInfo* info) { int last_time = now; int endturn = 0; ChessGameResult game_result = CHESS_RESULT_CONTINUE; while (!endturn) { ChessStepType result; int ch; if (ChessTimeCountDown(info, 1, now - last_time)) { info->lefttime[1] = 0; /* to make him break out igetch() */ ChessMessageSend(info, CHESS_STEP_NOP); } last_time = now; ChessDrawLine(info, CHESS_DRAWING_TIME_ROW); move(1, 0); oflush(); switch (ch = igetch()) { case 'q': { char buf[4]; DO_WITHOUT_PEER(30, getdata(b_lines, 0, "是否真的要認輸?(y/N)", buf, sizeof(buf), DOECHO), buf[0] = 'n'); ChessDrawHelpLine(info); if (buf[0] == 'y' || buf[0] == 'Y') { game_result = CHESS_RESULT_LOST; endturn = 1; } } break; case 'u': if (info->history.used > 0) { strcpy(info->warnmsg, ANSI_COLOR(1;31) "要求悔棋!" ANSI_RESET); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); ChessMessageSend(info, CHESS_STEP_UNDO); } break; case I_OTHERDATA: result = ChessStepReceive(info, &info->step_tmp); if (result == CHESS_STEP_FAILURE || result == CHESS_STEP_DROP) { game_result = CHESS_RESULT_WIN; endturn = 1; } else if (result == CHESS_STEP_PASS) { strcpy(info->last_movestr, "虛手"); info->pass[(int) info->turn] = 1; endturn = 1; } else if (result == CHESS_STEP_TIE) { if (ChessAnswerRequest(info, "和棋")) { ChessMessageSend(info, CHESS_STEP_TIE_ACC); game_result = CHESS_RESULT_TIE; endturn = 1; } else ChessMessageSend(info, CHESS_STEP_TIE_REJ); } else if (result == CHESS_STEP_NORMAL || result == CHESS_STEP_SPECIAL) { info->actions->prepare_step(info, &info->step_tmp); switch (info->actions->apply_step(info->board, &info->step_tmp)) { case CHESS_RESULT_LOST: game_result = CHESS_RESULT_WIN; break; case CHESS_RESULT_WIN: game_result = CHESS_RESULT_LOST; break; default: game_result = CHESS_RESULT_CONTINUE; } endturn = 1; info->pass[(int) info->turn] = 0; ChessStepMade(info, 1); info->actions->drawstep(info, &info->step_tmp); } else if (result == CHESS_STEP_UNDO_ACC) { strcpy(info->warnmsg, ANSI_COLOR(1;31) "接受悔棋!" ANSI_RESET); info->actions->init_board(info->board); info->current_step = 0; ChessReplayUntil(info, info->history.used - 1); info->history.used--; ChessRedraw(info); bell(); endturn = 1; } else if (result == CHESS_STEP_UNDO_REJ) { strcpy(info->warnmsg, ANSI_COLOR(1;31) "悔棋被拒!" ANSI_RESET); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); } case I_TIMEOUT: break; case KEY_UNKNOWN: break; default: if (info->actions->process_key) { DO_WITHOUT_PEER(30, endturn = info->actions->process_key(info, ch, &game_result), ); } } } ChessTimeCountDown(info, 1, now - last_time); ChessDrawLine(info, CHESS_DRAWING_TIME_ROW); ChessDrawLine(info, CHESS_DRAWING_STEP_ROW); return game_result; } static ChessGameResult ChessPlayFuncWatch(ChessInfo* info) { int end_watch = 0; while (!end_watch) { ChessStepType result; info->actions->prepare_play(info); if (info->sock == -1) strlcpy(info->warnmsg, ANSI_COLOR(1;33) "棋局已結束" ANSI_RESET, sizeof(info->warnmsg)); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); ChessDrawLine(info, CHESS_DRAWING_STEP_ROW); move(1, 0); switch (igetch()) { case I_OTHERDATA: /* new step */ result = ChessStepReceive(info, &info->step_tmp); if (result == CHESS_STEP_FAILURE) { IGNORE_PEER(); info->sock = -1; break; } else if (result == CHESS_STEP_UNDO_ACC) { if (info->current_step == info->history.used) { /* at head but redo-ed */ info->actions->init_board(info->board); info->current_step = 0; ChessReplayUntil(info, info->history.used - 1); ChessRedraw(info); } info->history.used--; } else if (result == CHESS_STEP_NORMAL || result == CHESS_STEP_SPECIAL) { if (info->current_step == info->history.used - 1) { /* was watching up-to-date board */ info->turn = info->current_step++ & 1; info->actions->prepare_step(info, &info->step_tmp); info->actions->apply_step(info->board, &info->step_tmp); info->actions->drawstep(info, &info->step_tmp); } } else if (result == CHESS_STEP_PASS) strcpy(info->last_movestr, "虛手"); break; case KEY_LEFT: /* 往前一步 */ if (info->current_step == 0) bell(); else { /* TODO: implement without re-apply all steps */ int current = info->current_step; info->actions->init_board(info->board); info->current_step = 0; ChessReplayUntil(info, current - 1); ChessRedraw(info); } break; case KEY_RIGHT: /* 往後一步 */ if (info->current_step == info->history.used) bell(); else { const void* step = ChessHistoryRetrieve(info, info->current_step); info->turn = info->current_step++ & 1; info->actions->prepare_step(info, step); info->actions->apply_step(info->board, step); info->actions->drawstep(info, step); } break; case KEY_UP: /* 往前十步 */ if (info->current_step == 0) bell(); else { /* TODO: implement without re-apply all steps */ int current = info->current_step; info->actions->init_board(info->board); info->current_step = 0; ChessReplayUntil(info, current - 10); ChessRedraw(info); } break; case KEY_DOWN: /* 往後十步 */ if (info->current_step == info->history.used) bell(); else { ChessReplayUntil(info, MIN(info->current_step + 10, info->history.used)); ChessRedraw(info); } break; case KEY_PGUP: /* 起始盤面 */ if (info->current_step == 0) bell(); else { info->actions->init_board(info->board); info->current_step = 0; ChessRedraw(info); } break; case KEY_PGDN: /* 最新盤面 */ if (info->current_step == info->history.used) bell(); else { ChessReplayUntil(info, info->history.used); ChessRedraw(info); } break; case 'q': end_watch = 1; } } return CHESS_RESULT_END; } static void ChessWatchRequest(int sig) { int sock = establish_talk_connection(&SHM->uinfo[currutmp->destuip]); ChessBroadcastListNode* node; if (sock < 0 || !CurrentPlayingGameInfo) return; node = ChessBroadcastListInsert(&CurrentPlayingGameInfo->broadcast_list); node->sock = sock; #define SEND(X) write(sock, &(X), sizeof(X)) SEND(CurrentPlayingGameInfo->myturn); SEND(CurrentPlayingGameInfo->turn); if (!CurrentPlayingGameInfo->timelimit) write(sock, "T", 1); else { write(sock, "L", 1); SEND(*(CurrentPlayingGameInfo->timelimit)); } SEND(CurrentPlayingGameInfo->history.used); write(sock, CurrentPlayingGameInfo->history.body, CurrentPlayingGameInfo->constants->step_entry_size * CurrentPlayingGameInfo->history.used); #undef SEND } static void ChessReceiveWatchInfo(ChessInfo* info) { char time_mode; #define RECV(X) read(info->sock, &(X), sizeof(X)) RECV(info->myturn); RECV(info->turn); RECV(time_mode); if (time_mode == 'L') { info->timelimit = (ChessTimeLimit*) malloc(sizeof(ChessTimeLimit)); RECV(*(info->timelimit)); } RECV(info->history.used); for (info->history.size = CHESS_HISTORY_INITIAL_BUFFER_SIZE; info->history.size < info->history.used; info->history.size += CHESS_HISTORY_BUFFER_INCREMENT); info->history.body = calloc(info->history.size, info->constants->step_entry_size); read(info->sock, info->history.body, info->history.used * info->constants->step_entry_size); #undef RECV } static void ChessGenLogGlobal(ChessInfo* info, ChessGameResult result) { fileheader_t log_header; FILE *fp; char fname[PATHLEN]; int bid; if ((bid = getbnum(info->constants->log_board)) == 0) return; setbpath(fname, info->constants->log_board); stampfile(fname, &log_header); fp = fopen(fname, "w"); if (fp != NULL) { strlcpy(log_header.owner, "[棋譜機器人]", sizeof(log_header.owner)); snprintf(log_header.title, sizeof(log_header.title), "[棋譜] %s VS %s", info->user1.userid, info->user2.userid); fprintf(fp, "作者: %s 看板: %s\n標題: %s \n", log_header.owner, info->constants->log_board, log_header.title); fprintf(fp, "時間: %s\n", ctime4(&now)); info->actions->genlog(info, fp, result); fclose(fp); setbdir(fname, info->constants->log_board); append_record(fname, &log_header, sizeof(log_header)); setbtotal(bid); } } static void ChessGenLogUser(ChessInfo* info, ChessGameResult result) { fileheader_t log_header; FILE *fp; char fname[PATHLEN]; sethomepath(fname, cuser.userid); stampfile(fname, &log_header); fp = fopen(fname, "w"); if (fp != NULL) { info->actions->genlog(info, fp, result); fclose(fp); snprintf(log_header.owner, sizeof(log_header.owner), "[%s]", info->constants->chess_name); if(info->myturn == 0) sprintf(log_header.title, "%s V.S. %s", info->user1.userid, info->user2.userid); else sprintf(log_header.title, "%s V.S. %s", info->user2.userid, info->user1.userid); log_header.filemode = 0; sethomedir(fname, cuser.userid); append_record_forward(fname, &log_header, sizeof(log_header), cuser.userid); } } static void ChessGenLog(ChessInfo* info, ChessGameResult result) { char a = 0; if (info->mode == CHESS_MODE_VERSUS && info->myturn == 0 && info->constants->log_board) { ChessGenLogGlobal(info, result); } a = vans((cuser.uflag & DEFBACKUP_FLAG) ? "是否將棋譜寄回信箱? [Y/n]" : "是否將棋譜寄回信箱? [y/N]"); if (TOBACKUP(a)) ChessGenLogUser(info, result); } void ChessPlay(ChessInfo* info) { ChessGameResult game_result; void (*old_handler)(int); const char* game_result_str = 0; sigset_t old_sigset; if (info == NULL) return; if (!ChessCheckAlive(info)) { if (info->sock) close(info->sock); return; } /* XXX */ if (!info->timelimit) { info->timelimit = _current_time_limit; _current_time_limit = NULL; } CurrentPlayingGameInfo = info; { char buf[4] = ""; sigset_t sigset; if(info->mode == CHESS_MODE_VERSUS) getdata(b_lines, 0, "是否接受觀棋? (Y/n)", buf, sizeof(buf), DOECHO); if(buf[0] == 'n' || buf[0] == 'N') old_handler = Signal(SIGUSR1, SIG_IGN); else old_handler = Signal(SIGUSR1, &ChessWatchRequest); sigemptyset(&sigset); sigaddset(&sigset, SIGUSR1); sigprocmask(SIG_UNBLOCK, &sigset, &old_sigset); } if (info->mode == CHESS_MODE_WATCH) { int i; for (i = 0; i < info->history.used; ++i) info->actions->apply_step(info->board, ChessHistoryRetrieve(info, i)); info->current_step = info->history.used; } /* playing initialization */ ChessRedraw(info); info->turn = 1; info->lefttime[0] = info->lefttime[1] = info->timelimit ? info->timelimit->free_time : info->constants->traditional_timeout; info->lefthand[0] = info->lefthand[1] = 0; /* main loop */ CONNECT_PEER(); for (game_result = CHESS_RESULT_CONTINUE; game_result == CHESS_RESULT_CONTINUE; info->turn ^= 1) { if (info->actions->prepare_play(info)) info->pass[(int) info->turn] = 1; else { ChessDrawLine(info, CHESS_DRAWING_TURN_ROW); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); game_result = info->play_func[(int) info->turn](info); } if (info->pass[0] && info->pass[1]) game_result = CHESS_RESULT_END; } if (game_result == CHESS_RESULT_END && info->actions->post_game && (info->mode == CHESS_MODE_VERSUS || info->mode == CHESS_MODE_PERSONAL)) game_result = info->actions->post_game(info); IGNORE_PEER(); if (info->sock) close(info->sock); /* end processing */ if (info->mode == CHESS_MODE_VERSUS) { switch (game_result) { case CHESS_RESULT_WIN: game_result_str = "對方認輸了!"; break; case CHESS_RESULT_LOST: game_result_str = "你認輸了!"; break; case CHESS_RESULT_TIE: game_result_str = "和棋"; break; default: assert_not_reached(); } } else if (info->mode == CHESS_MODE_WATCH) game_result_str = "結束觀棋"; else if (info->mode == CHESS_MODE_PERSONAL) game_result_str = "結束打譜"; else if (info->mode == CHESS_MODE_REPLAY) game_result_str = "結束看譜"; if (game_result_str) { strlcpy(info->warnmsg, game_result_str, sizeof(info->warnmsg)); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); } info->actions->gameend(info, game_result); if (info->mode != CHESS_MODE_REPLAY) ChessGenLog(info, game_result); // currutmp->sig = -1; sigprocmask(SIG_SETMASK, &old_sigset, NULL); Signal(SIGUSR1, old_handler); CurrentPlayingGameInfo = NULL; } static userinfo_t* ChessSearchUser(int sig, const char* title) { char uident[16]; userinfo_t *uin; vs_hdr(title); CompleteOnlineUser(msg_uid, uident); if (uident[0] == '\0') return NULL; if ((uin = search_ulist_userid(uident)) == NULL) return NULL; if (sig >= 0) uin->sig = sig; return uin; } int ChessStartGame(char func_char, int sig, const char* title) { userinfo_t *uin; char buf[4]; if ((uin = ChessSearchUser(sig, title)) == NULL) return -1; uin->turn = 1; currutmp->turn = 0; strlcpy(uin->mateid, currutmp->userid, sizeof(uin->mateid)); strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid)); vs_hdr(title); buf[0] = 0; getdata(2, 0, "使用傳統模式 (T), 限時限步模式 (L) 或是 讀秒模式 (C)? (T/l/c)", buf, 3, DOECHO); if (buf[0] == 'l' || buf[0] == 'L' || buf[0] == 'c' || buf[0] == 'C') { _current_time_limit = (ChessTimeLimit*) malloc(sizeof(ChessTimeLimit)); if (buf[0] == 'l' || buf[0] == 'L') _current_time_limit->time_mode = CHESS_TIMEMODE_MULTIHAND; else _current_time_limit->time_mode = CHESS_TIMEMODE_COUNTING; do { getdata_str(3, 0, "請設定局時 (自由時間) 以分鐘為單位:", buf, 3, DOECHO, "30"); _current_time_limit->free_time = atoi(buf); } while (_current_time_limit->free_time < 0 || _current_time_limit->free_time > 90); _current_time_limit->free_time *= 60; /* minute -> second */ if (_current_time_limit->time_mode == CHESS_TIMEMODE_MULTIHAND) { char display_buf[128]; do { getdata_str(4, 0, "請設定步時, 以分鐘為單位:", buf, 3, DOECHO, "5"); _current_time_limit->limit_time = atoi(buf); } while (_current_time_limit->limit_time < 0 || _current_time_limit->limit_time > 30); _current_time_limit->limit_time *= 60; /* minute -> second */ snprintf(display_buf, sizeof(display_buf), "請設定限步 (每 %d 分鐘需走幾步):", _current_time_limit->limit_time / 60); do { getdata_str(5, 0, display_buf, buf, 3, DOECHO, "10"); _current_time_limit->limit_hand = atoi(buf); } while (_current_time_limit->limit_hand < 1); } else { _current_time_limit->limit_hand = 1; do { getdata_str(4, 0, "請設定讀秒, 以秒為單位", buf, 3, DOECHO, "60"); _current_time_limit->limit_time = atoi(buf); } while (_current_time_limit->limit_time < 0); } } else _current_time_limit = NULL; my_talk(uin, friend_stat(currutmp, uin), func_char); return 0; } int ChessWatchGame(void (*play)(int, ChessGameMode), int game, const char* title) { int sock, msgsock; userinfo_t *uin; if ((uin = ChessSearchUser(-1, title)) == NULL) return -1; if (uin->uid == currutmp->uid || uin->mode != game) { vmsg("無法建立連線"); return -1; } if (vans("是否進行觀棋? [N/y]") != 'y') return 0; if ((sock = make_connection_to_somebody(uin, 10)) < 0) { vmsg("無法建立連線"); return -1; } msgsock = accept(sock, (struct sockaddr *) 0, (socklen_t *) 0); close(sock); if (msgsock < 0) return -1; strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid)); play(msgsock, CHESS_MODE_WATCH); close(msgsock); return 0; } int ChessReplayGame(const char* fname) { ChessInfo *info; FILE *fp = fopen(fname, "r"); int found = -1; char buf[256]; screen_backup_t oldscreen; if(fp == NULL) { vmsg("檔案無法開啟, 可能被刪除了"); return -1; } while (found == -1 && fgets(buf, sizeof(buf), fp)) { if (buf[0] == '<') { const int line_len = strlen(buf); if (strcmp(buf + line_len - 5, "log>\n") == 0) { int i; for (i = 0; ChessReplayMap[i].name; ++i) if (ChessReplayMap[i].name_len == line_len - 6 && strncmp(buf + 1, ChessReplayMap[i].name, ChessReplayMap[i].name_len) == 0) { found = i; break; } } } } if (found == -1) { fclose(fp); return -1; } info = ChessReplayMap[found].func(fp); fclose(fp); if (info) { scr_dump(&oldscreen); ChessPlay(info); scr_restore(&oldscreen); DeleteChessInfo(info); } return 0; } static void ChessInitUser(ChessInfo* info) { char userid[2][IDLEN + 1]; const userinfo_t* uinfo; userec_t urec; switch (info->mode) { case CHESS_MODE_PERSONAL: strlcpy(userid[0], cuser.userid, sizeof(userid[0])); strlcpy(userid[1], cuser.userid, sizeof(userid[1])); break; case CHESS_MODE_WATCH: uinfo = search_ulist_userid(currutmp->mateid); if (uinfo) { strlcpy(userid[0], uinfo->userid, sizeof(userid[0])); strlcpy(userid[1], uinfo->mateid, sizeof(userid[1])); } else { strlcpy(userid[0], currutmp->mateid, sizeof(userid[0])); userid[1][0] = 0; } break; case CHESS_MODE_VERSUS: strlcpy(userid[0], cuser.userid, sizeof(userid[0])); strlcpy(userid[1], currutmp->mateid, sizeof(userid[1])); break; case CHESS_MODE_REPLAY: return; } uinfo = search_ulist_userid(userid[0]); if (uinfo) info->actions->init_user(uinfo, &info->user1); else if (getuser(userid[0], &urec)) info->actions->init_user_rec(&urec, &info->user1); uinfo = search_ulist_userid(userid[1]); if (uinfo) info->actions->init_user(uinfo, &info->user2); else if (getuser(userid[1], &urec)) info->actions->init_user_rec(&urec, &info->user2); } #ifdef CHESSCOUNTRY static char* ChessPhotoInitial(ChessInfo* info) { char genbuf[256]; int line; FILE* fp; static const char * const blank_photo[6] = { "┌──────┐", "│ 空 │", "│ 白 │", "│ 照 │", "│ 片│", "└──────┘" }; char country[5], level[11]; userec_t xuser; char* photo; int hasphoto = 0; if (info->mode == CHESS_MODE_REPLAY) return NULL; if(is_validuserid(info->user1.userid)) { sethomefile(genbuf, info->user1.userid, info->constants->photo_file_name); if (dashf(genbuf)) hasphoto++; } if(is_validuserid(info->user2.userid)) { sethomefile(genbuf, info->user2.userid, info->constants->photo_file_name); if (dashf(genbuf)) hasphoto++; } if(hasphoto==0) return NULL; photo = (char*) calloc( CHESS_PHOTO_LINE * CHESS_PHOTO_COLUMN, sizeof(char)); /* simulate photo as two dimensional array */ #define PHOTO(X) (photo + (X) * CHESS_PHOTO_COLUMN) fp = NULL; if(getuser(info->user2.userid, &xuser)) { sethomefile(genbuf, info->user2.userid, info->constants->photo_file_name); fp = fopen(genbuf, "r"); } if (fp == NULL) { strcpy(country, "無"); level[0] = 0; } else { int i, j; for (line = 1; line < 8; ++line) fgets(genbuf, sizeof(genbuf), fp); fgets(genbuf, sizeof(genbuf), fp); chomp(genbuf); strip_ansi(genbuf + 11, genbuf + 11, STRIP_ALL); /* country name may have color */ for (i = 11, j = 0; genbuf[i] && j < 4; ++i) if (genbuf[i] != ' ') /* and spaces */ country[j++] = genbuf[i]; country[j] = 0; /* two chinese words */ fgets(genbuf, sizeof(genbuf), fp); chomp(genbuf); strlcpy(level, genbuf + 11, 11); /* five chinese words*/ rewind(fp); } for (line = 0; line < 6; ++line) { if (fp != NULL) { if (fgets(genbuf, sizeof(genbuf), fp)) { chomp(genbuf); sprintf(PHOTO(line), "%s", genbuf); } else strcpy(PHOTO(line), " "); } else strcpy(PHOTO(line), blank_photo[line]); switch (line) { case 0: sprintf(genbuf, " <代號> %s", xuser.userid); break; case 1: sprintf(genbuf, " <暱稱> %.16s", xuser.nickname); break; case 2: sprintf(genbuf, " <上站> %d", xuser.numlogins); break; case 3: sprintf(genbuf, " <文章> %d", xuser.numposts); break; case 4: sprintf(genbuf, " <職位> %-4s %s", country, level); break; case 5: sprintf(genbuf, " <來源> %.16s", xuser.lasthost); break; default: genbuf[0] = 0; } strcat(PHOTO(line), genbuf); } if (fp != NULL) fclose(fp); sprintf(PHOTO(6), " %s%2.2s棋" ANSI_RESET, info->constants->turn_color[(int) info->myturn ^ 1], info->constants->turn_str[(int) info->myturn ^ 1]); strcpy(PHOTO(7), " V.S "); sprintf(PHOTO(8), " %s%2.2s棋" ANSI_RESET, info->constants->turn_color[(int) info->myturn], info->constants->turn_str[(int) info->myturn]); fp = NULL; if(getuser(info->user1.userid, &xuser)) {; sethomefile(genbuf, info->user1.userid, info->constants->photo_file_name); fp = fopen(genbuf, "r"); } if (fp == NULL) { strcpy(country, "無"); level[0] = 0; } else { int i, j; for (line = 1; line < 8; ++line) fgets(genbuf, sizeof(genbuf), fp); fgets(genbuf, sizeof(genbuf), fp); chomp(genbuf); strip_ansi(genbuf + 11, genbuf + 11, STRIP_ALL); /* country name may have color */ for (i = 11, j = 0; genbuf[i] && j < 4; ++i) if (genbuf[i] != ' ') /* and spaces */ country[j++] = genbuf[i]; country[j] = 0; /* two chinese words */ fgets(genbuf, sizeof(genbuf), fp); chomp(genbuf); strlcpy(level, genbuf + 11, 11); /* five chinese words*/ rewind(fp); } for (line = 9; line < 15; ++line) { move(line, 37); switch (line - 9) { case 0: sprintf(PHOTO(line), "<代號> %-16.16s ", xuser.userid); break; case 1: sprintf(PHOTO(line), "<暱稱> %-16.16s ", xuser.nickname); break; case 2: sprintf(PHOTO(line), "<上站> %-16d ", xuser.numlogins); break; case 3: sprintf(PHOTO(line), "<文章> %-16d ", xuser.numposts); break; case 4: sprintf(PHOTO(line), "<職位> %-4s %-10s ", country, level); break; case 5: sprintf(PHOTO(line), "<來源> %-16.16s ", xuser.lasthost); break; } if (fp != NULL) { if (fgets(genbuf, 200, fp)) { chomp(genbuf); strcat(PHOTO(line), genbuf); } else strcat(PHOTO(line), " "); } else strcat(PHOTO(line), blank_photo[line - 9]); } if (fp != NULL) fclose(fp); #undef PHOTO return photo; } #endif /* defined(CHESSCOUNTRY) */ static void ChessInitPlayFunc(ChessInfo* info) { switch (info->mode) { case CHESS_MODE_VERSUS: info->play_func[(int) info->myturn] = &ChessPlayFuncMy; info->play_func[info->myturn ^ 1] = &ChessPlayFuncHis; break; case CHESS_MODE_WATCH: case CHESS_MODE_REPLAY: info->play_func[0] = info->play_func[1] = &ChessPlayFuncWatch; break; case CHESS_MODE_PERSONAL: info->play_func[0] = info->play_func[1] = &ChessPlayFuncMy; break; } } ChessInfo* NewChessInfo(const ChessActions* actions, const ChessConstants* constants, int sock, ChessGameMode mode) { /* allocate memory for the structure and extra space for temporary * steping information storage (step_tmp[0]). */ ChessInfo* info = (ChessInfo*) calloc(1, sizeof(ChessInfo) + constants->step_entry_size); if (mode == CHESS_MODE_PERSONAL) strcpy(currutmp->mateid, cuser.userid); /* compiler don't know it's actually const... */ info->actions = (ChessActions*) actions; info->constants = (ChessConstants*) constants; info->mode = mode; info->sock = sock; if (mode == CHESS_MODE_VERSUS) info->myturn = currutmp->turn; else if (mode == CHESS_MODE_PERSONAL) info->myturn = 1; else if (mode == CHESS_MODE_REPLAY) info->myturn = 1; else if (mode == CHESS_MODE_WATCH) ChessReceiveWatchInfo(info); ChessInitUser(info); #ifdef CHESSCOUNTRY info->photo = ChessPhotoInitial(info); #endif if (mode != CHESS_MODE_WATCH) ChessHistoryInit(&info->history, constants->step_entry_size); ChessBroadcastListInit(&info->broadcast_list); ChessInitPlayFunc(info); return info; } void DeleteChessInfo(ChessInfo* info) { #define NULL_OR_FREE(X) if (X) free(X); else (void) 0 NULL_OR_FREE(info->timelimit); NULL_OR_FREE(info->photo); NULL_OR_FREE(info->history.body); ChessBroadcastListClear(&info->broadcast_list); #undef NULL_OR_FREE } void ChessEstablishRequest(int sock) { /* XXX */ if (!_current_time_limit) write(sock, "T", 1); /* traditional */ else { write(sock, "L", 1); /* limited */ write(sock, _current_time_limit, sizeof(ChessTimeLimit)); } } void ChessAcceptingRequest(int sock) { /* XXX */ char mode; read(sock, &mode, 1); if (mode == 'T') _current_time_limit = NULL; else { _current_time_limit = (ChessTimeLimit*) malloc(sizeof(ChessTimeLimit)); read(sock, _current_time_limit, sizeof(ChessTimeLimit)); } } void ChessShowRequest(void) { /* XXX */ if (!_current_time_limit) mvouts(10, 5, "使用傳統計時方式, 單步限時五分鐘"); else if (_current_time_limit->time_mode == CHESS_TIMEMODE_MULTIHAND) { mvouts(10, 5, "使用限時限步規則:"); move(12, 8); prints("局時 (自由時間): %2d 分 %02d 秒", _current_time_limit->free_time / 60, _current_time_limit->free_time % 60); move(13, 8); prints("限時步時: %2d 分 %02d 秒 / %2d 手", _current_time_limit->limit_time / 60, _current_time_limit->limit_time % 60, _current_time_limit->limit_hand); } else if (_current_time_limit->time_mode == CHESS_TIMEMODE_COUNTING) { mvouts(10, 5, "使用讀秒規則:"); move(12, 8); prints("局時 (自由時間): %2d 分 %02d 秒", _current_time_limit->free_time / 60, _current_time_limit->free_time % 60); move(13, 8); prints("讀秒時間: 每手 %2d 秒", _current_time_limit->limit_time); } } inline static const char* ChessTimeStr(int second) { static char buf[10]; snprintf(buf, sizeof(buf), "%d:%02d", second / 60, second % 60); return buf; } void ChessDrawExtraInfo(const ChessInfo* info, int line, int space) { if (line == b_lines || line == 0) return; if (info->photo) { if (line >= 3 && line < 3 + CHESS_PHOTO_LINE) { if (space > 3) outs(" "); outs(info->photo + (line - 3) * CHESS_PHOTO_COLUMN); } else if (line >= CHESS_DRAWING_PHOTOED_STEP_ROW && line <= CHESS_DRAWING_PHOTOED_WARN_ROW) { prints("%*s", space, ""); if (line == CHESS_DRAWING_PHOTOED_STEP_ROW) outs(info->last_movestr); else if (line == CHESS_DRAWING_PHOTOED_TURN_ROW) prints(ANSI_COLOR(1;33) "%s" ANSI_RESET, info->myturn == info->turn ? "輪到你下棋了" : "等待對方下棋"); else if (line == CHESS_DRAWING_PHOTOED_TIME_ROW1) { if (info->mode == CHESS_MODE_WATCH) { if (!info->timelimit) prints("每手限時五分鐘"); else prints("局時: %5s", ChessTimeStr(info->timelimit->free_time)); } else if (info->lefthand[0]) prints("我方剩餘時間 %s / %2d 步", ChessTimeStr(info->lefttime[0]), info->lefthand[0]); else prints("我方剩餘時間 %s", ChessTimeStr(info->lefttime[0])); } else if (line == CHESS_DRAWING_PHOTOED_TIME_ROW2) { if (info->mode == CHESS_MODE_WATCH) { if (info->timelimit) { if (info->timelimit->time_mode == CHESS_TIMEMODE_MULTIHAND) prints("步時: %s / %2d 步", ChessTimeStr(info->timelimit->limit_time), info->timelimit->limit_hand); else prints("讀秒: %5d 秒", info->timelimit->limit_time); } } else if (info->lefthand[1]) prints("對方剩餘時間 %s / %2d 步", ChessTimeStr(info->lefttime[1]), info->lefthand[1]); else prints("對方剩餘時間 %s", ChessTimeStr(info->lefttime[1])); } else if (line == CHESS_DRAWING_PHOTOED_WARN_ROW) outs(info->warnmsg); } } else if (line >= 3 && line <= CHESS_DRAWING_HISWIN_ROW) { prints("%*s", space, ""); if (line >= 3 && line < 3 + (int)dim(ChessHintStr)) { outs(ChessHintStr[line - 3]); } else if (line == CHESS_DRAWING_SIDE_ROW) { prints(ANSI_COLOR(1) "你是%s%s" ANSI_RESET, info->constants->turn_color[(int) info->myturn], info->constants->turn_str[(int) info->myturn]); } else if (line == CHESS_DRAWING_REAL_TURN_ROW) { prints(ANSI_COLOR(1;33) "%s" ANSI_RESET, info->myturn == info->turn ? "輪到你下棋了" : "等待對方下棋"); } else if (line == CHESS_DRAWING_REAL_STEP_ROW && info->last_movestr) { outs(info->last_movestr); } else if (line == CHESS_DRAWING_REAL_TIME_ROW1) { if (info->lefthand[0]) prints("我方剩餘時間 %s / %2d 步", ChessTimeStr(info->lefttime[0]), info->lefthand[0]); else prints("我方剩餘時間 %s", ChessTimeStr(info->lefttime[0])); } else if (line == CHESS_DRAWING_REAL_TIME_ROW2) { if (info->lefthand[1]) prints("對方剩餘時間 %s / %2d 步", ChessTimeStr(info->lefttime[1]), info->lefthand[1]); else prints("對方剩餘時間 %s", ChessTimeStr(info->lefttime[1])); } else if (line == CHESS_DRAWING_REAL_WARN_ROW) { outs(info->warnmsg); } else if (line == CHESS_DRAWING_MYWIN_ROW) { prints(ANSI_COLOR(1;33) "%12.12s " ANSI_COLOR(1;31) "%2d" ANSI_COLOR(37) "勝 " ANSI_COLOR(34) "%2d" ANSI_COLOR(37) "敗 " ANSI_COLOR(36) "%2d" ANSI_COLOR(37) "和" ANSI_RESET, info->user1.userid, info->user1.win, info->user1.lose - 1, info->user1.tie); } else if (line == CHESS_DRAWING_HISWIN_ROW) { prints(ANSI_COLOR(1;33) "%12.12s " ANSI_COLOR(1;31) "%2d" ANSI_COLOR(37) "勝 " ANSI_COLOR(34) "%2d" ANSI_COLOR(37) "敗 " ANSI_COLOR(36) "%2d" ANSI_COLOR(37) "和" ANSI_RESET, info->user2.userid, info->user2.win, info->user2.lose, info->user2.tie); } } }