src/chess/rules.c

Tue, 01 Apr 2014 14:04:00 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 01 Apr 2014 14:04:00 +0200
changeset 26
e0a76ee1bb2b
parent 25
3ab0c2e1a4e2
child 27
efeb98bc69c9
permissions
-rw-r--r--

introduced single machine mode

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2014 Mike Becker. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "rules.h"
#include "chess.h"
#include <string.h>
#include <stdlib.h>

void gamestate_cleanup(GameState *gamestate) {
    MoveList *elem;
    elem = gamestate->movelist;
    while (elem) {
        MoveList *cur = elem;
        elem = elem->next;
        free(cur);
    };
}

static void addmove(GameState* gamestate, Move *move) {
    MoveList *elem = malloc(sizeof(MoveList));
    elem->next = NULL;
    elem->move = *move;
    
    if (gamestate->lastmove) {
        gamestate->lastmove->next = elem;
        gamestate->lastmove = elem;
    } else {
        gamestate->movelist = gamestate->lastmove = elem;
    }
}

char getpiecechr(uint8_t piece) {
    switch (piece & PIECE_MASK) {
    case ROOK: return 'R';
    case KNIGHT: return 'N';
    case BISHOP: return 'B';
    case QUEEN: return 'Q';
    case KING: return 'K';
    default: return '\0';
    }
}

uint8_t getpiece(char c) {
    switch (c) {
        case 'R': return ROOK;
        case 'N': return KNIGHT;
        case 'B': return BISHOP;
        case 'Q': return QUEEN;
        case 'K': return KING;
        default: return 0;
    }
}

static int getlocation(GameState *gamestate, Move *move) {   
    uint8_t piece = move->piece & PIECE_MASK;
    switch (piece) {
        case PAWN: return pawn_getlocation(gamestate, move);
        case ROOK: return rook_getlocation(gamestate, move);
        case KNIGHT: return knight_getlocation(gamestate, move);
        case BISHOP: return bishop_getlocation(gamestate, move);
        case QUEEN: return queen_getlocation(gamestate, move);
        case KING: return king_getlocation(gamestate, move);
        default: return INVALID_MOVE_SYNTAX;
    }
}

_Bool is_covered(GameState *gamestate,uint8_t row,uint8_t file,uint8_t color) {
    Move threats[16];
    int threatcount = 0;
    for (uint8_t r = 0 ; r < 8 ; r++) {
        for (uint8_t f = 0 ; f < 8 ; f++) {
            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
                threats[threatcount].piece = gamestate->board[r][f];
                threats[threatcount].fromrow = r;
                threats[threatcount].fromfile = f;
                threats[threatcount].torow = row;
                threats[threatcount].tofile = file;
                threatcount++;
            }
        }
    }
    
    for (int i = 0 ; i < threatcount ; i++) {
        if (validate_move(gamestate, &(threats[i]))) {
            return 1;
        }
    }
    
    return 0;
}

void apply_move(GameState *gamestate, Move *move) {
    uint8_t piece = move->piece & PIECE_MASK;
    uint8_t color = move->piece & COLOR_MASK;
    
    /* en passant capture */
    if (move->capture && piece == PAWN &&
        mdst(gamestate->board, move) == 0) {
        gamestate->board[move->fromrow][move->tofile] = 0;
    }
    
    /* remove old en passant threats */
    for (uint8_t file = 0 ; file < 8 ; file++) {
        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
    }
    
    /* add new en passant threat */
    if (piece == PAWN && (
        (move->fromrow == 1 && move->torow == 3) ||
        (move->fromrow == 6 && move->torow == 4))) {
        move->piece |= ENPASSANT_THREAT;
    }
    
    /* move (and maybe capture or promote) */
    msrc(gamestate->board, move) = 0;
    if (move->promotion) {
        mdst(gamestate->board, move) = move->promotion;
    } else {
        mdst(gamestate->board, move) = move->piece;
    }
    
    /* castling */
    if (piece == KING &&
        move->fromfile == fileidx('e')) {
        
        if (move->tofile == fileidx('g')) {
            gamestate->board[move->torow][fileidx('h')] = 0;
            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
        } else if (move->tofile == fileidx('c')) {
            gamestate->board[move->torow][fileidx('a')] = 0;
            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
        }
    }
    
    addmove(gamestate, move);
}

_Bool validate_move(GameState *gamestate, Move *move) {
    _Bool result;
    
    /* validate indices (don't trust opponent) */
    if (!chkidx(move)) {
        return 0;
    }
    
    /* must move */
    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
        return 0;
    }
    
    /* does piece exist */
    result = msrc(gamestate->board, move) == move->piece;
    
    /* can't capture own pieces */
    if ((mdst(gamestate->board, move) & COLOR_MASK)
        == (move->piece & COLOR_MASK)) {
        return 0;
    }
    
    /* validate individual rules */
    switch (move->piece & PIECE_MASK) {
    case PAWN:
        result = result && pawn_chkrules(gamestate, move);
        result = result && !pawn_isblocked(gamestate, move);
        break;
    case ROOK:
        result = result && rook_chkrules(move);
        result = result && !rook_isblocked(gamestate, move);
        break;
    case KNIGHT:
        result = result && knight_chkrules(move);
        result = result && !knight_isblocked(gamestate, move);
        break;
    case BISHOP:
        result = result && bishop_chkrules(move);
        result = result && !bishop_isblocked(gamestate, move);
        break;
    case QUEEN:
        result = result && queen_chkrules(move);
        result = result && !queen_isblocked(gamestate, move);
        break;
    case KING:
        result = result && king_chkrules(gamestate, move);
        result = result && !king_isblocked(gamestate, move);
        break;
    default:
        result = 0;
    }
    
    /* cancel processing to avoid recursion overflow with is_covered() */
    if (!result) {
        return 0;
    }
    
    /* is piece pinned */
    // TODO: make it so
    
    /* correct check and checkmate flags (move is still valid) */
    // TODO: make it so
    
    return result;
}

int eval_move(GameState *gamestate, char *mstr, Move *move) {
    memset(move, 0, sizeof(Move));
    move->fromfile = POS_UNSPECIFIED;
    move->fromrow = POS_UNSPECIFIED;

    size_t len = strlen(mstr);
    
    /* evaluate check/checkmate flags */
    if (mstr[len-1] == '+') {
        len--; mstr[len] = '\0';
        move->check = 1;
    } else if (mstr[len-1] == '#') {
        len--; mstr[len] = '\0';
        move->checkmate = 1;
    }
    
    /* evaluate promotion */
    if (len > 3 && mstr[len-2] == '=') {
        move->promotion = getpiece(mstr[len-1]);
        if (!move->promotion) {
            return INVALID_MOVE_SYNTAX;
        } else {
            move->promotion |= gamestate->mycolor;
            len -= 2;
            mstr[len] = 0;
        }
    }
    
    if (len == 2) {
        /* pawn move (e.g. "e4") */
        move->piece = PAWN;
        move->tofile = fileidx(mstr[0]);
        move->torow = rowidx(mstr[1]);
    } else if (len == 3) {
        if (strcmp(mstr, "O-O") == 0) {
            /* king side castling */
            move->piece = KING;
            move->fromfile = fileidx('e');
            move->tofile = fileidx('g');
            move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
        } else {
            /* move (e.g. "Nf3") */
            move->piece = getpiece(mstr[0]);
            move->tofile = fileidx(mstr[1]);
            move->torow = rowidx(mstr[2]);
        }
        
    } else if (len == 4) {
        move->piece = getpiece(mstr[0]);
        if (!move->piece) {
            move->piece = PAWN;
            move->fromfile = fileidx(mstr[0]);
        }
        if (mstr[1] == 'x') {
            /* capture (e.g. "Nxf3", "dxe5") */
            move->capture = 1;
        } else {
            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
            if (isfile(mstr[1])) {
                move->fromfile = fileidx(mstr[1]);
                if (move->piece == PAWN) {
                    move->piece = 0;
                }
            } else {
                move->fromrow = rowidx(mstr[1]);
            }
        }
        move->tofile = fileidx(mstr[2]);
        move->torow = rowidx(mstr[3]);
    } else if (len == 5) {
        if (strcmp(mstr, "O-O-O") == 0) {
            /* queen side castling "O-O-O" */
            move->piece = KING;
            move->fromfile = fileidx('e');
            move->tofile = fileidx('c');
            move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
        } else {
            move->piece = getpiece(mstr[0]);
            if (mstr[2] == 'x') {
                move->capture = 1;
                if (move->piece) {
                    /* capture (e.g. "Ndxf3") */
                    move->fromfile = fileidx(mstr[1]);
                } else {
                    /* long notation capture (e.g. "e5xf6") */
                    move->piece = PAWN;
                    move->fromfile = fileidx(mstr[0]);
                    move->fromrow = rowidx(mstr[1]);
                }
            } else {
                /* long notation move (e.g. "Nc5a4") */
                move->fromfile = fileidx(mstr[1]);
                move->fromrow = rowidx(mstr[2]);
            }
            move->tofile = fileidx(mstr[3]);
            move->torow = rowidx(mstr[4]);
        }
    } else if (len == 6) {
        /* long notation capture (e.g. "Nc5xf3") */
        if (mstr[3] == 'x') {
            move->capture = 1;
            move->piece = getpiece(mstr[0]);
            move->fromfile = fileidx(mstr[1]);
            move->fromrow = rowidx(mstr[2]);
            move->tofile = fileidx(mstr[4]);
            move->torow = rowidx(mstr[5]);
        }
    }

    
    if (move->piece) {
        if (move->piece == PAWN
            && move->torow == (gamestate->mycolor==WHITE?7:0)
            && !move->promotion) {
            return NEED_PROMOTION;
        }
        
        move->piece |= gamestate->mycolor;
        if (move->fromfile == POS_UNSPECIFIED
            || move->fromrow == POS_UNSPECIFIED) {
            return getlocation(gamestate, move);
        } else {
            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
        }
    } else {
        return INVALID_MOVE_SYNTAX;
    }
}

mercurial