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