/* $Id$ */
#include "bbs.h"

extern const double elo_exp_tab[1000];

enum Turn {
    BLK,
    RED 
};
enum Kind {
    KIND_K=1,
    KIND_A,
    KIND_E,
    KIND_R,
    KIND_H,
    KIND_C,
    KIND_P,
};
#define CENTER(a, b)	(((a) + (b)) >> 1)
#define CHC_TIMEOUT	300
#define CHC_LOG		"chc_log"	/* log file name */

typedef int     (*play_func_t) (int, const chcusr_t *, const chcusr_t *, board_t, board_t);

typedef struct drc_t {
    rc_t            from, to;
}               drc_t;

struct CHCData {
    rc_t    from, to, select, cursor;
    int	    lefttime;
    int	    my; /* �ڤ�����ζ�, 0 ��, 1 ��. �[��=1 */
    int	    turn, selected, firststep;
    char    mode;
    char    warnmsg[64];
    /* color(7)+step(4*2)+normal(3)+color(7)+eat(2*2)+normal(3)+1=33 */
    char    last_movestr[36];
    char    ipass, hepass;
    /* chessfp is for logging the step */
    FILE      *chessfp;
    board_t   *bp;
    chc_act_list *act_list;
};
static struct CHCData *chcd;

static const char * const turn_color[2]={BLACK_COLOR, RED_COLOR};


/* some constant variable definition */

static const char * const turn_str[2] = {"�ª�", "����"};

static const char * const num_str[2][10] = {
    {"", "��", "��", "��", "��", "��", "��", "��", "��", "��"},
    {"", "�@", "�G", "�T", "�|", "��", "��", "�C", "�K", "�E"},
};

static const char * const chess_str[2][8] = {
    /* 0     1     2     3     4     5     6     7 */
    {"  ", "�N", "�h", "�H", "��", "��", "�]", "��"},
    {"  ", "��", "�K", "��", "��", "�X", "��", "�L"}
};

static const char * const chess_brd[BRD_ROW * 2 - 1] = {
    /* 0   1   2   3   4   5   6   7   8 */
    "�z�w�s�w�s�w�s�w�s�w�s�w�s�w�s�w�{",	/* 0 */
    "�x  �x  �x  �x�@�x���x  �x  �x  �x",
    "�u�w�q�w�q�w�q�w�q�w�q�w�q�w�q�w�t",	/* 1 */
    "�x  �x  �x  �x���x�@�x  �x  �x  �x",
    "�u�w�q�w�q�w�q�w�q�w�q�w�q�w�q�w�t",	/* 2 */
    "�x  �x  �x  �x  �x  �x  �x  �x  �x",
    "�u�w�q�w�q�w�q�w�q�w�q�w�q�w�q�w�t",	/* 3 */
    "�x  �x  �x  �x  �x  �x  �x  �x  �x",
    "�u�w�r�w�r�w�r�w�r�w�r�w�r�w�r�w�t",	/* 4 */
    "�x  ��    �e          �~    ��  �x",
    "�u�w�s�w�s�w�s�w�s�w�s�w�s�w�s�w�t",	/* 5 */
    "�x  �x  �x  �x  �x  �x  �x  �x  �x",
    "�u�w�q�w�q�w�q�w�q�w�q�w�q�w�q�w�t",	/* 6 */
    "�x  �x  �x  �x  �x  �x  �x  �x  �x",
    "�u�w�q�w�q�w�q�w�q�w�q�w�q�w�q�w�t",	/* 7 */
    "�x  �x  �x  �x�@�x���x  �x  �x  �x",
    "�u�w�q�w�q�w�q�w�q�w�q�w�q�w�q�w�t",	/* 8 */
    "�x  �x  �x  �x���x�@�x  �x  �x  �x",
    "�|�w�r�w�r�w�r�w�r�w�r�w�r�w�r�w�}"	/* 9 */
};

static char * const hint_str[] = {
    "  q      �{�����}",
    "  p      �n�D�M��",
    "��V��   ���ʹC��",
    "Enter    ���/����"
};

/*
 * Start of the network communication function.
 */
static int
chc_recvmove(int s)
{
    drc_t           buf;

    if (read(s, &buf, sizeof(buf)) != sizeof(buf))
	return 0;
    chcd->from = buf.from, chcd->to = buf.to;
    return 1;
}

static int
chc_sendmove(int s)
{
    drc_t           buf;

    buf.from = chcd->from, buf.to = chcd->to;
    if (write(s, &buf, sizeof(buf)) != sizeof(buf))
	return 0;
    return 1;
}

// XXX return value
// XXX die because of SIGPIPE !?

/* return false if your adversary is off-line */
static void
chc_broadcast(chc_act_list **head, board_t board){
    chc_act_list *p = *head;
    void (*orig_handler)(int);
    
    if (!p)
	return;
    
    orig_handler = Signal(SIGPIPE, SIG_IGN);
    if (!chc_sendmove(p->sock)) {
	/* do nothing */
    }

    while(p->next){
	if (!chc_sendmove(p->next->sock)) {
	    chc_act_list *tmp = p->next->next;
	    free(p->next);
	    p->next = tmp;
	} else
	    p = p->next;
    }
    Signal(SIGPIPE, orig_handler);
}

static int
chc_broadcast_recv(chc_act_list *act_list, board_t board){
    if (!chc_recvmove(act_list->sock))
	return 0;
    chc_broadcast(&act_list->next, board);
    return 1;
}

static int
chc_broadcast_send(chc_act_list *act_list, board_t board){
    chc_broadcast(&act_list, board);
    return 1;
}

/*
 * End of the network communication function.
 */

/*
 * Start of the drawing function.
 */
static void
chc_movecur(int r, int c)
{
    move(r * 2 + 3, c * 4 + 4);
}

static char *
getstep(board_t board, const rc_t *from, const rc_t *to, char buf[])
{
    int             turn, fc, tc;
    char           *dir;
    int		    twin = 0, twin_r = 0;
    int		    len = 0;

    turn = CHE_O(board[from->r][from->c]);
    if(CHE_P(board[from->r][from->c] != KIND_P)) { // TODO �ثe���ާL��e��
	int i;
	for(i=0;i<10;i++)
	    if(board[i][from->c]==board[from->r][from->c]) {
		if(i!=from->r) {
		    twin=1;
		    twin_r=i;
		}
	    }
    }
    fc = (turn == (chcd->my ^ 1) ? from->c + 1 : 9 - from->c);
    tc = (turn == (chcd->my ^ 1) ? to->c + 1 : 9 - to->c);
    if (from->r == to->r)
	dir = "��";
    else {
	if (from->c == to->c)
	    tc = from->r - to->r;
	if (tc < 0)
	    tc = -tc;

	if ((turn == (chcd->my ^ 1) && to->r > from->r) ||
	    (turn == chcd->my && to->r < from->r))
	    dir = "�i";
	else
	    dir = "�h";
    }


    len=sprintf(buf, "%s", turn_color[turn]);
    /* �X�G|�e�X */
    if(twin) {
	len+=sprintf(buf+len, "%s%s",
		((from->r>twin_r)==(turn==(chcd->my^1)))?"�e":"��",
		chess_str[turn][CHE_P(board[from->r][from->c])]);
    } else {
	len+=sprintf(buf+len, "%s%s",
		chess_str[turn][CHE_P(board[from->r][from->c])],
		num_str[turn][fc]);
    }
    /* �i�T */
    len+=sprintf(buf+len, "%s%s\033[m", dir, num_str[turn][tc]);
    /* �G�H */
    if(board[to->r][to->c]) {
	len+=sprintf(buf+len,"�G%s%s\033[m",
		turn_color[turn^1],
		chess_str[turn^1][CHE_P(board[to->r][to->c])]);
    }
    return buf;
}

static void
showstep(board_t board)
{
    outs(chcd->last_movestr);
}

static void
chc_drawline(board_t board, const chcusr_t *user1, const chcusr_t *user2, int line)
{
    int             i, j;

    move(line, 0);
    clrtoeol();
    if (line == 0) {
	prints("\033[1;46m   �H�ѹ��   \033[45m%30s VS %-20s%10s\033[m",
	       user1->userid, user2->userid, chcd->mode & CHC_WATCH ? "[�[�ѼҦ�]" : "");
    } else if (line >= 3 && line <= 21) {
	outs("   ");
	for (i = 0; i < 9; i++) {
	    j = board[RTL(line)][i];
	    if ((line & 1) == 1 && j) {
		if (chcd->selected &&
		    chcd->select.r == RTL(line) && chcd->select.c == i) {
		    prints("%s%s\033[m",
			   CHE_O(j) == BLK ? BLACK_REVERSE : RED_REVERSE,
			   chess_str[CHE_O(j)][CHE_P(j)]);
		}
		else {
		    prints("%s%s\033[m",
			   turn_color[CHE_O(j)],
			   chess_str[CHE_O(j)][CHE_P(j)]);
		}
	    } else
		prints("%c%c", chess_brd[line - 3][i * 4],
		       chess_brd[line - 3][i * 4 + 1]);
	    if (i != 8)
		prints("%c%c", chess_brd[line - 3][i * 4 + 2],
		       chess_brd[line - 3][i * 4 + 3]);
	}
	outs("        ");

	if (line >= 3 && line < 3 + (int)dim(hint_str)) {
	    outs(hint_str[line - 3]);
	} else if (line == SIDE_ROW) {
	    prints("\033[1m�A�O%s%s\033[m",
		    turn_color[chcd->my],
		    turn_str[chcd->my]);
	} else if (line == TURN_ROW) {
	    prints("%s%s\033[m",
		   TURN_COLOR,
		   chcd->my == chcd->turn ? "����A�U�ѤF" : "���ݹ��U��");
	} else if (line == STEP_ROW && !chcd->firststep) {
	    showstep(board);
	} else if (line == TIME_ROW) {
	    prints("�Ѿl�ɶ� %d:%02d", chcd->lefttime / 60, chcd->lefttime % 60);
	} else if (line == WARN_ROW) {
	    outs(chcd->warnmsg);
	} else if (line == MYWIN_ROW) {
	    prints("\033[1;33m%12.12s    "
		   "\033[1;31m%2d\033[37m�� "
		   "\033[34m%2d\033[37m�� "
		   "\033[36m%2d\033[37m�M\033[m",
		   user1->userid,
		   user1->win, user1->lose - 1, user1->tie);
	} else if (line == HISWIN_ROW) {
	    prints("\033[1;33m%12.12s    "
		   "\033[1;31m%2d\033[37m�� "
		   "\033[34m%2d\033[37m�� "
		   "\033[36m%2d\033[37m�M\033[m",
		   user2->userid,
		   user2->win, user2->lose - 1, user2->tie);
	}
    } else if (line == 2 || line == 22) {
	outs("   ");
	if (line == 2)
	    for (i = 1; i <= 9; i++)
		prints("%s  ", num_str[0][i]);
	else
	    for (i = 9; i >= 1; i--)
		prints("%s  ", num_str[1][i]);
    }
}

static void
chc_redraw(const chcusr_t *user1, const chcusr_t *user2, board_t board)
{
    int             i;
    for (i = 0; i <= 22; i++)
	chc_drawline(board, user1, user2, i);
}
/*
 * End of the drawing function.
 */


/*
 * Start of the log function.
 */
int
chc_log_open(const chcusr_t *user1, const chcusr_t *user2, const char *file)
{
    char buf[128];
    if ((chcd->chessfp = fopen(file, "w")) == NULL)
	return -1;
    if(chcd->my == RED)
	sprintf(buf, "%s V.S. %s\n", user1->userid, user2->userid);
    else
	sprintf(buf, "%s V.S. %s\n", user2->userid, user1->userid);
    fputs(buf, chcd->chessfp);
    return 0;
}

void
chc_log_close(void)
{
    if (chcd->chessfp) {
	fclose(chcd->chessfp);
	chcd->chessfp=NULL;
    }
}

int
chc_log(const char *desc)
{
    if (chcd->chessfp)
	return fputs(desc, chcd->chessfp);
    return -1;
}

int
chc_log_step(board_t board, const rc_t *from, const rc_t *to)
{
    char buf[80];
    sprintf(buf, "  %s\n", chcd->last_movestr);
    return chc_log(buf);
}

static int
#if defined(__linux__)
chc_filter(const struct dirent *dir)
#else
chc_filter(struct dirent *dir)
#endif
{
    if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0 )
	return 0;
    return strstr(dir->d_name, ".poem") != NULL;
}

int
chc_log_poem(void)
{
    struct dirent **namelist;
    int n;

    // TODO use readdir(), don't use lots of memory
    n = scandir(BBSHOME"/etc/chess", &namelist, chc_filter, alphasort);
    if (n < 0)
	perror("scandir");
    else {
	char buf[80];
	FILE *fp;
	sprintf(buf, BBSHOME"/etc/chess/%s", namelist[random() % n]->d_name);
	if ((fp = fopen(buf, "r")) == NULL)
	    return -1;

	while(fgets(buf, sizeof(buf), fp) != NULL)
	    chc_log(buf);
	while(n--)
	    free(namelist[n]);
	free(namelist);
	fclose(fp);
    }
    return 0;
}
/*
 * End of the log function.
 */


/*
 * Start of the rule function.
 */

static void
chc_init_board(board_t board)
{
    memset(board, 0, sizeof(board_t));
    board[0][4] = CHE(KIND_K, chcd->my ^ 1);	/* �N */
    board[0][3] = board[0][5] = CHE(KIND_A, chcd->my ^ 1);	/* �h */
    board[0][2] = board[0][6] = CHE(KIND_E, chcd->my ^ 1);	/* �H */
    board[0][0] = board[0][8] = CHE(KIND_R, chcd->my ^ 1);	/* �� */
    board[0][1] = board[0][7] = CHE(KIND_H, chcd->my ^ 1);	/* �� */
    board[2][1] = board[2][7] = CHE(KIND_C, chcd->my ^ 1);	/* �] */
    board[3][0] = board[3][2] = board[3][4] =
	board[3][6] = board[3][8] = CHE(KIND_P, chcd->my ^ 1);	/* �� */

    board[9][4] = CHE(KIND_K, chcd->my);	/* �� */
    board[9][3] = board[9][5] = CHE(KIND_A, chcd->my);	/* �K */
    board[9][2] = board[9][6] = CHE(KIND_E, chcd->my);	/* �� */
    board[9][0] = board[9][8] = CHE(KIND_R, chcd->my);	/* �� */
    board[9][1] = board[9][7] = CHE(KIND_H, chcd->my);	/* �X */
    board[7][1] = board[7][7] = CHE(KIND_C, chcd->my);	/* �� */
    board[6][0] = board[6][2] = board[6][4] =
	board[6][6] = board[6][8] = CHE(KIND_P, chcd->my);	/* �L */
}

static void
chc_movechess(board_t board)
{
    board[chcd->to.r][chcd->to.c] = board[chcd->from.r][chcd->from.c];
    board[chcd->from.r][chcd->from.c] = 0;
}

/* �D��y�Ц�ΦC(rowcol)���Z�� */
static int
dist(rc_t from, rc_t to, int rowcol)
{
    int             d;

    d = rowcol ? from.c - to.c : from.r - to.r;
    return d > 0 ? d : -d;
}

/* ��y��(��ΦCrowcol)�������X���Ѥl */
static int
between(board_t board, rc_t from, rc_t to, int rowcol)
{
    int             i, rtv = 0;

    if (rowcol) {
	if (from.c > to.c)
	    i = from.c, from.c = to.c, to.c = i;
	for (i = from.c + 1; i < to.c; i++)
	    if (board[to.r][i])
		rtv++;
    } else {
	if (from.r > to.r)
	    i = from.r, from.r = to.r, to.r = i;
	for (i = from.r + 1; i < to.r; i++)
	    if (board[i][to.c])
		rtv++;
    }
    return rtv;
}

static int
chc_canmove(board_t board, rc_t from, rc_t to)
{
    int             i;
    int             rd, cd, turn;

    rd = dist(from, to, 0);
    cd = dist(from, to, 1);
    turn = CHE_O(board[from.r][from.c]);

    /* general check */
    if (board[to.r][to.c] && CHE_O(board[to.r][to.c]) == turn)
	return 0;

    /* individual check */
    switch (CHE_P(board[from.r][from.c])) {
    case KIND_K:		/* �N �� */
	if (!(rd == 1 && cd == 0) &&
	    !(rd == 0 && cd == 1))
	    return 0;
	if ((turn == (chcd->my ^ 1) && to.r > 2) ||
	    (turn == chcd->my && to.r < 7) ||
	    to.c < 3 || to.c > 5)
	    return 0;
	break;
    case KIND_A:		/* �h �K */
	if (!(rd == 1 && cd == 1))
	    return 0;
	if ((turn == (chcd->my ^ 1) && to.r > 2) ||
	    (turn == chcd->my && to.r < 7) ||
	    to.c < 3 || to.c > 5)
	    return 0;
	break;
    case KIND_E:		/* �H �� */
	if (!(rd == 2 && cd == 2))
	    return 0;
	if ((turn == (chcd->my ^ 1) && to.r > 4) ||
	    (turn == chcd->my && to.r < 5))
	    return 0;
	/* ��H�L */
	if (board[CENTER(from.r, to.r)][CENTER(from.c, to.c)])
	    return 0;
	break;
    case KIND_R:		/* �� */
	if (!(rd > 0 && cd == 0) &&
	    !(rd == 0 && cd > 0))
	    return 0;
	if (between(board, from, to, rd == 0))
	    return 0;
	break;
    case KIND_H:		/* �� �X */
	if (!(rd == 2 && cd == 1) &&
	    !(rd == 1 && cd == 2))
	    return 0;
	/* �䰨�} */
	if (rd == 2) {
	    if (board[CENTER(from.r, to.r)][from.c])
		return 0;
	} else {
	    if (board[from.r][CENTER(from.c, to.c)])
		return 0;
	}
	break;
    case KIND_C:		/* �] �� */
	if (!(rd > 0 && cd == 0) &&
	    !(rd == 0 && cd > 0))
	    return 0;
	i = between(board, from, to, rd == 0);
	if ((i > 1) ||
	    (i == 1 && !board[to.r][to.c]) ||
	    (i == 0 && board[to.r][to.c]))
	    return 0;
	break;
    case KIND_P:		/* �� �L */
	if (!(rd == 1 && cd == 0) &&
	    !(rd == 0 && cd == 1))
	    return 0;
	if (((turn == (chcd->my ^ 1) && to.r < 5) ||
	     (turn == chcd->my && to.r > 4)) &&
	    cd != 0)
	    return 0;
	if ((turn == (chcd->my ^ 1) && to.r < from.r) ||
	    (turn == chcd->my && to.r > from.r))
	    return 0;
	break;
    }
    return 1;
}

/* �� turn's king ���y�� */
static void
findking(board_t board, int turn, rc_t * buf)
{
    int             i, r, c;

    r = (turn == (chcd->my ^ 1)) ? 0 : 7;
    for (i = 0; i < 3; r++, i++)
	for (c = 3; c < 6; c++)
	    if (CHE_P(board[r][c]) == KIND_K &&
		CHE_O(board[r][c]) == turn) {
		buf->r = r, buf->c = c;
		return;
	    }
}

static int
chc_iskfk(board_t board)
{
    rc_t            from, to;

    findking(board, BLK, &to);
    findking(board, RED, &from);
    if (from.c == to.c && between(board, from, to, 0) == 0)
	return 1;
    return 0;
}

static int
chc_ischeck(board_t board, int turn)
{
    rc_t            from, to;

    findking(board, turn, &to);
    for (from.r = 0; from.r < BRD_ROW; from.r++)
	for (from.c = 0; from.c < BRD_COL; from.c++)
	    if (board[from.r][from.c] &&
		CHE_O(board[from.r][from.c]) != turn)
		if (chc_canmove(board, from, to))
		    return 1;
    return 0;
}

/*
 * End of the rule function.
 */

static void
chcusr_put(userec_t *userec, const chcusr_t *user)
{
    userec->chc_win = user->win;
    userec->chc_lose = user->lose;
    userec->chc_tie = user->tie;
    userec->chess_elo_rating = user->rating;
}

static void
chcusr_get(const userec_t *userec, chcusr_t *user)
{
    strlcpy(user->userid, userec->userid, sizeof(user->userid));
    user->win = userec->chc_win;
    user->lose = userec->chc_lose;
    user->tie = userec->chc_tie;
    user->rating = userec->chess_elo_rating;
    if(user->rating == 0)
	user->rating = 1500; /* ELO initial value */
    user->orig_rating = user->rating;
}

static int
hisplay(int s, const chcusr_t *user1, const chcusr_t *user2, board_t board, board_t tmpbrd)
{
    int             start_time;
    int             endgame = 0, endturn = 0;

    start_time = now;
    while (!endturn) {
	chcd->lefttime = CHC_TIMEOUT - (now - start_time);
	if (chcd->lefttime < 0) {
	    chcd->lefttime = 0;

	    /* to make him break out igetch() */
	    chcd->from.r = -2;
	    chc_broadcast_send(chcd->act_list, board);
	}
	chc_drawline(board, user1, user2, TIME_ROW);
	move(1, 0);
	oflush();
	switch (igetch()) {
	case 'q':
	    endgame = 2;
	    endturn = 1;
	    break;
	case 'p':
	    if (chcd->hepass) {
		chcd->from.r = -1;
		chc_broadcast_send(chcd->act_list, board);
		endgame = 3;
		endturn = 1;
	    }
	    break;
	case I_OTHERDATA:
	    if (!chc_broadcast_recv(chcd->act_list, board)) {	/* disconnect */
		endturn = 1;
		endgame = 1;
	    } else {
		if (chcd->from.r == -1) {
		    chcd->hepass = 1;
		    strlcpy(chcd->warnmsg, "\033[1;33m�n�D�M��!\033[m", sizeof(chcd->warnmsg));
		    chc_drawline(board, user1, user2, WARN_ROW);
		} else {
		    /* �y���ܴ�
		     *   (CHC_WATCH_PERSONAL �]�w��
		     *    ���[�Ѫ̬ݪ��ѧ�����H���Ъ��ѧ�)
		     *   �ѽL�ݭ˸m���M�p�~�n�ഫ
		     */
		    /* 1.�p�G�b�[�� �B�ѧ��O�O�H�b���� �B����A ��*/
		    if ( ((chcd->mode & CHC_WATCH) && (chcd->mode & CHC_WATCH_PERSONAL)) ||
			    /* 2.�ۤv�b���� */
			    (chcd->mode & CHC_PERSONAL) ||
			    ((chcd->mode & CHC_WATCH) && !chcd->turn)
			  )
			; // do nothing
		    else {
			chcd->from.r = 9 - chcd->from.r, chcd->from.c = 8 - chcd->from.c;
			chcd->to.r = 9 - chcd->to.r, chcd->to.c = 8 - chcd->to.c;
		    }
		    chcd->cursor = chcd->to;
		    if (CHE_P(board[chcd->to.r][chcd->to.c]) == KIND_K)
			endgame = 2;
		    endturn = 1;
		    chcd->hepass = 0;
		    getstep(board, &chcd->from, &chcd->to, chcd->last_movestr);
		    chc_drawline(board, user1, user2, STEP_ROW);
		    chc_log_step(board, &chcd->from, &chcd->to);
		    chc_movechess(board);
		    chc_drawline(board, user1, user2, LTR(chcd->from.r));
		    chc_drawline(board, user1, user2, LTR(chcd->to.r));
		}
	    }
	    break;
	}
    }
    return endgame;
}

static int
myplay(int s, const chcusr_t *user1, const chcusr_t *user2, board_t board, board_t tmpbrd)
{
    int             ch, start_time;
    int             endgame = 0, endturn = 0;

    chcd->ipass = 0, chcd->selected = 0;
    start_time = now;
    chcd->lefttime = CHC_TIMEOUT - (now - start_time);
    bell();
    while (!endturn) {
	chc_drawline(board, user1, user2, TIME_ROW);
	chc_movecur(chcd->cursor.r, chcd->cursor.c);
	oflush();
	ch = igetch();
	chcd->lefttime = CHC_TIMEOUT - (now - start_time);
	if (chcd->lefttime < 0)
	    ch = 'q';
	switch (ch) {
	case I_OTHERDATA:
	    if (!chc_broadcast_recv(chcd->act_list, board)) {	/* disconnect */
		endgame = 1;
		endturn = 1;
	    } else if (chcd->from.r == -1 && chcd->ipass) {
		endgame = 3;
		endturn = 1;
	    }
	    break;
	case KEY_UP:
	    chcd->cursor.r--;
	    if (chcd->cursor.r < 0)
		chcd->cursor.r = BRD_ROW - 1;
	    break;
	case KEY_DOWN:
	    chcd->cursor.r++;
	    if (chcd->cursor.r >= BRD_ROW)
		chcd->cursor.r = 0;
	    break;
	case KEY_LEFT:
	    chcd->cursor.c--;
	    if (chcd->cursor.c < 0)
		chcd->cursor.c = BRD_COL - 1;
	    break;
	case KEY_RIGHT:
	    chcd->cursor.c++;
	    if (chcd->cursor.c >= BRD_COL)
		chcd->cursor.c = 0;
	    break;
	case 'q':
	    endgame = 2;
	    endturn = 1;
	    break;
	case 'p':
	    chcd->ipass = 1;
	    chcd->from.r = -1;
	    chc_broadcast_send(chcd->act_list, board);
	    strlcpy(chcd->warnmsg, "\033[1;33m�n�D�M��!\033[m", sizeof(chcd->warnmsg));
	    chc_drawline(board, user1, user2, WARN_ROW);
	    bell();
	    break;
	case '\r':
	case '\n':
	case ' ':
	    if (chcd->selected) {
		if (chcd->cursor.r == chcd->select.r &&
		    chcd->cursor.c == chcd->select.c) {
		    chcd->selected = 0;
		    chc_drawline(board, user1, user2, LTR(chcd->cursor.r));
		} else if (chc_canmove(board, chcd->select, chcd->cursor)) {
		    if (CHE_P(board[chcd->cursor.r][chcd->cursor.c]) == KIND_K)
			endgame = 1;
		    chcd->from = chcd->select;
		    chcd->to = chcd->cursor;
		    if (!endgame) {
			memcpy(tmpbrd, board, sizeof(board_t));
			chc_movechess(tmpbrd);
		    }
		    if (endgame || !chc_iskfk(tmpbrd)) {
			getstep(board, &chcd->from, &chcd->to, chcd->last_movestr);
			chc_drawline(board, user1, user2, STEP_ROW);
			chc_log_step(board, &chcd->from, &chcd->to);
			chc_movechess(board);
			chc_broadcast_send(chcd->act_list, board);
			chcd->selected = 0;
			chc_drawline(board, user1, user2, LTR(chcd->from.r));
			chc_drawline(board, user1, user2, LTR(chcd->to.r));
			endturn = 1;
		    } else {
			strlcpy(chcd->warnmsg, "\033[1;33m���i�H������\033[m", sizeof(chcd->warnmsg));
			bell();
			chc_drawline(board, user1, user2, WARN_ROW);
		    }
		}
	    } else if (board[chcd->cursor.r][chcd->cursor.c] &&
		     CHE_O(board[chcd->cursor.r][chcd->cursor.c]) == chcd->turn) {
		chcd->selected = 1;
		chcd->select = chcd->cursor;
		chc_drawline(board, user1, user2, LTR(chcd->cursor.r));
	    }
	    break;
	}
    }
    return endgame;
}

int round_to_int(double x)
{
    /* assume that double cast to int will drop fraction parts */
    if(x>=0)
	return (int)(x+0.5);
    return (int)(x-0.5);
}
    
/*
 * ELO rating system
 * see http://www.wordiq.com/definition/ELO_rating_system
 */
static void
count_chess_elo_rating(chcusr_t *user1, const chcusr_t *user2, double myres)
{
    double k;
    double exp_res;
    int diff;
    int newrating;

    if(user1->rating < 1800)
	k = 30;
    else if(user1->rating < 2000)
	k = 25;
    else if(user1->rating < 2200)
	k = 20;
    else if(user1->rating < 2400)
	k = 15;
    else
	k = 10;

    //exp_res = 1.0/(1.0 + pow(10.0, (user2->rating-user1->rating)/400.0));
    //user1->rating += (int)floor(k*(myres-exp_res)+0.5);
    diff=(int)user2->rating-(int)user1->rating;
    if(diff<=-1000 || diff>=1000)
       exp_res=diff>0?0.0:1.0;
    else if(diff>=0)
       exp_res=elo_exp_tab[diff];
    else
       exp_res=1.0-elo_exp_tab[-diff];
    newrating = (int)user1->rating + round_to_int(k*(myres-exp_res));
    if(newrating > 3000) newrating = 3000;
    if(newrating < 1) newrating = 1;
    user1->rating = newrating;
}

static void
mainloop(int s, chcusr_t *user1, chcusr_t *user2, board_t board, play_func_t play_func[2])
{
    int             endgame;
    char	    buf[80];
    board_t         tmpbrd;

    if (!(chcd->mode & CHC_WATCH))
	chcd->turn = 1;
    for (endgame = 0; !endgame; chcd->turn ^= 1) {
	chcd->firststep = 0;
	chc_drawline(board, user1, user2, TURN_ROW);
	if (chc_ischeck(board, chcd->turn)) {
	    strlcpy(chcd->warnmsg, "\033[1;31m�N�x!\033[m", sizeof(chcd->warnmsg));
	    bell();
	} else
	    chcd->warnmsg[0] = 0;
	chc_drawline(board, user1, user2, WARN_ROW);
	endgame = play_func[chcd->turn] (s, user1, user2, board, tmpbrd);
    }

    if (chcd->mode & CHC_VERSUS) {
	user1->rating = user1->orig_rating;
	user1->lose--;
	if(chcd->my==RED) {
	    /* �Ѭ���@ log. �O���O�U�ѫe����l���� */
	    /* NOTE, �Y�����_�u�h�L log */
	    time_t t=time(NULL);
	    char buf[100];
	    sprintf(buf, "%s %s(%d,W%d/D%d/L%d) %s %s(%d,W%d/D%d/L%d)\n", ctime(&t),
		    user1->userid, user1->rating, user1->win, user1->tie, user1->lose,
		    (endgame==3?"�M":endgame==1?"��":"�t"),
		    user2->userid, user2->rating, user2->win, user2->tie, user2->lose);
	    buf[24]=' '; // replace '\n'
	    log_file(BBSHOME"/log/chc.log", LOG_CREAT, buf);
	}
	if (endgame == 1) {
	    strlcpy(chcd->warnmsg, "���{��F!", sizeof(chcd->warnmsg));
	    count_chess_elo_rating(user1, user2, 1.0);
	    user1->win++;
	    currutmp->chc_win++;
	} else if (endgame == 2) {
	    strlcpy(chcd->warnmsg, "�A�{��F!", sizeof(chcd->warnmsg));
	    count_chess_elo_rating(user1, user2, 0.0);
	    user1->lose++;
	    currutmp->chc_lose++;
	} else {
	    strlcpy(chcd->warnmsg, "�M��", sizeof(chcd->warnmsg));
	    count_chess_elo_rating(user1, user2, 0.5);
	    user1->tie++;
	    currutmp->chc_tie++;
	}
	currutmp->chess_elo_rating = user1->rating;
	chcusr_put(&cuser, user1);
	passwd_update(usernum, &cuser);
    }
    else if (chcd->mode & CHC_WATCH) {
	strlcpy(chcd->warnmsg, "�����[��", sizeof(chcd->warnmsg));
    }
    else {
	strlcpy(chcd->warnmsg, "��������", sizeof(chcd->warnmsg));
    }

    chc_log("=> ");
    if (endgame == 3)
	chc_log("�M��");
    else{
	sprintf(buf, "%s��\n", (chcd->my==RED) == (endgame == 1) ? "��" : "��");
	chc_log(buf);
    }

    chc_drawline(board, user1, user2, WARN_ROW);
    bell();
    oflush();
}

static void
chc_init_play_func(chcusr_t *user1, chcusr_t *user2, play_func_t play_func[2])
{
    char	    userid[2][IDLEN + 1];
    userec_t        xuser;

    if (chcd->mode & CHC_PERSONAL) {
	strlcpy(userid[0], cuser.userid, sizeof(userid[0]));
	strlcpy(userid[1], cuser.userid, sizeof(userid[1]));
	play_func[0] = play_func[1] = myplay;
    }
    else if (chcd->mode & CHC_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]));
	play_func[0] = play_func[1] = hisplay;
    }
    else {
	strlcpy(userid[0], cuser.userid, sizeof(userid[0]));
	strlcpy(userid[1], currutmp->mateid, sizeof(userid[1]));
	play_func[chcd->my] = myplay;
	play_func[chcd->my ^ 1] = hisplay;
    }

    getuser(userid[0], &xuser);
    chcusr_get(&xuser, user1);
    getuser(userid[1], &xuser);
    chcusr_get(&xuser, user2);
}

static void
chc_watch_request(int signo)
{
    chc_act_list *tmp;
#if _TO_SYNC_
    sigset_t mask;
#endif

    if (!(currstat & CHC))
	return;
    if(chcd == NULL) return;
    for(tmp = chcd->act_list; tmp->next != NULL; tmp = tmp->next); // XXX �@�w�n���b�̫��?
    tmp->next = (chc_act_list *)malloc(sizeof(chc_act_list));
    tmp->next->sock = establish_talk_connection(&SHM->uinfo[currutmp->destuip]);
    if (tmp->next->sock < 0) {
       free(tmp->next);
       tmp->next = NULL;
       return;
    }

    tmp = tmp->next;
    tmp->next = NULL;

#if _TO_SYNC_
    /* �ɥ� SIGALRM */
    sigfillset(&mask);
    sigdelset(&mask, SIGALRM);
    sigsuspend(&mask);
#endif

    /* what if the spectator get off-line intentionally !? (SIGPIPE) */
    write(tmp->sock, chcd->bp, sizeof(board_t));
    write(tmp->sock, &chcd->my, sizeof(chcd->my));
    write(tmp->sock, &chcd->turn, sizeof(chcd->turn));
    write(tmp->sock, &currutmp->turn, sizeof(currutmp->turn));
    write(tmp->sock, &chcd->firststep, sizeof(chcd->firststep));
    write(tmp->sock, &chcd->mode, sizeof(chcd->mode));
}

static int 
chc_init(int s, chcusr_t *user1, chcusr_t *user2, board_t board, play_func_t play_func[2])
{
    userinfo_t     *my = currutmp;
    userec_t        xuser;

    if (chcd->mode & CHC_WATCH)
	setutmpmode(CHESSWATCHING);
    else
	setutmpmode(CHC);
    clear();
    chcd->warnmsg[0] = 0;

    /* �q���P�ӷ���l�ƦU���ܼ� */
    if (!(chcd->mode & CHC_WATCH)) {
	if (chcd->mode & CHC_PERSONAL)
	    chcd->my = RED;
	else
	    chcd->my = my->turn;
	chcd->firststep = 1;
	chc_init_board(board);
	chcd->cursor.r = 9, chcd->cursor.c = 0;
    }
    else {
	char mode;
	userinfo_t *uin = &SHM->uinfo[currutmp->destuip];
	if (uin == NULL)
	    return -1;
#if _TO_SYNC_
	// choose one signal execpt SIGUSR1
	kill(uin->pid, SIGALRM);
#endif
	if(read(s, board, sizeof(board_t)) != sizeof(board_t) ||
		read(s, &chcd->my, sizeof(chcd->my)) != sizeof(chcd->my) ||
		read(s, &chcd->turn, sizeof(chcd->turn)) != sizeof(chcd->turn) ||
		read(s, &my->turn, sizeof(my->turn)) != sizeof(my->turn) ||
		read(s, &chcd->firststep, sizeof(chcd->firststep))
		!= sizeof(chcd->firststep) ||
		read(s, &mode, sizeof(mode)) != sizeof(mode)){
	    add_io(0, 0);
	    close(s);
	    return -1;
	}
	if (mode & CHC_PERSONAL)
	    chcd->mode |= CHC_WATCH_PERSONAL;
    }

    chcd->act_list = (chc_act_list *)malloc(sizeof(*chcd->act_list));
    chcd->act_list->sock = s;
    chcd->act_list->next = 0;

    chc_init_play_func(user1, user2, play_func);

    chc_redraw(user1, user2, board);
    add_io(s, 0);

    if (!(chcd->mode & CHC_WATCH)) {
	Signal(SIGUSR1, chc_watch_request);
    }

//    if (my->turn && !(chcd->mode & CHC_WATCH))
//	chc_broadcast_recv(chcd->act_list, board);

    if (chcd->mode & CHC_VERSUS) {
	user1->lose++;
	count_chess_elo_rating(user1, user2, 0.0);
	passwd_query(usernum, &xuser);
	chcusr_put(&xuser, user1);
	passwd_update(usernum, &xuser);
    }

    if (!my->turn) {
	if (!(chcd->mode & CHC_WATCH))
	    chc_broadcast_send(chcd->act_list, board);
	if (chcd->mode & CHC_VERSUS)
	    user2->lose++;
    }
    chc_redraw(user1, user2, board);

    return 0;
}

/* �H�ѥ\��i�J�I:
 * chc_main: �﫳
 * chc_personal: ����
 * chc_watch: �[��
 * talk.c: �﫳
 */
void
chc(int s, int mode)
{
    chcusr_t	    user1, user2;
    play_func_t     play_func[2];
    board_t	    board;
    char	    mode0 = currutmp->mode;
    char	    file[80];

    if(chcd != NULL) {
	vmsg("�H�ѥ\\�ಧ�`");
	return;
    }
    chcd = (struct CHCData*)malloc(sizeof(struct CHCData));
    if(chcd == NULL) {
	vmsg("����H�ѥ\\�ॢ��");
	return;
    }
    memset(chcd, 0, sizeof(struct CHCData));
    chcd->mode = mode;

    if (!(chcd->mode & CHC_WATCH))
	Signal(SIGUSR1, SIG_IGN);

    chcd->bp = &board;
    if (chc_init(s, &user1, &user2, board, play_func) < 0) {
	free(chcd);
	chcd = NULL;
	return;
    }
    
    setuserfile(file, CHC_LOG);
    if (chc_log_open(&user1, &user2, file) < 0)
	vmsg("�L�k�����ѧ�");
    
    mainloop(s, &user1, &user2, board, play_func);

    /* close these fd */
    if (chcd->mode & CHC_PERSONAL)
	chcd->act_list = chcd->act_list->next;
    while(chcd->act_list){
	close(chcd->act_list->sock);
	chcd->act_list = chcd->act_list->next;
    }

    add_io(0, 0);
    if (chcd->my == RED)
	pressanykey();

    currutmp->mode = mode0;

    if (getans("�O�_�N���бH�^�H�c�H[N/y]") == 'y') {
	char title[80];
	if(chcd->my == RED)
	    sprintf(title, "%s V.S. %s", user1.userid, user2.userid);
	else
	    sprintf(title, "%s V.S. %s", user2.userid, user1.userid);
	chc_log("\n--\n\n");
	chc_log_poem();
	chc_log_close();
	mail_id(cuser.userid, title, file, "[���e�~��]");
    }
    else
	chc_log_close();

    if (!(chcd->mode & CHC_WATCH))
	Signal(SIGUSR1, talk_request);

    free(chcd);
    chcd = NULL;
}

static userinfo_t *
chc_init_utmp(void)
{
    char            uident[16];
    userinfo_t	   *uin;

    stand_title("���e�~�ɤ���");
    CompleteOnlineUser(msg_uid, uident);
    if (uident[0] == '\0')
	return NULL;

    if ((uin = search_ulist_userid(uident)) == NULL)
	return NULL;

    uin->sig = SIG_CHC;
    return uin;
}

int
chc_main(void)
{
    userinfo_t     *uin;
    
    if ((uin = chc_init_utmp()) == 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));
    
    my_talk(uin, friend_stat(currutmp, uin), 'c');
    return 0;
}

int
chc_personal(void)
{
    chc(0, CHC_PERSONAL);
    return 0;
}

int
chc_watch(void)
{
    int 	    sock, msgsock;
    userinfo_t     *uin;

    if ((uin = chc_init_utmp()) == NULL)
	return -1;

    if (uin->uid == currutmp->uid || uin->mode != CHC)
	return -1;

    if (getans("�O�_�i���[��? [N/y]") != 'y')
	return 0;

    if ((sock = make_connection_to_somebody(uin, 10)) < 0) {
	vmsg("�L�k�إ߳s�u");
	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));
    chc(msgsock, CHC_WATCH);
    close(msgsock);
    return 0;
}