EMPTY is now its own "type" and "color" of piece

This allows many functions to be moved into the Piece class, since there
doesn't need to be the same degree of null-checking. Pieces can be more
directly compared to each other, as well, like with the new
piece.isEnemyOf(piece) function
This commit is contained in:
Sage Vaillancourt 2021-01-02 01:51:28 -05:00
parent 5ffdf0a7a1
commit 6cad21b79f
1 changed files with 90 additions and 100 deletions

View File

@ -16,6 +16,8 @@ const BISHOP = 3;
const QUEEN = 4;
const KING = 5;
const EMPTY = -1;
const Images = [
'./white_pawn.svg',
'./white_rook.svg',
@ -51,40 +53,8 @@ function range(n) {
return Array.from(Array(n).keys());
}
function isBlack(piece) {
return piece != null && piece.color === BLACK;
}
function isWhite(piece) {
return piece != null && piece.color === WHITE;
}
function isPawn(piece) {
return piece != null && piece.type === PAWN;
}
function isRook(piece) {
return piece != null && piece.type === ROOK;
}
function isBishop(piece) {
return piece != null && piece.type === BISHOP;
}
function isQueen(piece) {
return piece != null && piece.type === QUEEN;
}
function isKnight(piece) {
return piece != null && piece.type === KNIGHT;
}
function isKing(piece) {
return piece != null && piece.type === KING;
}
function imageFromPiece(piece) {
if (piece) {
if (piece && piece.type >= 0) {
const image = piece.color === WHITE ? piece.type : piece.type + 6;
return Images[image];
}
@ -126,6 +96,50 @@ class Piece {
return "Has made " + this.moves + " moves"
}
}
isEmpty() {
return this.type === EMPTY;
}
isFull() {
return !this.isEmpty();
}
isBlack() {
return this.color === BLACK;
}
isWhite() {
return this.color === WHITE;
}
isEnemyOf(piece) {
if (this.color === EMPTY || piece.color === EMPTY) {
return false;
} else {
return this.color !== piece.color;
}
}
isFriendOf(piece) {
if (this.color === EMPTY || piece.color === EMPTY) {
return false;
} else {
return this.color === piece.color;
}
}
is(type) {
return this.type === type;
}
hasMoved() {
return this.moves !== 0;
}
hasntMoved() {
return !this.hasMoved();
}
}
class Board extends React.Component {
@ -183,7 +197,7 @@ class Board extends React.Component {
case 'p':
return new Piece(color, PAWN);
default:
return null;
return new Piece(EMPTY, EMPTY);
}
}),
};
@ -243,17 +257,13 @@ class Board extends React.Component {
const mainRow = [ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK];
function add(num, color, type) {
for(let i = 0; i < num; i++) {
if(color != null && type != null) {
squares.push(new Piece(color, type));
} else {
squares.push(null);
}
squares.push(new Piece(color, type));
}
}
mainRow.forEach(type => add(1, WHITE, type));
add(8, WHITE, PAWN);
add(32, null, null);
add(32, EMPTY, EMPTY);
add(8, BLACK, PAWN);
mainRow.forEach(type => add(1, BLACK, type));
@ -286,7 +296,7 @@ class Board extends React.Component {
return this.state.squares.length;
}
squareAt(i) {
pieceAtIndex(i) {
return i >= 0 && i < 64 ? this.state.squares[i] : null;
}
@ -294,39 +304,19 @@ class Board extends React.Component {
if (this.isValidXY(x, y)) {
return this.state.squares[this.getIndex(x, y)];
} else {
return null;
return new Piece(EMPTY, EMPTY);
}
}
whiteAt(x, y) {
const square = this.pieceAt(x, y);
if (square == null) {
return false;
}
return isWhite(square);
}
blackAt(x, y) {
const square = this.pieceAt(x, y);
if (square == null) {
return false;
}
return isBlack(square);
}
isEnemyOf(piece, x, y) {
return isBlack(piece) ? this.whiteAt(x, y) : this.blackAt(x, y);
}
getValidMovesAt(piece, x, y) {
let moves = [];
const tryAddMove = (x, y) => {
if (this.isValidXY(x, y)) {
if(this.pieceAt(x, y) == null) {
if(this.pieceAt(x, y).isEmpty()) {
moves.push({x, y});
// Keep searching
return 0;
} else if (this.isEnemyOf(piece, x, y)) {
} else if (piece.isEnemyOf(this.pieceAt(x, y))) {
moves.push({x, y});
}
// Stop searching
@ -341,70 +331,71 @@ class Board extends React.Component {
}
}
if (isPawn(piece)) {
const pieceIsBlack = isBlack(piece);
if (piece.is(PAWN)) {
const pieceIsBlack = piece.isBlack();
const shift = pieceIsBlack ? -1 : 1;
const startLine = pieceIsBlack ? 6 : 1;
// Check for en passant
const left = this.pieceAt(x - 1, y);
const right = this.pieceAt(x + 1, y);
if (left != null && left.passantable && left.color !== piece.color) {
if (left && left.passantable && left.isEnemyOf(piece)) {
moves.push({x: x - 1, y: y + shift, passant: {x: x - 1, y}})
}
if (right != null && right.passantable && right.color !== piece.color) {
if (right && right.passantable && right.isEnemyOf(piece)) {
moves.push({x: x + 1, y: y + shift, passant: {x: x + 1, y}})
}
if (this.pieceAt(x, y + shift) == null) {
if (this.pieceAt(x, y + shift).isEmpty()) {
moves.push({x, y: y + shift});
// Pawn moving two spaces becomes en-passantable
if (y === startLine && this.pieceAt(x, y + (shift * 2)) == null) {
if (y === startLine && this.pieceAt(x, y + (shift * 2)).isEmpty()) {
moves.push({x, y: y + (shift * 2), passantable: true});
}
}
[x + 1, x - 1].forEach(x => {
if (this.isValidXY(x, y + shift) && this.isEnemyOf(piece, x, y + shift)) {
moves.push({x, y: y + shift});
const y2 = y + shift;
if (this.isValidXY(x, y2) && piece.isEnemyOf(this.pieceAt(x, y2))) {
moves.push({x, y: y2});
}
});
} else if (isRook(piece)) {
} else if (piece.is(ROOK)) {
addBunch(n => {return x;}, n => {return y + n;});
addBunch(n => {return x;}, n => {return y - n;});
addBunch(n => {return x + n;}, n => {return y;});
addBunch(n => {return x - n;}, n => {return y;});
} else if (isBishop(piece)) {
} else if (piece.is(BISHOP)) {
addBunch(n => {return x + n;}, n => {return y + n;});
addBunch(n => {return x - n;}, n => {return y + n;});
addBunch(n => {return x + n;}, n => {return y - n;});
addBunch(n => {return x - n;}, n => {return y - n;});
} else if (isQueen(piece)) {
} else if (piece.is(QUEEN)) {
const [rook, bishop] =
[new Piece(piece.color, ROOK), new Piece(piece.color, BISHOP)];
moves = moves.concat(this.getValidMovesAt(rook, x, y));
moves = moves.concat(this.getValidMovesAt(bishop, x, y));
} else if (isKnight(piece)) {
} else if (piece.is(KNIGHT)) {
[
[2, 1], [2, -1], [-2, 1], [-2, -1],
[1, 2], [1, -2], [-1, 2], [-1, -2],
].forEach(delta => tryAddMove(x + delta[0], y + delta[1]));
} else if (isKing(piece)) {
} else if (piece.is(KING)) {
[[1, 1], [1, -1], [-1, 1], [-1, -1], [0, 1], [0, -1], [1, 0], [-1, 0]]
.forEach(delta => tryAddMove(x + delta[0], y + delta[1]));
if (piece.moves === 0) {
if (piece.hasntMoved()) {
const kingIndex = this.findIndex(piece);
const [x, y] = this.getXandY(kingIndex);
let leftRook = this.pieceAt(0, y);
if(isRook(leftRook) && leftRook.moves === 0) {
if(leftRook.is(ROOK) && leftRook.hasntMoved()) {
// Check if spaces between rook and king are empty
if(this.pieceAt(1, y) == null &&
this.pieceAt(2, y) == null &&
this.pieceAt(3, y) == null) {
if(this.pieceAt(1, y).isEmpty() &&
this.pieceAt(2, y).isEmpty() &&
this.pieceAt(3, y).isEmpty()) {
// Check if between space puts king in check
let board = this.clone();
board.state.squares[board.getIndex(x - 1, y)] = piece;
board.state.squares[kingIndex] = null;
board.state.squares[kingIndex].isEmpty();
if(board.inCheck(piece) == null) {
moves.push({x: x - 2, y, castle: [x - 1, y]});
}
@ -412,14 +403,14 @@ class Board extends React.Component {
}
let rightRook = this.pieceAt(7, y);
if(isRook(rightRook) && rightRook.moves === 0) {
if(rightRook.is(ROOK) && rightRook.hasntMoved()) {
// Check if spaces between rook and king are empty
if(this.pieceAt(5, y) == null &&
this.pieceAt(6, y) == null) {
if(this.pieceAt(5, y).isEmpty() &&
this.pieceAt(6, y).isEmpty()) {
// Check if between space puts king in check
let board = this.clone();
board.state.squares[board.getIndex(x + 1, y)] = piece;
board.state.squares[kingIndex] = null;
board.state.squares[kingIndex].isEmpty();
if(board.inCheck(piece) == null) {
moves.push({x: x + 2, y, castle: [x + 1, y]});
}
@ -433,7 +424,7 @@ class Board extends React.Component {
findIndex(piece) {
for(let i = 0; i < this.squareCount(); i++) {
const check = this.state.squares[i];
if(check && check.type === piece.type && check.color === piece.color) {
if(check.type === piece.type && check.color === piece.color) {
return i;
}
}
@ -457,8 +448,7 @@ class Board extends React.Component {
const kingPos = this.getXandY(this.findIndex(piece));
for(let i = 0; i < this.squareCount(); i++) {
const [x, y] = this.getXandY(i);
if(this.isEnemyOf(piece, x, y)) {
if(piece.isEnemyOf(this.pieceAtIndex(i))) {
const moves = this.getValidMoves(i);
for(let j = 0; j < moves.length; j++) {
if(moves[j].x === kingPos[0] && moves[j].y === kingPos[1]) {
@ -481,16 +471,16 @@ class Board extends React.Component {
if (checkedKing != null) {
// For each square
for(let i = 0; i < this.squareCount(); i++) {
const piece = this.squareAt(i);
const piece = this.pieceAtIndex(i);
// If that piece is on the checked team
if (piece != null && isBlack(piece) === isBlack(checkedKing)) {
if (piece != null && piece.isFriendOf(checkedKing)) {
// For each move of the above piece
const moves = this.getValidMoves(i)
for(const move of moves) {
// Copy the board
const board = this.clone();
board.state.squares[board.getIndex(move.x, move.y)] = board.state.squares[i];
board.state.squares[i] = null;
board.state.squares[i].isEmpty();
const check = board.inCheck(checkedKing);
if (check == null || check.color !== checkedKing.color) {
return false;
@ -507,7 +497,7 @@ class Board extends React.Component {
getValidMoves(source) {
const [x, y] = this.getXandY(source);
const piece = this.squareAt(source);
const piece = this.pieceAtIndex(source);
return this.getValidMovesAt(piece, x, y);
}
@ -536,7 +526,7 @@ class Board extends React.Component {
const move = this.isValidMove(from, to)
if (move) {
if (move.passant) {
squares[this.getIndex(move.passant.x, move.passant.y)] = null;
squares[this.getIndex(move.passant.x, move.passant.y)] = new Piece(EMPTY, EMPTY);
}
if (move.castle) {
// .castle holds the position where the rook should end up
@ -548,7 +538,7 @@ class Board extends React.Component {
console.log([rookX, move.castle[1]]);
squares[this.getIndex(move.castle[0], move.castle[1])] =
squares[this.getIndex(rookX, move.castle[1])];
squares[this.getIndex(rookX, move.castle[1])] = null;
squares[this.getIndex(rookX, move.castle[1])] = new Piece(EMPTY, EMPTY);
}
// Remove existing passantable states
squares.forEach(square => {
@ -561,7 +551,7 @@ class Board extends React.Component {
}
const y = this.getXandY(to)[1];
squares[to] = squares[from];
squares[from] = null;
squares[from] = new Piece(EMPTY, EMPTY);
if (squares[to].type === PAWN && (y === 0 || y === 7)) {
squares[to].setType(QUEEN);
}
@ -586,7 +576,7 @@ class Board extends React.Component {
// Copy the board
let board = this.clone();
board.state.squares[i] = board.state.squares[board.heldPiece()];
board.state.squares[this.heldPiece()] = null;
board.state.squares[this.heldPiece()] = new Piece(EMPTY, EMPTY);
const moversKing = this.state.blackIsNext ?
new Piece(BLACK, KING) : new Piece(WHITE, KING);
@ -598,8 +588,8 @@ class Board extends React.Component {
heldPiece: null,
});
}
} else if (this.state.squares[i] != null) {
const isSquareBlack = isBlack(this.state.squares[i]);
} else if (this.state.squares[i].isFull()) {
const isSquareBlack = this.state.squares[i].isBlack();
if(isSquareBlack === this.state.blackIsNext) {
this.setHand({
heldPiece: i,