/* $Id$ */
#include "bbs.h"
#include "chess.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_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 CHESS_DRAWING_PHOTOED_STEP_ROW 23
static const char * const ChessHintStr[] = {
" q 認輸離開",
" p 要求和棋",
"方向鍵 移動遊標",
"Enter 選擇/移動"
};
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]);
}
void
ChessDrawLine(const ChessInfo* info, int line)
{
if (line == b_lines)
ChessDrawHelpLine(info);
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) {
info->actions->drawline(info, CHESS_DRAWING_PHOTOED_TIME_ROW1);
info->actions->drawline(info, CHESS_DRAWING_PHOTOED_TIME_ROW2);
} else {
info->actions->drawline(info, CHESS_DRAWING_REAL_TIME_ROW1);
info->actions->drawline(info, 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 = CHESS_DRAWING_REAL_STEP_ROW;
info->actions->drawline(info, line);
}
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 (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->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;
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_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':
{
char buf[4];
getdata(b_lines, 0, "是否真的要認輸?(y/N)",
buf, sizeof(buf), DOECHO);
ChessDrawHelpLine(info);
if (buf[0] == 'y' || buf[0] == 'Y') {
game_result = CHESS_RESULT_LOST;
endturn = 1;
}
}
break;
case 'p':
{
char buf[4];
getdata(b_lines, 0, "是否真的要和棋?(y/N)",
buf, sizeof(buf), DOECHO);
ChessDrawHelpLine(info);
if (buf[0] == 'y' || buf[1] == 'Y') {
info->ipass = 1;
ChessMessageSend(info, CHESS_STEP_PASS);
strlcpy(info->warnmsg,
ANSI_COLOR(1;33) "要求和棋!" ANSI_RESET,
sizeof(info->warnmsg));
ChessDrawLine(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);
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;
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 (igetch()) {
case 'q':
{
char buf[4];
getdata(b_lines, 0, "是否真的要認輸?(y/N)",
buf, sizeof(buf), DOECHO);
ChessDrawHelpLine(info);
if (buf[0] == 'y' || buf[0] == 'Y') {
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));
ChessDrawLine(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);
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) {
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);
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->board);
info->current_step = 0;
if (current > 1)
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->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->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->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 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), "[棋譜] %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);
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(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->myturn == 0 &&
info->constants->log_board) {
ChessGenLogGlobal(info, result);
}
if (getans("是否將棋譜寄回信箱?[N/y]") == 'y')
ChessGenLogUser(info, result);
}
void
ChessPlay(ChessInfo* info)
{
ChessGameResult game_result;
void (*old_handler)(int);
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;
{
sigset_t sigset;
old_handler = Signal(SIGUSR1, &ChessWatchRequest);
sigemptyset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
}
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);
ChessDrawLine(info, CHESS_DRAWING_TURN_ROW);
ChessDrawLine(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));
ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);
}
info->actions->gameend(info, game_result);
ChessGenLog(info, game_result);
currutmp->sig = -1;
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;
}
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]));
}
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);
uinfo = search_ulist_userid(userid[1]);
info->actions->init_user(uinfo, &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->myturn],
info->constants->turn_str[(int) info->myturn]);
strcpy(PHOTO(7), " V.S ");
sprintf(PHOTO(8), " %s%2.2s棋" ANSI_RESET,
info->constants->turn_color[info->myturn ^ 1],
info->constants->turn_str[info->myturn ^ 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->myturn] = &ChessPlayFuncMy;
info->play_func[info->myturn ^ 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);
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_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);
}
}
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)
{
if (info->photo) {
if (line >= 3 && line < 3 + CHESS_PHOTO_LINE) {
outs(" ");
outs(info->photo + (line - 3) * CHESS_PHOTO_COLUMN);
} else if (line >= CHESS_DRAWING_PHOTOED_TURN_ROW &&
line <= CHESS_DRAWING_PHOTOED_WARN_ROW) {
outs(" ");
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) {
outs(" ");
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);
}
}
}