diff options
-rw-r--r-- | include/chess.h | 1 | ||||
-rw-r--r-- | include/proto.h | 2 | ||||
-rw-r--r-- | mbbsd/chc.c | 160 | ||||
-rw-r--r-- | mbbsd/chess.c | 100 | ||||
-rw-r--r-- | mbbsd/gomo.c | 76 | ||||
-rw-r--r-- | mbbsd/pmore.c | 4 | ||||
-rw-r--r-- | mbbsd/talk.c | 1 |
7 files changed, 317 insertions, 27 deletions
diff --git a/include/chess.h b/include/chess.h index 8a151ace..904db6cf 100644 --- a/include/chess.h +++ b/include/chess.h @@ -175,6 +175,7 @@ void ChessPlay(ChessInfo* info); int ChessStartGame(char func_char, int sig, const char* title); int ChessWatchGame(void (*play)(int, ChessGameMode), int game, const char* title); +int ChessReplayGame(const char* fname); ChessInfo* NewChessInfo(const ChessActions* actions, const ChessConstants* constants, int sock, ChessGameMode mode); diff --git a/include/proto.h b/include/proto.h index 7a33212b..4c0003d2 100644 --- a/include/proto.h +++ b/include/proto.h @@ -197,6 +197,7 @@ void chc(int s, ChessGameMode mode); int chc_main(void); int chc_personal(void); int chc_watch(void); +ChessInfo* chc_replay(FILE* fp); /* chicken */ void ch_buyitem(int money, const char *picture, int *item, int haveticket); @@ -297,6 +298,7 @@ void gomoku(int s, ChessGameMode mode); int gomoku_main(void); int gomoku_personal(void); int gomoku_watch(void); +ChessInfo* gomoku_replay(FILE* fp); /* guess */ int guess_main(void); diff --git a/mbbsd/chc.c b/mbbsd/chc.c index 0dbe3514..37831aa1 100644 --- a/mbbsd/chc.c +++ b/mbbsd/chc.c @@ -260,7 +260,7 @@ chc_drawline(const ChessInfo* info, int line) /* * Start of the log function. */ -void +static void chc_log_step(FILE* fp, board_t board, const drc_t *step) { char buf[80]; @@ -270,6 +270,22 @@ chc_log_step(FILE* fp, board_t board, const drc_t *step) fputc('\n', fp); } +static void +chc_log_machine_step(FILE* fp, board_t board, const drc_t *step) +{ + const static 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])], + BRD_COL - step->from.c - 1 + 'a', BRD_ROW - step->from.r - 1, + board[step->to.r][step->to.c] ? 'x' : '-', + BRD_COL - step->to.c - 1 + 'a', BRD_ROW - step->to.r - 1 + ); +} + static int #if defined(__linux__) chc_filter(const struct dirent *dir) @@ -337,11 +353,60 @@ chc_genlog(ChessInfo* info, FILE* fp, ChessGameResult result) (info->myturn == RED) == (result== CHESS_RESULT_WIN) ? "紅" : "黑"); - fputs("\n--\n\n", fp); + /* generate machine readable log. + * http://www.elephantbase.net/protocol/cchess_pgn.htm */ + { + /* machine readable header */ + time_t temp = (time_t)*clock; + struct tm *mytm = localtime(&temp); + + fprintf(fp, + "\n\n<chclog>\n" + "[Game \"Chinese Chess\"]\n" + "[Date \"%d.%d.%d\"]\n" + "[Red \"%s\"]\n" + "[Black \"%s\"]\n" + "[Result \"%s\"]\n" + "[Notation \"Coord\"]\n" + "[FEN \"rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/9/1C5C1/9/RN2K2NR" + " r - - 0 1\"]\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, + result == CHESS_RESULT_TIE ? "0.5-0.5" : + (info->myturn == RED) == (result== CHESS_RESULT_WIN) ? + "1-0" : "0-1" + ); + } + 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); - /* TODO: generate machine readable log. - * e.g. http://www.nchess.com/ccff.html */ + 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("</chclog>\n\n--\n\n", fp); chc_log_poem(fp); } /* @@ -599,6 +664,18 @@ chc_init_user(const userinfo_t *uinfo, ChessUser *user) 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 void chc_prepare_play(ChessInfo* info) @@ -833,3 +910,78 @@ 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("</chclog>\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; + ChessUser *user = + (buf[0] == 'R' ? &info->user1 : &info->user2); + + strtok(buf, "\""); + userid = strtok(NULL, "\""); + if (userid != NULL && getuser(userid, &rec)) + chc_init_user_userec(&rec, user); + } + } else { + /* " 1. Ch2-e2 Nb9-c7" */ + drc_t step = { 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); + } + } + + 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; +} diff --git a/mbbsd/chess.c b/mbbsd/chess.c index e131459d..b35d386f 100644 --- a/mbbsd/chess.c +++ b/mbbsd/chess.c @@ -32,6 +32,16 @@ static const char * const ChessHintStr[] = { "Enter 選擇/移動" }; +static const struct { + const char* name; + int name_len; + ChessInfo* (*func)(FILE* fp); +} ChessReplayMap[] = { + { "gomoku", 6, &gomoku_replay }, + { "chc", 3, &chc_replay }, + { NULL } +}; + static ChessInfo * CurrentPlayingGameInfo; /* XXX: This is a BAD way to pass information. @@ -625,6 +635,7 @@ ChessPlayFuncWatch(ChessInfo* info) sizeof(info->warnmsg)); ChessDrawLine(info, CHESS_DRAWING_WARN_ROW); + ChessDrawLine(info, CHESS_DRAWING_STEP_ROW); move(1, 0); switch (igetch()) { @@ -954,7 +965,9 @@ ChessPlay(ChessInfo* info) } info->actions->gameend(info, game_result); - ChessGenLog(info, game_result); + + if (info->mode != CHESS_MODE_REPLAY) + ChessGenLog(info, game_result); currutmp->sig = -1; Signal(SIGUSR1, old_handler); @@ -1083,31 +1096,80 @@ ChessWatchGame(void (*play)(int, ChessGameMode), int game, const char* title) return 0; } +int +ChessReplayGame(const char* fname) +{ + ChessInfo *info; + FILE *fp = fopen(fname, "r"); + int found = -1; + char buf[256]; + screen_backup_t oldscreen; + + 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); + + screen_backup(&oldscreen); + ChessPlay(info); + screen_restore(&oldscreen); + + return 0; +} + static void ChessInitUser(ChessInfo* info) { char userid[2][IDLEN + 1]; const userinfo_t* uinfo; - if (info->mode == CHESS_MODE_PERSONAL) { - strlcpy(userid[0], cuser.userid, sizeof(userid[0])); - strlcpy(userid[1], cuser.userid, sizeof(userid[1])); - } - else if (info->mode == CHESS_MODE_WATCH) { - userinfo_t *uinfo = search_ulist_userid(currutmp->mateid); - strlcpy(userid[0], uinfo->userid, sizeof(userid[0])); - strlcpy(userid[1], uinfo->mateid, sizeof(userid[1])); + 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); + strlcpy(userid[0], uinfo->userid, sizeof(userid[0])); + strlcpy(userid[1], uinfo->mateid, sizeof(userid[1])); + 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; } - else if (info->mode == CHESS_MODE_VERSUS) { - strlcpy(userid[0], cuser.userid, sizeof(userid[0])); - strlcpy(userid[1], currutmp->mateid, sizeof(userid[1])); - } else - assert_not_reached(); uinfo = search_ulist_userid(userid[0]); - info->actions->init_user(uinfo, &info->user1); + if (uinfo) + info->actions->init_user(uinfo, &info->user1); + uinfo = search_ulist_userid(userid[1]); - info->actions->init_user(uinfo, &info->user2); + if (uinfo) + info->actions->init_user(uinfo, &info->user2); } #ifdef CHESSCOUNTRY @@ -1269,17 +1331,13 @@ ChessInitPlayFunc(ChessInfo* info) 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; - - case CHESS_MODE_REPLAY: - /* TODO: not implemented yet */ - assert_not_reached(); - break; } } diff --git a/mbbsd/gomo.c b/mbbsd/gomo.c index 521fdb76..872648e5 100644 --- a/mbbsd/gomo.c +++ b/mbbsd/gomo.c @@ -5,7 +5,7 @@ #define QCAST int (*)(const void *, const void *) #define BOARD_LINE_ON_SCREEN(X) ((X) + 2) -static const char* turn_color[] = { ANSI_COLOR(30;43), ANSI_COLOR(37;43) }; +static const char* turn_color[] = { ANSI_COLOR(37;43), ANSI_COLOR(30;43) }; enum Turn { WHT = 0, @@ -261,6 +261,15 @@ gomo_init_user(const userinfo_t* uinfo, ChessUser* user) } static void +gomo_init_user_userec(const userec_t* urec, ChessUser* user) +{ + strlcpy(user->userid, urec->userid, sizeof(user->userid)); + user->win = urec->five_win; + user->lose = urec->five_lose; + user->tie = urec->five_tie; +} + +static void gomo_init_board(board_t board) { memset(board, 0xff, sizeof(board_t)); @@ -402,6 +411,9 @@ gomo_gameend(ChessInfo* info, ChessGameResult result) cuser.five_tie = user1->tie; passwd_update(usernum, &cuser); + } else if (info->mode == CHESS_MODE_REPLAY) { + free(info->board); + free(info->tag); } } @@ -488,3 +500,65 @@ gomoku_watch(void) { return ChessWatchGame(&gomoku, M_FIVE, "五子棋"); } + +ChessInfo* +gomoku_replay(FILE* fp) +{ + ChessInfo *info; + char buf[256]; + + info = NewChessInfo(&gomo_actions, &gomo_constants, + 0, CHESS_MODE_REPLAY); + + while (fgets(buf, sizeof(buf), fp)) { + if (strcmp("</gomokulog>\n", buf) == 0) + break; + else if (strncmp("black:", buf, 6) == 0 || + strncmp("white:", buf, 6) == 0) { + /* /(black|white):([a-zA-Z0-9]+)/; $2 */ + userec_t rec; + ChessUser *user = (buf[0] == 'b' ? &info->user1 : &info->user2); + + chomp(buf); + if (getuser(buf + 6, &rec)) + gomo_init_user_userec(&rec, user); + } else if (buf[0] == '[') { + /* "[ 1]● ==> H8 [ 2]○ ==> H9" * + * 012345678901234567890123456789 */ + gomo_step_t step = { CHESS_STEP_NORMAL, BLK }; + int c = buf[11] - 'A'; + int r = BRDSIZ - 1 - (buf[12] - '0'); + +#define INVALID_ROW(R) ((R) < 0 || (R) >= BRDSIZ) +#define INVALID_COL(C) ((C) < 0 || (C) >= BRDSIZ) + if (INVALID_COL(c) || INVALID_ROW(r)) + continue; + + step.loc.r = r; + step.loc.c = c; + ChessHistoryAppend(info, &step); + + if (strlen(buf) < 28) + continue; + + c = buf[28] - 'A'; + r = BRDSIZ - (buf[29] - '0'); + + if (INVALID_COL(c) || INVALID_ROW(r)) + continue; + + step.color = WHT; + step.loc.r = r; + step.loc.c = c; + ChessHistoryAppend(info, &step); + } + } + + info->board = malloc(sizeof(board_t)); + info->tag = malloc(sizeof(int)); + + gomo_init_board(info->board); + *(int*)(info->tag) = 0; + + return info; +} diff --git a/mbbsd/pmore.c b/mbbsd/pmore.c index 3eddc925..7459a6a4 100644 --- a/mbbsd/pmore.c +++ b/mbbsd/pmore.c @@ -2352,6 +2352,10 @@ pmore(char *fpath, int promptend) MFDISP_DIRTY(); break; #endif + + case 'z': + ChessReplayGame(fpath); + break; } /* DO NOT DO ANYTHING HERE. NOT SAFE RIGHT NOW. */ } diff --git a/mbbsd/talk.c b/mbbsd/talk.c index ce7c8efd..3b7beab5 100644 --- a/mbbsd/talk.c +++ b/mbbsd/talk.c @@ -1430,7 +1430,6 @@ my_talk(userinfo_t * uin, int fri_stat, char defact) (!ch && (uin->chatid[0] == 1 || uin->chatid[0] == 3)) || uin->lockmode == M_FIVE || uin->lockmode == CHC) { if (ch == CHC || ch == M_FIVE || ch == CHESSWATCHING) { - kill(uin->pid, SIGUSR1); sock = make_connection_to_somebody(uin, 20); if (sock < 0) vmsg("無法建立連線"); |