ArduChess/Movegen.h

260 lines
7.0 KiB
C++

#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