From 6cad21b79fa84c5a74bd2c710b8c7396c75d1a52 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Sat, 2 Jan 2021 01:51:28 -0500 Subject: [PATCH] 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 --- src/index.js | 190 ++++++++++++++++++++++++--------------------------- 1 file changed, 90 insertions(+), 100 deletions(-) diff --git a/src/index.js b/src/index.js index 0e3c9a2..e25783c 100644 --- a/src/index.js +++ b/src/index.js @@ -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,