UNPKG

js-chess-engine

Version:

Simple and fast Node.js chess engine without dependencies

944 lines (818 loc) 32.4 kB
import { AI_LEVELS, AI_DEPTH_BY_LEVEL, COLORS, NEW_GAME_BOARD_CONFIG, NEW_GAME_SETTINGS, down, downByColor, downLeft, downLeftByColor, downLeftDown, downLeftLeft, downRight, downRightByColor, downRightDown, downRightRight, left, right, scoreByPosition, up, upByColor, upLeft, upLeftByColor, upLeftLeft, upLeftUp, upRight, upRightByColor, upRightRight, upRightUp, } from './const/board.mjs' import { getPieceValue, getJSONfromFEN, isPieceValid, isLocationValid } from './utils.mjs' const SCORE = { MIN: -1000, MAX: 1000, } const PIECE_VALUE_MULTIPLIER = 10 export default class Board { constructor (configuration = JSON.parse(JSON.stringify(NEW_GAME_BOARD_CONFIG))) { if (typeof configuration === 'object') { this.configuration = Object.assign({}, NEW_GAME_SETTINGS, configuration) } else if (typeof configuration === 'string') { this.configuration = Object.assign({}, NEW_GAME_SETTINGS, getJSONfromFEN(configuration)) } else { throw new Error(`Unknown configuration type ${typeof config}.`) } if (!this.configuration.castling) { this.configuration.castling = { whiteShort: true, blackShort: true, whiteLong: true, blackLong: true, } } this.history = [] } getAttackingFields (color = this.getPlayingColor()) { let attackingFields = [] for (const location in this.configuration.pieces) { const piece = this.getPiece(location) if (this.getPieceColor(piece) === color) { attackingFields = [...attackingFields, ...this.getPieceMoves(piece, location)] } } return attackingFields } isAttackingKing (color = this.getPlayingColor()) { let kingLocation = null for (const location in this.configuration.pieces) { const piece = this.getPiece(location) if (this.isKing(piece) && this.getPieceColor(piece) !== color) { kingLocation = location break } } return this.isPieceUnderAttack(kingLocation) } isPieceUnderAttack (pieceLocation) { const playerColor = this.getPieceOnLocationColor(pieceLocation) const enemyColor = this.getEnemyColor(playerColor) let isUnderAttack = false let field = pieceLocation let distance = 0 while (up(field) && !isUnderAttack) { field = up(field) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (down(field) && !isUnderAttack) { field = down(field) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (left(field) && !isUnderAttack) { field = left(field) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (right(field) && !isUnderAttack) { field = right(field) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isRook(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (upRightByColor(field, playerColor) && !isUnderAttack) { field = upRightByColor(field, playerColor) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isBishop(piece) || this.isQueen(piece) || (distance === 1 && (this.isKing(piece) || this.isPawn(piece))))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (upLeftByColor(field, playerColor) && !isUnderAttack) { field = upLeftByColor(field, playerColor) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isBishop(piece) || this.isQueen(piece) || (distance === 1 && (this.isKing(piece) || this.isPawn(piece))))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (downRightByColor(field, playerColor) && !isUnderAttack) { field = downRightByColor(field, playerColor) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isBishop(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) { isUnderAttack = true } if (piece) break } field = pieceLocation distance = 0 while (downLeftByColor(field, playerColor) && !isUnderAttack) { field = downLeftByColor(field, playerColor) distance++ const piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && (this.isBishop(piece) || this.isQueen(piece) || (this.isKing(piece) && distance === 1))) { isUnderAttack = true } if (piece) break } field = upRightUp(pieceLocation) let piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = upRightRight(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = upLeftLeft(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = upLeftUp(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = downLeftDown(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = downLeftLeft(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = downRightDown(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } field = downRightRight(pieceLocation) piece = this.getPiece(field) if (piece && this.getPieceColor(piece) === enemyColor && this.isKnight(piece)) { isUnderAttack = true } return isUnderAttack } hasPlayingPlayerCheck () { return this.isAttackingKing(this.getNonPlayingColor()) } hasNonPlayingPlayerCheck () { return this.isAttackingKing(this.getPlayingColor()) } getLowestValuePieceAttackingLocation (location, color = this.getPlayingColor()) { let pieceValue = null for (const field in this.configuration.pieces) { const piece = this.getPiece(field) if (this.getPieceColor(piece) === color) { this.getPieceMoves(piece, field).map(attackingLocation => { if (attackingLocation === location && (pieceValue === null || getPieceValue(piece) < pieceValue)) { pieceValue = getPieceValue(piece) } }) } } return pieceValue } getMoves (color = this.getPlayingColor(), movablePiecesRequiredToSkipTest = null) { const allMoves = {} let movablePiecesCount = 0 for (const location in this.configuration.pieces) { const piece = this.getPiece(location) if (this.getPieceColor(piece) === color) { const moves = this.getPieceMoves(piece, location) if (moves.length) { movablePiecesCount++ } Object.assign(allMoves, { [location]: moves }) } } const enemyAttackingFields = this.getAttackingFields(this.getNonPlayingColor()) if (this.isLeftCastlingPossible(enemyAttackingFields)) { if (this.isPlayingWhite()) allMoves.E1.push('C1') if (this.isPlayingBlack()) allMoves.E8.push('C8') } if (this.isRightCastlingPossible(enemyAttackingFields)) { if (this.isPlayingWhite()) allMoves.E1.push('G1') if (this.isPlayingBlack()) allMoves.E8.push('G8') } if (movablePiecesRequiredToSkipTest && movablePiecesCount > movablePiecesRequiredToSkipTest) return allMoves const moves = {} for (const from in allMoves) { allMoves[from].map(to => { const testConfiguration = { pieces: Object.assign({}, this.configuration.pieces), castling: Object.assign({}, this.configuration.castling), } const testBoard = new Board(testConfiguration) testBoard.move(from, to) if ( (this.isPlayingWhite() && !testBoard.isAttackingKing(COLORS.BLACK)) || (this.isPlayingBlack() && !testBoard.isAttackingKing(COLORS.WHITE)) ) { if (!moves[from]) { moves[from] = [] } moves[from].push(to) } }) } if (!Object.keys(moves).length) { this.configuration.isFinished = true if (this.hasPlayingPlayerCheck()) { this.configuration.checkMate = true } } return moves } isLeftCastlingPossible (enemyAttackingFields) { if (this.isPlayingWhite() && !this.configuration.castling.whiteLong) return false if (this.isPlayingBlack() && !this.configuration.castling.blackLong) return false let kingLocation = null if (this.isPlayingWhite() && this.getPiece('E1') === 'K' && this.getPiece('A1') === 'R' && !enemyAttackingFields.includes('E1')) { kingLocation = 'E1' } else if (this.isPlayingBlack() && this.getPiece('E8') === 'k' && this.getPiece('A8') === 'r' && !enemyAttackingFields.includes('E8')) { kingLocation = 'E8' } if (!kingLocation) return false let field = left(kingLocation) if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false field = left(field) if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false field = left(field) if (this.getPiece(field)) return false return true } isRightCastlingPossible (enemyAttackingFields) { if (this.isPlayingWhite() && !this.configuration.castling.whiteShort) return false if (this.isPlayingBlack() && !this.configuration.castling.blackShort) return false let kingLocation = null if (this.isPlayingWhite() && this.getPiece('E1') === 'K' && this.getPiece('H1') === 'R' && !enemyAttackingFields.includes('E1')) { kingLocation = 'E1' } else if (this.isPlayingBlack() && this.getPiece('E8') === 'k' && this.getPiece('H8') === 'r' && !enemyAttackingFields.includes('E8')) { kingLocation = 'E8' } if (!kingLocation) return false let field = right(kingLocation) if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false field = right(field) if (this.getPiece(field) || enemyAttackingFields.includes(field)) return false return true } getPieceMoves (piece, location) { if (this.isPawn(piece)) return this.getPawnMoves(piece, location) if (this.isKnight(piece)) return this.getKnightMoves(piece, location) if (this.isRook(piece)) return this.getRookMoves(piece, location) if (this.isBishop(piece)) return this.getBishopMoves(piece, location) if (this.isQueen(piece)) return this.getQueenMoves(piece, location) if (this.isKing(piece)) return this.getKingMoves(piece, location) return [] } isPawn (piece) { return piece.toUpperCase() === 'P' } isKnight (piece) { return piece.toUpperCase() === 'N' } isRook (piece) { return piece.toUpperCase() === 'R' } isBishop (piece) { return piece.toUpperCase() === 'B' } isQueen (piece) { return piece.toUpperCase() === 'Q' } isKing (piece) { return piece.toUpperCase() === 'K' } getPawnMoves (piece, location) { const moves = [] const color = this.getPieceColor(piece) let move = upByColor(location, color) if (move && !this.getPiece(move)) { moves.push(move) move = upByColor(move, color) if (isInStartLine(color, location) && move && !this.getPiece(move)) { moves.push(move) } } move = upLeftByColor(location, color) if (move && ((this.getPiece(move) && this.getPieceOnLocationColor(move) !== color) || (move === this.configuration.enPassant))) { moves.push(move) } move = upRightByColor(location, color) if (move && ((this.getPiece(move) && this.getPieceOnLocationColor(move) !== color) || (move === this.configuration.enPassant))) { moves.push(move) } function isInStartLine (color, location) { if (color === COLORS.WHITE && location[1] === '2') { return true } if (color === COLORS.BLACK && location[1] === '7') { return true } return false } return moves } getKnightMoves (piece, location) { const moves = [] const color = this.getPieceColor(piece) let field = upRightUp(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = upRightRight(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = upLeftUp(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = upLeftLeft(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = downLeftLeft(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = downLeftDown(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = downRightRight(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = downRightDown(location) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } return moves } getRookMoves (piece, location) { const moves = [] const color = this.getPieceColor(piece) let field = location while (up(field)) { field = up(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } field = location while (down(field)) { field = down(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } field = location while (right(field)) { field = right(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } field = location while (left(field)) { field = left(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } return moves } getBishopMoves (piece, location) { const moves = [] const color = this.getPieceColor(piece) let field = location while (upLeft(field)) { field = upLeft(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } field = location while (upRight(field)) { field = upRight(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } field = location while (downLeft(field)) { field = downLeft(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } field = location while (downRight(field)) { field = downRight(field) const pieceOnFieldColor = this.getPieceOnLocationColor(field) if (this.getPieceOnLocationColor(field) !== color) { moves.push(field) } if (pieceOnFieldColor) break } return moves } getQueenMoves (piece, location) { const moves = [ ...this.getRookMoves(piece, location), ...this.getBishopMoves(piece, location), ] return moves } getKingMoves (piece, location) { const moves = [] const color = this.getPieceColor(piece) let field = location field = up(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = right(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = down(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = left(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = upLeft(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = upRight(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = downLeft(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } field = location field = downRight(field) if (field && this.getPieceOnLocationColor(field) !== color) { moves.push(field) } return moves } getPieceColor (piece) { if (piece.toUpperCase() === piece) return COLORS.WHITE return COLORS.BLACK } getPieceOnLocationColor (location) { const piece = this.getPiece(location) if (!piece) return null if (piece.toUpperCase() === piece) return COLORS.WHITE return COLORS.BLACK } getPiece (location) { return this.configuration.pieces[location] } setPiece (location, piece) { if (!isPieceValid(piece)) { throw new Error(`Invalid piece ${piece}`) } if (!isLocationValid(location)) { throw new Error(`Invalid location ${location}`) } this.configuration.pieces[location.toUpperCase()] = piece } removePiece (location) { if (!isLocationValid(location)) { throw new Error(`Invalid location ${location}`) } delete this.configuration.pieces[location.toUpperCase()] } isEmpty (location) { if (!isLocationValid(location)) { throw new Error(`Invalid location ${location}`) } return !this.configuration.pieces[location.toUpperCase()] } getEnemyColor (playerColor) { return playerColor === COLORS.WHITE ? COLORS.BLACK : COLORS.WHITE } getPlayingColor () { return this.configuration.turn } getNonPlayingColor () { return this.isPlayingWhite() ? COLORS.BLACK : COLORS.WHITE } isPlayingWhite () { return this.configuration.turn === COLORS.WHITE } isPlayingBlack () { return this.configuration.turn === COLORS.BLACK } addMoveToHistory (from, to) { this.history.push({ from, to, configuration: JSON.parse(JSON.stringify(this.configuration)) }) } move (from, to) { // Move logic const chessmanFrom = this.getPiece(from) const chessmanTo = this.getPiece(to) if (!chessmanFrom) { throw new Error(`There is no piece at ${from}`) } Object.assign(this.configuration.pieces, { [to]: chessmanFrom }) delete this.configuration.pieces[from] // pawn reaches an end of a chessboard if (this.isPlayingWhite() && this.isPawn(chessmanFrom) && to[1] === '8') { Object.assign(this.configuration.pieces, { [to]: 'Q' }) } if (this.isPlayingBlack() && this.isPawn(chessmanFrom) && to[1] === '1') { Object.assign(this.configuration.pieces, { [to]: 'q' }) } // En passant check if (this.isPawn(chessmanFrom) && to === this.configuration.enPassant) { delete this.configuration.pieces[downByColor(to, this.getPlayingColor())] } // pawn En passant special move history if (this.isPawn(chessmanFrom) && this.isPlayingWhite() && from[1] === '2' && to[1] === '4') { this.configuration.enPassant = `${from[0]}3` } else if (this.isPawn(chessmanFrom) && this.isPlayingBlack() && from[1] === '7' && to[1] === '5') { this.configuration.enPassant = `${from[0]}6` } else { this.configuration.enPassant = null } // Castling - disabling if (from === 'E1') { Object.assign(this.configuration.castling, { whiteLong: false, whiteShort: false }) } if (from === 'E8') { Object.assign(this.configuration.castling, { blackLong: false, blackShort: false }) } if (from === 'A1') { Object.assign(this.configuration.castling, { whiteLong: false }) } if (from === 'H1') { Object.assign(this.configuration.castling, { whiteShort: false }) } if (from === 'A8') { Object.assign(this.configuration.castling, { blackLong: false }) } if (from === 'H8') { Object.assign(this.configuration.castling, { blackShort: false }) } // Castling - rook is moving too if (this.isKing(chessmanFrom)) { if (from === 'E1' && to === 'C1') return this.move('A1', 'D1') if (from === 'E8' && to === 'C8') return this.move('A8', 'D8') if (from === 'E1' && to === 'G1') return this.move('H1', 'F1') if (from === 'E8' && to === 'G8') return this.move('H8', 'F8') } this.configuration.turn = this.isPlayingWhite() ? COLORS.BLACK : COLORS.WHITE if (this.isPlayingWhite()) { this.configuration.fullMove++ } this.configuration.halfMove++ if (chessmanTo || this.isPawn(chessmanFrom)) { this.configuration.halfMove = 0 } } exportJson () { return { moves: this.getMoves(), pieces: this.configuration.pieces, turn: this.configuration.turn, isFinished: this.configuration.isFinished, check: this.hasPlayingPlayerCheck(), checkMate: this.configuration.checkMate, castling: this.configuration.castling, enPassant: this.configuration.enPassant, halfMove: this.configuration.halfMove, fullMove: this.configuration.fullMove, } } calculateAiMove (level) { const scores = this.calculateAiMoves(level) return scores[0] } calculateAiMoves (level) { level = parseInt(level) if (!AI_LEVELS.includes(level)) { throw new Error(`Invalid level ${level}. You can choose ${AI_LEVELS.join(',')}`) } if (this.shouldIncreaseLevel()) { level++ } const scoreTable = [] const initialScore = this.calculateScore(this.getPlayingColor()) const moves = this.getMoves() for (const from in moves) { moves[from].map(to => { const testBoard = this.getTestBoard() const wasScoreChanged = Boolean(testBoard.getPiece(to)) testBoard.move(from, to) scoreTable.push({ from, to, score: testBoard.testMoveScores(this.getPlayingColor(), level, wasScoreChanged, wasScoreChanged ? testBoard.calculateScore(this.getPlayingColor()) : initialScore, to).score + testBoard.calculateScoreByPiecesLocation(this.getPlayingColor()) + (Math.floor(Math.random() * (this.configuration.halfMove > 10 ? this.configuration.halfMove - 10 : 1) * 10) / 10), }) }) } scoreTable.sort((previous, next) => { return previous.score < next.score ? 1 : previous.score > next.score ? -1 : 0 }) return scoreTable } shouldIncreaseLevel () { return this.getIngamePiecesValue() < 50 } getIngamePiecesValue () { let scoreIndex = 0 for (const location in this.configuration.pieces) { const piece = this.getPiece(location) scoreIndex += getPieceValue(piece) } return scoreIndex } getTestBoard () { const testConfiguration = { pieces: Object.assign({}, this.configuration.pieces), castling: Object.assign({}, this.configuration.castling), turn: this.configuration.turn, enPassant: this.configuration.enPassant, } return new Board(testConfiguration) } testMoveScores (playingPlayerColor, level, capture, initialScore, move, depth = 1) { let nextMoves = null if (depth < AI_DEPTH_BY_LEVEL.EXTENDED[level] && this.hasPlayingPlayerCheck()) { nextMoves = this.getMoves(this.getPlayingColor()) } else if (depth < AI_DEPTH_BY_LEVEL.BASE[level] || (capture && depth < AI_DEPTH_BY_LEVEL.EXTENDED[level])) { nextMoves = this.getMoves(this.getPlayingColor(), 5) } if (this.configuration.isFinished) { return { score: this.calculateScore(playingPlayerColor) + (this.getPlayingColor() === playingPlayerColor ? depth : -depth), max: true, } } if (!nextMoves) { if (initialScore !== null) return { score: initialScore, max: false } const score = this.calculateScore(playingPlayerColor) return { score, max: false, } } let bestScore = this.getPlayingColor() === playingPlayerColor ? SCORE.MIN : SCORE.MAX let maxValueReached = false for (const from in nextMoves) { if (maxValueReached) continue nextMoves[from].map(to => { if (maxValueReached) return const testBoard = this.getTestBoard() const wasScoreChanged = Boolean(testBoard.getPiece(to)) testBoard.move(from, to) if (testBoard.hasNonPlayingPlayerCheck()) return const result = testBoard.testMoveScores(playingPlayerColor, level, wasScoreChanged, wasScoreChanged ? testBoard.calculateScore(playingPlayerColor) : initialScore, to, depth + 1) if (result.max) { maxValueReached = true } if (this.getPlayingColor() === playingPlayerColor) { bestScore = Math.max(bestScore, result.score) } else { bestScore = Math.min(bestScore, result.score) } }) } return { score: bestScore, max: false } } calculateScoreByPiecesLocation (player = this.getPlayingColor()) { const columnMapping = { A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7 } const scoreMultiplier = 0.5 let score = 0 for (const location in this.configuration.pieces) { const piece = this.getPiece(location) if (scoreByPosition[piece]) { const scoreIndex = scoreByPosition[piece][location[1] - 1][columnMapping[location[0]]] score += (this.getPieceColor(piece) === player ? scoreIndex : -scoreIndex) * scoreMultiplier } } return score } calculateScore (playerColor = this.getPlayingColor()) { let scoreIndex = 0 if (this.configuration.checkMate) { if (this.getPlayingColor() === playerColor) { return SCORE.MIN } else { return SCORE.MAX } } if (this.configuration.isFinished) { if (this.getPlayingColor() === playerColor) { return SCORE.MAX } else { return SCORE.MIN } } for (const location in this.configuration.pieces) { const piece = this.getPiece(location) if (this.getPieceColor(piece) === playerColor) { scoreIndex += getPieceValue(piece) * PIECE_VALUE_MULTIPLIER } else { scoreIndex -= getPieceValue(piece) * PIECE_VALUE_MULTIPLIER } } return scoreIndex } }