I am currently creating a chess game in JavaScript, some aspects are yet to be done such as the computer player and turns, but before I get into writing these features I would like to know how to restructure or edit the code so it is more flexible to changes, and is more manageable. Right now it seems that to implement some of these features, I would have to keep on copy-pasting similar sections of code.
const canvas = document.getElementById('canvas');
const c = canvas.getContext('2d');
const difficultySlider = document.getElementsByClassName('slider');
const chessPieceSWidth = 800/6;
const chessPieceSHeight = 267/2;
const chessPiecesImgSrc = "chessPieces.png";
const whiteKing = {image: {sx: 0 * chessPieceSWidth, sy: 0 * chessPieceSHeight}};
const whiteQueen = {image: {sx: 1 * chessPieceSWidth, sy: 0 * chessPieceSHeight}};
const whiteBishop = {image: {sx: 2 * chessPieceSWidth, sy: 0 * chessPieceSHeight}};
const whiteHorse = {image: {sx: 3 * chessPieceSWidth, sy: 0 * chessPieceSHeight}};
const whiteCastle = {image: {sx: 4 * chessPieceSWidth, sy: 0 * chessPieceSHeight}};
const whitePawn = {image: {sx: 5 * chessPieceSWidth, sy: 0 * chessPieceSHeight}};
const blackKing = {image: {sx: 0 * chessPieceSWidth, sy: 1 * chessPieceSHeight}};
const blackQueen = {image: {sx: 1 * chessPieceSWidth, sy: 1 * chessPieceSHeight}};
const blackBishop = {image: {sx: 2 * chessPieceSWidth, sy: 1 * chessPieceSHeight}};
const blackHorse = {image: {sx: 3 * chessPieceSWidth, sy: 1 * chessPieceSHeight}};
const blackCastle = {image: {sx: 4 * chessPieceSWidth, sy: 1 * chessPieceSHeight}};
const blackPawn = {image: {sx: 5 * chessPieceSWidth, sy: 1 * chessPieceSHeight}};
const whitePieces = [whiteCastle, whiteHorse, whiteBishop, whiteQueen, whiteKing, whitePawn];
const blackPieces = [blackCastle, blackHorse, blackBishop, blackQueen, blackKing, blackPawn];
let standardBoard = [
[blackCastle, blackHorse, blackBishop, blackQueen, blackKing, blackBishop, blackHorse, blackCastle],
[blackPawn, blackPawn, blackPawn, blackPawn, blackPawn, blackPawn, blackPawn, blackPawn],
["vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant"],
["vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant"],
["vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant"],
["vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant", "vacant"],
[whitePawn, whitePawn, whitePawn, whitePawn, whitePawn, whitePawn, whitePawn, whitePawn],
[whiteCastle, whiteHorse, whiteBishop, whiteQueen, whiteKing, whiteBishop, whiteHorse, whiteCastle]
];
let hasClicked = false;
let canMove = false;
let isHighlightPossibleMoves = false;
let canAdvancePiece = false;
let highlightPos = undefined;
let pieceMoves = undefined;
let advancePosition = undefined;
let isCastling = false;
if(Math.round(Math.random()) == 0){
humanPlayer = whitePieces;
board = copyBoardArray(standardBoard);
} else {
humanPlayer = blackPieces;
board = reverseArray(copyBoardArray(standardBoard));
}
function reverseArray(array){
return array.reverse();
}
function switchSides(){
if(humanPlayer == whitePieces){
humanPlayer = blackPieces;
} else {
humanPlayer = whitePieces;
}
board = reverseArray(board);
}
function reload(){
location.reload();
}
document.addEventListener('click', function(event){
if(!hasClicked){
if(event.clientX < 480 && event.clientY < 480 && board[Math.floor(event.clientY / 60)][Math.floor(event.clientX / 60)] != "vacant"){
if(humanPlayer.indexOf(board[Math.floor(event.clientY / 60)][Math.floor(event.clientX / 60)]) != -1){
canMove = true;
isHighlightPossibleMoves = true;
hasClicked = true;
highlightPos = {x: Math.floor(event.clientX / 60), y: Math.floor(event.clientY / 60)};
pieceMoves = processMoves({x: Math.floor(event.clientX / 60), y: Math.floor(event.clientY / 60)}, board);
} else {
hasClicked = true;
highlightPos = {x: Math.floor(event.clientX / 60), y: Math.floor(event.clientY / 60)};
canMove = false;
}
}
} else {
if(canMove){
advancePosition = {x: Math.floor(event.clientX / 60), y: Math.floor(event.clientY / 60)};
for(i = 0; i < pieceMoves.moves.length; i++){
if(advancePosition.x == pieceMoves.moves[i].x && advancePosition.y == pieceMoves.moves[i].y){
if(board[highlightPos.y][highlightPos.x] == blackKing || board[highlightPos.y][highlightPos.x] == whiteKing){
if(pieceMoves.moves[i].x - 2 == highlightPos.x || pieceMoves.moves[i].x + 2 == highlightPos.x){
isCastling = true;
} else {
isCastling = false;
}
}
if(isCastling){
board = chess.returnCastledBoard({x: highlightPos.x, y: highlightPos.y}, pieceMoves.moves[i]);
chess = new Chess(board);
isCastling = false;
} else {
board[highlightPos.y][highlightPos.x].hasClicked = true;
board = chess.updateBoard(highlightPos, advancePosition);
chess = new Chess(board);
break;
}
}
}
}
hasClicked = false;
canMove = false;
highlightPos = undefined;
pieceMoves = undefined;
advancePosition = undefined;
}
});
function getPieceType(position, board){
if(blackPieces.indexOf(board[position.y][position.x]) != -1 && board[position.y][position.x] != "vacant"){
return blackPieces;
} else if(whitePieces.indexOf(board[position.y][position.x]) != -1 && board[position.y][position.x] != "vacant"){
return whitePieces;
}
}
function isCheck(player, board){
if(player == blackPieces){
checkKing = blackKing;
opponent = whitePieces;
} else {
checkKing = whiteKing;
opponent = blackPieces;
}
for(rows = 0; rows < 8; rows++){
for(columns = 0; columns < 8; columns++){
if(board[rows][columns] == checkKing){
kingPos = {x: columns, y: rows};
break;
}
}
}
opponentMoves = [];
threatningPieces = [];
check = false;
for(rows = 0; rows < 8; rows++){
for(columns = 0; columns < 8; columns++){
if(opponent.indexOf(board[rows][columns]) != -1){
opponentMoves.push(move({x: columns, y: rows}, board));
}
}
}
for(len = 0; len < opponentMoves.length; len++){
for(subLen = 0; subLen < opponentMoves[len].moves.length; subLen++){
if(opponentMoves[len].moves[subLen].x == kingPos.x && opponentMoves[len].moves[subLen].y == kingPos.y){
check = true;
threatningPieces.push(opponentMoves[len].playerPos);
}
}
}
if(check){
threatningPieces.push(kingPos);
}
return {state: check, threatningPieces: threatningPieces};
}
function castleMove(position, board){
moves = [];
let pieceType = getPieceType(position, board);
for(i = position.x + 1; i < 8; i++){
if(board[position.y][i] != "vacant" && pieceType.indexOf(board[position.y][i]) != -1){
break;
}
if(board[position.y][i] != "vacant" && pieceType.indexOf(board[position.y][i]) == -1){
moves.push({x: i, y: position.y});
break;
}
moves.push({x: i, y: position.y});
}
for(i = position.x - 1; i >= 0; i--){
if(board[position.y][i] != "vacant" && pieceType.indexOf(board[position.y][i]) != -1){
break;
}
if(board[position.y][i] != "vacant" && pieceType.indexOf(board[position.y][i]) == -1){
moves.push({x: i, y: position.y});
break;
}
moves.push({x: i, y: position.y});
}
for(i = position.y + 1; i < 8; i++){
if(board[i][position.x] != "vacant" && pieceType.indexOf(board[i][position.x]) != -1){
break;
}
if(board[i][position.x] != "vacant" && pieceType.indexOf(board[i][position.x]) == -1){
moves.push({x: position.x, y: i});
break;
}
moves.push({x: position.x, y: i});
}
for(i = position.y - 1; i >= 0; i--){
if(board[i][position.x] != "vacant" && pieceType.indexOf(board[i][position.x]) != -1){
break;
}
if(board[i][position.x] != "vacant" && pieceType.indexOf(board[i][position.x]) == -1){
moves.push({x: position.x, y: i});
break;
}
moves.push({x: position.x, y: i});
}
return moves;
}
function horseMove(position, board){
moves = [];
let pieceType = getPieceType(position, board);
if(position.x + 1 < 8 && position.y + 2 < 8){
if(board[position.y + 2][position.x + 1] == "vacant" || pieceType.indexOf(board[position.y + 2][position.x + 1]) == -1){
moves.push({x: position.x + 1, y: position.y + 2});
}
}
if(position.x - 1 >= 0 && position.y + 2 < 8){
if(board[position.y + 2][position.x - 1] == "vacant" || pieceType.indexOf(board[position.y + 2][position.x - 1]) == -1){
moves.push({x: position.x - 1, y: position.y + 2});
}
}
if(position.x + 1 < 8 && position.y - 2 >= 0){
if(board[position.y - 2][position.x + 1] == "vacant" || pieceType.indexOf(board[position.y - 2][position.x + 1]) == -1){
moves.push({x: position.x + 1, y: position.y - 2});
}
}
if(position.x - 1 >= 0 && position.y - 2 >= 0){
if(board[position.y - 2][position.x - 1] == "vacant" || pieceType.indexOf(board[position.y - 2][position.x - 1]) == -1){
moves.push({x: position.x - 1, y: position.y - 2});
}
}
if(position.x + 2 < 8 && position.y + 1 < 8){
if(board[position.y + 1][position.x + 2] == "vacant" || pieceType.indexOf(board[position.y + 1][position.x + 2]) == -1){
moves.push({x: position.x + 2, y: position.y + 1});
}
}
if(position.x - 2 >= 0 && position.y + 1 < 8){
if(board[position.y + 1][position.x - 2] == "vacant" || pieceType.indexOf(board[position.y + 1][position.x - 2]) == -1){
moves.push({x: position.x - 2, y: position.y + 1});
}
}
if(position.x + 2 < 8 && position.y - 1 >= 0){
if(board[position.y - 1][position.x + 2] == "vacant" || pieceType.indexOf(board[position.y - 1][position.x + 2]) == -1){
moves.push({x: position.x + 2, y: position.y - 1});
}
}
if(position.x - 2 >= 0 && position.y - 1 >= 0){
if(board[position.y - 1][position.x - 2] == "vacant" || pieceType.indexOf(board[position.y - 1][position.x - 2]) == -1){
moves.push({x: position.x - 2, y: position.y - 1});
}
}
return moves;
}
function bishopMove(position, board){
moves = [];
let pieceType = getPieceType(position, board);
x = position.x + 1;
y = position.y + 1;
while(x < 8 && y < 8){
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) != -1){
break;
}
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) == -1){
moves.push({x: x, y: y});
break;
}
moves.push({x: x, y: y});
x += 1;
y += 1;
}
x = position.x - 1;
y = position.y - 1;
while(x >= 0 && y >= 0){
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) != -1){
break;
}
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) == -1){
moves.push({x: x, y: y});
break;
}
moves.push({x: x, y: y});
x -= 1;
y -= 1;
}
x = position.x - 1;
y = position.y + 1;
while(x >= 0 && y < 8){
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) != -1){
break;
}
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) == -1){
moves.push({x: x, y: y});
break;
}
moves.push({x: x, y: y});
x -= 1;
y += 1;
}
x = position.x + 1;
y = position.y - 1;
while(x < 8 && y >= 0){
if(board[y][x] != "vacant" && pieceType.indexOf(board[y][x]) != -1){
break;
}
if(board[y][x] != "vacant" && pieceType.indexOf() == -1){
moves.push({x: x, y: y});
break;
}
moves.push({x: x, y: y});
x += 1;
y -= 1;
}
return moves;
}
function kingMove(position, board){
moves = [];
let pieceType = getPieceType(position, board);
if(position.x + 1 < 8){
if(board[position.y][position.x + 1] == "vacant" || pieceType.indexOf(board[position.y][position.x + 1]) == -1){
moves.push({x: position.x + 1, y: position.y});
}
}
if(position.x - 1 >= 0){
if(board[position.y][position.x - 1] == "vacant" || pieceType.indexOf(board[position.y][position.x - 1]) == -1){
moves.push({x: position.x - 1, y: position.y});
}
}
if(position.y + 1 < 8){
if(board[position.y + 1][position.x] == "vacant" || pieceType.indexOf(board[position.y + 1][position.x]) == -1){
moves.push({x: position.x, y: position.y + 1});
}
}
if(position.y - 1 >= 0){
if(this.board[position.y - 1][position.x] == "vacant" || pieceType.indexOf(this.board[position.y - 1][position.x]) == -1){
moves.push({x: position.x, y: position.y - 1});
}
}
if(position.y - 1 >= 0 && position.x - 1 >= 0){
if(board[position.y - 1][position.x - 1] == "vacant" || pieceType.indexOf(board[position.y - 1][position.x - 1]) == -1){
moves.push({x: position.x - 1, y: position.y - 1});
}
}
if(position.y + 1 < 8 && position.x + 1 < 8){
if(board[position.y + 1][position.x + 1] == "vacant" || pieceType.indexOf(board[position.y + 1][position.x + 1]) == -1){
moves.push({x: position.x + 1, y: position.y + 1});
}
}
if(position.y + 1 < 8 && position.x - 1 >= 0){
if(board[position.y + 1][position.x - 1] == "vacant" || pieceType.indexOf(board[position.y + 1][position.x - 1]) == -1){
moves.push({x: position.x - 1, y: position.y + 1});
}
}
if(position.y - 1 >= 0 && position.x + 1 < 8){
if(board[position.y - 1][position.x + 1] == "vacant" || pieceType.indexOf(board[position.y - 1][position.x + 1]) == -1){
moves.push({x: position.x + 1, y: position.y - 1});
}
}
return moves;
}
function pawnMove(position, board){
moves = [];
let pieceType = getPieceType(position, board);
if(humanPlayer == whitePieces){
standardPawn = whitePawn;
} else {
standardPawn = blackPawn;
}
if(this.board[position.y][position.x] == standardPawn){
if(position.y == 6){
if(board[position.y - 1][position.x] == "vacant"){
moves.push({x: position.x, y: position.y - 1});
}
if(board[position.y - 2][position.x] == "vacant" && board[position.y - 1][position.x] == "vacant"){
moves.push({x: position.x, y: position.y - 2});
}
} else if(position.y - 1 >= 0){
if(board[position.y - 1][position.x] == "vacant"){
moves.push({x: position.x, y: position.y - 1});
}
}
if(position.x + 1 < 8 && position.y - 1 >= 0){
if(getPieceType({x: position.x + 1, y: position.y - 1}, this.board) != pieceType && board[position.y - 1][position.x + 1] != "vacant"){
moves.push({x: position.x + 1, y: position.y - 1});
}
}
if(position.x - 1 >= 0 && position.y - 1 >= 0){
if(getPieceType({x: position.x - 1, y: position.y - 1}, this.board) != pieceType && board[position.y - 1][position.x - 1] != "vacant"){
moves.push({x: position.x - 1, y: position.y - 1});
}
}
} else {
if(position.y == 1){
if(board[position.y + 1][position.x] == "vacant"){
moves.push({x: position.x, y: position.y + 1});
}
if(board[position.y + 2][position.x] == "vacant" && board[position.y + 1][position.x] == "vacant"){
moves.push({x: position.x, y: position.y + 2});
}
} else if(position.y + 1 < 8){
if(board[position.y + 1][position.x] == "vacant"){
moves.push({x: position.x, y: position.y + 1});
}
}
if(position.x + 1 < 8 && position.y + 1 < 8){
if(getPieceType({x: position.x + 1, y: position.y + 1}, this.board) != pieceType && board[position.y + 1][position.x + 1] != "vacant"){
moves.push({x: position.x + 1, y: position.y + 1});
}
}
if(position.x - 1 >= 0 && position.y + 1 < 8){
if(getPieceType({x: position.x - 1, y: position.y + 1}, this.board) != pieceType && board[position.y + 1][position.x - 1] != "vacant"){
moves.push({x: position.x - 1, y: position.y + 1});
}
}
}
return moves;
}
function move(position, board){
let boardPos = board[position.y][position.x];
if(boardPos == blackCastle || boardPos == whiteCastle){
return {playerPos: {x: position.x, y: position.y}, moves: castleMove(position, board)};
} else if(boardPos == blackHorse || boardPos == whiteHorse){
return {playerPos: {x: position.x, y: position.y}, moves: horseMove(position, board)};
} else if(boardPos == blackBishop || boardPos == whiteBishop){
return {playerPos: {x: position.x, y: position.y}, moves: bishopMove(position, board)};
} else if(boardPos == blackQueen || boardPos == whiteQueen){
possibleMoves = castleMove(position, board);
for(i = 0; i < bishopMove(position, board).length; i++){
possibleMoves.push(bishopMove(position, board)[i]);
}
return {playerPos: {x: position.x, y: position.y}, moves: possibleMoves};
} else if(boardPos == whiteKing || boardPos == blackKing){
return {boardPos: {x: position.x, y: position.y}, moves: kingMove(position, board)}
} else if(boardPos == whitePawn || boardPos == blackPawn){
return {playerPos: {x: position.x, y: position.y}, moves: pawnMove(position, board)};
}
}
function processMoves(position, board){
let pieceType = getPieceType(position, board);
let posMoves = move(position, board).moves;
for(index = posMoves.length - 1; index >= 0; index--){
bCopy = copyBoardArray(board);
cCopy = new Chess(bCopy);
bCopy = cCopy.updateBoard(position, posMoves[index]);
if(isCheck(pieceType, bCopy).state){
posMoves.splice(index, 1);
}
if(board[position.y][position.x] == blackKing || board[position.y][position.x] == whiteKing){
castleMoves = castlingMoves(position, board);
for(indI = 0; indI < castleMoves.length; indI++){
posMoves.push(castleMoves[indI]);
}
}
}
return {playerPos: {x: position.x, y: position.y}, moves: posMoves};
}
function castlingMoves(position, board){
let pieceType = getPieceType(position, board);
let castleMoves = [];
if(board[position.y][position.x].hasClicked === undefined){
if(getPieceType({x: 0, y: position.y}, board) == pieceType && board[position.y][0].hasClicked === undefined){
for(key = position.x - 1; key >= 1; key--){
if(board[position.y][key] == "vacant"){
isPieceBlocking = false;
} else {
isPieceBlocking = true;
break;
}
}
if(!isPieceBlocking){
for(key = position.x; key > position.x - 3; key--){
bdCopy = copyBoardArray(board);
chessCpy = new Chess(bdCopy);
bdCopy = chessCpy.updateBoard(position, {x: key, y: position.y});
if(isCheck(pieceType, bdCopy).state){
isIllegal = true;
break;
} else {
isIllegal = false;
}
}
}
if(!isPieceBlocking && !isIllegal){
castleMoves.push({x: position.x - 2, y: position.y});
}
}
if(getPieceType({x: 7, y: position.y}, board) == pieceType && board[position.y][7].hasClicked === undefined){
for(key = position.x + 1; key < 7; key++){
if(board[position.y][key] == "vacant"){
isPieceBlocking = false;
} else {
isPieceBlocking = true;
break;
}
}
if(!isPieceBlocking){
for(key = position.x; key < position.x + 3; key++){
bdCopy = copyBoardArray(board);
chessCpy = new Chess(bdCopy);
bdCopy = chessCpy.updateBoard(position, {x: key, y: position.y});
if(isCheck(pieceType, bdCopy).state){
isIllegal = true;
break;
} else {
isIllegal = false;
}
}
}
if(!isPieceBlocking && !isIllegal){
castleMoves.push({x: position.x + 2, y: position.y});
}
}
}
return castleMoves;
}
function Chess(board){
this.board = board;
this.updateBoard = function(playerPastPos, playerNextPos){
let boardDeepClone = copyBoardArray(this.board);
let player = this.board[playerPastPos.y][playerPastPos.x];
boardDeepClone[playerPastPos.y][playerPastPos.x] = "vacant";
boardDeepClone[playerNextPos.y][playerNextPos.x] = player;
return boardDeepClone;
}
this.returnCastledBoard = function(kingPos, movePos){
let king = this.board[kingPos.y][kingPos.x];
if(movePos.x > kingPos.x){
targetCastle = this.board[movePos.y][7];
boardDeepClone = copyBoardArray(this.board);
boardDeepClone[kingPos.y][kingPos.x] = "vacant";
boardDeepClone[kingPos.y][kingPos.x + 2] = king;
boardDeepClone[kingPos.y][7] = "vacant";
boardDeepClone[kingPos.y][kingPos.x + 1] = targetCastle;
} else {
targetCastle = this.board[movePos.y][0];
boardDeepClone = copyBoardArray(this.board);
boardDeepClone[kingPos.y][kingPos.x] = "vacant";
boardDeepClone[kingPos.y][kingPos.x - 2] = king;
boardDeepClone[kingPos.y][0] = "vacant";
boardDeepClone[kingPos.y][kingPos.x - 1] = targetCastle;
}
return boardDeepClone;
}
}
function copyBoardArray(board){
let boardCopy = [];
for(i = 0; i < 8; i++){
boardCopy.push([0]);
for(j = 0; j < 8; j++){
boardCopy[i][j] = board[i][j];
}
}
return boardCopy;
}
function rect(x, y, width, height, color){
c.beginPath();
c.rect(x, y, width, height);
c.fillStyle = color;
c.fill();
c.closePath();
}
let chess = new Chess(board);
function render(){
for(i = 0; i < 8; i++){
for(j = 0; j < 8; j++){
if(i % 2 == 0){
if(j % 2 == 0){
rect(j * 60, i * 60, 60, 60, "peru");
} else {
rect(j * 60, i * 60, 60, 60, "seashell");
}
c.stroke();
} else {
if(j % 2 == 0){
rect(j * 60, i * 60, 60, 60, "seashell");
} else {
rect(j * 60, i * 60, 60, 60, "peru");
}
}
c.stroke();
}
}
if(isCheck(humanPlayer, board).state){
for(ind = 0; ind < isCheck(humanPlayer, board).threatningPieces.length; ind++){
rect(isCheck(humanPlayer, board).threatningPieces[ind].x * 60, isCheck(humanPlayer, board).threatningPieces[ind].y * 60, 60, 60, "red");
c.stroke();
}
}
if(highlightPos != undefined){
rect(highlightPos.x * 60, highlightPos.y * 60, 60, 60, "yellow");
c.stroke();
}
for(i = 0; i < 8; i++){
for(j = 0; j < 8; j++){
if(board[i][j] != "vacant"){
let image = new Image();
image.src = "chessPieces.png";
c.drawImage(image, board[i][j].image.sx, board[i][j].image.sy, chessPieceSWidth, chessPieceSHeight, j * 60, i * 60, 60, 60);
}
}
}
if(pieceMoves != undefined){
c.globalAlpha = 0.6;
for(i = 0; i < pieceMoves.moves.length; i++){
c.beginPath();
c.arc(pieceMoves.moves[i].x * 60 + 30, pieceMoves.moves[i].y * 60 + 30, 12, 12, 0, Math.PI * 2);
c.fillStyle = "grey";
c.fill();
c.closePath();
}
c.globalAlpha = 1;
}
}
setInterval(render, 10);
````