260 lines
7.0 KiB
C++
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
|