365行的象棋程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
namespace abc { // abc: the basic facts about a particular subject | a basic chess | alpha beta chess
int _side; // 0:红 1:黑
enum { SHI=0, XIANG, PAWN, MA, PAO, CHE, KING=6 }; // 红子8~14, 黑子16~22, 无子0
inline int myPiece(int zi=0) { return  8 + (_side << 3) + zi; } // But who am I?
inline int yourZi (int zi=0) { return 16 - (_side << 3) + zi; } // ?:是if的马甲; if导致流水线不满
char _brd[256]; // 9x10的棋盘放在16x16的数组中, 左上角(3,3); 左上51, 右下203
enum { LEFT=3, TOP=3, BEGIN=TOP * 16 + LEFT, END=(TOP + 9) * 16 + LEFT + 9 };
int xytoi(int x, int y) { return (y << 4) + x; } // xy转下标(8位)
void printb() {
    static const char *D = "9876543210", *Z = "·11223344556677仕相兵马砲车帅15士象卒馬炮車將";
    puts(""); for (int y = 0; y < 10; y++) {
        printf(" %.2s", &D[y * 2]); // %.2s相当于%c%c
        for (int x = 0; x < 9; x++) printf("%.2s", &Z[_brd[xytoi(LEFT + x, TOP + y)] * 2]);
        puts("");
    } puts("  abcdefghi\n");
}
int _whereIs[24]; // 各个棋子在brd中的下标
#define Z16 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
const char POS_FLAG[256] = { Z16, Z16, Z16, // 位置标志
    0, 0, 0, 1, 1, 1, 9, 9, 9, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 9, 9, 9, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 9, 9, 9, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 以下红方
    0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 9, 9, 9, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 9, 9, 9, 1, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 9, 9, 9, 1, 1, 1, // (11, 9)
};
inline int inBrd(int i) { return POS_FLAG[i]; }
inline int inJiugong(int i) { return POS_FLAG[i] == 9; }
inline int forward(int i) { return i - 16 + (_side << 5); } // 红向上, 黑向下, 帅五进一和将5进1的进
inline int sameHalf(int i, int j) { return !((i ^ j) & 0x80); } // 相用这个
inline int guoHeLe(int i) { return (i & 0x80) == (_side << 7); } // 过河了; 兵只能用这个
inline int empty(int i) { return inBrd(i) && !_brd[i]; }
int c3toi(int x, int y) { // c3, c4这样的坐标是另一种xy
    x -= 'a'; if (x < 0 || x > 8) throw 0; x += LEFT; 
    y = 9 - (y - '0'); if (y < 0 || y > 9) throw 1; y += TOP;
    return xytoi(x, y);
}
void itoc3(int& x, int& y, int i) { x = ((i & 15) - LEFT) + 'a'; y = 9 - ((i >> 4) & 15) + TOP + '0'; }
inline int fold(int i) { return 254 - i; } // 黑方查POS_V时要颠倒
const int POS_V[7][256] = { // 棋子在不同位置的价值,注意要和棋子顺序一致
    { // 仕(士)
        Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16,
        0,    0,    0,    0,    0,    0, 20,    0, 20,    0,    0,    0,    0,    0,    0,    0,
        0,    0,    0,    0,    0,    0,    0, 23,    0,    0,    0,    0,    0,    0,    0,    0,
        0,    0,    0,    0,    0,    0, 20,    0, 20,
    }, { // 相(象)
        Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16,
        0,    0,    0,    0,    0, 20,    0,    0,    0, 20,    0,    0,    0,    0,    0,    0,
        0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        0,    0,    0, 18,    0,    0,    0, 23,    0,    0,    0, 18,    0,    0,    0,    0,
        0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
        0,    0,    0,    0,    0, 20,    0,    0,    0, 20,    
    }, { // 兵(卒)
        Z16, Z16, Z16,
        0,    0,    0,    9,    9,    9, 11, 13, 11,    9,    9,    9,    0,    0,    0,    0,
        0,    0,    0, 19, 24, 34, 42, 44, 42, 34, 24, 19,    0,    0,    0,    0,
        0,    0,    0, 19, 24, 32, 37, 37, 37, 32, 24, 19,    0,    0,    0,    0,
        0,    0,    0, 19, 23, 27, 29, 30, 29, 27, 23, 19,    0,    0,    0,    0,
        0,    0,    0, 14, 18, 20, 27, 29, 27, 20, 18, 14,    0,    0,    0,    0,
        0,    0,    0,    7,    0, 13,    0, 16,    0, 13,    0,    7,    0,    0,    0,    0,
        0,    0,    0,    7,    0,    7,    0, 15,    0,    7,    0,    7,
    }, { //
        Z16, Z16, Z16, // 107的位置既可卧槽又可挂角; 河口象脚101; 中兵头顶103
        0,    0,    0, 90, 90, 90, 96, 90, 96, 90, 90, 90,    0,    0,    0,    0,
        0,    0,    0, 90, 96,103, 97, 94, 97,103, 96, 90,    0,    0,    0,    0,
        0,    0,    0, 92, 98, 99,103, 99,103, 99, 98, 92,    0,    0,    0,    0,
        0,    0,    0, 93,108,100,107,100,107,100,108, 93,    0,    0,    0,    0,
        0,    0,    0, 90,100, 99,103,104,103, 99,100, 90,    0,    0,    0,    0,
        0,    0,    0, 90, 98,101,102,103,102,101, 98, 90,    0,    0,    0,    0,
        0,    0,    0, 92, 94, 98, 95, 98, 95, 98, 94, 92,    0,    0,    0,    0,
        0,    0,    0, 93, 92, 94, 95, 92, 95, 94, 92, 93,    0,    0,    0,    0,
        0,    0,    0, 85, 90, 92, 93, 78, 93, 92, 90, 85,    0,    0,    0,    0,
        0,    0,    0, 88, 85, 90, 88, 90, 88, 90, 85, 88,
    }, { //
        Z16, Z16, Z16, // 当头炮101; 天炮地炮都100
        0,    0,    0,100,100, 96, 91, 90, 91, 96,100,100,    0,    0,    0,    0,
        0,    0,    0, 98, 98, 96, 92, 89, 92, 96, 98, 98,    0,    0,    0,    0,
        0,    0,    0, 97, 97, 96, 91, 92, 91, 96, 97, 97,    0,    0,    0,    0,
        0,    0,    0, 96, 99, 99, 98,100, 98, 99, 99, 96,    0,    0,    0,    0,
        0,    0,    0, 96, 96, 96, 96,100, 96, 96, 96, 96,    0,    0,    0,    0,
        0,    0,    0, 95, 96, 99, 96,100, 96, 99, 96, 95,    0,    0,    0,    0,
        0,    0,    0, 96, 96, 96, 96, 96, 96, 96, 96, 96,    0,    0,    0,    0,
        0,    0,    0, 97, 96,100, 99,101, 99,100, 96, 97,    0,    0,    0,    0,
        0,    0,    0, 96, 97, 98, 98, 98, 98, 98, 97, 96,    0,    0,    0,    0,
        0,    0,    0, 96, 96, 97, 99, 99, 99, 97, 96, 96,
    }, { //
        Z16, Z16, Z16,
        0,    0,    0,206,208,207,213,214,213,207,208,206,    0,    0,    0,    0,
        0,    0,    0,206,212,209,216,233,216,209,212,206,    0,    0,    0,    0,
        0,    0,    0,206,208,207,214,216,214,207,208,206,    0,    0,    0,    0,
        0,    0,    0,206,213,213,216,216,216,213,213,206,    0,    0,    0,    0,
        0,    0,    0,208,211,211,214,215,214,211,211,208,    0,    0,    0,    0,
        0,    0,    0,208,212,212,214,215,214,212,212,208,    0,    0,    0,    0,
        0,    0,    0,204,209,204,212,214,212,204,209,204,    0,    0,    0,    0,
        0,    0,    0,198,208,204,212,212,212,204,208,198,    0,    0,    0,    0,
        0,    0,    0,200,208,206,212,200,212,206,208,200,    0,    0,    0,    0,
        0,    0,    0,194,206,204,212,200,212,204,206,194, 
    }, { // 帅(将)
        Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16,
        0,    0,    0,    0,    0,    0,    1,    1,    1,    0,    0,    0,    0,    0,    0,    0,
        0,    0,    0,    0,    0,    0,    2,    2,    2,    0,    0,    0,    0,    0,    0,    0,
        0,    0,    0,    0,    0,    0, 11, 15, 11 }
}; // 开局每方合计888
int _material[2]; // Material, a term determined by the sum of piece _material of each side
inline int FROM(int mv) { return mv & 255; } // mv:move
inline int TO(int mv) { return (mv >> 8) & 255; }
inline int MV(int from, int to) { // 高16位用来排序。原则:将军优先, 大子先动
    int k = _whereIs[yourZi(KING)];
    int x = to & 15, y = to >> 4, x2 = k & 15, y2 = k >> 4;
    int d = abs(x2 - x) + abs(y2 - y);
    return ((20 - d) << 24) | (_brd[from] << 16) | (to << 8) | from;
}
int c3c4tomv(const char* s) { return MV(c3toi(*s, s[1]), c3toi(s[2], s[3])); }
char* mvtoc3c4(int mv, char* s=0) {
    static char buf[16]; s = s ? s : buf;
    if (mv & 0xffff) {
        int x, y, x2, y2; itoc3(x, y, FROM(mv)); itoc3(x2, y2, TO(mv));
        sprintf(s, "%c%c%c%c", x, y, x2, y2, mv >> 16);
    }
    else strcpy(s, "none");
    return s;
}

int genSortedMoves(int* mvs) { // 不检查将军
    // !(zi & mine): 不是自己子(即空白或对方子); (zi & yours): 对方子
    static const int DELTA_SHIZI[4] = { -16, -1, 1, 16 }; // 十字型移动, 帅车炮用
    static const int DELTA_XIE[4] = { -17, -15, 15, 17 }; // 斜十字型, 仕相用
    static const int LEG[4] = { -16, -1, 1, 16 }; // 马腿
    static const int TI[4][2] = { {-33, -31}, {-18, 14}, {-14, 18}, {31, 33} }; // 马蹄
    int i, n = 1, mine = myPiece(), yours = yourZi();  // 0100=红 1000=黑
    for (int from = BEGIN; from < END; from++) {
        int to, d, zi = _brd[from];
        if ((zi & mine) == 0) continue; // 不是己方棋子。0000=无 01xx=红 10xx=黑
        switch (int type = zi - mine) {
        case CHE:
            for (i = 0; i < 4; i++) {
                d = DELTA_SHIZI[i];
                for (to = from + d; inBrd(to); to += d) {
                    if (const int zi = _brd[to]) {
                        if (zi & yours) mvs[n++] = MV(from, to); // 吃子
                        break; // 前方有子
                    }
                    else // 不吃子时继续移动
                        mvs[n++] = MV(from, to);
                }
            }
            break;
        case PAO:
            for (i = 0; i < 4; i++) {
                d = DELTA_SHIZI[i];
                for (to = from + d; empty(to); to += d) { // 移动和找炮架
                    mvs[n++] = MV(from, to);
                }
                for (to += d; empty(to); to += d); // 从炮架往后找
                zi = _brd[to];
                if (zi & yours) mvs[n++] = MV(from, to);
            }
            break;
        case MA:
            for (i = 0; i < 4; i++) { // 马最多有4*2=8种走法
                if (_brd[to = from + LEG[i]]) continue; // 马腿有子
                for (int j = 0; j < 2; j++) {
                    if (!inBrd(to = from + TI[i][j])) continue;
                    zi = _brd[to];
                    if (!(zi & mine)) mvs[n++] = MV(from, to);
                }
            }
            break;
        case PAWN:
            if (inBrd(to = forward(from))) {
                zi = _brd[to];
                if (!(zi & mine)) mvs[n++] = MV(from, to);
            }
            if (guoHeLe(from)) {
                for (d = -1; d <= 1; d += 2) {
                    if (inBrd(to = from + d)) {
                        zi = _brd[to];
                        if (!(zi & mine)) mvs[n++] = MV(from, to);
                    }
                }
            }
            break;
        case XIANG:
             for (i = 0; i < 4; i++) {
                if (_brd[to = from + DELTA_XIE[i]]) continue; // 象眼有子
                zi = _brd[to += DELTA_XIE[i]]; // 继续向斜方向移动
                if (inBrd(to) && sameHalf(from, to)) {
                    if (!(zi & mine)) mvs[n++] = MV(from, to);
                }
            }
            break;
        case KING:
            for (i = 0; i < 4; i ++) {
                if (!inJiugong(to = from + DELTA_SHIZI[i])) continue;
                zi = _brd[to];
                if (!(zi & mine)) mvs[n++] = MV(from, to);
            }
            for (to = forward(from); empty(to); to = forward(to));
            if (_brd[to] == yourZi(KING)) mvs[n++] = MV(from, to); // 老将不能见面用互相吃判断
            break;
        default: // case SHI:
            for (i = 0; i < 4; i ++) {
                if (!inJiugong(to = from + DELTA_XIE[i])) continue;
                zi = _brd[to];
                if (!(zi & mine)) mvs[n++] = MV(from, to);
            }
            break;
        } // switch
    }
    *mvs = INT_MAX; for (i = 2; i < n; i++) { // 几十个元素插入排序快
        int cur = mvs[i], j;
        for (j = i - 1; cur > mvs[j]; j--) mvs[j + 1] = mvs[j];
        mvs[j + 1] = cur;
    }
    //for (i = 1; i < n; i++) { printf("%s ", mvtoc3c4(mvs[i])); } puts("");
    return n - 1; // 用mvs[1..n]返回
}

inline void PutPiece(int i, int zi) { // assert(zi);
    _brd[i] = char(zi); _whereIs[zi] = i;
    if (zi < 16) _material[0] += POS_V[zi - 8][i];
    else _material[1] += POS_V[zi - 16][fold(i)];
}
inline void TakePiece(int i, int zi) { // assert(zi);
    _brd[i] = 0; _whereIs[zi] = 0;
    if (zi < 16) _material[0] -= POS_V[zi - 8][i];
    else _material[1] -= POS_V[zi - 16][fold(i)];
}

inline int makeMove(int mv) { // 返回后走棋方改变; 不检查将军
    int from = FROM(mv), to = TO(mv), zi = _brd[from], victim = _brd[to];
    if (victim) { TakePiece(to, victim); } TakePiece(from, zi); PutPiece(to, zi);
    _side ^= 1;
    return victim;
}
inline void unmakeMove(int mv, int victim) {
    _side ^= 1;
    int from = FROM(mv), to = TO(mv), zi = _brd[to];
    TakePiece(to, zi); PutPiece(from, zi); if (victim) PutPiece(to, victim);
}

// https://www.chessprogramming.org/Negamax The Evaluation function must return a score relative to the side to being evaluated.
// 比如车10分兵1分。红车和黑车相对,兵和卒相对。如果固定红-黑,回到root层后-1 > -10,那岂不成了吃兵好?
inline int eval() { return _side ? (_material[1] - _material[0]) : (_material[0] - _material[1]); }
enum { KING_MISSING=5555, CHECKMATE=999 };
int _bestMove;

int negMax(int d=0) { // depth +1
    if (!_whereIs[myPiece(KING)]) return -KING_MISSING;
    if (!_whereIs[yourZi(KING)]) return KING_MISSING;
    if (d == 3) return eval();
    int max = -INT_MAX;
    int mvs[128], n = genSortedMoves(mvs);
    for (int i = 1; i <= n; i++) {
        int mv = mvs[i], victim = makeMove(mv);
        int score = -negMax(d + 1);
        unmakeMove(mv, victim);
        if (score == -KING_MISSING) continue; // 自己老将被吃
        else if (score == KING_MISSING) score = CHECKMATE; // 对方老将被吃
        if (score > max) {
            max = score;
            if (d) continue; // ...all the root moves...that is where you find the particular move attached to the score
            //printf("%6d %s\n", score, mvtoc3c4(mv));
            //_bestMove = mv;
        }
    }
    return max;
}

int alphaBeta(int a, int b, int d=0) { // https://www.chessprogramming.org/Alpha-Beta
    int best = -INT_MAX;
    if (d == 2) return negMax();
    int mvs[128], n = genSortedMoves(mvs);
    for (int i = 1; i <= n; i++) {
        int mv = mvs[i], victim = makeMove(mv);
        int score = -alphaBeta(-b, -a, d + 1);
        unmakeMove(mv, victim);
        if (score >= b) return score; // https://www.chessprogramming.org/Fail-Soft
        if (score > best) {
            best = score;
            if (score > a) {
                a = score;
                if (d) continue;
                printf("%6d %s\n", score, mvtoc3c4(mv));
                _bestMove = mv;
            }
        }
    }
    return best;
}

void ComputerGo() {
    _bestMove = 0;
    //int score = negMax(); // 别忘了uncomment negMax里的_bestMove = mv;
    int score = alphaBeta(-INT_MAX, INT_MAX);
    printf("%6d %s best\n", score, mvtoc3c4(_bestMove));
    if (score == -CHECKMATE) throw printf("电脑含笑认负。");
    else if (score == CHECKMATE) throw printf("你迟早会输。");
    if (_bestMove) makeMove(_bestMove);
    puts(""); printb();
}

bool inCheck(int mv) {
    int victim = makeMove(mv);
    bool inCheck = negMax() == CHECKMATE;
    unmakeMove(mv, victim);
    return inCheck;
}

bool isLegalMove(int mv) {
    mv &= 0xffff;
    int mvs[128], n = genSortedMoves(mvs);
    for (int i = 1; i <= n; i++) if (mv == (mvs[i] & 0xffff)) return true;
    return false;
}

void ManGo() {
    for (;;) try {
        char s[80] = ""; printf("输入黑棋走法: "); strlwr(gets(s));
        if (*s == 'q') exit(0);
        int mv = c3c4tomv(s);
        if (isLegalMove(mv) && !inCheck(mv)) { makeMove(mv); break; }
    } catch(int) {}
}

int fen_atoi(int c) {
    switch (tolower(c)) {
    case 'k': return KING; case 'a': return SHI; case 'b': return XIANG; case 'n': return MA;
    case 'r': return CHE;  case 'c': return PAO; case 'p': return PAWN;  default: throw 1;
    }
}

void Init() {
    puts("电脑走红棋,你走黑棋(將象士車馬炮卒)");
    const char* s = "rnbakab2/9/1c2c1n2/p1p1p1p1p/9/9/P1P1P1PrP/1C2C1N2/3R5/RNBAKAB2 w - - 0 2";
    //const char* s = "4ka2n/4a4/3c5/9/9/9/R5C2/9/9/4K4";
    //const char* s = "3k5/9/2R1R4/9/9/9/9/9/9/5K3";
    //const char* s = "3k5/9/9/9/9/9/2R6/9/9/4K4";
    //const char* s = "4k4/9/9/6r2/9/9/9/9/9/5K3";
    //const char* s = "3k5/9/9/9/9/p8/8P/9/9/4K4 w - - 0 2";
    for (int y = TOP, x = LEFT; *s && *s != ' '; ++s) {
        const char c = *s;
        if (c == '/') (x = LEFT, ++y);
        else if (c >= '1' && c <= '9') x += c - '0';
        else _brd[xytoi(x++, y)] = char((c >= 'a' ? 16 : 8) + fen_atoi(c));
    }
    for (int i = BEGIN; i < END; i++) { if (char zi = _brd[i]) PutPiece(i, zi); }
    printb();
}
} // namespace abc
using namespace abc;
int main() {
    try { for(Init();;ComputerGo(),ManGo()); } catch(int){}
    printf("按回车键退出。"); getchar(); return 0;
}

在WSL的debian (gcc version 10.2.1)里试了下:

  • 不让用gets了,可改成fgets(s, sizeof(s), stdin);
  • strlwr不是ANSI,可不用,记着别输入大写就行
  • 默认没有中文locale, sudo dpkg-reconfigure locales安装。zh_CN.gbk显示不了汉字,zh_CN.utf8可以
  • 改用UTF-8编码后,汉字都是3个字节,·则是C2 B7两个字节,打印棋盘的函数要改
posted @ 2022-12-17 21:10  Fun_with_Words  阅读(69)  评论(0编辑  收藏  举报









 张牌。