#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 <= 0x77) { 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) { m = generate_pawn(); } else if(piece_type & 0b0100) { // bishop, rook and queen are 01xx. m = generate_sliding(piece_type); } else { m = generate_non_sliding(piece_type); } if(m.sq_to != 255) { return m; } } square++; direction = 0; target_square = square; } 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() { // TODO: implement capture promotion 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_square & 0x88)) { 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_square & 0x88)) { 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