summaryrefslogtreecommitdiffstats
path: root/mbbsd/chess.c
diff options
context:
space:
mode:
authorscw <scw@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2005-08-08 04:21:12 +0800
committerscw <scw@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2005-08-08 04:21:12 +0800
commitf9f4c71a8beec3d7c2931a266d3df20d00abaeba (patch)
treee4ca0bb95cc1a5bbd1dd0724ba73348112c7b604 /mbbsd/chess.c
parentb3bb4b2108f3c2d60bb005fd63dafc5e8a72045a (diff)
downloadpttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.tar
pttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.tar.gz
pttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.tar.bz2
pttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.tar.lz
pttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.tar.xz
pttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.tar.zst
pttbbs-f9f4c71a8beec3d7c2931a266d3df20d00abaeba.zip
New chess framework
* Provides common parts of all chess games * Chinese chess fully ported * Improved watching * Leaving possibility to implement replaying !!!NOTE!!! Protocal not backward compatible, STOP ALL clients before upgrade. git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@3002 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
Diffstat (limited to 'mbbsd/chess.c')
-rw-r--r--mbbsd/chess.c1181
1 files changed, 1181 insertions, 0 deletions
diff --git a/mbbsd/chess.c b/mbbsd/chess.c
new file mode 100644
index 00000000..c59f4099
--- /dev/null
+++ b/mbbsd/chess.c
@@ -0,0 +1,1181 @@
+/* $Id$ */
+#include "bbs.h"
+#include "chess.h"
+
+#define assert_not_reached() assert(!"Should never be here!!!")
+
+#define CHESS_HISTORY_INITIAL_BUFFER_SIZE 300
+#define CHESS_HISTORY_BUFFER_INCREMENT 50
+
+static ChessInfo * CurrentPlayingGameInfo;
+
+/* XXX: This is a BAD way to pass information.
+ * Fix this by handling chess request ourselves.
+ */
+static ChessTimeLimit * _current_time_limit;
+
+#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_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_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,
+ };
+
+ mouts(b_lines, 0, HelpStr[info->mode]);
+}
+
+static void
+ChessRedraw(const ChessInfo* info)
+{
+ int i;
+ for (i = 0; i < b_lines; ++i)
+ info->actions->drawline(info, i);
+ ChessDrawHelpLine(info);
+}
+
+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 0;
+ }
+
+ return 0;
+}
+
+int
+ChessTimeCountDown(ChessInfo* info, int who, int length)
+{
+ int result = ChessTimeCountDownCalc(info, who, length);
+ info->actions->drawline(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->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;
+}
+
+ChessStepType
+ChessStepReceive(ChessInfo* info, void* step)
+{
+ ChessStepType result = ChessRecvMove(info, info->sock, step);
+
+ if (result != CHESS_STEP_FAILURE) {
+ /* automatical routing */
+ ChessStepBroadcast(info, step);
+
+ /* and logging */
+ ChessHistoryAppend(info, step);
+ }
+
+ return result;
+}
+
+int
+ChessStepSendOpposite(ChessInfo* info, const void* step)
+{
+ void (*orig_handler)(int);
+ int result = 1;
+
+ orig_handler = Signal(SIGPIPE, SIG_IGN);
+
+ if (!ChessSendMove(info, info->sock, step))
+ result = 0;
+
+ Signal(SIGPIPE, orig_handler);
+ return result;
+}
+
+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);
+}
+
+inline static void
+ChessReplayUntil(ChessInfo* info, int n)
+{
+ const void* step;
+
+ 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);
+ info->actions->prepare_step(info, step);
+ info->actions->apply_step(info->board, step);
+ ++(info->current_step);
+}
+
+static ChessGameResult
+ChessPlayFuncMy(ChessInfo* info)
+{
+ int last_time = now;
+ int endturn = 0;
+ ChessGameResult game_result = CHESS_RESULT_CONTINUE;
+ int ch;
+
+ info->ipass = 0;
+ bell();
+
+ while (!endturn) {
+ ChessStepType result;
+
+ info->actions->drawline(info, CHESS_DRAWING_TIME_ROW);
+ info->actions->movecur(info->cursor.r, info->cursor.c);
+ oflush();
+
+ ch = igetch();
+ if (ChessTimeCountDown(info, 0, now - last_time))
+ ch = 'q';
+ 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_PASS && info->ipass) {
+ game_result = CHESS_RESULT_TIE;
+ endturn = 1;
+ }
+ 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:
+ --(info->cursor.c);
+ if (info->cursor.c < 0)
+ info->cursor.c = info->constants->board_width - 1;
+ break;
+
+ case KEY_RIGHT:
+ ++(info->cursor.c);
+ if (info->cursor.c >= info->constants->board_width)
+ info->cursor.c = 0;
+ break;
+
+ case 'q':
+ game_result = CHESS_RESULT_LOST;
+ endturn = 1;
+ break;
+
+ case 'p':
+ info->ipass = 1;
+ ChessMessageSend(info, CHESS_STEP_PASS);
+ strlcpy(info->warnmsg,
+ ANSI_COLOR(1;33) "要求和棋!" ANSI_RESET,
+ sizeof(info->warnmsg));
+ info->actions->drawline(info, CHESS_DRAWING_WARN_ROW);
+ bell();
+ break;
+
+ case '\r':
+ case '\n':
+ case ' ':
+ endturn = info->actions->select(info, info->cursor, &game_result);
+ break;
+ }
+ }
+ ChessTimeCountDown(info, 0, now - last_time);
+ ChessStepMade(info, 0);
+ info->actions->drawline(info, CHESS_DRAWING_TIME_ROW);
+ info->actions->drawline(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;
+
+ 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;
+
+ info->actions->drawline(info, CHESS_DRAWING_TIME_ROW);
+ move(1, 0);
+ oflush();
+
+ switch (igetch()) {
+ case 'q':
+ game_result = CHESS_RESULT_LOST;
+ endturn = 1;
+ break;
+
+ case 'p':
+ if (info->hepass) {
+ ChessMessageSend(info, CHESS_STEP_PASS);
+ game_result = CHESS_RESULT_TIE;
+ endturn = 1;
+ }
+ 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) {
+ info->hepass = 1;
+ strlcpy(info->warnmsg,
+ ANSI_COLOR(1;33) "要求和局!" ANSI_RESET,
+ sizeof(info->warnmsg));
+ info->actions->drawline(info, CHESS_DRAWING_WARN_ROW);
+ } else {
+ info->actions->prepare_step(info, &info->step_tmp);
+ if (info->actions->apply_step(info->board, &info->step_tmp))
+ game_result = CHESS_RESULT_LOST;
+ endturn = 1;
+ info->hepass = 0;
+ ChessStepMade(info, 1);
+ info->actions->drawstep(info, &info->step_tmp);
+ }
+ }
+ }
+ ChessTimeCountDown(info, 1, now - last_time);
+ info->actions->drawline(info, CHESS_DRAWING_TIME_ROW);
+ info->actions->drawline(info, CHESS_DRAWING_STEP_ROW);
+ return game_result;
+}
+
+static ChessGameResult
+ChessPlayFuncWatch(ChessInfo* info)
+{
+ int end_watch = 0;
+
+ while (!end_watch) {
+ info->actions->prepare_play(info);
+ if (info->sock == -1)
+ strlcpy(info->warnmsg, ANSI_COLOR(1;33) "棋局已結束" ANSI_RESET,
+ sizeof(info->warnmsg));
+
+ info->actions->drawline(info, CHESS_DRAWING_WARN_ROW);
+ move(1, 0);
+
+ switch (igetch()) {
+ case I_OTHERDATA: /* new step */
+ if (ChessStepReceive(info, &info->step_tmp) == CHESS_STEP_FAILURE) {
+ add_io(0, 0);
+ info->sock = -1;
+ break;
+ }
+
+ if (info->current_step == info->history.used - 1) {
+ /* was watching up-to-date board */
+ info->actions->prepare_step(info, &info->step_tmp);
+ info->actions->apply_step(info->board, &info->step_tmp);
+ info->actions->drawstep(info, &info->step_tmp);
+ ++(info->current_step);
+ }
+ 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, 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->actions->prepare_step(info, step);
+ info->actions->apply_step(info->board, step);
+ info->actions->drawstep(info, step);
+ ++(info->current_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, info->board);
+ info->current_step = 0;
+
+ if (current > 10)
+ 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, 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)
+ return;
+
+ node = ChessBroadcastListInsert(&CurrentPlayingGameInfo->broadcast_list);
+ node->sock = sock;
+
+#define SEND(X) write(sock, &(X), sizeof(X))
+ SEND(CurrentPlayingGameInfo->my);
+ 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->my);
+ 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 buf[256];
+ int bid;
+
+ if ((bid = getbnum(info->constants->log_board)) == 0)
+ return;
+
+ setbpath(buf, info->constants->log_board);
+ stampfile(buf, &log_header);
+
+ fp = fopen(buf, "w");
+ if (fp != NULL) {
+ info->actions->genlog(info, fp, result);
+ fclose(fp);
+
+ strlcpy(log_header.owner, "[棋譜機器人]", sizeof(log_header.owner));
+ snprintf(log_header.title, sizeof(log_header.title),
+ ANSI_COLOR(37;41) "棋譜" ANSI_RESET " %s VS %s",
+ info->user1.userid, info->user2.userid);
+
+ setbdir(buf, info->constants->log_board);
+ append_record(buf, &log_header, sizeof(log_header));
+
+ setbtotal(bid);
+ }
+}
+
+static void
+ChessGenLogUser(ChessInfo* info, ChessGameResult result)
+{
+ fileheader_t log_header;
+ FILE *fp;
+ char buf[256];
+
+ sethomepath(buf, cuser.userid);
+ stampfile(buf, &log_header);
+
+ fp = fopen(buf, "w");
+ if (fp != NULL) {
+ info->actions->genlog(info, fp, result);
+ fclose(fp);
+
+ strlcpy(log_header.owner, "[楚河漢界]", sizeof(log_header.owner));
+ if(info->my == 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(buf, cuser.userid);
+ append_record_forward(buf, &log_header, sizeof(log_header),
+ cuser.userid);
+
+ mailalert(cuser.userid);
+ }
+}
+
+static void
+ChessGenLog(ChessInfo* info, ChessGameResult result)
+{
+ if (info->mode == CHESS_MODE_VERSUS && info->my == 0 &&
+ info->constants->log_board) {
+ ChessGenLogGlobal(info, result);
+ }
+
+ if (getans("是否將棋譜寄回信箱?[N/y]") == 'y')
+ ChessGenLogUser(info, result);
+}
+
+void
+ChessPlay(ChessInfo* info)
+{
+ ChessGameResult game_result;
+ sighandler_t old_handler;
+ const char* game_result_str = 0;
+
+ if (info == NULL)
+ return;
+
+ /* XXX */
+ if (!info->timelimit) {
+ info->timelimit = _current_time_limit;
+ _current_time_limit = NULL;
+ }
+
+ CurrentPlayingGameInfo = info;
+ old_handler = Signal(SIGUSR1, &ChessWatchRequest);
+
+ 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 */
+ add_io(info->sock, 0);
+ for (game_result = CHESS_RESULT_CONTINUE;
+ game_result == CHESS_RESULT_CONTINUE;
+ info->turn ^= 1) {
+ info->actions->prepare_play(info);
+ info->actions->drawline(info, CHESS_DRAWING_TURN_ROW);
+ info->actions->drawline(info, CHESS_DRAWING_WARN_ROW);
+ game_result = info->play_func[(int) info->turn](info);
+ }
+ add_io(0, 0);
+
+ 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));
+ info->actions->drawline(info, CHESS_DRAWING_WARN_ROW);
+ }
+
+ info->actions->gameend(info, game_result);
+ ChessGenLog(info, game_result);
+
+ Signal(SIGUSR1, old_handler);
+ CurrentPlayingGameInfo = NULL;
+}
+
+static userinfo_t*
+ChessSearchUser(int sig, const char* title)
+{
+ char uident[16];
+ userinfo_t *uin;
+
+ stand_title(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));
+
+ stand_title(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)
+ return -1;
+
+ if (getans("是否進行觀棋? [N/y]") != 'y')
+ return 0;
+
+ if ((sock = make_connection_to_somebody(uin, 10)) < 0) {
+ vmsg("無法建立連線");
+ return -1;
+ }
+#if defined(Solaris) && __OS_MAJOR_VERSION__ == 5 && __OS_MINOR_VERSION__ < 7
+ msgsock = accept(sock, (struct sockaddr *) 0, 0);
+#else
+ msgsock = accept(sock, (struct sockaddr *) 0, (socklen_t *) 0);
+#endif
+ close(sock);
+ if (msgsock < 0)
+ return -1;
+
+ strlcpy(currutmp->mateid, uin->userid, sizeof(currutmp->mateid));
+ play(msgsock, CHESS_MODE_WATCH);
+ close(msgsock);
+ return 0;
+}
+
+static void
+ChessInitUser(ChessInfo* info)
+{
+ char userid[2][IDLEN + 1];
+ userec_t xuser;
+
+ 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]));
+ }
+ else {
+ strlcpy(userid[0], cuser.userid, sizeof(userid[0]));
+ strlcpy(userid[1], currutmp->mateid, sizeof(userid[1]));
+ }
+
+ getuser(userid[0], &xuser);
+ info->actions->init_user(&xuser, &info->user1);
+ getuser(userid[1], &xuser);
+ info->actions->init_user(&xuser, &info->user2);
+}
+
+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;
+
+ if (info->mode == CHESS_MODE_REPLAY)
+ return NULL;
+
+ sethomefile(genbuf, info->user1.userid, info->constants->photo_file_name);
+ if (!dashf(genbuf)) {
+ sethomefile(genbuf, info->user2.userid, info->constants->photo_file_name);
+ if (!dashf(genbuf))
+ 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)
+
+ 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 = 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->my],
+ info->constants->turn_str[(int) info->my]);
+ strcpy(PHOTO(7), " V.S ");
+ sprintf(PHOTO(8), " %s%2.2s棋" ANSI_RESET,
+ info->constants->turn_color[info->my ^ 1],
+ info->constants->turn_str[info->my ^ 1]);
+
+ getuser(info->user2.userid, &xuser);
+ sethomefile(genbuf, info->user2.userid, "photo_cchess");
+ 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;
+}
+
+static void
+ChessInitPlayFunc(ChessInfo* info)
+{
+ switch (info->mode) {
+ case CHESS_MODE_VERSUS:
+ info->play_func[(int) info->my] = &ChessPlayFuncMy;
+ info->play_func[info->my ^ 1] = &ChessPlayFuncHis;
+ break;
+
+ case CHESS_MODE_WATCH:
+ 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;
+ }
+}
+
+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);
+
+ /* 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->my = currutmp->turn;
+ else if (mode == CHESS_MODE_PERSONAL)
+ info->my = 1;
+ else if (mode == CHESS_MODE_WATCH)
+ ChessReceiveWatchInfo(info);
+
+ ChessInitUser(info);
+
+ info->photo = ChessPhotoInitial(info);
+
+ 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)
+ mouts(10, 5, "使用傳統計時方式, 單步限時五分鐘");
+ else if (_current_time_limit->time_mode == CHESS_TIMEMODE_MULTIHAND) {
+ mouts(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) {
+ mouts(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);
+ }
+}
+