/* $Id$ */ #include "bbs.h" #include "chc.h" #define assert_not_reached() assert(!"Should never be here!!!") extern const double elo_exp_tab[1000]; enum Turn { BLK = 0, RED }; enum Kind { KIND_K=1, KIND_A, KIND_E, KIND_R, KIND_H, KIND_C, KIND_P, }; #define CENTER(a, b) (((a) + (b)) >> 1) #define CHC_TIMEOUT 300 #define PHOTO_LINE 15 #define PHOTO_COLUMN (256 + 25) typedef struct drc_t { ChessStepType type; /* necessary one */ rc_t from, to; } drc_t; typedef struct { rc_t select; char selected; } chc_tag_data_t; /* chess framework action functions */ static void chc_init_user(const userinfo_t *uinfo, ChessUser *user); static void chc_init_user_userec(const userec_t *urec, ChessUser *user); static void chc_init_board(board_t board); static void chc_drawline(const ChessInfo* info, int line); static void chc_movecur(int r, int c); static int chc_prepare_play(ChessInfo* info); static int chc_select(ChessInfo* info, rc_t scrloc, ChessGameResult* result); static void chc_prepare_step(ChessInfo* info, const void* step); static ChessGameResult chc_movechess(board_t board, const drc_t* move); static void chc_drawstep(ChessInfo* info, const drc_t* move); static void chc_gameend(ChessInfo* info, ChessGameResult result); static void chc_genlog(ChessInfo* info, FILE* fp, ChessGameResult result); static const char * const turn_color[2]={BLACK_COLOR, RED_COLOR}; /* some constant variable definition */ static const char * const turn_str[2] = {"黑的", "紅的"}; static const char * const num_str[2][10] = { {"", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, {"", "一", "二", "三", "四", "五", "六", "七", "八", "九"}, }; static const char * const chess_str[2][8] = { /* 0 1 2 3 4 5 6 7 */ {" ", "將", "士", "象", "車", "馬", "包", "卒"}, {" ", "帥", "仕", "相", "車", "傌", "炮", "兵"} }; static const char * const chess_brd[BRD_ROW * 2 - 1] = { /* 0 1 2 3 4 5 6 7 8 */ "┌─┬─┬─┬─┬─┬─┬─┬─┐", /* 0 */ "│ │ │ │\│/│ │ │ │", "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 1 */ "│ │ │ │/│\│ │ │ │", "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 2 */ "│ │ │ │ │ │ │ │ │", "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 3 */ "│ │ │ │ │ │ │ │ │", "├─┴─┴─┴─┴─┴─┴─┴─┤", /* 4 */ "│ 楚 河 漢 界 │", "├─┬─┬─┬─┬─┬─┬─┬─┤", /* 5 */ "│ │ │ │ │ │ │ │ │", "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 6 */ "│ │ │ │ │ │ │ │ │", "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 7 */ "│ │ │ │\│/│ │ │ │", "├─┼─┼─┼─┼─┼─┼─┼─┤", /* 8 */ "│ │ │ │/│\│ │ │ │", "└─┴─┴─┴─┴─┴─┴─┴─┘" /* 9 */ }; static char * const hint_str[] = { " q 認輸離開", " p 要求和棋", "方向鍵 移動遊標", "Enter 選擇/移動" }; static const ChessActions chc_actions = { &chc_init_user, &chc_init_user_userec, (void (*) (void*)) &chc_init_board, &chc_drawline, &chc_movecur, &chc_prepare_play, NULL, /* process_key */ &chc_select, &chc_prepare_step, (ChessGameResult (*) (void*, const void*)) &chc_movechess, (void (*)(ChessInfo*, const void*)) &chc_drawstep, NULL, /* post_game */ &chc_gameend, &chc_genlog }; static const ChessConstants chc_constants = { sizeof(drc_t), CHC_TIMEOUT, BRD_ROW, BRD_COL, 0, "楚河漢界", "photo_cchess", #ifdef BN_CCHESS_LOG BN_CCHESS_LOG, #else NULL, #endif { BLACK_COLOR, RED_COLOR }, {"黑的", "紅的"} }; /* * Start of the drawing function. */ static void chc_movecur(int r, int c) { move(r * 2 + 3, c * 4 + 4); } static char * getstep(board_t board, const rc_t *from, const rc_t *to, char buf[]) { int turn, fc, tc; char *dir; int twin = 0, twin_r = 0; int len = 0; turn = CHE_O(board[from->r][from->c]); if(CHE_P(board[from->r][from->c] != KIND_P)) { // TODO 目前不管兵卒前後 int i; for(i=0;i<10;i++) if(board[i][from->c]==board[from->r][from->c]) { if(i!=from->r) { twin=1; twin_r=i; } } } fc = (turn == BLK ? from->c + 1 : 9 - from->c); tc = (turn == BLK ? to->c + 1 : 9 - to->c); if (from->r == to->r) dir = "平"; else { if (from->c == to->c) tc = from->r - to->r; if (tc < 0) tc = -tc; if ((turn == BLK && to->r > from->r) || (turn == RED && to->r < from->r)) dir = "進"; else dir = "退"; } len=sprintf(buf, "%s", turn_color[turn]); /* 傌二|前傌 */ if(twin) { len+=sprintf(buf+len, "%s%s", ((from->r>twin_r)==(turn==(BLK)))?"前":"後", chess_str[turn][CHE_P(board[from->r][from->c])]); } else { len+=sprintf(buf+len, "%s%s", chess_str[turn][CHE_P(board[from->r][from->c])], num_str[turn][fc]); } /* 進三 */ len+=sprintf(buf+len, "%s%s" ANSI_RESET, dir, num_str[turn][tc]); /* :象 */ if(board[to->r][to->c]) { len+=sprintf(buf+len,":%s%s" ANSI_RESET, turn_color[turn^1], chess_str[turn^1][CHE_P(board[to->r][to->c])]); } return buf; } inline static const char* chc_timestr(int second) { static char str[10]; snprintf(str, sizeof(str), "%d:%02d", second / 60, second % 60); return str; } static void chc_drawline(const ChessInfo* info, int line) { int i, j; board_p board = (board_p) info->board; chc_tag_data_t *tag = info->tag; if (line == 0) { prints(ANSI_COLOR(1;46) " 象棋對戰 " ANSI_COLOR(45) "%30s VS %-20s%10s" ANSI_RESET, info->user1.userid, info->user2.userid, info->mode == CHESS_MODE_WATCH ? "[觀棋模式]" : ""); } else if (line >= 3 && line <= 21) { outs(" "); for (i = 0; i < 9; i++) { j = board[RTL(info,line)][CTL(info,i)]; if ((line & 1) == 1 && j) { if (tag->selected && tag->select.r == RTL(info,line) && tag->select.c == CTL(info,i)) { prints("%s%s" ANSI_RESET, CHE_O(j) == BLK ? BLACK_REVERSE : RED_REVERSE, chess_str[CHE_O(j)][CHE_P(j)]); } else { prints("%s%s" ANSI_RESET, turn_color[CHE_O(j)], chess_str[CHE_O(j)][CHE_P(j)]); } } else prints("%c%c", chess_brd[line - 3][i * 4], chess_brd[line - 3][i * 4 + 1]); if (i != 8) prints("%c%c", chess_brd[line - 3][i * 4 + 2], chess_brd[line - 3][i * 4 + 3]); } } else if (line == 2 || line == 22) { outs(" "); if (line == 2) for (i = 1; i <= 9; i++) prints("%s ", num_str[REDDOWN(info)?0:1][i]); else for (i = 9; i >= 1; i--) prints("%s ", num_str[REDDOWN(info)?1:0][i]); } ChessDrawExtraInfo(info, line, 8); } /* * End of the drawing function. */ /* * Start of the log function. */ static void chc_log_machine_step(FILE* fp, board_t board, const drc_t *step) { static const char chess_char[8] = { 0, 'K', 'A', 'B', 'R', 'N', 'C', 'P' }; /* We have black at bottom in rc_t but the standard is * the red side at bottom, so that a rotation is needed. */ fprintf(fp, "%c%c%d%c%c%d ", chess_char[CHE_P(board[step->from.r][step->from.c])], step->from.c + 'a', BRD_ROW - step->from.r - 1, board[step->to.r][step->to.c] ? 'x' : '-', step->to.c + 'a', BRD_ROW - step->to.r - 1 ); } static int #if defined(__linux__) chc_filter(const struct dirent *dir) #else chc_filter(struct dirent *dir) #endif { if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0 ) return 0; return strstr(dir->d_name, ".poem") != NULL; } static int chc_log_poem(FILE* outfp) { struct dirent **namelist; int n; // TODO use readdir(), don't use lots of memory n = scandir(BBSHOME"/etc/chess", &namelist, chc_filter, alphasort); if (n < 0) perror("scandir"); else { char buf[80]; FILE *fp; sprintf(buf, BBSHOME"/etc/chess/%s", namelist[random() % n]->d_name); if ((fp = fopen(buf, "r")) == NULL) return -1; while(fgets(buf, sizeof(buf), fp) != NULL) fputs(buf, outfp); while(n--) free(namelist[n]); free(namelist); fclose(fp); } return 0; } static void chc_genlog(ChessInfo* info, FILE* fp, ChessGameResult result) { const int nStep = info->history.used; board_t board; int i; fprintf(fp, "按 z 可進入打譜模式\n"); fprintf(fp, "\n"); if (info->myturn == RED) fprintf(fp, "%s(%d) V.S. %s(%d)\n", info->user1.userid, info->user1.orig_rating, info->user2.userid, info->user2.orig_rating); else fprintf(fp, "%s(%d) V.S. %s(%d)\n", info->user2.userid, info->user2.orig_rating, info->user1.userid, info->user1.orig_rating); chc_init_board(board); /* format: "%3d. %8.8s %8.8s %3d. %8.8s %8.8s\n" */ for (i = 0; i < nStep; i++) { char buf[80]; const drc_t *move = (const drc_t*) ChessHistoryRetrieve(info, i); buf[0]='\0'; if (move->type == CHESS_STEP_NORMAL) { getstep(board, &move->from, &move->to, buf); chc_movechess(board, move); if(i%2==0) fprintf(fp, "%3d. ",i/2+1); strip_ansi(buf, buf, STRIP_ALL); fprintf(fp, "%8.8s ", buf); if(i%4==3 || i==nStep-1) fputc('\n', fp); } } if (result == CHESS_RESULT_TIE) fprintf(fp, "=> 和局\n"); else if (result == CHESS_RESULT_WIN || result == CHESS_RESULT_LOST) fprintf(fp, "=> %s 勝\n", (info->myturn == RED) == (result== CHESS_RESULT_WIN) ? "紅" : "黑"); /* generate machine readable log. * http://www.elephantbase.net/protocol/cchess_pgn.htm */ { /* machine readable header */ time_t temp = (time_t) now; struct tm *mytm = localtime(&temp); fprintf(fp, "\n\n\n" "[Game \"Chinese Chess\"]\n" "[Date \"%d.%d.%d\"]\n" "[Red \"%s\"]\n" "[Black \"%s\"]\n", mytm->tm_year + 1900, mytm->tm_mon + 1, mytm->tm_mday, info->myturn == RED ? info->user1.userid : info->user2.userid, info->myturn == RED ? info->user2.userid : info->user1.userid ); if (result == CHESS_RESULT_TIE || result == CHESS_RESULT_WIN || result == CHESS_RESULT_LOST) fprintf(fp, "[Result \"%s\"]\n", result == CHESS_RESULT_TIE ? "0.5-0.5" : (info->myturn == RED) == (result== CHESS_RESULT_WIN) ? "1-0" : "0-1"); else fprintf(fp, "[Result \"*\"]\n"); fprintf(fp, "[Notation \"Coord\"]\n" "[FEN \"rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR" " r - - 0 1\"]\n"); } chc_init_board(board); for (i = 0; i < nStep - 1; i += 2) { const drc_t *move = (const drc_t*) ChessHistoryRetrieve(info, i); fprintf(fp, "%2d. ", i / 2 + 1); if (move->type == CHESS_STEP_NORMAL) { chc_log_machine_step(fp, board, move); chc_movechess(board, move); } fputs(" ", fp); move = (const drc_t*) ChessHistoryRetrieve(info, i + 1); if (move->type == CHESS_STEP_NORMAL) { chc_log_machine_step(fp, board, move); chc_movechess(board, move); } fputc('\n', fp); } if (i < nStep) { const drc_t *move = (const drc_t*) ChessHistoryRetrieve(info, i); if (move->type == CHESS_STEP_NORMAL) { fprintf(fp, "%2d. ", i / 2 + 1); chc_log_machine_step(fp, board, move); fputc('\n', fp); } } fputs("\n\n--\n\n", fp); chc_log_poem(fp); } /* * End of the log function. */ /* * Start of the rule function. */ static void chc_init_board(board_t board) { memset(board, 0, sizeof(board_t)); board[0][4] = CHE(KIND_K, BLK); /* 將 */ board[0][3] = board[0][5] = CHE(KIND_A, BLK); /* 士 */ board[0][2] = board[0][6] = CHE(KIND_E, BLK); /* 象 */ board[0][0] = board[0][8] = CHE(KIND_R, BLK); /* 車 */ board[0][1] = board[0][7] = CHE(KIND_H, BLK); /* 馬 */ board[2][1] = board[2][7] = CHE(KIND_C, BLK); /* 包 */ board[3][0] = board[3][2] = board[3][4] = board[3][6] = board[3][8] = CHE(KIND_P, BLK); /* 卒 */ board[9][4] = CHE(KIND_K, RED); /* 帥 */ board[9][3] = board[9][5] = CHE(KIND_A, RED); /* 仕 */ board[9][2] = board[9][6] = CHE(KIND_E, RED); /* 相 */ board[9][0] = board[9][8] = CHE(KIND_R, RED); /* 車 */ board[9][1] = board[9][7] = CHE(KIND_H, RED); /* 傌 */ board[7][1] = board[7][7] = CHE(KIND_C, RED); /* 炮 */ board[6][0] = board[6][2] = board[6][4] = board[6][6] = board[6][8] = CHE(KIND_P, RED); /* 兵 */ } static void chc_prepare_step(ChessInfo* info, const void* step) { const drc_t* move = (const drc_t*) step; getstep((board_p) info->board, &move->from, &move->to, info->last_movestr); } static ChessGameResult chc_movechess(board_t board, const drc_t* move) { int end = (CHE_P(board[move->to.r][move->to.c]) == KIND_K); board[move->to.r][move->to.c] = board[move->from.r][move->from.c]; board[move->from.r][move->from.c] = 0; return end ? CHESS_RESULT_WIN : CHESS_RESULT_CONTINUE; } static void chc_drawstep(ChessInfo* info, const drc_t* move) { ChessDrawLine(info, LTR(info, move->from.r)); ChessDrawLine(info, LTR(info, move->to.r)); } /* 求兩座標行或列(rowcol)的距離 */ static int dist(rc_t from, rc_t to, int rowcol) { int d; d = rowcol ? from.c - to.c : from.r - to.r; return d > 0 ? d : -d; } /* 兩座標(行或列rowcol)中間有幾顆棋子 */ static int between(board_t board, rc_t from, rc_t to, int rowcol) { int i, rtv = 0; if (rowcol) { if (from.c > to.c) i = from.c, from.c = to.c, to.c = i; for (i = from.c + 1; i < to.c; i++) if (board[to.r][i]) rtv++; } else { if (from.r > to.r) i = from.r, from.r = to.r, to.r = i; for (i = from.r + 1; i < to.r; i++) if (board[i][to.c]) rtv++; } return rtv; } static int chc_canmove(board_t board, rc_t from, rc_t to) { int i; int rd, cd, turn; if(0 || !(0<=from.r && from.r 2) || (turn == RED && to.r < 7) || to.c < 3 || to.c > 5) return 0; break; case KIND_A: /* 士 仕 */ if (!(rd == 1 && cd == 1)) return 0; if ((turn == BLK && to.r > 2) || (turn == RED && to.r < 7) || to.c < 3 || to.c > 5) return 0; break; case KIND_E: /* 象 相 */ if (!(rd == 2 && cd == 2)) return 0; if ((turn == BLK && to.r > 4) || (turn == RED && to.r < 5)) return 0; /* 拐象腿 */ if (board[CENTER(from.r, to.r)][CENTER(from.c, to.c)]) return 0; break; case KIND_R: /* 車 */ if (!(rd > 0 && cd == 0) && !(rd == 0 && cd > 0)) return 0; if (between(board, from, to, rd == 0)) return 0; break; case KIND_H: /* 馬 傌 */ if (!(rd == 2 && cd == 1) && !(rd == 1 && cd == 2)) return 0; /* 拐馬腳 */ if (rd == 2) { if (board[CENTER(from.r, to.r)][from.c]) return 0; } else { if (board[from.r][CENTER(from.c, to.c)]) return 0; } break; case KIND_C: /* 包 炮 */ if (!(rd > 0 && cd == 0) && !(rd == 0 && cd > 0)) return 0; i = between(board, from, to, rd == 0); if ((i > 1) || (i == 1 && !board[to.r][to.c]) || (i == 0 && board[to.r][to.c])) return 0; break; case KIND_P: /* 卒 兵 */ if (!(rd == 1 && cd == 0) && !(rd == 0 && cd == 1)) return 0; if (((turn == BLK && to.r < 5) || (turn == RED && to.r > 4)) && cd != 0) return 0; if ((turn == BLK && to.r < from.r) || (turn == RED && to.r > from.r)) return 0; break; } return 1; } /* 找 turn's king 的座標 */ static int findking(board_t board, int turn, rc_t * buf) { int i, r, c; r = (turn == BLK ? 0 : 7); for (i = 0; i < 3; r++, i++) for (c = 3; c < 6; c++) if (CHE_P(board[r][c]) == KIND_K && CHE_O(board[r][c]) == turn) { buf->r = r, buf->c = c; return 1; } /* one's king may be eaten */ return 0; } static int chc_iskfk(board_t board) { rc_t from, to; if (!findking(board, BLK, &to)) return 0; if (!findking(board, RED, &from)) return 0; if (from.c == to.c && between(board, from, to, 0) == 0) return 1; return 0; } static int chc_ischeck(board_t board, int turn) { rc_t from, to; if (!findking(board, turn, &to)) return 0; for (from.r = 0; from.r < BRD_ROW; from.r++) for (from.c = 0; from.c < BRD_COL; from.c++) if (board[from.r][from.c] && CHE_O(board[from.r][from.c]) != turn) if (chc_canmove(board, from, to)) return 1; return 0; } /* * End of the rule function. */ static void chcusr_put(userec_t* userec, const ChessUser* user) { userec->chc_win = user->win; userec->chc_lose = user->lose; userec->chc_tie = user->tie; userec->chess_elo_rating = user->rating; } static void chc_init_user(const userinfo_t *uinfo, ChessUser *user) { strlcpy(user->userid, uinfo->userid, sizeof(user->userid)); user->win = uinfo->chc_win; user->lose = uinfo->chc_lose; user->tie = uinfo->chc_tie; user->rating = uinfo->chess_elo_rating; if(user->rating == 0) user->rating = 1500; /* ELO initial value */ user->orig_rating = user->rating; } static void chc_init_user_userec(const userec_t *urec, ChessUser *user) { strlcpy(user->userid, urec->userid, sizeof(user->userid)); user->win = urec->chc_win; user->lose = urec->chc_lose; user->tie = urec->chc_tie; user->rating = urec->chess_elo_rating; if(user->rating == 0) user->rating = 1500; /* ELO initial value */ user->orig_rating = user->rating; } static int chc_prepare_play(ChessInfo* info) { if (chc_ischeck((board_p) info->board, info->turn)) { strlcpy(info->warnmsg, ANSI_COLOR(1;31) "將軍!" ANSI_RESET, sizeof(info->warnmsg)); bell(); } else info->warnmsg[0] = 0; return 0; } static int chc_select(ChessInfo* info, rc_t scrloc, ChessGameResult* result) { chc_tag_data_t* tag = (chc_tag_data_t*) info->tag; board_p board = (board_p) info->board; rc_t loc; assert(tag); /* transform from screen to internal coordinate */ if(REDDOWN(info)) { loc = scrloc; } else { loc.r = BRD_ROW-scrloc.r-1; loc.c = BRD_COL-scrloc.c-1; } if (!tag->selected) { /* trying to pick something */ if (board[loc.r][loc.c] && CHE_O(board[loc.r][loc.c]) == info->turn) { /* they can pick up this */ tag->selected = 1; tag->select = loc; ChessDrawLine(info, LTR(info, loc.r)); } return 0; } else if (tag->select.r == loc.r && tag->select.c == loc.c) { /* cancel selection */ tag->selected = 0; ChessDrawLine(info, LTR(info, loc.r)); return 0; } else if (chc_canmove(board, tag->select, loc)) { /* moving the chess */ drc_t moving = { CHESS_STEP_NORMAL, tag->select, loc }; board_t tmpbrd; int valid_step = 1; if (CHE_P(board[loc.r][loc.c]) == KIND_K) /* 移到對方將帥 */ *result = CHESS_RESULT_WIN; else { memcpy(tmpbrd, board, sizeof(board_t)); chc_movechess(tmpbrd, &moving); valid_step = !chc_iskfk(tmpbrd); } if (valid_step) { getstep(board, &moving.from, &moving.to, info->last_movestr); chc_movechess(board, &moving); ChessDrawLine(info, LTR(info, moving.from.r)); ChessDrawLine(info, LTR(info, moving.to.r)); ChessHistoryAppend(info, &moving); ChessStepSend(info, &moving); tag->selected = 0; return 1; } else { /* 王見王 */ strlcpy(info->warnmsg, ANSI_COLOR(1;33) "不可以王見王" ANSI_RESET, sizeof(info->warnmsg)); bell(); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); return 0; } } else /* nothing happened */ return 0; } static int round_to_int(double x) { /* assume that double cast to int will drop fraction parts */ if(x>=0) return (int)(x+0.5); return (int)(x-0.5); } /* * ELO rating system * see http://www.wordiq.com/definition/ELO_rating_system */ static void count_chess_elo_rating(ChessUser* user1, const ChessUser* user2, double myres) { double k; double exp_res; int diff; int newrating; if(user1->rating < 1800) k = 30; else if(user1->rating < 2000) k = 25; else if(user1->rating < 2200) k = 20; else if(user1->rating < 2400) k = 15; else k = 10; //exp_res = 1.0/(1.0 + pow(10.0, (user2->rating-user1->rating)/400.0)); //user1->rating += (int)floor(k*(myres-exp_res)+0.5); diff=(int)user2->rating-(int)user1->rating; if(diff<=-1000 || diff>=1000) exp_res=diff>0?0.0:1.0; else if(diff>=0) exp_res=elo_exp_tab[diff]; else exp_res=1.0-elo_exp_tab[-diff]; newrating = (int)user1->rating + round_to_int(k*(myres-exp_res)); if(newrating > 3000) newrating = 3000; if(newrating < 1) newrating = 1; user1->rating = newrating; } /* 象棋功能進入點: * chc_main: 對奕 * chc_personal: 打譜 * chc_watch: 觀棋 * talk.c: 對奕 */ void chc(int s, ChessGameMode mode) { ChessInfo* info = NewChessInfo(&chc_actions, &chc_constants, s, mode); board_t board; chc_tag_data_t tag; chc_init_board(board); tag.selected = 0; info->board = board; info->tag = &tag; if (info->mode == CHESS_MODE_VERSUS) { /* Assume that info->user1 is me. */ info->user1.lose++; count_chess_elo_rating(&info->user1, &info->user2, 0.0); passwd_sync_query(usernum, &cuser); chcusr_put(&cuser, &info->user1); passwd_sync_update(usernum, &cuser); } if (mode == CHESS_MODE_WATCH) setutmpmode(CHESSWATCHING); else setutmpmode(CHC); currutmp->sig = SIG_CHC; ChessPlay(info); DeleteChessInfo(info); } static void chc_gameend(ChessInfo* info, ChessGameResult result) { ChessUser* const user1 = &info->user1; ChessUser* const user2 = &info->user2; if (info->mode == CHESS_MODE_VERSUS) { if (info->myturn == RED) { /* 由紅方作 log. 記的是下棋前的原始分數 */ /* NOTE, 若紅方斷線則無 log */ time_t t = time(NULL); char buf[100]; sprintf(buf, "%s %s(%d,W%d/D%d/L%d) %s %s(%d,W%d/D%d/L%d)\n", ctime(&t), user1->userid, user1->rating, user1->win, user1->tie, user1->lose - 1, (result == CHESS_RESULT_TIE ? "和" : result == CHESS_RESULT_WIN ? "勝" : "負"), user2->userid, user2->rating, user2->win, user2->tie, user2->lose - 1); buf[24] = ' '; // replace '\n' log_file(BBSHOME "/log/chc.log", LOG_CREAT, buf); } user1->rating = user1->orig_rating; user1->lose--; if (result == CHESS_RESULT_WIN) { count_chess_elo_rating(user1, user2, 1.0); user1->win++; currutmp->chc_win++; } else if (result == CHESS_RESULT_LOST) { count_chess_elo_rating(user1, user2, 0.0); user1->lose++; currutmp->chc_lose++; } else { count_chess_elo_rating(user1, user2, 0.5); user1->tie++; currutmp->chc_tie++; } currutmp->chess_elo_rating = user1->rating; chcusr_put(&cuser, user1); passwd_sync_update(usernum, &cuser); } else if (info->mode == CHESS_MODE_REPLAY) { free(info->board); free(info->tag); } } int chc_main(void) { return ChessStartGame('c', SIG_CHC, "楚河漢界之爭"); } int chc_personal(void) { chc(0, CHESS_MODE_PERSONAL); return 0; } int chc_watch(void) { return ChessWatchGame(&chc, CHC, "楚河漢界之爭"); } ChessInfo* chc_replay(FILE* fp) { ChessInfo *info; char buf[256]; info = NewChessInfo(&chc_actions, &chc_constants, 0, CHESS_MODE_REPLAY); while (fgets(buf, sizeof(buf), fp)) { if (strcmp("\n", buf) == 0) break; if (buf[0] == '[') { if (strncmp(buf + 1, "Red", 3) == 0 || strncmp(buf + 1, "Black", 5) == 0) { /* /\[(Red|Black) "([a-zA-Z0-9]+)"\]/; $2 */ userec_t rec; char *userid; char *strtok_pos = NULL; ChessUser *user = (buf[1] == 'R' ? &info->user1 : &info->user2); strtok_r(buf, "\"", &strtok_pos); userid = strtok_r(NULL, "\"", &strtok_pos); if (userid != NULL && getuser(userid, &rec)) chc_init_user_userec(&rec, user); } } else { /* " 1. Ch2-e2 Nb9-c7" */ drc_t step = { .type = CHESS_STEP_NORMAL }; const char *p = strchr(buf, '.'); if (p == NULL) continue; ++p; /* skip '.' */ while (*p && isspace(*p)) ++p; if (!*p) continue; /* p -> "Ch2-e2 ...." */ step.from.c = p[1] - 'a'; step.from.r = BRD_ROW - 1 - (p[2] - '0'); step.to.c = p[4] - 'a'; step.to.r = BRD_ROW - 1 - (p[5] - '0'); #define INVALID_ROW(R) ((R) < 0 || (R) >= BRD_ROW) #define INVALID_COL(C) ((C) < 0 || (C) >= BRD_COL) #define INVALID_LOC(S) (INVALID_ROW(S.r) || INVALID_COL(S.c)) if (INVALID_LOC(step.from) || INVALID_LOC(step.to)) continue; ChessHistoryAppend(info, &step); p += 6; while (*p && isspace(*p)) ++p; if (!*p) continue; /* p -> "Nb9-c7\n" */ step.from.c = p[1] - 'a'; step.from.r = BRD_ROW - 1 - (p[2] - '0'); step.to.c = p[4] - 'a'; step.to.r = BRD_ROW - 1 - (p[5] - '0'); if (INVALID_LOC(step.from) || INVALID_LOC(step.to)) continue; ChessHistoryAppend(info, &step); #undef INVALID_ROW #undef INVALID_COL #undef INVALID_LOC } } info->board = malloc(sizeof(board_t)); info->tag = malloc(sizeof(chc_tag_data_t)); chc_init_board(info->board); ((chc_tag_data_t*) info->tag)->selected = 0; return info; }