/* $Id$ */
#include "bbs.h"
#define MAX_TIME (300)
#define BRDSIZ (8) /* 棋盤單邊大小 */
#define NONE_CHESS " "
#define WHITE_CHESS "●"
#define BLACK_CHESS "○"
#define HINT_CHESS "#"
#define NONE 0
#define HINT 1
#define BLACK 2
#define WHITE 3
#define STARTY 10
#define INVERT(COLOR) (((COLOR))==WHITE?BLACK:WHITE)
#define IS_BLANK(COLOR) ((COLOR) < BLACK) /* NONE or HINT */
#define IS_CHESS(COLOR) ((COLOR) >= BLACK)
#define TURN_TO_COLOR(TURN) (WHITE - (TURN))
#define COLOR_TO_TURN(COLOR) (WHITE - (COLOR))
typedef char color_t;
typedef color_t board_t[BRDSIZ + 2][BRDSIZ + 2];
typedef color_t (*board_p)[BRDSIZ + 2];
/* [0] & [9] are dummy */
typedef struct {
ChessStepType type; /* necessary one */
color_t color;
rc_t loc;
} reversi_step_t;
typedef struct {
int number[2];
} reversi_tag_t;
/* chess framework action functions */
static void reversi_init_user(const userinfo_t *uinfo, ChessUser *user);
static void reversi_init_user_userec(const userec_t *urec, ChessUser *user);
static void reversi_init_board(board_t board);
static void reversi_drawline(const ChessInfo* info, int line);
static void reversi_movecur(int r, int c);
static int reversi_prepare_play(ChessInfo* info);
static int reversi_select(ChessInfo* info, rc_t scrloc, ChessGameResult* result);
static void reversi_prepare_step(ChessInfo* info, const reversi_step_t* step);
static ChessGameResult reversi_apply_step(board_t board, const reversi_step_t* step);
static void reversi_drawstep(ChessInfo* info, const void* move);
static ChessGameResult reversi_post_game(ChessInfo* info);
static void reversi_gameend(ChessInfo* info, ChessGameResult result);
static void reversi_genlog(ChessInfo* info, FILE* fp, ChessGameResult result);
static const char *CHESS_TYPE[] = {NONE_CHESS, HINT_CHESS, BLACK_CHESS, WHITE_CHESS};
static const char DIRX[] = {-1, -1, -1, 0, 1, 1, 1, 0};
static const char DIRY[] = {-1, 0, 1, 1, 1, 0, -1, -1};
static const ChessActions reversi_actions = {
&reversi_init_user,
&reversi_init_user_userec,
(void (*) (void*)) &reversi_init_board,
&reversi_drawline,
&reversi_movecur,
&reversi_prepare_play,
NULL, /* process_key */
&reversi_select,
(void (*)(ChessInfo*, const void*)) &reversi_prepare_step,
(ChessGameResult (*)(void*, const void*)) &reversi_apply_step,
&reversi_drawstep,
&reversi_post_game,
&reversi_gameend,
&reversi_genlog
};
static const ChessConstants reversi_constants = {
sizeof(reversi_step_t),
MAX_TIME,
BRDSIZ,
BRDSIZ,
0,
"黑白棋",
"photo_reversi",
#ifdef BN_REVERSI_LOG
BN_REVERSI_LOG,
#else
NULL,
#endif
{ "", "" },
{ "白棋", "黑棋" },
};
static int
can_put(board_t board, color_t who, int x, int y)
{
int i, temp, checkx, checky;
if (IS_BLANK(board[x][y]))
for (i = 0; i < 8; ++i) {
checkx = x + DIRX[i];
checky = y + DIRY[i];
temp = board[checkx][checky];
if (IS_BLANK(temp))
continue;
if (temp != who) {
while (board[checkx += DIRX[i]][checky += DIRY[i]] == temp);
if (board[checkx][checky] == who)
return 1;
}
}
return 0;
}
static int
caculate_hint(board_t board, color_t who)
{
int i, j, count = 0;
for (i = 1; i <= 8; i++)
for (j = 1; j <= 8; j++) {
if (board[i][j] == HINT)
board[i][j] = NONE;
if (can_put(board, who, i, j)) {
board[i][j] = HINT;
++count;
}
}
return count;
}
static void
reversi_init_user(const userinfo_t* uinfo, ChessUser* user)
{
strlcpy(user->userid, uinfo->userid, sizeof(user->userid));
user->win =
user->lose =
user->tie = 0;
}
static void
reversi_init_user_userec(const userec_t* urec, ChessUser* user)
{
strlcpy(user->userid, urec->userid, sizeof(user->userid));
user->win =
user->lose =
user->tie = 0;
}
static void
reversi_init_board(board_t board)
{
memset(board, NONE, sizeof(board_t));
board[4][4] = board[5][5] = WHITE;
board[4][5] = board[5][4] = BLACK;
caculate_hint(board, BLACK);
}
static void
reversi_drawline(const ChessInfo* info, int line){
static const char* num_str[] =
{"", "1", "2", "3", "4", "5", "6", "7", "8"};
if(line)
move(line, STARTY);
if (line == 0) {
prints(ANSI_COLOR(1;46) " 黑白棋對戰 " ANSI_COLOR(45)
"%30s VS %-20s%10s" ANSI_RESET,
info->user1.userid, info->user2.userid,
info->mode == CHESS_MODE_WATCH ? "[觀棋模式]" : "");
} else if (line == 2)
outs(" A B C D E F G H");
else if (line == 3)
outs("┌─┬─┬─┬─┬─┬─┬─┬─┐");
else if (line == 19)
outs("└─┴─┴─┴─┴─┴─┴─┴─┘");
else if (line == 20)
prints(" (" BLACK_CHESS ") %-15s%2d%*s",
info->myturn ? info->user1.userid : info->user2.userid,
((reversi_tag_t*)info->tag)->number[COLOR_TO_TURN(BLACK)],
34 - 24, "");
else if (line == 21)
prints(" (" WHITE_CHESS ") %-15s%2d%*s",
info->myturn ? info->user2.userid : info->user1.userid,
((reversi_tag_t*)info->tag)->number[COLOR_TO_TURN(WHITE)],
34 - 24, "");
else if (line > 3 && line < 19) {
if ((line & 1) == 1)
outs("├─┼─┼─┼─┼─┼─┼─┼─┤");
else {
int x = line / 2 - 1;
int y;
board_p board = (board_p) info->board;
move(line, STARTY - 2);
prints("%s│", num_str[x]);
for(y = 1; y <= 8; ++y)
prints("%s│", CHESS_TYPE[(int) board[x][y]]);
}
}
ChessDrawExtraInfo(info, line, 4);
}
static void
reversi_movecur(int r, int c)
{
move(r * 2 + 4, c * 4 + STARTY + 2);
}
static int
reversi_prepare_play(ChessInfo* info)
{
int x, y;
int result;
board_p board = (board_p) info->board;
reversi_tag_t* tag = (reversi_tag_t*) info->tag;
tag->number[0] = tag->number[1] = 0;
for(x = 1; x <= 8; ++x)
for(y = 1; y <= 8; ++y)
if (IS_CHESS(board[x][y]))
++tag->number[COLOR_TO_TURN(board[x][y])];
result = !caculate_hint(board, TURN_TO_COLOR(info->turn));
if (result) {
reversi_step_t step = { .type = CHESS_STEP_SPECIAL, .color = TURN_TO_COLOR(info->turn) };
if (info->turn == info->myturn) {
ChessStepSend(info, &step);
ChessHistoryAppend(info, &step);
strcpy(info->last_movestr, "你必須放棄這一步!!");
} else {
ChessStepReceive(info, &step);
strcpy(info->last_movestr, "對方必須放棄這一步!!");
}
}
ChessRedraw(info);
return result;
}
static int
reversi_select(ChessInfo* info, rc_t loc, ChessGameResult* result GCC_UNUSED)
{
board_p board = (board_p) info->board;
++loc.r; ++loc.c;
if (can_put(board, TURN_TO_COLOR(info->turn), loc.r, loc.c)) {
reversi_step_t step = { CHESS_STEP_NORMAL,
TURN_TO_COLOR(info->turn), loc };
reversi_apply_step(board, &step);
snprintf(info->last_movestr, sizeof(info->last_movestr),
"%c%d", step.loc.c - 1 + 'A', step.loc.r);
ChessStepSend(info, &step);
ChessHistoryAppend(info, &step);
return 1;
} else
return 0;
}
static ChessGameResult
reversi_apply_step(board_t board, const reversi_step_t* step)
{
int i;
color_t opposite = INVERT(step->color);
if (step->type != CHESS_STEP_NORMAL)
return CHESS_RESULT_CONTINUE;
for (i = 0; i < 8; ++i) {
int x = step->loc.r;
int y = step->loc.c;
while (board[x += DIRX[i]][y += DIRY[i]] == opposite);
if (board[x][y] == step->color) {
x = step->loc.r;
y = step->loc.c;
while (board[x += DIRX[i]][y += DIRY[i]] == opposite)
board[x][y] = step->color;
}
}
board[step->loc.r][step->loc.c] = step->color;
return CHESS_RESULT_CONTINUE;
}
static void
reversi_prepare_step(ChessInfo* info, const reversi_step_t* step)
{
if (step->type == CHESS_STEP_NORMAL)
snprintf(info->last_movestr, sizeof(info->last_movestr),
"%c%d", step->loc.c - 1 + 'A', step->loc.r);
else if (step->color == TURN_TO_COLOR(info->myturn))
strcpy(info->last_movestr, "你必須放棄這一步!!");
else
strcpy(info->last_movestr, "對方必須放棄這一步!!");
}
static void
reversi_drawstep(ChessInfo* info, const void* move GCC_UNUSED)
{
ChessRedraw(info);
}
static ChessGameResult
reversi_post_game(ChessInfo* info)
{
int x, y;
board_p board = (board_p) info->board;
reversi_tag_t* tag = (reversi_tag_t*) info->tag;
tag->number[0] = tag->number[1] = 0;
for(x = 1; x <= 8; ++x)
for(y = 1; y <= 8; ++y)
if (board[x][y] == HINT)
board[x][y] = NONE;
else if (IS_CHESS(board[x][y]))
++tag->number[COLOR_TO_TURN(board[x][y])];
ChessRedraw(info);
if (tag->number[0] == tag->number[1])
return CHESS_RESULT_TIE;
else if (tag->number[(int) info->myturn] < tag->number[info->myturn ^ 1])
return CHESS_RESULT_LOST;
else
return CHESS_RESULT_WIN;
}
static void
reversi_gameend(ChessInfo* info GCC_UNUSED, ChessGameResult result GCC_UNUSED)
{
/* nothing to do now
* TODO game record */
}
static void
reversi_genlog(ChessInfo* info, FILE* fp, ChessGameResult result GCC_UNUSED)
{
char buf[ANSILINELEN] = "";
const int nStep = info->history.used;
int i;
VREFCUR cur;
cur = vcur_save();
for (i = 2; i <= 21; i++)
{
move(i, 0);
inansistr(buf, sizeof(buf)-1);
fprintf(fp, "%s\n", buf);
}
vcur_restore(cur);
fprintf(fp, "\n");
fprintf(fp, "按 z 可進入打譜模式\n");
fprintf(fp, "\n");
fprintf(fp, "<reversilog>\nblack:%s\nwhite:%s\n",
info->myturn ? info->user1.userid : info->user2.userid,
info->myturn ? info->user2.userid : info->user1.userid);
for (i = 0; i < nStep; ++i) {
const reversi_step_t* const step =
(const reversi_step_t*) ChessHistoryRetrieve(info, i);
if (step->type == CHESS_STEP_NORMAL)
fprintf(fp, "[%2d]%s ==> %c%-5d", i + 1,
CHESS_TYPE[(int) step->color],
'A' + step->loc.c - 1, step->loc.r);
else
fprintf(fp, "[%2d]%s ==> pass ", i + 1,
CHESS_TYPE[(int) step->color]);
if (i % 2)
fputc('\n', fp);
}
if (i % 2)
fputc('\n', fp);
fputs("</reversilog>\n", fp);
}
static int
reversi_loadlog(FILE *fp, ChessInfo *info)
{
char buf[256];
#define INVALID_ROW(R) ((R) <= 0 || (R) > 8)
#define INVALID_COL(C) ((C) <= 0 || (C) > 8)
while (fgets(buf, sizeof(buf), fp)) {
if (strcmp("</reversilog>\n", buf) == 0)
return 1;
else if (strncmp("black:", buf, 6) == 0 ||
strncmp("white:", buf, 6) == 0) {
/* /(black|white):([a-zA-Z0-9]+)/; $2 */
userec_t rec;
ChessUser *user = (buf[0] == 'b' ? &info->user1 : &info->user2);
chomp(buf);
if (getuser(buf + 6, &rec))
reversi_init_user_userec(&rec, user);
} else if (buf[0] == '[') {
/* "[ 1]● ==> C4 [ 2]○ ==> C5" */
reversi_step_t step = { .type = CHESS_STEP_NORMAL };
int c, r;
const char *p = buf;
int i;
for(i=0; i<2; i++) {
p = strchr(p, '>');
if (p == NULL) break;
++p; /* skip '>' */
while (*p && isspace(*p)) ++p;
if (!*p) break;
/* i=0, p -> "C4 ..." */
/* i=1, p -> "C5\n" */
if (strncmp(p, "pass", 4) == 0)
/* [..] .. => pass */
step.type = CHESS_STEP_SPECIAL;
else {
c = p[0] - 'A' + 1;
r = atoi(p + 1);
if (INVALID_COL(c) || INVALID_ROW(r))
break;
step.loc.r = r;
step.loc.c = c;
}
step.color = i==0 ? BLACK : WHITE;
ChessHistoryAppend(info, &step);
}
}
}
#undef INVALID_ROW
#undef INVALID_COL
return 0;
}
void
reversi(int s, ChessGameMode mode)
{
ChessInfo* info = NewChessInfo(&reversi_actions, &reversi_constants, s, mode);
board_t board;
reversi_tag_t tag = { { 2, 2 } }; /* will be overridden */
reversi_init_board(board);
info->board = board;
info->tag = &tag;
info->cursor.r = 3;
info->cursor.c = 3;
if (mode == CHESS_MODE_WATCH)
setutmpmode(CHESSWATCHING);
else
setutmpmode(REVERSI);
currutmp->sig = SIG_REVERSI;
ChessPlay(info);
DeleteChessInfo(info);
}
int
reversi_main(void)
{
return ChessStartGame('r', SIG_REVERSI, "黑白棋");
}
int
reversi_personal(void)
{
reversi(0, CHESS_MODE_PERSONAL);
return 0;
}
int
reversi_watch(void)
{
return ChessWatchGame(&reversi, REVERSI, "黑白棋");
}
ChessInfo*
reversi_replay(FILE* fp)
{
ChessInfo *info;
info = NewChessInfo(&reversi_actions, &reversi_constants,
0, CHESS_MODE_REPLAY);
if(!reversi_loadlog(fp, info)) {
DeleteChessInfo(info);
return NULL;
}
info->board = malloc(sizeof(board_t));
info->tag = malloc(sizeof(reversi_tag_t));
reversi_init_board(info->board);
/* tag will be initialized later */
return info;
}