├── Board.js
├── Game.js
├── History.js
├── README.md
├── SimulationGame.js
├── ai.js
├── chess.css
├── chess.html
├── favicon.ico
├── img
├── black-bishop.webp
├── black-king.webp
├── black-knight.webp
├── black-pawn.webp
├── black-queen.webp
├── black-rook.webp
├── share.jpeg
├── white-bishop.webp
├── white-king.webp
├── white-knight.webp
├── white-pawn.webp
├── white-queen.webp
└── white-rook.webp
└── piece.js
/Board.js:
--------------------------------------------------------------------------------
1 | const startBoard = (game, options = { playAgainst: 'human', aiColor: 'black', aiLevel: 'dumb' }) => {
2 |
3 | const aiPlayer = options.playAgainst === 'ai' ? ai(options.aiColor) : null;
4 |
5 | const board = document.getElementById('board');
6 | const squares = board.querySelectorAll('.square');
7 | const whiteSematary = document.getElementById('whiteSematary');
8 | const blackSematary = document.getElementById('blackSematary');
9 | const turnSign = document.getElementById('turn');
10 | let clickedPieceName;
11 |
12 | const resetSematary = () => {
13 | whiteSematary.querySelectorAll('div').forEach(div => div.innerHTML = '');
14 | blackSematary.querySelectorAll('div').forEach(div => div.innerHTML = '');
15 | }
16 |
17 | const resetBoard = () => {
18 | resetSematary();
19 |
20 | for (const square of squares) {
21 | square.innerHTML = '';
22 | }
23 |
24 | for (const piece of game.pieces) {
25 | const square = document.getElementById(piece.position);
26 | square.innerHTML = ``;
27 | }
28 |
29 | document.getElementById('endscene').classList.remove('show');
30 | }
31 |
32 | resetBoard();
33 |
34 | const setGameState = state => {
35 | gameState = state;
36 | if (gameState === 'ai_thinking') {
37 | turnSign.innerHTML += ' (thinking...)';
38 | }
39 | }
40 |
41 | const setAllowedSquares = (pieceImg) => {
42 | clickedPieceName = pieceImg.id;
43 | const allowedMoves = game.getPieceAllowedMoves(clickedPieceName);
44 | if (allowedMoves) {
45 | const clickedSquare = pieceImg.parentNode;
46 | clickedSquare.classList.add('clicked-square');
47 |
48 | allowedMoves.forEach( allowedMove => {
49 | if (document.contains(document.getElementById(allowedMove))) {
50 | document.getElementById(allowedMove).classList.add('allowed');
51 | }
52 | });
53 | }
54 | else{
55 | clearSquares();
56 | }
57 | }
58 |
59 | const clearSquares = () => {
60 | board.querySelectorAll('.allowed').forEach( allowedSquare => allowedSquare.classList.remove('allowed') );
61 |
62 | const clickedSquare = document.getElementsByClassName('clicked-square')[0];
63 | if (clickedSquare) {
64 | clickedSquare.classList.remove('clicked-square');
65 | }
66 | }
67 |
68 | const setLastMoveSquares = (from, to) => {
69 | document.querySelectorAll('.last-move').forEach( lastMoveSquare => lastMoveSquare.classList.remove('last-move') );
70 | from.classList.add('last-move');
71 | to.classList.add('last-move');
72 | }
73 |
74 | function movePiece(square) {
75 | if (gameState === 'ai_thinking') {
76 | return;
77 | }
78 |
79 | const position = square.getAttribute('id');
80 | const existedPiece = game.getPieceByPos(position);
81 |
82 | if (existedPiece && existedPiece.color === game.turn) {
83 | const pieceImg = document.getElementById(existedPiece.name);
84 | clearSquares();
85 | return setAllowedSquares(pieceImg);
86 | }
87 |
88 | game.movePiece(clickedPieceName, position);
89 | }
90 |
91 | squares.forEach( square => {
92 | square.addEventListener("click", function () {
93 | movePiece(this);
94 | });
95 | square.addEventListener("dragover", function(event){
96 | event.preventDefault();
97 | });
98 | square.addEventListener("drop", function () {
99 | movePiece(this);
100 | });
101 | });
102 |
103 | game.pieces.forEach( piece => {
104 | const pieceImg = document.getElementById(piece.name);
105 | pieceImg.addEventListener("drop", function () {
106 | const square = document.getElementById(piece.position);
107 | movePiece(square);
108 | });
109 | });
110 |
111 | document.querySelectorAll('img.piece').forEach( pieceImg => {
112 | pieceImg.addEventListener("dragstart", function(event) {
113 | if (gameState === 'ai_thinking') {
114 | return;
115 | }
116 | event.stopPropagation();
117 | event.dataTransfer.setData("text", event.target.id);
118 | clearSquares();
119 | setAllowedSquares(event.target)
120 | });
121 | pieceImg.addEventListener("drop", function(event) {
122 | if (gameState === 'ai_thinking') {
123 | return;
124 | }
125 | event.stopPropagation();
126 | clearSquares();
127 | setAllowedSquares(event.target)
128 | });
129 | });
130 |
131 | const startTurn = turn => {
132 | gameState = turn + '_turn';
133 | turnSign.innerHTML = turn === 'white' ? "White's Turn" : "Black's Turn";
134 |
135 | if (gameState !== 'checkmate' && options.playAgainst === 'ai' && turn === options.aiColor) {
136 | setGameState('ai_thinking');
137 | aiPlayer.play(game.pieces, aiPlay => {
138 | setGameState('human_turn');
139 | game.movePiece(aiPlay.move.pieceName, aiPlay.move.position);
140 | });
141 | }
142 | }
143 |
144 | game.on('pieceMove', move => {
145 | const from = document.getElementById(move.from);
146 | const to = document.getElementById(move.piece.position);
147 | to.append( document.getElementById(move.piece.name) );
148 | clearSquares();
149 |
150 | setLastMoveSquares(from, to);
151 | });
152 |
153 | game.on('turnChange', startTurn);
154 |
155 | game.on('promotion', queen => {
156 | const square = document.getElementById(queen.position);
157 | square.innerHTML = `
`;
158 | })
159 |
160 | game.on('kill', piece => {
161 | const pieceImg = document.getElementById(piece.name);
162 | pieceImg.parentNode.removeChild(pieceImg);
163 | pieceImg.className = '';
164 |
165 | const sematary = piece.color === 'white' ? whiteSematary : blackSematary;
166 | sematary.querySelector('.'+piece.rank).append(pieceImg);
167 | });
168 |
169 | game.on('checkMate', color => {
170 | const endScene = document.getElementById('endscene');
171 | endScene.getElementsByClassName('winning-sign')[0].innerHTML = color + ' Wins';
172 | endScene.classList.add('show');
173 | setGameState('checkmate');
174 | });
175 |
176 | startTurn('white');
177 | }
178 |
179 | const pieces = [
180 | { rank: 'knight', position: 12, color: 'white', name: 'whiteKnight1' },
181 | { rank: 'knight', position: 17, color: 'white', name: 'whiteKnight2' },
182 | { rank: 'queen', position: 14, color: 'white', name: 'whiteQueen' },
183 | { rank: 'bishop', position: 13, color: 'white', name: 'whiteBishop1' },
184 | { rank: 'bishop', position: 16, color: 'white', name: 'whiteBishop2' },
185 | { rank: 'pawn', position: 24, color: 'white', name: 'whitePawn4' },
186 | { rank: 'pawn', position: 25, color: 'white', name: 'whitePawn5' },
187 | { rank: 'pawn', position: 26, color: 'white', name: 'whitePawn6' },
188 | { rank: 'pawn', position: 21, color: 'white', name: 'whitePawn1' },
189 | { rank: 'pawn', position: 22, color: 'white', name: 'whitePawn2' },
190 | { rank: 'pawn', position: 23, color: 'white', name: 'whitePawn3' },
191 | { rank: 'pawn', position: 27, color: 'white', name: 'whitePawn7' },
192 | { rank: 'pawn', position: 28, color: 'white', name: 'whitePawn8' },
193 | { rank: 'rook', position: 11, color: 'white', name: 'whiteRook1', ableToCastle: true },
194 | { rank: 'rook', position: 18, color: 'white', name: 'whiteRook2', ableToCastle: true },
195 | { rank: 'king', position: 15, color: 'white', name: 'whiteKing', ableToCastle: true },
196 |
197 | { rank: 'knight', position: 82, color: 'black', name: 'blackKnight1' },
198 | { rank: 'knight', position: 87, color: 'black', name: 'blackKnight2' },
199 | { rank: 'queen', position: 84, color: 'black', name: 'blackQueen' },
200 | { rank: 'bishop', position: 83, color: 'black', name: 'blackBishop1' },
201 | { rank: 'bishop', position: 86, color: 'black', name: 'blackBishop2' },
202 | { rank: 'pawn', position: 74, color: 'black', name: 'blackPawn4' },
203 | { rank: 'pawn', position: 75, color: 'black', name: 'blackPawn5' },
204 | { rank: 'pawn', position: 76, color: 'black', name: 'blackPawn6' },
205 | { rank: 'pawn', position: 71, color: 'black', name: 'blackPawn1' },
206 | { rank: 'pawn', position: 72, color: 'black', name: 'blackPawn2' },
207 | { rank: 'pawn', position: 73, color: 'black', name: 'blackPawn3' },
208 | { rank: 'pawn', position: 77, color: 'black', name: 'blackPawn7' },
209 | { rank: 'pawn', position: 78, color: 'black', name: 'blackPawn8' },
210 | { rank: 'rook', position: 81, color: 'black', name: 'blackRook1', ableToCastle: true },
211 | { rank: 'rook', position: 88, color: 'black', name: 'blackRook2', ableToCastle: true },
212 | { rank: 'king', position: 85, color: 'black', name: 'blackKing', ableToCastle: true },
213 | ];
214 | const game = new Game(pieces, 'white');
215 |
216 | const startNewGame = () => {
217 | document.querySelectorAll('.scene').forEach( scene => scene.classList.remove('show') );
218 |
219 | const playAgainst = document.querySelector('input[name="oponent"]:checked').value;
220 | const humanColor = document.querySelector('input[name="human_color"]:checked')?.value;
221 | const aiColor = humanColor === 'white' ? 'black' : 'white';
222 | const aiLevel = 'dumb';
223 |
224 | startBoard(game, {playAgainst, aiColor, aiLevel});
225 | }
226 |
227 | const showColorSelect = () => document.querySelector('.select-color-container').classList.add('show');
228 | const hideColorSelect = () => document.querySelector('.select-color-container').classList.remove('show');
--------------------------------------------------------------------------------
/Game.js:
--------------------------------------------------------------------------------
1 | class Game {
2 | constructor(pieces, turn) {
3 | this.startNewGame(pieces, turn);
4 | }
5 |
6 | startNewGame(pieces, turn) {
7 | this._setPieces(pieces);
8 |
9 | this.turn = turn;
10 | this.clickedPiece = null;
11 | this._events = {
12 | pieceMove: [],
13 | kill: [],
14 | check: [],
15 | promotion: [],
16 | checkMate: [],
17 | turnChange: []
18 | }
19 | this.history = new History();
20 | }
21 |
22 | _setPieces(pieces) {
23 | this.pieces = Array(pieces.length);
24 | pieces.forEach( (piece, i) => {
25 | this.pieces[i] = { rank: piece.rank, position: piece.position, color: piece.color, name: piece.name, ableToCastle: piece.ableToCastle }
26 | });
27 | this.playerPieces = {
28 | white: this.pieces.filter(piece => piece.color === 'white'),
29 | black: this.pieces.filter(piece => piece.color === 'black')
30 | }
31 | }
32 |
33 | _removePiece(piece) {
34 | this.pieces.splice(this.pieces.indexOf(piece), 1);
35 | this.playerPieces[piece.color].splice(this.playerPieces[piece.color].indexOf(piece), 1)
36 | }
37 |
38 | _addPiece(piece) {
39 | this.pieces.push(piece);
40 | this.playerPieces[piece.color].push(piece);
41 | }
42 |
43 | saveHistory() {
44 | this.history.save();
45 | }
46 |
47 | addToHistory(move) {
48 | this.history.add(move);
49 | }
50 |
51 | clearEvents() {
52 | this._events = {};
53 | }
54 |
55 | undo() {
56 | const step = this.history.pop();
57 |
58 | if (!step) {
59 | return false;
60 | }
61 |
62 | for (const subStep of step) {
63 | changePosition(subStep.piece, subStep.from);
64 | if (subStep.from !== 0) {
65 | if (subStep.to === 0) {
66 | this._addPiece(subStep.piece);
67 | }
68 | else if (subStep.castling) {
69 | subStep.piece.ableToCastle = true;
70 | }
71 | this.triggerEvent('pieceMove', subStep);
72 | }
73 | else {
74 | this._removePiece(subStep.piece);
75 | this.triggerEvent('kill', subStep.piece);
76 | }
77 |
78 | if (subStep.from !== 0 && subStep.to !== 0 && (!subStep.castling || subStep.piece.rank === 'king') ) {
79 | this.softChangeTurn();
80 | }
81 | }
82 | }
83 |
84 | on(eventName, callback) {
85 | if (this._events[eventName] && typeof callback === 'function') {
86 | this._events[eventName].push(callback);
87 | }
88 | }
89 |
90 | softChangeTurn() {
91 | this.turn = this.turn === 'white' ? 'black' : 'white';
92 | this.triggerEvent('turnChange', this.turn);
93 | }
94 |
95 | changeTurn() {
96 | this.softChangeTurn();
97 | this.saveHistory();
98 | }
99 |
100 | getPiecesByColor(color) {
101 | return this.playerPieces[color];
102 | }
103 |
104 | getPlayerPositions(color){
105 | return this.getPiecesByColor(color).map(piece => piece.position);
106 | }
107 |
108 | filterPositions(positions) {
109 | return positions.filter(pos => {
110 | const x = pos % 10;
111 | return pos > 10 && pos < 89 && x !== 9 && x !== 0;
112 | });
113 | };
114 |
115 | unblockedPositions(piece, allowedPositions, checking=true) {
116 | const unblockedPositions = [];
117 |
118 | const myColor = piece.color;
119 | const otherColor = piece.color === 'white' ? 'black' : 'white';
120 |
121 | const myBlockedPositions = this.getPlayerPositions(myColor);
122 | const otherBlockedPositions = this.getPlayerPositions(otherColor);
123 |
124 | if (piece.rank === 'pawn') {
125 | for (const move of allowedPositions[0]) { //attacking moves
126 | if (checking && this.myKingChecked(move)) continue;
127 | if (otherBlockedPositions.indexOf(move) !== -1) unblockedPositions.push(move);
128 | }
129 |
130 | for (const move of allowedPositions[1]) { //moving moves
131 | if (myBlockedPositions.indexOf(move) !== -1 || otherBlockedPositions.indexOf(move) !== -1) {
132 | break;
133 | }
134 | else if (checking && this.myKingChecked(move, false)) continue;
135 | unblockedPositions.push(move);
136 | }
137 | }
138 | else{
139 | allowedPositions.forEach( allowedPositionsGroup => {
140 | for (const move of allowedPositionsGroup) {
141 | if (myBlockedPositions.indexOf(move) !== -1) {
142 | break;
143 | }
144 | else if ( checking && this.myKingChecked(move) ) {
145 | if (otherBlockedPositions.indexOf(move) !== -1) {
146 | break;
147 | }
148 | continue;
149 | }
150 | unblockedPositions.push(move);
151 |
152 | if (otherBlockedPositions.indexOf(move) !== -1) {
153 | break;
154 | }
155 | }
156 | });
157 | }
158 |
159 | return this.filterPositions(unblockedPositions);
160 | }
161 |
162 | getPieceAllowedMoves(pieceName){
163 | const piece = this.getPieceByName(pieceName);
164 | if(this.turn === piece.color){
165 | this.setClickedPiece(piece);
166 |
167 | let pieceAllowedMoves = getAllowedMoves(piece);
168 | if (piece.rank === 'king') {
169 | pieceAllowedMoves = this.getCastlingSquares(piece, pieceAllowedMoves);
170 | }
171 |
172 | return this.unblockedPositions(piece, pieceAllowedMoves, true);
173 | }
174 | else{
175 | return [];
176 | }
177 | }
178 |
179 | getCastlingSquares(king, allowedMoves) {
180 | if ( !king.ableToCastle || this.king_checked(this.turn) ) return allowedMoves;
181 | const rook1 = this.getPieceByName(this.turn+'Rook1');
182 | const rook2 = this.getPieceByName(this.turn+'Rook2');
183 | if (rook1 && rook1.ableToCastle) {
184 | const castlingPosition = rook1.position + 2
185 | if(
186 | !this.positionHasExistingPiece(castlingPosition - 1) &&
187 | !this.positionHasExistingPiece(castlingPosition) && !this.myKingChecked(castlingPosition, true) &&
188 | !this.positionHasExistingPiece(castlingPosition + 1) && !this.myKingChecked(castlingPosition + 1, true)
189 | )
190 | allowedMoves[1].push(castlingPosition);
191 | }
192 | if (rook2 && rook2.ableToCastle) {
193 | const castlingPosition = rook2.position - 1;
194 | if(
195 | !this.positionHasExistingPiece(castlingPosition - 1) && !this.myKingChecked(castlingPosition - 1, true) &&
196 | !this.positionHasExistingPiece(castlingPosition) && !this.myKingChecked(castlingPosition, true)
197 | )
198 | allowedMoves[0].push(castlingPosition);
199 | }
200 | return allowedMoves;
201 | }
202 |
203 | getPieceByName(piecename) {
204 | for (const piece of this.pieces) {
205 | if (piece.name === piecename) {
206 | return piece;
207 | }
208 | }
209 | }
210 |
211 | getPieceByPos(position) {
212 | for (const piece of this.pieces) {
213 | if (piece.position == position) {
214 | return piece;
215 | }
216 | }
217 | }
218 |
219 | positionHasExistingPiece(position) {
220 | return this.getPieceByPos(position) !== undefined;
221 | }
222 |
223 | setClickedPiece(piece) {
224 | this.clickedPiece = piece;
225 | }
226 |
227 | triggerEvent(eventName, params) {
228 | if (this._events[eventName]) {
229 | for (const cb of this._events[eventName]) {
230 | cb(params);
231 | }
232 | }
233 | }
234 |
235 | movePiece(pieceName, position) {
236 | const piece = this.getPieceByName(pieceName);
237 |
238 | position = parseInt(position);
239 |
240 | if (piece && this.getPieceAllowedMoves(piece.name).indexOf(position) !== -1) {
241 | const prevPosition = piece.position;
242 | const existedPiece = this.getPieceByPos(position)
243 |
244 | if (existedPiece) {
245 | this.kill(existedPiece);
246 | }
247 |
248 | const castling = !existedPiece && piece.rank === 'king' && piece.ableToCastle === true;
249 |
250 | if (castling) {
251 | if (position - prevPosition === 2) {
252 | this.castleRook(piece.color + 'Rook2');
253 | }
254 | else if (position - prevPosition === -2) {
255 | this.castleRook(piece.color + 'Rook1');
256 | }
257 | changePosition(piece, position, true);
258 | }
259 | else {
260 | changePosition(piece, position);
261 | }
262 |
263 | const move = { from: prevPosition, to: position, piece: piece, castling };
264 | this.addToHistory(move);
265 | this.triggerEvent('pieceMove', move);
266 |
267 | if (piece.rank === 'pawn' && (position > 80 || position < 20)) {
268 | this.promote(piece);
269 | }
270 |
271 | this.changeTurn();
272 |
273 | if (this.king_checked(this.turn)) {
274 | this.triggerEvent('check', this.turn);
275 |
276 | if (this.king_dead(this.turn)) {
277 | this.checkmate(piece.color);
278 | }
279 | else{
280 | // alert('check');
281 | }
282 | }
283 |
284 | return true;
285 | }
286 | else{
287 | return false;
288 | }
289 | }
290 |
291 | kill(piece) {
292 | this._removePiece(piece);
293 | this.addToHistory({from: piece.position, to: 0, piece: piece});
294 | this.triggerEvent('kill', piece);
295 | }
296 |
297 | castleRook(rookName) {
298 | const rook = this.getPieceByName(rookName);
299 | const prevPosition = rook.position;
300 | const newPosition = rookName.indexOf('Rook2') !== -1 ? rook.position - 2 : rook.position + 3;
301 |
302 | changePosition(rook, newPosition);
303 | const move = {from: prevPosition, to: newPosition, piece: rook, castling: true};
304 | this.triggerEvent('pieceMove', move);
305 | this.addToHistory(move);
306 | }
307 |
308 | promote(pawn) {
309 | pawn.name = pawn.name.replace('Pawn', 'Queen');
310 | pawn.rank = 'queen';
311 | this.addToHistory({from: 0, to: pawn.position, piece: pawn});
312 | this.triggerEvent('promotion', pawn);
313 | }
314 |
315 | myKingChecked(pos, kill=true){
316 | const piece = this.clickedPiece;
317 | const originalPosition = piece.position;
318 | const otherPiece = this.getPieceByPos(pos);
319 | const should_kill_other_piece = kill && otherPiece && otherPiece.rank !== 'king';
320 | changePosition(piece, pos);
321 | if (should_kill_other_piece) {
322 | this._removePiece(otherPiece);
323 | }
324 | if (this.king_checked(piece.color)) {
325 | changePosition(piece, originalPosition);
326 | if (should_kill_other_piece) {
327 | this._addPiece(otherPiece);
328 | }
329 | return 1;
330 | }
331 | else{
332 | changePosition(piece, originalPosition);
333 | if (should_kill_other_piece) {
334 | this._addPiece(otherPiece);
335 | }
336 | return 0;
337 | }
338 | }
339 |
340 | king_dead(color) {
341 | const pieces = this.getPiecesByColor(color);
342 | for (const piece of pieces) {
343 | this.setClickedPiece(piece);
344 | const allowedMoves = this.unblockedPositions(piece, getAllowedMoves(piece), true);
345 | if (allowedMoves.length) {
346 | this.setClickedPiece(null);
347 | return 0;
348 | }
349 | }
350 | this.setClickedPiece(null);
351 | return 1;
352 | }
353 |
354 | king_checked(color) {
355 | const piece = this.clickedPiece;
356 | const king = this.getPieceByName(color + 'King');
357 | const enemyColor = (color === 'white') ? 'black' : 'white';
358 | const enemyPieces = this.getPiecesByColor(enemyColor);
359 | for (const enemyPiece of enemyPieces) {
360 | this.setClickedPiece(enemyPiece);
361 | const allowedMoves = this.unblockedPositions(enemyPiece, getAllowedMoves(enemyPiece), false);
362 | if (allowedMoves.indexOf(king.position) !== -1) {
363 | this.setClickedPiece(piece);
364 | return 1;
365 | }
366 | }
367 | this.setClickedPiece(piece);
368 | return 0;
369 | }
370 |
371 | checkmate(color){
372 | this.triggerEvent('checkMate', color);
373 | this.clearEvents();
374 | }
375 | }
--------------------------------------------------------------------------------
/History.js:
--------------------------------------------------------------------------------
1 | class History {
2 |
3 | constructor() {
4 | this._lastStep = [];
5 | this._history = [];
6 | }
7 |
8 | add(step) {
9 | this._lastStep.push(step);
10 | }
11 |
12 | save() {
13 | this._history.push(this._lastStep);
14 | this._lastStep = [];
15 | }
16 |
17 | pop() {
18 | return this._history.pop();
19 | }
20 |
21 | lastMove() {
22 | return this._history[ this._history.length - 1 ];
23 | }
24 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a complete well automated chess game made by object oriented javascript I hope you like it.
2 |
3 | Play it from here: https://ahmadalkholy.github.io/Javascript-Chess-Game/chess.html
4 |
5 | ------------
6 |
7 | The chess pieces images are originaly made by Clker-Free-Vector-Images from Pixabay you can get get it from here.
--------------------------------------------------------------------------------
/SimulationGame.js:
--------------------------------------------------------------------------------
1 | class SimulationGame extends Game {
2 |
3 | startNewGame(pieces, turn) {
4 | this._setPieces(pieces);
5 | this.turn = turn;
6 | this.clickedPiece = null;
7 | }
8 |
9 | saveHistory() {}
10 |
11 | addToHistory(move) {}
12 |
13 | triggerEvent(eventName, params) {}
14 |
15 | clearEvents() {}
16 |
17 | undo() {}
18 |
19 | getPieceAllowedMoves(pieceName){
20 | const piece = this.getPieceByName(pieceName);
21 | if(piece && this.turn === piece.color){
22 | this.setClickedPiece(piece);
23 |
24 | let pieceAllowedMoves = getAllowedMoves(piece);
25 | if (piece.rank === 'king') {
26 | pieceAllowedMoves = this.getCastlingSquares(piece, pieceAllowedMoves);
27 | }
28 |
29 | return this.unblockedPositions(piece, pieceAllowedMoves, true);
30 | }
31 | else{
32 | return [];
33 | }
34 | }
35 |
36 | movePiece(pieceName, position) {
37 | const piece = this.getPieceByName(pieceName);
38 |
39 | /*if (!piece) {
40 | return false;
41 | }*/
42 |
43 | const prevPosition = piece.position;
44 | const existedPiece = this.getPieceByPos(position)
45 |
46 | if (existedPiece) {
47 | this.kill(existedPiece);
48 | }
49 |
50 | const castling = !existedPiece && piece.rank === 'king' && piece.ableToCastle === true;
51 |
52 | if (castling) {
53 | if (position - prevPosition === 2) {
54 | this.castleRook(piece.color + 'Rook2');
55 | }
56 | else if (position - prevPosition === -2) {
57 | this.castleRook(piece.color + 'Rook1');
58 | }
59 | changePosition(piece, position, true);
60 | }
61 | else {
62 | changePosition(piece, position);
63 | }
64 |
65 | if (piece.rank === 'pawn' && (position > 80 || position < 20)) {
66 | this.promote(piece);
67 | }
68 |
69 | // this.history.add({ from: prevPosition, to: position, piece: piece, castling });
70 | this.changeTurn();
71 |
72 | return true;
73 | }
74 |
75 | king_checked(color) {
76 | const piece = this.clickedPiece;
77 | const king = this.getPieceByName(color + 'King');
78 | if (!king) {
79 | return true;
80 | }
81 | const enemyColor = (color === 'white') ? 'black' : 'white';
82 | const enemyPieces = this.getPiecesByColor(enemyColor);
83 | for (const enemyPiece of enemyPieces) {
84 | this.setClickedPiece(enemyPiece);
85 | const allowedMoves = this.unblockedPositions(enemyPiece, getAllowedMoves(enemyPiece), false);
86 | if (allowedMoves.indexOf(king.position) !== -1) {
87 | this.setClickedPiece(piece);
88 | return 1;
89 | }
90 | }
91 | this.setClickedPiece(piece);
92 | return 0;
93 | }
94 |
95 | checkmate(color){}
96 | }
--------------------------------------------------------------------------------
/ai.js:
--------------------------------------------------------------------------------
1 | const ai = (aiTurn) => {
2 | const ranks = { pawn: 1, king: 2, bishop: 3, knight: 3, rook: 5, queen: 9 };
3 |
4 | const simulationGame = new SimulationGame([], 'white');
5 |
6 | const deepest = 3;
7 |
8 | const humanTurn = aiTurn === 'white' ? 'black' : 'white';
9 |
10 | const middleSquares = [44, 45, 54, 55];
11 | const widerMiddleSquares = [43, 46, 53, 56];
12 |
13 | const isPieceInMiddle = piece => middleSquares.indexOf(piece.position) !== -1;
14 | const isPieceInWiderMiddle = piece => widerMiddleSquares.indexOf(piece.position) !== -1;
15 |
16 | const score = pieces => {
17 | return pieces.reduce( (total, piece) => {
18 | let weight = piece.color === aiTurn ? ranks[piece.rank] : -1 * ranks[piece.rank];
19 | if ( isPieceInMiddle(piece) ) {
20 | weight *= 1.05;
21 | }
22 | else if ( isPieceInWiderMiddle(piece) ) {
23 | weight *= 1.02;
24 | }
25 | total += weight;
26 | return total;
27 | }, 0 );
28 | }
29 |
30 | const isBetterScore = (score1, score2, turn) => turn === aiTurn ? score1 >= score2 : score1 <= score2;
31 |
32 | const isScoreGoodEnough = (score, turn) => turn === aiTurn ? score > 7 : score < -7;
33 |
34 | const minimax = (pieces, turn, depth = 0) => {
35 | simulationGame.startNewGame(pieces, turn);
36 |
37 | if ( !simulationGame.getPieceByName(humanTurn + 'King') || simulationGame.king_dead(humanTurn ) ) {
38 | return {score: -Infinity, depth};
39 | }
40 | if ( !simulationGame.getPieceByName(aiTurn + 'King') || simulationGame.king_dead(aiTurn ) ) {
41 | return {score: Infinity, depth};
42 | }
43 |
44 | let bestPlay = { move: null, score: turn === aiTurn ? -Infinity : Infinity };
45 |
46 | for (const piece of pieces) {
47 | const allowedMoves = simulationGame.getPieceAllowedMoves(piece.name);
48 |
49 | for (const move of allowedMoves) {
50 |
51 | const currentTestPlayInfo = {};
52 |
53 | currentTestPlayInfo.move = {pieceName: piece.name, position: move};
54 | simulationGame.movePiece(piece.name, move);
55 |
56 | const curScore = score(simulationGame.pieces);
57 |
58 | if ( depth === deepest || isBetterScore(bestPlay.score, curScore, turn) || isScoreGoodEnough(curScore, turn) ) {
59 | currentTestPlayInfo.score = curScore;
60 | }
61 | else if (turn === aiTurn) {
62 | const result = minimax(simulationGame.pieces, humanTurn, depth + 1);
63 | currentTestPlayInfo.score = result.score;
64 | } else {
65 | const result = minimax(simulationGame.pieces, aiTurn, depth + 1);
66 | currentTestPlayInfo.score = result.score;
67 | }
68 |
69 | if ( isBetterScore(currentTestPlayInfo.score, bestPlay.score, turn) ){
70 | bestPlay.move = currentTestPlayInfo.move;
71 | bestPlay.score = currentTestPlayInfo.score;
72 | }
73 |
74 | simulationGame.startNewGame(pieces, turn);
75 | }
76 | }
77 |
78 | return bestPlay;
79 | }
80 |
81 | let play;
82 |
83 | if ( isTestEnv() ) {
84 | play = (pieces, callback) => {
85 | setTimeout( () => {
86 | testFuncTime( () => {
87 | const aiPlay = minimax(pieces, aiTurn);
88 | callback(aiPlay);
89 | });
90 | }, 100);
91 | }
92 | }
93 | else {
94 | play = (pieces, callback) => {
95 | setTimeout( () => {
96 | const aiPlay = minimax(pieces, aiTurn);
97 | callback(aiPlay);
98 | }, 100);
99 | }
100 | }
101 |
102 | return {
103 | play
104 | }
105 | }
106 |
107 | const isTestEnv = function() {
108 | const url = new URL(location.href);
109 | const params = new URLSearchParams(url.searchParams);
110 | return Boolean(params.get('testing'))
111 | }
112 |
113 | const testFuncTime = func => {
114 | const label = 'Timer ' + Date.now();
115 | console.time(label);
116 | console.log( 'Output:', func() );
117 | console.timeLog(label);
118 | }
--------------------------------------------------------------------------------
/chess.css:
--------------------------------------------------------------------------------
1 | *{
2 | margin: 0;
3 | padding: 0;
4 | }
5 | body{
6 | background: #ddd;
7 | }
8 | .board-container {
9 | width: 600px;
10 | max-width: 90%;
11 | justify-content: center;
12 | margin:auto;
13 | position:relative;
14 | }
15 | .semataries {
16 | width: 100%;
17 | margin:auto;
18 | display: flex;
19 | justify-content: center;
20 | }
21 | .sematary {
22 | width: 50%;
23 | }
24 | #blackSematary {
25 | text-align: right;
26 | }
27 | #board{
28 | aspect-ratio: 1 / 1;
29 | margin: auto;
30 | border:3px #333 solid;
31 | }
32 | #board div div{
33 | float: left;
34 | width: calc(100% / 8);
35 | aspect-ratio: 1 / 1;
36 | box-sizing: border-box;
37 | border: #000 solid .01cm;
38 | }
39 |
40 | #board .even div:nth-child(even){
41 | background: #ccd;
42 | }
43 |
44 | #board .even div:nth-child(odd){
45 | background: rgb(112,112,112);/*621700*/
46 | }
47 |
48 | #board .odd div:nth-child(even){
49 | background: rgb(112,112,112);/*621700*/
50 | }
51 |
52 | #board .odd div:nth-child(odd){
53 | background: #ccd;
54 | }
55 |
56 | .animate{
57 | animation: rotateBoard 1s ease-out;
58 | animation-fill-mode: both;
59 | }
60 | @keyframes rotateBoard {
61 | 0% {
62 | transform: rotateZ(0);
63 | }
64 | 100%{
65 | transform: rotateZ(-180deg);
66 | }
67 |
68 | }
69 | .forward{
70 | transform: rotateZ(-180deg);
71 | }
72 | .backward{
73 | transform: rotateZ(0);
74 | }
75 | .animate-backward{
76 | animation: rotateBoardBackward 1s ease-out;
77 | animation-fill-mode: both;
78 | }
79 | @keyframes rotateBoardBackward {
80 | 0% {
81 | transform: rotateZ(-180deg);
82 | }
83 | 100%{
84 | transform: rotateZ(0);
85 | }
86 |
87 | }
88 | img.piece{
89 | width: 100%;
90 | height: 100%;
91 | float: left;
92 | }
93 | .allowed{
94 | opacity: .8;
95 | background: radial-gradient(#333,#222 )!important;
96 | /*-webkit-box-shadow: inset 1px -4px 92px 0px rgba(0,0,0,0.75);
97 | -moz-box-shadow: inset 1px -4px 92px 0px rgba(0,0,0,0.75);
98 | box-shadow: inset 1px -4px 92px 0px rgba(0,0,0,0.75);*/
99 | border:1px solid black !important;
100 | }
101 | .last-move {
102 | background: #30b030 !important;
103 | }
104 | .clicked-square{
105 | background: radial-gradient(#333,#222 )!important;
106 | border:1px solid black !important;
107 | }
108 | .sematary img {
109 | transform: rotateZ(0);
110 | width: 1.8rem;
111 | height: 1.8rem;
112 | }
113 | #blackSematary div{
114 | overflow-y: auto;
115 | margin-bottom: 2px;
116 | }
117 | .scene{
118 | position: relative;
119 | opacity: 0;
120 | display: none;
121 | z-index: 1;
122 | }
123 | .overlay{
124 | position: fixed;
125 | width: 100%;
126 | height: 100%;
127 | background: #000;
128 | opacity: .7;
129 | z-index: 1;
130 | }
131 | .scene .scene-content{
132 | position: fixed;
133 | color:#fff;
134 | z-index: 2;
135 | width: 100%;
136 | text-align: center;
137 | margin-top: 40vh;
138 | font-size: 40px;
139 | height: 100vh;
140 | }
141 |
142 | .scene-content h2 {
143 | font-weight: 500;
144 | margin-bottom: 15px;
145 | }
146 |
147 | @media screen and (max-width: 600px) {
148 | .scene-content h2 {
149 | font-size: 2rem;
150 | }
151 | }
152 |
153 | .show{
154 | display: block !important;
155 | animation: showMessage 1s ease-out;
156 | animation-fill-mode: both;
157 | }
158 |
159 | .hidden {
160 | display: none;
161 | }
162 |
163 | @keyframes showMessage {
164 | 0% {
165 | opacity: 0;
166 | }
167 | 100%{
168 | opacity: 1;
169 | }
170 |
171 | }
172 | #turn{
173 | text-align: center;
174 | font-size: 18px;
175 | }
176 | .winning-sign:first-letter{
177 | text-transform: uppercase;
178 | }
179 |
180 | .flip-board{
181 | padding: 10px 20px;
182 | border-radius: 5px !important;
183 | outline: 0;
184 | background: #7f979e;
185 | color: white;
186 | border: 0;
187 | }
188 |
189 |
190 | input[type="radio"] {
191 | display: none;
192 | }
193 | label {
194 | background-color: rgb(112,112,112);
195 | position: relative;
196 | font-family: "Poppins", sans-serif;
197 | cursor: pointer;
198 | display: inline-flex;
199 | align-items: center;
200 | gap: 0.8em;
201 | padding: 1em 2em;
202 | border-radius: 0.5em;
203 | font-size: 23px;
204 | }
205 |
206 | input[type="radio"]:checked + label {
207 | background-color: #4189e0;
208 | color: #ffffff;
209 | }
210 |
211 | .button {
212 | background-color: rgb(112,112,112);
213 | border-radius: 10px;
214 | border: none;
215 | color: white;
216 | padding: 20px 40px;
217 | text-align: center;
218 | text-decoration: none;
219 | display: inline-block;
220 | font-size: 20px;
221 | cursor: pointer;
222 | }
223 |
224 | .button:hover, label:hover {
225 | background-color: rgb(160, 160, 160);
226 | }
227 |
228 | .button-big {
229 | padding: 30px 60px;
230 | font-size: 25px;
231 | }
--------------------------------------------------------------------------------
/chess.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
White's Turn
197 | 198 |