summaryrefslogtreecommitdiffstats
path: root/console/chess.c
diff options
context:
space:
mode:
Diffstat (limited to 'console/chess.c')
-rw-r--r--console/chess.c1762
1 files changed, 1762 insertions, 0 deletions
diff --git a/console/chess.c b/console/chess.c
new file mode 100644
index 00000000..a86ca766
--- /dev/null
+++ b/console/chess.c
@@ -0,0 +1,1762 @@
+/* $Id$ */
+#include "bbs.h"
+#include "chess.h"
+#include <setjmp.h>
+
+#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 選擇/移動"
+};
+
+static const struct {
+ const char* name;
+ int name_len;
+ ChessInfo* (*func)(FILE* fp);
+} 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,
+ };
+
+ mouts(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 '\r':
+ case '\n':
+ 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 = getans((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;
+
+ 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) {
+ vmsg("無法建立連線");
+ 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;
+}
+
+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)
+ 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);
+ }
+}
+
+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);
+ }
+ }
+}