summaryrefslogblamecommitdiffstats
path: root/mbbsd/go.c
blob: f92c82fd643b1b9018140d0d48d53bf267de49b7 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
          



                        




                                        
 








                                              


                                          















                                                                           
  
 




















                                                       
 
                                                     
 




                                                                       
                                             

























                                                                              
 















                                             

           
                                
 






                                                                   

 
                                                     
          
                                                              
 

















                                                               

         
 
                 

 


                                                    

             
                 
 


                                    
 
                                              

 
                            
            
                                                     




                                

                                                            

 
                      
           
                                                
 



















                                                                              
                     

                     
          


                 








                                                                     

                
                            
 



                      
 






                                              
     
 












                                                         


                    




                                                
     
 
                   

 

                                                                    
 

















                                                               
 
                 

 

                                         
 

                             
 

                              
 


                                    
 
                                                    


          
                                         


                                

                              




               

                                                   

         
                               




               

                                                    

        
                               




               

                                                   
     
                                




               

                                                    

        
                               
 
                           








                               

                      
 
                           

                                                         
                          





                  
                          
 




                                            
 
                                                      



                                
                                      
             





                                                            
                 

                
                                           
 










                                                                
        

                                                               

 

                                             
 











                                                                                

 

                          
 









                               
 



                                                               


                                
 
 



                                                              


                               
 
 




                                           
 



                                            



                               

                                      

                    
                    
                   













                                                                   
                                                     






                                                   
                












                                                                               
         
     
 

                                                    
 










                                                                          
 




                           
 
          







                                                          
 

                                                                     

             
 
 









                                                                   
             
 


                                                                          
 
                                   
 

                                               
                    

                                                                  
 







                                                              
 


                                                                 
 


                                                                                  
             
         


























                                                                            
 



                                                                  
 



                                                                 
 

                                                               
 




                                                                     
 

                                                    
 



                                                       
 









                                                                      
 






















                                                                              
                    


                                                               
 



                                                                      
             
















                                                                      

                                             
 
 


                                                   


                                        





                                                          
 


                                           
 


                                                          
 





                                                                         
 








                                                               
 



                                                            
 


                                                       
 



                                                
 




                                                                      
 
                                                       
 
                                                                      
 
                      
 
                                 
 

                  
 


                                                   





















                                                    


                          
 
 






                                                            
 




                                                             

     

                                                                       
 



                                         




                                                                  
 
















































                                                                          
     
                                   

 

                                  
 


                                                                        
 

                         
 

                        
 

                       
 


                                   
                              
                           
 
                    
 

                          
 




                                               
 





                                    
 


                   
                                                      
 
 




















                                                       


                        






































































































































                                                                       
 
/* $Id$ */

#include "bbs.h"
#include <sys/socket.h> 

#define BBLANK  (-1)    /* 空白 */
#define BWHITE  (0)     /* 白子, 後手 */
#define BBLACK  (1)     /* 黑子, 先手 */
#define LWHITE  (2)     /* 白空 */
#define LBLACK  (3)     /* 黑空 */

/* only used for communicating */
#define SETHAND (5)    /* 讓子 */
#define CLEAN   (6)    /* 清除死子 */
#define UNCLEAN (7)    /* 清除錯子,重新來過*/
#define CLEANDONE (8)  /* 開始計地 */

#define MAX_TIME (300)

#define BOARD_LINE_ON_SCREEN(X) ((X) + 2)

#define BRDSIZ  (19)    /* 棋盤單邊大小 */

static const char* turn_color[] = { ANSI_COLOR(37;43), ANSI_COLOR(30;43) };

static const rc_t SetHandPoints[] =
{
    /* 1 */ { 0,  0},
    /* 2 */ { 3,  3}, {15, 15},
    /* 3 */ { 3,  3}, { 3, 15}, {15, 15},
    /* 4 */ { 3,  3}, { 3, 15}, {15,  3}, {15, 15},
    /* 5 */ { 3,  3}, { 3, 15}, { 9,  9}, {15,  3}, {15, 15},
    /* 6 */ { 3,  3}, { 3, 15}, { 9,  3}, { 9, 15}, {15,  3}, {15, 15},
    /* 7 */ { 3,  3}, { 3, 15}, { 9,  3}, { 9,  9}, { 9, 15}, {15,  3},
        {15, 15},
    /* 8 */ { 3,  3}, { 3,  9}, { 3, 15}, { 9,  3}, { 9, 15}, {15,  3},
        {15,  9}, {15, 15},
    /* 9 */ { 3,  3}, { 3,  9}, { 3, 15}, { 9,  3}, { 9,  9}, { 9, 15},
        {15,  3}, {15,  9}, {15, 15},
};

typedef char board_t[BRDSIZ][BRDSIZ];
typedef char (*board_p)[BRDSIZ];

typedef struct {
    ChessStepType type;  /* necessary one */
    int           color;
    rc_t          loc;
} go_step_t;
#define RC_T_EQ(X,Y) ((X).r == (Y).r && (X).c == (Y).c)

typedef struct {
    board_t backup_board;
    char    game_end;
    char    clean_end;    /* bit 1 => I, bit 2 => he */
    char    need_redraw;
    float   feed_back;    /* 貼還 */
    int     eaten[2];
    int     backup_eaten[2];
    rc_t    forbidden[2]; /* 打劫之禁手 */
} go_tag_t;
#define GET_TAG(INFO) ((go_tag_t*)(INFO)->tag)

static char * const locE = "ABCDEFGHJKLMNOPQRST";

static void go_init_user(const userinfo_t* uinfo, ChessUser* user);
static void go_init_user_userec(const userec_t* urec, ChessUser* user);
static void go_init_board(board_t board);
static void go_drawline(const ChessInfo* info, int line);
static void go_movecur(int r, int c);
static int  go_prepare_play(ChessInfo* info);
static int  go_process_key(ChessInfo* info, int key, ChessGameResult* result);
static int  go_select(ChessInfo* info, rc_t location,
    ChessGameResult* result);
static void go_prepare_step(ChessInfo* info, const go_step_t* step);
static ChessGameResult go_apply_step(board_t board, const go_step_t* step);
static void go_drawstep(ChessInfo* info, const go_step_t* step);
static ChessGameResult go_post_game(ChessInfo* info);
static void go_gameend(ChessInfo* info, ChessGameResult result);
static void go_genlog(ChessInfo* info, FILE* fp, ChessGameResult result);

const static ChessActions go_actions = {
    &go_init_user,
    &go_init_user_userec,
    (void (*)(void*)) &go_init_board,
    &go_drawline,
    &go_movecur,
    &go_prepare_play,
    &go_process_key,
    &go_select,
    (void (*)(ChessInfo*, const void*)) &go_prepare_step,
    (ChessGameResult (*)(void*, const void*)) &go_apply_step,
    (void (*)(ChessInfo*, const void*)) &go_drawstep,
    &go_post_game,
    &go_gameend,
    &go_genlog
};

const static ChessConstants go_constants = {
    sizeof(go_step_t),
    MAX_TIME,
    BRDSIZ,
    BRDSIZ,
    1,
    "圍棋",
    "photo_go",
#ifdef GLOBAL_GOCHESS_LOG
    GLOBAL_GOCHESS_LOG,
#else
    NULL,
#endif
    { ANSI_COLOR(37;43), ANSI_COLOR(30;43) },
    { "白棋", "黑棋" },
};

static void
go_sethand(board_t board, int n)
{
    if (n >= 2 && n <= 9) {
    const int lower = n * (n - 1) / 2;
    const int upper = lower + n;
    int i;
    for (i = lower; i < upper; ++i)
        board[SetHandPoints[i].r][SetHandPoints[i].c] = BBLACK;
    }
}

/* 計算某子的氣數, recursion part of go_countlib() */
static int
go_count(board_t board, board_t mark, int x, int y, int color)
{
    const static int diff[][2] = {
    {1, 0}, {-1, 0}, {0, 1}, {0, -1}
    };
    int i;
    int total = 0;

    mark[x][y] = 0;

    for (i = 0; i < 4; ++i) {
    int xx = x + diff[i][0];
    int yy = y + diff[i][1];

    if (xx >= 0 && xx < BRDSIZ && yy >= 0 && yy < BRDSIZ) {
        if (board[xx][yy] == BBLANK && mark[xx][yy]) {
        ++total;
        mark[xx][yy] = 0;
        } else if (board[xx][yy] == color && mark[xx][yy])
        total += go_count(board, mark, xx, yy, color);
    }
    }

    return total;
}

/* 計算某子的氣數 */
static int
go_countlib(board_t board, int x, int y, char color)
{
    int i, j;
    board_t mark;

    for (i = 0; i < BRDSIZ; i++)
    for (j = 0; j < BRDSIZ; j++)
        mark[i][j] = 1;

    return go_count(board, mark, x, y, color);
}

/* 計算盤面上每個子的氣數 */
static void 
go_eval(board_t board, int lib[][BRDSIZ], char color)
{
    int i, j;

    for (i = 0; i < 19; i++)
    for (j = 0; j < 19; j++)
        if (board[i][j] == color)
        lib[i][j] = go_countlib(board, i, j, color);
}

/* 檢查一步是否合法 */
static int 
go_check(ChessInfo* info, const go_step_t* step)
{
    board_p board = (board_p) info->board;
    int lib = go_countlib(board, step->loc.r, step->loc.c, step->color);

    if (lib == 0) {
    int i, j;
    int board_lib[BRDSIZ][BRDSIZ];
    go_tag_t* tag = (go_tag_t*) info->tag;

    board[step->loc.r][step->loc.c] = step->color;
    go_eval(board, board_lib, !step->color);
    board[step->loc.r][step->loc.c] = BBLANK;   /* restore to open */

    lib = 0;
    for (i = 0; i < BRDSIZ; i++)
        for (j = 0; j < BRDSIZ; j++)
        if (board[i][j] == !step->color && !board_lib[i][j])
            ++lib;

    if (lib == 0 ||
        (lib == 1 && RC_T_EQ(step->loc, tag->forbidden[step->color])))
        return 0;
    else
        return 1;
    } else
    return 1;
}

/* Clean up the dead chess of color `color,' summarize number of
 * eaten chesses and set the forbidden point.
 *
 * `info' might be NULL which means no forbidden point check is
 * needed and don't have to count the number of eaten chesses.
 *
 * Return: 1 if any chess of color `color' was eaten; 0 otherwise. */
static int
go_examboard(board_t board, int color, ChessInfo* info)
{
    int i, j, n;
    int lib[BRDSIZ][BRDSIZ];

    rc_t  dummy_rc;
    rc_t *forbidden;
    int   dummy_eaten;
    int  *eaten;

    if (info) {
    go_tag_t* tag = (go_tag_t*) info->tag;
    forbidden = &tag->forbidden[color];
    eaten     = &tag->eaten[!color];
    } else {
    forbidden = &dummy_rc;
    eaten     = &dummy_eaten;
    }

    go_eval(board, lib, color);

    forbidden->r = -1;
    forbidden->c = -1;

    n = 0;
    for (i = 0; i < BRDSIZ; i++)
    for (j = 0; j < BRDSIZ; j++)
        if (board[i][j] == color && lib[i][j] == 0) {
        board[i][j] = BBLANK;
        forbidden->r = i;
        forbidden->c = j;
        ++*eaten;
        ++n;
        }

    if ( n != 1 ) {
    /* No or more than one chess were eaten,
     * no forbidden points, then. */
    forbidden->r = -1;
    forbidden->c = -1;
    }

    return (n > 0);
}

static int
go_clean(board_t board, int mark[][BRDSIZ], int x, int y, int color)
{
    const static int diff[][2] = {
    {1, 0}, {-1, 0}, {0, 1}, {0, -1}
    };
    int i;
    int total = 1;

    mark[x][y] = 0;
    board[x][y] = BBLANK;

    for (i = 0; i < 4; ++i) {
    int xx = x + diff[i][0];
    int yy = y + diff[i][1];

    if (xx >= 0 && xx < BRDSIZ && yy >= 0 && yy < BRDSIZ) {
        if ((board[xx][yy] == color) && mark[xx][yy])
        total += go_clean(board, mark, xx, yy, color);
    }
    }

    return total;
}

static int
go_cleandead(board_t board, int x, int y)
{
    int mark[BRDSIZ][BRDSIZ];
    int i, j;

    if (board[x][y] == BBLANK)
    return 0;

    for (i = 0; i < BRDSIZ; i++)
    for (j = 0; j < BRDSIZ; j++)
        mark[i][j] = 1;

    return go_clean(board, mark, x, y, board[x][y]);
}

static int
go_findcolor(board_p board, int x, int y)
{
    int k, result = 0, color[4];

    if (board[x][y] != BBLANK)
    return BBLANK;

    if (x > 0)
    {
    k = x;
    do --k;
    while ((board[k][y] == BBLANK) && (k > 0));
    color[0] = board[k][y];
    }
    else 
    color[0] = board[x][y];

    if (x < 18)
    {
    k = x;
    do ++k;
    while ((board[k][y] == BBLANK) && (k < 18));
    color[1] = board[k][y];
    }
    else
    color[1] = board[x][y];

    if (y > 0)
    {
    k = y;
    do --k;
    while ((board[x][k] == BBLANK) && (k > 0));
    color[2] = board[x][k];
    }
    else color[2] = board[x][y];

    if (y < 18)
    {
    k = y;
    do ++k;
    while ((board[x][k] == BBLANK) && (k < 18));
    color[3] = board[x][k];
    }
    else
    color[3] = board[x][y];

    for (k = 0; k < 4; k++)
    {
    if (color[k] == BBLANK)
        continue;
    else
    {
        result = color[k];
        break;
    }
    }
    if (k == 4)
    return BBLANK;

    for (k = 0; k < 4; k++)
    {
    if ((color[k] != BBLANK) && (color[k] != result))
        return BBLANK;
    }

    return result;
}

static int
go_result(ChessInfo* info)
{
    int       i, j;
    int       count[2];
    board_p   board = (board_p) info->board;
    go_tag_t *tag   = (go_tag_t*) info->tag;
    board_t   result_board;

    memcpy(result_board, board, sizeof(result_board));
    count[0] = count[1] = 0;

    for (i = 0; i < 19; i++)
    for (j = 0; j < 19; j++)
        if (board[i][j] == BBLANK)
        {
        int result = go_findcolor(board, i, j);
        if (result != BBLANK) {
            count[result]++;

            /* BWHITE => LWHITE, BBLACK => LBLACK */
            result_board[i][j] = result + 2;
        }
        }
        else
        count[(int) board[i][j]]++;

    memcpy(board, result_board, sizeof(result_board));

    /* 死子回填 */
    count[0] -= tag->eaten[1];
    count[1] -= tag->eaten[0];

    tag->eaten[0] = count[0];
    tag->eaten[1] = count[1];

    if (tag->feed_back < 0.01 && tag->eaten[0] == tag->eaten[1])
    return BBLANK; /* tie */
    else
    return tag->eaten[0] + tag->feed_back > tag->eaten[1] ?
        BWHITE : BBLACK;
}

static char*
go_getstep(const go_step_t* step, char buf[])
{
    const static char* const ColName = "ABCDEFGHJKLMNOPQRST";
    const static char* const RawName = "19181716151413121110987654321";
    const static int ansi_length     = sizeof(ANSI_COLOR(30;43)) - 1;

    strcpy(buf, turn_color[step->color]);
    buf[ansi_length    ] = ColName[step->loc.c * 2];
    buf[ansi_length + 1] = ColName[step->loc.c * 2 + 1];
    buf[ansi_length + 2] = RawName[step->loc.r * 2];
    buf[ansi_length + 3] = RawName[step->loc.r * 2 + 1];
    strcpy(buf + ansi_length + 4, ANSI_RESET "     ");

    return buf;
}

static void
go_init_tag(go_tag_t* tag)
{
    tag->game_end        = 0;
    tag->need_redraw     = 0;
    tag->feed_back       = 5.5;
    tag->eaten[0]        = 0;
    tag->eaten[1]        = 0;
    tag->forbidden[0].r  = -1;
    tag->forbidden[0].c  = -1;
    tag->forbidden[1].r  = -1;
    tag->forbidden[1].c  = -1;
}

static void
go_init_user(const userinfo_t* uinfo, ChessUser* user)
{
    strlcpy(user->userid, uinfo->userid, sizeof(user->userid));
    user->win  = uinfo->go_win;
    user->lose = uinfo->go_lose;
    user->tie  = uinfo->go_tie;
}

static void
go_init_user_userec(const userec_t* urec, ChessUser* user)
{
    strlcpy(user->userid, urec->userid, sizeof(user->userid));
    user->win  = urec->go_win;
    user->lose = urec->go_lose;
    user->tie  = urec->go_tie;
}

static void
go_init_board(board_t board)
{
    memset(board, BBLANK, sizeof(board_t));
}

static void
go_drawline(const ChessInfo* info, int line)
{
    const static char* const BoardPic[] = {
    "╔", "╤", "╤", "╗",
    "╟", "┼", "┼", "╢",
    "╟", "┼", "+", "╢",
    "╚", "╧", "╧", "╝",
    };
    const static int BoardPicIndex[] =
    { 0, 1, 1, 2, 1,
      1, 1, 1, 1, 2,
      1, 1, 1, 1, 1,
      2, 1, 1, 3 };

    board_p board = (board_p) info->board;
    go_tag_t* tag = (go_tag_t*) info->tag;

    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 == 1) {
    outs("   A B C D E F G H J K L M N O P Q R S T");
    } else if (line >= 2 && line <= 20) {
    const int board_line = line - 2;
    const char* const* const pics =
        &BoardPic[BoardPicIndex[board_line] * 4];
    int i;

    prints("%2d" ANSI_COLOR(30;43), 21 - line);

    for (i = 0; i < BRDSIZ; ++i)
        if (board[board_line][i] == BBLANK)
        outs(pics[BoardPicIndex[i]]);
        else
        outs(bw_chess[(int) board[board_line][i]]);

    outs(ANSI_RESET);
    } else if (line >= 21 && line < b_lines)
    prints("%40s", "");
    else if (line == b_lines) {
    if (info->mode == CHESS_MODE_VERSUS ||
        info->mode == CHESS_MODE_PERSONAL) {
        if (tag->game_end)
        outs(ANSI_COLOR(31;47) "(w)" ANSI_COLOR(30) "計地" ANSI_RESET);
        else if (info->history.used == 0 && (info->myturn == BWHITE
            || info->mode == CHESS_MODE_PERSONAL))
        outs(ANSI_COLOR(31;47) "(x)" ANSI_COLOR(30) "授子" ANSI_RESET);
    }
    }

    if (line == 1 || line == 2) {
    int color = line - 1; /* BWHITE or BBLACK */

    if (tag->game_end && tag->clean_end == 3)
        prints("   " ANSI_COLOR(30;43) "%s" ANSI_RESET
            " 方子空:%3.1f", bw_chess[color],
            tag->eaten[color] +
            (color == BWHITE ? tag->feed_back : 0.0));
    else
        prints("   " ANSI_COLOR(30;43) "%s" ANSI_RESET
            " 方提子數:%3d", bw_chess[color], tag->eaten[color]);
    } else
    ChessDrawExtraInfo(info, line, 3);
}

static void
go_movecur(int r, int c)
{
    move(r + 2, c * 2 + 3);
}

static int
go_prepare_play(ChessInfo* info)
{
    if (((go_tag_t*) info->tag)->game_end) {
    strlcpy(info->warnmsg, "請清除死子,以便計算勝負",
        sizeof(info->warnmsg));
    if (info->last_movestr[0] != ' ')
        strcpy(info->last_movestr, "        ");
    }

    if (info->history.used == 1)
    ChessDrawLine(info, b_lines); /* clear the 'x' instruction */

    return 0;
}

static int
go_process_key(ChessInfo* info, int key, ChessGameResult* result)
{
    go_tag_t* tag = (go_tag_t*) info->tag;
    if (tag->game_end) {
    if (key == 'w') {
        if (!(tag->clean_end & 1)) {
        go_step_t step = { CHESS_STEP_SPECIAL, CLEANDONE };
        ChessStepSend(info, &step);
        tag->clean_end |= 1;
        }

        if (tag->clean_end & 2 || info->mode == CHESS_MODE_PERSONAL) {
        /* both sides agree */
        int winner = go_result(info);

        tag->clean_end = 3;

        if (winner == BBLANK)
            *result = CHESS_RESULT_TIE;
        else
            *result = (winner == info->myturn ?
                CHESS_RESULT_WIN : CHESS_RESULT_LOST);

        ChessRedraw(info);
        return 1;
        }
    } else if (key == 'u') {
        char buf[4];
        getdata(b_lines, 0, "是否真的要重新點死子? (y/N)",
            buf, sizeof(buf), DOECHO);
        ChessDrawLine(info, b_lines);

        if (buf[0] == 'y' || buf[0] == 'Y') {
        go_step_t step = { CHESS_STEP_SPECIAL, UNCLEAN };
        ChessStepSend(info, &step);

        memcpy(info->board, tag->backup_board, sizeof(tag->backup_board));
        tag->eaten[0] = tag->backup_eaten[0];
        tag->eaten[1] = tag->backup_eaten[1];
        }
    }
    } else if (key == 'x' && info->history.used == 0 &&
        ((info->mode == CHESS_MODE_VERSUS && info->myturn == BWHITE) ||
          info->mode == CHESS_MODE_PERSONAL)) {
    char buf[4];
    int  n;

    getdata(22, 43, "要授多少子呢(2 - 9)? ", buf, sizeof(buf), DOECHO);
    n = atoi(buf);

    if (n >= 2 && n <= 9) {
        go_step_t step = { CHESS_STEP_NORMAL, SETHAND, {n, 0} };
        
        ChessStepSend(info, &step);
        ChessHistoryAppend(info, &step);

        go_sethand(info->board, n);
        ((go_tag_t*)info->tag)->feed_back = 0.0;

        snprintf(info->last_movestr, sizeof(info->last_movestr),
            ANSI_COLOR(1) "授 %d 子" ANSI_RESET, n);
        ChessRedraw(info);
        return 1;
    } else
        ChessDrawLine(info, 22);
    }
    return 0;
}

static int
go_select(ChessInfo* info, rc_t location, ChessGameResult* result)
{
    board_p   board = (board_p) info->board;

    if (GET_TAG(info)->game_end) {
    go_step_t step = { CHESS_STEP_SPECIAL, CLEAN, location };
    if (board[location.r][location.c] == BBLANK)
        return 0;

    GET_TAG(info)->eaten[!board[location.r][location.c]] +=
        go_cleandead(board, location.r, location.c);

    ChessStepSend(info, &step);
    ChessRedraw(info);
    return 0; /* don't have to return from ChessPlayFuncMy() */
    } else {
    go_step_t step = { CHESS_STEP_NORMAL, info->turn, location };

    if (board[location.r][location.c] != BBLANK)
        return 0;

    if (go_check(info, &step)) {
        board[location.r][location.c] = info->turn;
        ChessStepSend(info, &step);
        ChessHistoryAppend(info, &step);

        go_getstep(&step, info->last_movestr);
        if (go_examboard(board, !info->myturn, info))
        ChessRedraw(info);
        else
        ChessDrawLine(info, BOARD_LINE_ON_SCREEN(location.r));
        return 1;
    } else
        return 0;
    }
}

static void
go_prepare_step(ChessInfo* info, const go_step_t* step)
{
    go_tag_t* tag = GET_TAG(info);
    if (tag->game_end) {
    /* some actions need tag so are done here */
    if (step->color == CLEAN) {
        board_p board = (board_p) info->board;
        tag->eaten[!board[step->loc.r][step->loc.c]] +=
        go_cleandead(board, step->loc.r, step->loc.c);
    } else if (step->color == UNCLEAN) {
        memcpy(info->board, tag->backup_board, sizeof(tag->backup_board));
        tag->eaten[0] = tag->backup_eaten[0];
        tag->eaten[1] = tag->backup_eaten[1];
    } else if (step->color == CLEANDONE) {
        if (tag->clean_end & 1) {
        /* both sides agree */
        int winner = go_result(info);

        tag->clean_end = 3;

        if (winner == BBLANK)
            ((go_step_t*)step)->loc.r = (int) CHESS_RESULT_TIE;
        else
            ((go_step_t*)step)->loc.r = (int)
            (winner == info->myturn ?
             CHESS_RESULT_WIN : CHESS_RESULT_LOST);

        ChessRedraw(info);
        } else {
        ((go_step_t*)step)->color = BBLANK; /* tricks apply */
        tag->clean_end |= 2;
        }
    }
    } else if (step->type == CHESS_STEP_NORMAL) {
    if (step->color != SETHAND) {
        go_getstep(step, info->last_movestr);

        memcpy(tag->backup_board, info->board, sizeof(board_t));
        tag->backup_board[step->loc.r][step->loc.c] = step->color;

        /* if any chess was eaten, wholely redraw is needed */
        tag->need_redraw =
        go_examboard(tag->backup_board, !step->color, info);
    } else {
        snprintf(info->last_movestr, sizeof(info->last_movestr),
            ANSI_COLOR(1) "授 %d 子" ANSI_RESET, step->loc.r);
        tag->need_redraw = 1;
        ((go_tag_t*)info->tag)->feed_back = 0.0;
    }
    } else if (step->type == CHESS_STEP_PASS)
    strcpy(info->last_movestr, "虛手");
}

static ChessGameResult
go_apply_step(board_t board, const go_step_t* step)
{
    if (step->type != CHESS_STEP_NORMAL)
    return CHESS_RESULT_CONTINUE;

    switch (step->color) {
    case BWHITE:
    case BBLACK:
        board[step->loc.r][step->loc.c] = step->color;
        go_examboard(board, !step->color, NULL);
        break;

    case SETHAND:
        go_sethand(board, step->loc.r);
        break;

    case CLEAN:
        go_cleandead(board, step->loc.r, step->loc.c);
        break;

    case CLEANDONE:
        /* should be agreed by both sides, [see go_prepare_step()] */
        return (ChessGameResult) step->loc.r;
    }
    return CHESS_RESULT_CONTINUE;
}

static void
go_drawstep(ChessInfo* info, const go_step_t* step)
{
    go_tag_t* tag = GET_TAG(info);
    if (tag->game_end || tag->need_redraw)
    ChessRedraw(info);
    else
    ChessDrawLine(info, BOARD_LINE_ON_SCREEN(step->loc.r));
}

static ChessGameResult
go_post_game(ChessInfo* info)
{
    extern ChessGameResult ChessPlayFuncMy(ChessInfo* info);

    go_tag_t       *tag        = (go_tag_t*) info->tag;
    ChessTimeLimit *orig_limit = info->timelimit;
    ChessGameResult result;

    info->timelimit = NULL;
    info->turn      = info->myturn;
    strcpy(info->warnmsg, "請點除死子");
    ChessDrawLine(info, CHESS_DRAWING_WARN_ROW);

    memcpy(tag->backup_board, info->board, sizeof(tag->backup_board));
    tag->game_end  = 1;
    tag->clean_end = 0;
    tag->backup_eaten[0] = tag->eaten[0];
    tag->backup_eaten[1] = tag->eaten[1];

    ChessDrawLine(info, b_lines); /* 'w' instruction */

    while ((result = ChessPlayFuncMy(info)) == CHESS_RESULT_CONTINUE);

    ChessRedraw(info);

    info->timelimit = orig_limit;

    return result;
}

static void
go_gameend(ChessInfo* info, ChessGameResult result)
{
    if (info->mode == CHESS_MODE_VERSUS) {
    ChessUser* const user1 = &info->user1;
    /* ChessUser* const user2 = &info->user2; */

    user1->lose--;
    if (result == CHESS_RESULT_WIN) {
        user1->win++;
        currutmp->go_win++;
    } else if (result == CHESS_RESULT_LOST) {
        user1->lose++;
        currutmp->go_lose++;
    } else {
        user1->tie++;
        currutmp->go_tie++;
    }

    cuser.go_win  = user1->win;
    cuser.go_lose = user1->lose;
    cuser.go_tie  = user1->tie;

    passwd_update(usernum, &cuser);
    } else if (info->mode == CHESS_MODE_REPLAY) {
    free(info->board);
    free(info->tag);
    }
}

static void
go_genlog(ChessInfo* info, FILE* fp, ChessGameResult result)
{
    const static char ColName[] = "ABCDEFGHJKLMNOPQRST";
    const int nStep = info->history.used;
    int       i;
    int       sethand = 0;

    if (nStep > 0) {
    const go_step_t* const step =
        (const go_step_t*) ChessHistoryRetrieve(info, 0);
    if (step->color == SETHAND)
        sethand = step->loc.r;
    }

    for (i = 1; i <= 22; i++)
    fprintf(fp, "%.*s\n", big_picture[i].len, big_picture[i].data);

    fprintf(fp, "\n");
    fprintf(fp, "按 z 可進入打譜模式\n");
    fprintf(fp, "\n");

    if (sethand) {
    fprintf(fp, "[   1] 授 %d 子\n                ", sethand);
    i = 1;
    } else
    i = 0;

    for (; i < nStep; ++i) {
    const go_step_t* const step =
        (const go_step_t*) ChessHistoryRetrieve(info, i);
    if (step->type == CHESS_STEP_NORMAL)
        fprintf(fp, "[%3d]%s => %c%-4d", i + 1, bw_chess[step->color],
            ColName[step->loc.c], 19 - step->loc.r);
    else if (step->type == CHESS_STEP_PASS)
        fprintf(fp, "[%3d]%s => 虛手 ", i + 1, bw_chess[(i + 1) % 2]);
    else
        break;
    if (i % 5 == 4)
        fputc('\n', fp);
    }

    fprintf(fp,
        "\n\n《以下為 sgf 格式棋譜》\n<golog>\n(;GM[1]"
        "GN[%s-%s(W) Ptt]\n"
        "SZ[19]HA[%d]PB[%s]PW[%s]\n"
        "PC[FPG BBS/Ptt BBS: ptt.cc]\n",
        info->user1.userid, info->user2.userid,
        sethand,
        info->user1.userid, info->user2.userid);

    if (sethand) {
    const int lower = sethand * (sethand - 1) / 2;
    const int upper = lower + sethand;
    int j;
    fputs("AB", fp);
    for (j = lower; j < upper; ++j)
        fprintf(fp, "[%c%c]",
            SetHandPoints[j].c + 'a',
            SetHandPoints[j].r + 'a');
    fputc('\n', fp);
    }

    for (i = (sethand ? 1 : 0); i < nStep; ++i) {
    const go_step_t* const step =
        (const go_step_t*) ChessHistoryRetrieve(info, i);
    if (step->type == CHESS_STEP_NORMAL)
        fprintf(fp, ";%c[%c%c]",
            step->color == BWHITE ? 'W' : 'B',
            step->loc.c + 'a',
            step->loc.r + 'a');
    else if (step->type == CHESS_STEP_PASS)
        fprintf(fp, ";%c[]  ", i % 2 ? 'W' : 'B');
    else
        break;
    if (i % 10 == 9)
        fputc('\n', fp);
    }
    fprintf(fp, ";)\n<golog>\n\n");
}

void
gochess(int s, ChessGameMode mode)
{
    ChessInfo* info = NewChessInfo(&go_actions, &go_constants, s, mode);
    board_t    board;
    go_tag_t   tag;

    go_init_board(board);
    go_init_tag(&tag);

    info->board = board;
    info->tag   = &tag;

    info->cursor.r = 9;
    info->cursor.c = 9;

    if (mode == CHESS_MODE_WATCH)
    setutmpmode(CHESSWATCHING);
    else
    setutmpmode(UMODE_GO);
    currutmp->sig = SIG_GO;

    ChessPlay(info);

    DeleteChessInfo(info);
}

int
gochess_main(void)
{
    return ChessStartGame('g', SIG_GO, "圍棋");
}

int
gochess_personal(void)
{
    gochess(0, CHESS_MODE_PERSONAL);
    return 0;
}

int
gochess_watch(void)
{
    return ChessWatchGame(&gochess, UMODE_GO, "圍棋");
}

static int
mygetc(FILE* fp, char* buf, int* idx, int len)
{
    for (;;) {
    while (buf[*idx] && isspace(buf[*idx])) ++*idx;

    if (buf[*idx]) {
        ++*idx;
        return buf[*idx - 1];
    }

    if (fgets(buf, len, fp) == NULL)
        return EOF;

    if (strcmp(buf, "<golog>\n") == 0)
        return EOF;

    *idx = 0;
    }
}

ChessInfo*
gochess_replay(FILE* fp)
{
    ChessInfo *info;
    int        ch;
    char       userid[2][IDLEN + 1] = { "", "" };
    char       sethand_str[4] = "";
    char      *recording = NULL;
    char      *record_end = NULL;
    go_step_t  step;

    /* for mygetc */
    char buf[512] = "";
    int  idx = 0;

#define GETC() mygetc(fp, buf, &idx, sizeof(buf))

    /* sgf file started with "(;" */
    if (GETC() != '(' || GETC() != ';')
    return NULL;

    /* header info */
    while ((ch = GETC()) != EOF && ch != ';') {
    if (ch == '[') {
        if (recording) {
        while ((ch = GETC()) != EOF && ch != ']')
            if (recording < record_end)
            *recording++ = ch;
        *recording = 0;
        recording = NULL;
        } else
        while ((ch = GETC()) != EOF && ch != ']')
            continue;

        if (ch == EOF)
        break;
    } else if (ch == ';') /* next stage */
        break;
    else {
        int ch2 = GETC();

        if (ch2 == EOF) {
        ch = EOF;
        break;
        }

        if (ch == 'P') {
        if (ch2 == 'B') {
            recording  = userid[BBLACK];
            record_end = userid[BBLACK] + IDLEN;
        } else if (ch2 == 'W') {
            recording  = userid[BWHITE];
            record_end = userid[BWHITE] + IDLEN;
        }
        } else if (ch == 'H') {
        if (ch2 == 'A') {
            recording  = sethand_str;
            record_end = sethand_str + sizeof(sethand_str) - 1;
        }
        }
    }
    }

    if (ch == EOF)
    return NULL;

    info = NewChessInfo(&go_actions, &go_constants,
        0, CHESS_MODE_REPLAY);

    /* filling header information to info */
    if (userid[BBLANK][0]) {
    userec_t rec;
    if (getuser(userid[BBLANK], &rec))
        go_init_user_userec(&rec, &info->user1);
    }

    if (userid[BWHITE][0]) {
    userec_t rec;
    if (getuser(userid[BWHITE], &rec))
        go_init_user_userec(&rec, &info->user2);
    }

    if (sethand_str[0]) {
    int sethand = atoi(sethand_str);
    if (sethand >= 2 && sethand <= 9) {
        step.type  = CHESS_STEP_NORMAL;
        step.color = SETHAND;
        step.loc.r = sethand;
        ChessHistoryAppend(info, &step);
    }
    }

    /* steps, ends with ")" */
    while ((ch = GETC()) != EOF && ch != ')') {
    if (ch == ';')
        ChessHistoryAppend(info, &step);
    else if (ch == 'B')
        step.color = BBLACK;
    else if (ch == 'W')
        step.color = BWHITE;
    else if (ch == '[') {
        ch = GETC();
        if (ch == EOF)
        break;
        else if (ch == ']') {
        step.type = CHESS_STEP_PASS;
        continue;
        } else
        step.loc.c = ch - 'a';

        ch = GETC();
        if (ch == EOF)
        break;
        else if (ch == ']') {
        step.type = CHESS_STEP_PASS;
        continue;
        } else
        step.loc.r = ch - 'a';

        while ((ch = GETC()) != EOF && ch != ']');

        if (step.loc.r < 0 || step.loc.r >= BRDSIZ ||
            step.loc.c < 0 || step.loc.c >= BRDSIZ)
        step.type = CHESS_STEP_PASS;
        else
        step.type = CHESS_STEP_NORMAL;
    }
    }

    info->board = malloc(sizeof(board_t));
    info->tag   = malloc(sizeof(go_tag_t));

    go_init_board(info->board);
    go_init_tag(info->tag);

    return info;

#undef GETC
}