0

I tried to create a chess engine in C using the Negamax with alpha-beta pruning, but I couldn't get it working. It works as expected in the first few moves of the game, and starts to sacrifice pieces without an aim. But as it nears the endgame, it seems to behave as expected, again. I have tried debugging for about a week now, but without any success. Any help is greatly appreciated. Here is the related code:

#define INFINITY INT_MAX

char isCheckMateOrStaleMate(uint64_t const *board, uint64_t const *prevBoard, char brkrwrkr00, char isWhiteYourColor) {
    ...
}

struct node {
    unsigned long len;
    uint64_t *pos;
    uint64_t *basepos;
    char brkrwrkr00;
    struct node *branches;
    char *move;
    char color;
};

int max(int a, int b) {
    return a > b ? a : b;
}

int evaluateNode(struct node n) {
    char cmate = isCheckMateOrStaleMate(n.pos, n.basepos, n.brkrwrkr00, !n.color);

    if (cmate)
        return (isCheckOnKing(n.pos, 1) ? -64000 : isCheckOnKing(n.pos, 0) ? 64000 : 0) * (!n.color - n.color);

    int posAdv = 0;

    char p;
    for (char i = 0; i < 64; i++) {
        p = accessBoardAt(n.pos, i);
        posAdv += p == PAWN_B ? -10 : p == PAWN_W ? 10 : p == KNIGHT_B ? -29 : p == KNIGHT_W ? 29 : p == BISHOP_B ? -30 : p == BISHOP_W ? 30 : p == ROOK_B ? -50 : p == ROOK_W ? 50 : p == QUEEN_B ? -90 : p == QUEEN_W ? 90 : 0;
    }

    return (!n.color - n.color) * posAdv;
}

unsigned long generateNodes(uint64_t const *board, uint64_t const *prevBoard, char brkrwrkr00, char isWhiteYourColor, struct node **ret, int depth) {
    if (!depth)
        return 0;

    char *valids = 0;
    unsigned long _len_ = validMoves(board, prevBoard, brkrwrkr00, isWhiteYourColor, &valids);
    
    if (!_len_)
        return 0;

    *ret = malloc(_len_ / 3 * sizeof **ret);

    for (unsigned long i = 0, j = 0; i < _len_; i += 3, j++) {
        (*ret)[j].color = isWhiteYourColor;
        (*ret)[j].pos = {board[0], board[1], board[2], board[3]};
        (*ret)[j].basepos = {board[0], board[1], board[2], board[3]};
        (*ret)[j].brkrwrkr00 = brkrwrkr00;
        (*ret)[j].move = {valids[i], valids[i + 1], valids[i + 2]};
        makeForcedMove((*ret)[j].pos, &((*ret)[j].brkrwrkr00), (*ret)[j].move);
        (*ret)[j].len = generateNodes((*ret)[j].pos, board, (*ret)[j].brkrwrkr00, !isWhiteYourColor, &((*ret)[j].branches), depth - 1);
    }

    free(valids);

    return _len_ / 3;
};

int negamaxscore(struct node n, int depth, char maximizer, int alpha, int beta) {
    if (!depth || !n.len)
        return evaluateNode(n);

    int eval = -INFINITY;

    for (unsigned long i = 0; i < n.len; i++) {
        eval = max(-negamaxscore(n.branches[i], depth - 1, !maximizer, -beta, -alpha), eval);
        alpha = max(eval, alpha);

        if (beta <= alpha)
            break;
    }

    return eval;
}

char *theBestMove(uint64_t const *board, uint64_t const *prevBoard, char brkrwrkr00, char isWhiteYourColor, int depth) {
    if (!depth)
        return NULL;

    struct node *branches;
    unsigned long len = generateNodes(board, prevBoard, brkrwrkr00, isWhiteYourColor, &branches, depth);

    if (!len)
        return NULL;

    char *bestmove = NULL;
    int curr;
    int eval = -INFINITY;
    int alpha = -INFINITY;
    int beta = INFINITY;

    for (unsigned long i = 0; i < len; i++) {
        curr = -negamaxscore(branches[i], depth - 1, !isWhiteYourColor, -beta, -alpha);

        if (curr >= eval) {
            eval = curr;
            bestmove = branches[i].move;
        }

        alpha = max(eval, alpha);

        if (beta <= alpha)
            break;
    }

    bestmove = memcpy(malloc(3), bestmove, 3);

    return bestmove;
}

char *theBestMove(uint64_t const *board, uint64_t const *prevBoard, char brkrwrkr00, char isWhiteYourColor, int depth) {
    if (!depth)
        return NULL;

    struct node *branches;
    unsigned long len = generateNodes(board, prevBoard, brkrwrkr00, isWhiteYourColor, &branches, depth);

    if (!len)
        return NULL;

    char *bestmove = NULL;
    int curr;
    int eval = -INFINITY;
    int alpha = -INFINITY;
    int beta = INFINITY;

    char seq[depth * 5];

    for (unsigned long i = 0; i < len; i++) {
        curr = -negamaxscore(branches[i], depth - 1, !isWhiteYourColor, -beta, -alpha);

        if (curr >= eval) {
            eval = curr;
            bestmove = branches[i].move;
        }

        alpha = max(eval, alpha);

        if (beta <= alpha)
            break;
    }

    bestmove = memcpy(malloc(3), bestmove, 3);

    return bestmove;
}
Dhakshith
  • 66
  • 6
  • Maybe comment the TT code and debug then? – TheSlater Nov 15 '20 at 11:27
  • @TheSlater Thank you for the suggestion, I edited the question and also tried debugging but same behavior again :( – Dhakshith Nov 15 '20 at 11:35
  • 1
    non constant behaviour -- looks like using the value of an unassigned variable, can also be buffer overflow, dangling pointer, ... – pmg Nov 15 '20 at 11:37
  • @pmg Sorry if my usage of the word non constant is not clear - By non constant behavior I mean that the engine doesn't seem to always play best moves nor does it always play the worst moves. If I play the same sequence of moves in 2 games, it plays the same moves it played in the other game. – Dhakshith Nov 15 '20 at 11:41
  • It appears that you use fixed-depth, without a transposition table (yet) . No quiescence-search? Is there a global board+ undo/redo? [this can leak value] – wildplasser Nov 15 '20 at 11:57
  • `(*ret)[j].pos = memcpy(malloc(32), board, 32);` Why allocate dynamically for a fixed size ? (4 * 64 bit unsigned ints) – wildplasser Nov 15 '20 at 12:35
  • @Wildplasser Thank You! I use a Transposition table, but I excluded it in my question for debugging purposes. I haven't gotten familiar with quiescence-search. I store the board in a node, which I will be changing. I will also change dynamically allocated fixed size arrays to use normal arrays. – Dhakshith Nov 15 '20 at 15:07
  • @wildplasser I have removed dynamically allocated fixed size arrays where applicable. In some places, to pass data between functions, I have retained the dynamic allocations, even though it is a fixed size array. – Dhakshith Nov 15 '20 at 15:16
  • The point of quiescence search is that if there is an exchange (say: QxQ, RxQ) at the end of your search-depth, but only half of it is included in your search, your evaluation will be wrong (it doesn't include the recapture) You could test this by lowering the search-depth, and using some trivial board position. – wildplasser Nov 15 '20 at 15:38
  • @wildplasser Thank You So Much!!! I think that's where my engine goes wrong! It does a sacrifice for an incomplete capture! I will implement it soon and let you know if it works. – Dhakshith Nov 16 '20 at 05:18

0 Answers0