From 65015c9a6ca6c76b14946cfd266a899e7118e2e4 Mon Sep 17 00:00:00 2001 From: Quinten Kock Date: Fri, 19 Jun 2020 00:46:08 +0200 Subject: [PATCH] attempt at move generation --- Movegen.h | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/Movegen.h b/Movegen.h index ac9d329..7ae883f 100644 --- a/Movegen.h +++ b/Movegen.h @@ -1,4 +1,255 @@ #ifndef __MOVEGEN_H_INC #define __MOVEGEN_H_INC +#include "Types.h" +#include "Move.h" + +const static byte SLIDE_OFFSETS[] = {-1, 1, -16, 16, 15, 17, -15, -17}; +const static byte KNIGHT_OFFSETS[] = {-31, 31, -33, 33, -18, 18, -14, 14}; + +#define INVALID_MOVE Move{0xFF, 0xFF, P_EMPTY} + +class Movegen { + public: + Move next_move(); + + private: + byte square = 0; + byte promote = P_EMPTY; + byte direction = 0; + byte target_square = 0; + + Move generate_pawn(); + Move generate_non_sliding(byte piece_type); + Move generate_sliding(byte piece_type); +}; + +Move Movegen::next_move() { + while(square <= 0x7F) { + if(square & 0x88) square += 8; + + byte piece_type = field[square] & 0x7; + + if( + (field[square] & 0x7) && + (field[square] & 0x8) == black_moving() << 3 + ) { + // there is an own piece to investigate + Move m; + if(piece_type == W_PAWN) { + Serial.println("GENERATE PAWN"); + m = generate_pawn(); + } else if(piece_type & 0b0100) { + Serial.println("GENERATE SLIDING"); + // bishop, rook and queen are 01xx. + m = generate_sliding(piece_type); + } else { + Serial.println("GENERATE NONSLIDING"); + m = generate_non_sliding(piece_type); + } + if(m.sq_to != 255) { + return m; + } + } + square++; + direction = 0; + } + return INVALID_MOVE; +} + +Move Movegen::generate_sliding(byte piece_type) { + if(direction == 0) { + // if we can move horizontally (rook: 0b110, queen: 0b111): + // then start at the beginning, else (bishop: 0b101) start halfway + direction = piece_type & 0b10 ? 0 : 4; + } + + byte offset = SLIDE_OFFSETS[direction]; + target_square += offset; + while(target_square & 0x88 || promote == 0x10) { + promote = P_EMPTY; + // we leapt out of bounds, so find the next direction to move + NEXT_DIRECTION: + direction++; + if(direction >= 4 << (piece_type & 0b1)) { + // direction >= 4 for rook, direction >= 8 for bishop and queen + return INVALID_MOVE; + } + offset = SLIDE_OFFSETS[direction]; + target_square = square + offset; + } + // currently, we are at the next move target + // this means: we can try to generate this as a move! + byte piece = field[square]; + byte target = field[target_square]; + if(target) { + // we encountered a piece! there are two outcomes here: + // second, it can be the opponent's. then, we can capture it! + if((target & 0x8) == (piece & 0x8)) { + // first, it is possible it is one of our own. + // this means we are blocked + // therefore, invalidate our direction and try again. + goto NEXT_DIRECTION; + } else { + promote = 0x10; // signal value + return Move{square, target_square, P_EMPTY}; + } + } + // no obstructions means happy sliding piece :D + return Move{square, target_square, P_EMPTY}; +} + +Move Movegen::generate_non_sliding(byte piece_type) { + GNS_START: + if(direction >= 8) { + if(piece_type == W_KNGT) return INVALID_MOVE; + else { + // TODO implement castling + return INVALID_MOVE; + } + } + + const byte* offsets = piece_type == W_KING ? SLIDE_OFFSETS : KNIGHT_OFFSETS; + target_square = square + offsets[direction]; + + direction++; + + byte target = field[target_square]; + byte piece = field[square]; + + if((target_square & 0x88) || (target && (target & 0x8) == (piece & 0x8))) { + // uh oh, off board or same color obstacle + goto GNS_START; + } + return Move{square, target_square, P_EMPTY}; +} + +Move Movegen::generate_pawn() { + byte color = black_moving(); + byte offset; + byte target; + GP_START: + switch(direction) { + case 0: + // regular move 1 ahead + direction = 1; // next try, go ahead further + offset = color ? -0x10 : 0x10; + target_square = square + offset; + if(field[target_square] || + (square & 0x70) == (color ? 0x10 : 0x60) + ) { + // moving ahead is not possible, not even a capture! + // this is either due to a blockade or a pending promotion + // moving 2 ahead is also impossible, so dont try that. + direction = 2; + goto GP_START; + } else { + return Move{square, target_square, P_EMPTY}; + } + // fall through + case 1: + // move 2 ahead + direction = 2; + offset = color ? -0x20 : 0x20; + target_square = square + offset; + if(!(field[target_square]) && + (square & 0x70) == (color ? 0x60 : 0x10) + ) { + return Move{square, target_square, P_EMPTY}; + } + // no break because if this goes wrong + // we can always try the next possibility. + // fall through + case 2: + // capture left or EP-capture left + direction = 3; + offset = color ? -0x11 : 0xF; + target_square = square + offset; + target = field[target_square]; + if(target && (target & 0x8) != (field[square] & 0x8)) { + // normal capture allowded + return Move{square, target_square, P_EMPTY}; + } else if(field[PTR_ENPASSANT]) { + // note that EP being legal only happens + // when the target field is empty. so this saves some effort. + byte ep_col = field[PTR_ENPASSANT] & 0x7; + if( + ep_col == (target_square & 0x7) && + (square & 0x70) == (color ? 0x30 : 0x40) + ) { + // EP-capture possible + return Move{square, target_square, P_EMPTY}; + } + } + // fall through + case 3: + // capture right or EP-capture right + direction = 4; + offset = color ? -0xF : 0x11; + target_square = square + offset; + target = field[target_square]; + if(target && (target & 0x8) != (field[square] & 0x8)) { + // normal capture allowded + return Move{square, target_square, P_EMPTY}; + } else if(field[PTR_ENPASSANT]) { + // note that EP being legal only happens + // when the target field is empty. so this saves some effort. + byte ep_col = field[PTR_ENPASSANT] & 0x7; + if( + ep_col == (target_square & 0x7) && + (square & 0x70) == (color ? 0x30 : 0x40) + ) { + // EP-capture possible + return Move{square, target_square, P_EMPTY}; + } + } + // fall through + case 4: + // try promoting (queen) + direction = 5; + offset = color ? -0x10 : 0x10; + target_square = square + offset; + target = field[target_square]; + if(target && (target_square & 0x70) == (color ? 0x70 : 0x00)) { + // we can promote! + return Move{square, target_square, (Piece)(W_QUEN | color << 3)}; + } else { + // other promotions are also impossible, so skip them + direction = 8; + } + goto GP_START; + break; + case 5: + direction = 6; + offset = color ? -0x10 : 0x10; + target_square = square + offset; + // we can promote! we know this + // because it was possible in the previous case. + return Move{square, target_square, (Piece)(W_KNGT | color << 3)}; + case 6: + direction = 7; + offset = color ? -0x10 : 0x10; + target_square = square + offset; + // we can promote! we know this + // because it was possible in the previous case. + return Move{square, target_square, (Piece)(W_ROOK | color << 3)}; + case 7: + direction = 8; + offset = color ? -0x10 : 0x10; + target_square = square + offset; + // we can promote! we know this + // because it was possible in the previous case. + return Move{square, target_square, (Piece)(W_BSHP | color << 3)}; + default: + // to my knowledge, all a pawn can do is + // move 1 + // move 2 + // (EP-)capture left + // (EP-)capture right + // and the 4 promotions + return INVALID_MOVE; + } + } + + #endif