27 |
28 | ---
29 |
30 | ## Upcoming Features
31 |
32 | - allow users to pick a username
33 | - fix castling on Queen side of board
34 | - add ability for En pessant for pawns
35 | - add draw feature to allow for draws in close games or the ability to offer a draw to the opposing player.
36 | - ability to invite a friend using a unique url for multiplayer match.
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Simple Chess Game
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Player 1
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 00:00
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 00:00
36 |
37 |
38 |
39 |
40 |
41 |
Player 1
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/assets/css/Style.css:
--------------------------------------------------------------------------------
1 | *{
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body{
8 | font-family: Arial, Helvetica, sans-serif;
9 | width: 100vw;
10 | height: 100vh;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | background: #312E2B;
15 | }
16 |
17 |
18 | .chessboard{
19 | width: 500px;
20 | height: 500px;
21 | display: grid;
22 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
23 | box-shadow: 10px 0px 10px -10px rgba(255, 255, 255, 0.4);
24 | border-radius: 25px;
25 | }
26 |
27 | .chessboard > .chessboard-square{
28 | width: calc(500px / 8);
29 | height: calc(500px / 8);
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | }
34 |
35 | .chessboard > .chessboard-square[role="white"]{
36 | background: #779556;
37 | }
38 |
39 | .chessboard > .chessboard-square[role="black"]{
40 | background: #EBECD0;
41 | }
42 |
43 | .chessboard > .chessboard-square:nth-child(1){
44 | border-top-left-radius: 25px;
45 | }
46 |
47 | .chessboard > .chessboard-square:nth-child(8){
48 | border-top-right-radius: 25px;
49 | }
50 |
51 | .chessboard > .chessboard-square:nth-child(57){
52 | border-bottom-left-radius: 25px;
53 | }
54 |
55 | .chessboard > .chessboard-square:nth-child(64){
56 | border-bottom-right-radius: 25px;
57 | }
58 |
59 | .chessboard > .chessboard-square::before{
60 | content: "";
61 | width: 25px;
62 | height: 25px;
63 | border-radius: 50%;
64 | background: #BEC5A3;
65 | position: absolute;
66 | opacity: 0.50;
67 | visibility: hidden;
68 | }
69 |
70 | .chessboard > .chessboard-square.move::before{
71 | visibility: visible;
72 | }
73 |
74 | .chessboard > .chessboard-square.enemy::before{
75 | visibility: visible;
76 | }
77 |
78 | .chessboard > .chessboard-square.castling::before{
79 | visibility: visible;
80 | }
81 |
82 | .chessboard > .chessboard-square.from{
83 | background: #bd5c6c;
84 | }
85 |
86 | .chessboard > .chessboard-square.to{
87 | background: #e27587;
88 | }
89 |
90 | .chessboard > .chessboard-square > img.chessboard-piece{
91 | max-width: 50px;
92 | width: 50px;
93 | height: auto;
94 | cursor: grab;
95 | user-select: none;
96 | -webkit-user-select: none;
97 | -webkit-user-drag: none;
98 | }
99 |
100 | .chessboard > .chessboard-square > img.chessboard-piece:active{
101 | cursor: grabbing;
102 | }
103 |
104 | .player-card{
105 | padding: 2.5px 25px;
106 | display: flex;
107 | position: relative;
108 | }
109 |
110 | .player-card > .rows{
111 | display: flex;
112 | align-items: center;
113 | width: 100%;
114 | }
115 |
116 | .player-card > .row-1{
117 | cursor: pointer;
118 | border-radius: 6px;
119 | margin:2.5px;
120 | }
121 |
122 | .player-card > .row-1 > .icon {
123 | width:30px;
124 | height:30px;
125 | margin: 5px;
126 | border-radius: 50%;
127 | background: #ffffff;
128 | }
129 |
130 | .player-card > .row-1 > .text{
131 | margin: 0px 5px;
132 | color: #ffffff;
133 | }
134 |
135 | .player-card > .row-1 > .text > .status span{
136 | font-size: 12px;
137 | }
138 |
139 | .player-card.player-1 > .row-1{
140 | justify-content: flex-end;
141 | }
142 |
143 | .player-card.player-2 > .row-2{
144 | justify-content: flex-end;
145 | }
146 |
147 | .player-card.player-1 > .row-2 > .timer{
148 | text-align: left;
149 | }
150 |
151 | .player-card.player-2 > .row-2 > .timer{
152 | text-align: right
153 | }
154 |
155 | .player-card > .row-2 > .timer{
156 | padding: 10px;
157 | background: #52504E;
158 | border-radius: 6px;
159 | margin: 5px 0px;
160 | width: 80px;
161 | text-align: right;
162 | cursor: pointer;
163 | }
164 |
165 | .player-card > .row-2 > .timer > span{
166 | color: #ebeeee;
167 | font-weight: 600;
168 | font-size: 18px;
169 | user-select: none;
170 | }
171 |
172 | .player-card > .row-2 > .timer.timeout{
173 | background: #9E4F4C;
174 | }
175 |
176 | .player-card > .row-2 > .timer > span{
177 | color: #ffffff;
178 | font-weight: 600;
179 | }
180 |
181 | /* CSS */
182 | .button-64 {
183 | align-items: center;
184 | background-image: linear-gradient(144deg,#AF40FF, #5B42F3 50%,#00DDEB);
185 | border: 0;
186 | border-radius: 8px;
187 | box-shadow: rgba(151, 65, 252, 0.2) 0 15px 30px -5px;
188 | box-sizing: border-box;
189 | color: #FFFFFF;
190 | display: flex;
191 | font-family: Phantomsans, sans-serif;
192 | font-size: 20px;
193 | justify-content: center;
194 | line-height: 1em;
195 | max-width: 100%;
196 | min-width: 140px;
197 | padding: 3px;
198 | text-decoration: none;
199 | user-select: none;
200 | -webkit-user-select: none;
201 | touch-action: manipulation;
202 | white-space: nowrap;
203 | cursor: pointer;
204 | margin: 70px;
205 | margin-right: 10px;
206 | }
207 |
208 | .button-64:active,
209 | .button-64:hover {
210 | outline: 0;
211 | }
212 |
213 | .button-64 span {
214 | background-color: rgb(5, 6, 45);
215 | padding: 5px 2px;
216 | border-radius: 6px;
217 | width: 100%;
218 | height: 100%;
219 | transition: 300ms;
220 | }
221 |
222 | .button-64:hover span {
223 | background: none;
224 | }
225 |
226 | @media (min-width: 768px) {
227 | .button-64 {
228 | font-size: 20px;
229 | min-width: 130px;
230 | }
231 | }
--------------------------------------------------------------------------------
/assets/javascript/javascript/Script.js:
--------------------------------------------------------------------------------
1 | const chessboardParent = document.getElementById("chessboard");
2 |
3 |
4 | // Chess Game
5 | class Chess {
6 | constructor() {
7 | this.setDefault();
8 | }
9 |
10 | // set chess info as default
11 | setDefault() {
12 | this.info = {
13 | preview: false, // when previewing match history
14 | started: false, // when the started
15 | ended: false, // when the game is ended
16 | won: null, // Winning player
17 | turn: null, // Player turn
18 | timer: 10, // Five minutes Timer
19 | };
20 |
21 | this.data = {
22 | players: [], // all the players
23 | matchHistory: [], // storing match history
24 | board: null, // board
25 | };
26 | }
27 |
28 | // initialize game
29 | async init(callback) {
30 | // create new board
31 | this.data.board = new Board(this);
32 | // then create board elements
33 | this.data.board.create();
34 |
35 | // assign players
36 | await this.assignPlayers();
37 |
38 | // make sure that players is ready
39 | await this.data.players[0].init(this);
40 | await this.data.players[1].init(this);
41 |
42 | callback && callback.call(this);
43 | }
44 |
45 | // assign players (player1,player2)
46 | async assignPlayers() {
47 | // will return a promise
48 | return new Promise((resolve) => {
49 | const player1 = new Player({ username: "Player 1", id: 1, role: "white" }); // player 1
50 | const player2 = new Player({ username: "Player 2", id: 2, role: "black" }); // player 2
51 |
52 | this.data.players = [player1, player2]; // assign into the game players
53 |
54 | // player 1 is first to move
55 | this.info.turn = player1;
56 | player1.info.isTurn = true;
57 |
58 | resolve(); // return
59 | });
60 | }
61 |
62 | // when the game start
63 | start() {
64 | this.info.started = true;
65 | this.info.ended = false;
66 | this.info.won = false;
67 |
68 | this.data.board.placePiecesAsDefault(); // and place player pieces as default pos
69 | this.data.players.forEach((p) => p.startTimer()); // start the player
70 | }
71 |
72 | notify() {
73 | const players = this.data.players;
74 | const ischecked = players[0].info.isChecked || players[1].info.isChecked;
75 | const checkedPlayer = this.checkedPlayer();
76 | ischecked && console.log(checkedPlayer.data.username + " is in check");
77 | }
78 |
79 | // when their is a winner
80 | winner() {
81 | const Winner = this.info.won;
82 | const CreatePopUp = function () {
83 | alert(`The winner is ${Winner.data.username}`)
84 | };
85 |
86 | console.log(`The winner is ${Winner.data.username}`);
87 |
88 | CreatePopUp();
89 | }
90 |
91 | // end the game
92 | checkmate(player) {
93 | this.info.started = false;
94 | this.info.ended = true;
95 | this.info.won = player;
96 |
97 | console.log(`${this.info.turn.data.username} is in Mate`);
98 |
99 | this.winner();
100 | }
101 |
102 | updatePlayers() {
103 | this.data.players.forEach((player) => player.update());
104 | }
105 |
106 | checkedPlayer() {
107 | const players = this.data.players;
108 | return players.filter((player) => {
109 | return player.info.isChecked == true;
110 | })[0];
111 | }
112 |
113 | // change turning player
114 | changeTurn() {
115 | const turn = this.info.turn;
116 | const players = this.data.players;
117 | this.info.turn = players.filter((p, index) => {
118 | return players.indexOf(turn) != index;
119 | })[0];
120 | }
121 |
122 | // switch player into another player
123 | switchTurn(player) {
124 | const players = this.data.players;
125 | return players.filter((p, index) => {
126 | return players.indexOf(player) != index;
127 | })[0];
128 | }
129 |
130 | // test and check the move wheater checked or not
131 | // then alert when can't possibly moved
132 | testMove(piece, square) {
133 | const board = this.data.board;
134 | piece = board.filterPiece(this, piece); // filter piece
135 | square = board.filterSquare(square); // filter square
136 |
137 | if (!piece || !square) return false;
138 | const backup = { square: piece.square, piece: square.piece }; // back up current data
139 | let player = backup.piece ? backup.piece.player : null;
140 | let pieces = backup.piece ? player.data.pieces : null;
141 | let index = backup.piece ? pieces.indexOf(backup.piece) : null; // if there's piece inside store
142 | let status = false;
143 |
144 | // if there is piece, remove it from the board
145 | index && pieces.splice(index, 1);
146 |
147 | // then move the piece
148 | piece.silentMove(square);
149 |
150 | // then check how the board, react and possibilities
151 | status = this.data.board.analyze(); // will return false, if you are checked
152 |
153 | // move back again the piece in it's position
154 | piece.silentMove(backup.square);
155 |
156 | // place the piece in to it'square
157 | square.piece = backup.piece;
158 |
159 | // and place again in to the board
160 | index && pieces.splice(index, 0, backup.piece);
161 |
162 | return status;
163 | }
164 |
165 | // After the player moves
166 | moved(...param) {
167 | this.data.board.resetSquares(); // reset possible squares ui
168 | this.data.board.setMovedSquare(...param);
169 | this.changeTurn(); // whem player moves, change turn player
170 | this.notify(); // update, alert and prompt
171 | this.isMate(); // check if mate
172 | this.updatePlayers(); // update players
173 | this.insertToMatchHistory(...param); // insert into match history
174 | }
175 |
176 | // insert moves into game match history
177 | insertToMatchHistory(from, to) {
178 | const move = {
179 | piece: to.piece.getAlias(),
180 | from: from.info.position,
181 | to: to.info.position,
182 | };
183 |
184 | this.data.matchHistory.push(move);
185 | to.piece.player.data.movesHistory.push(move);
186 | console.log(JSON.stringify(this.data.matchHistory));
187 | }
188 |
189 | // load match history before it preview
190 | async loadMatchHistory(matchHistoryJsonFile) {
191 | const isjson = typeof matchHistoryJsonFile == "object";
192 | const isString = typeof matchHistoryJsonFile == "string";
193 |
194 | // when not json or url string
195 | if (!isjson && !isString) {
196 | throw new Error("Invalid Match History!");
197 | }
198 |
199 | // if it is url, fetch
200 | if (isString) {
201 | try {
202 | matchHistoryJsonFile = await fetch(matchHistoryJsonFile);
203 | matchHistoryJsonFile = await matchHistoryJsonFile.json();
204 | } catch (e) {
205 | // if somethings not right throw an error
206 | throw new Error("Error, Can't load match history!");
207 | }
208 | }
209 |
210 | // notify if the match history is empty
211 | if (matchHistoryJsonFile.length == 0) {
212 | throw new Error("Empty Match History!");
213 | } else {
214 | // otherwise preview it
215 | this.previewMatchHistory(matchHistoryJsonFile);
216 | }
217 | }
218 |
219 | // preview match history
220 | async previewMatchHistory(matchHistory, index = 0) {
221 | const board = this.data.board;
222 | // tell that you are previewing
223 | // so player can't move
224 | this.info.preview = true;
225 |
226 | // move piece that has transition effect
227 | const moveTo = async function (player, piece, square) {
228 | return new Promise((resolve) => {
229 | setTimeout(function () {
230 | if (!piece) resolve(); // if piece is undefine just return
231 | // analyze first the board
232 | board.setSquarePossibilities(piece.getPossibleSqOnly());
233 | // then move the piece
234 | resolve(player.move(piece, square)); //
235 | }, 1000);
236 | });
237 | };
238 |
239 | for (let i = 0; i < matchHistory.length; i++) {
240 | let player = this.data.players[index]; // the player
241 | let piece = board.filterPiece(player, matchHistory[i].piece); // convert it to piece class
242 | let square = board.filterSquare(matchHistory[i].to); // find the square
243 | let move = await moveTo(player, piece, square); // move the piece
244 |
245 | // if there's an error into a movement then throw an error
246 | if (!move)
247 | throw new Error(
248 | `Opps Something is wrong, Movement ${matchHistory[i].from} to ${matchHistory[i].to} is not right`
249 | );
250 |
251 | index = index == 0 ? 1 : 0;
252 | }
253 |
254 | // Previewing is Done, Player can move now
255 | this.info.preview = false;
256 | }
257 |
258 | // set time (min, sec)
259 | // will convert int into duration format => 5 mins = 300
260 | setTime(minutes, sec) {
261 | return 60 * parseInt(minutes) ?? 0 + parseInt(sec) ?? 0;
262 | }
263 |
264 | // parse time into string format
265 | parseTime(time) {
266 | let minutes = parseInt(time / 60, 10);
267 | let seconds = parseInt(time % 60, 10);
268 |
269 | minutes = minutes < 10 ? "0" + minutes : minutes;
270 | seconds = seconds < 10 ? "0" + seconds : seconds;
271 |
272 | return { minutes, seconds, text: minutes + ":" + seconds };
273 | }
274 |
275 | // is chess is ready
276 | isReady() {
277 | return this.info.started && !this.info.ended && !this.info.won;
278 | }
279 |
280 | // check if the player is mate
281 | isMate() {
282 | const playerTurn = this.info.turn; // turning player
283 | const pieces = playerTurn.data.pieces; // player pieces
284 | const King = this.data.board.findPiece(pieces, "King", true); // find a king
285 | const moves = []; // store the possible moves
286 |
287 | // if the player is checked
288 | if (playerTurn.info.isChecked) {
289 | for (const piece of pieces) {
290 | for (const square of piece.getPossibilities().moves) {
291 | if (this.testMove(piece, square)) {
292 | // if there was an success move
293 | // insert that move into moves array
294 | moves.push(piece);
295 | }
296 | }
297 | }
298 |
299 | // if there's no possible move, and King was don't have move also
300 | // then checkmate
301 | if (!moves.length && !King.getPossibleSqOnly()) {
302 | this.checkmate(this.switchTurn(playerTurn));
303 | return true;
304 | }
305 | }
306 | }
307 | }
308 |
309 | // Chess Board
310 | class Board {
311 | constructor(game) {
312 | this.default = {
313 | col_row: 8, // col len
314 | col: ["a", "b", "c", "d", "e", "f", "g", "h"], // col literals
315 | row: [8, 7, 6, 5, 4, 3, 2, 1], // row literals
316 | };
317 |
318 | this.game = game; // the game
319 | this.data = []; // empty data values
320 | }
321 |
322 | // create ui
323 | create() {
324 | const col_row = this.default.col_row;
325 | const col = this.default.col;
326 | const row = this.default.row;
327 |
328 | let role = "white"; // start with white
329 |
330 | // change role
331 | const setRole = () => {
332 | return (role = role == "white" ? "black" : "white");
333 | };
334 |
335 | for (let r = 0; r < col_row; r++) {
336 | const squares = []; // store all the square
337 | for (let c = 0; c < col_row; c++) {
338 | const letter = col[c];
339 | const number = row[r];
340 | const position = `${letter}${number}`; // new position
341 | const boardPos = { y: r, x: c };
342 | const square = new Square(boardPos, position, setRole(), this.game); // new square
343 |
344 | squares.push(square); // push the square
345 | }
346 |
347 | this.data.push(squares) && setRole(); // push the squares in the board data
348 | }
349 | }
350 |
351 | // place defaut piece into board
352 | placePiecesAsDefault() {
353 | const board = this;
354 | const game = this.game; // the game
355 | const players = game.data.players; // all player
356 |
357 | const place = function (piece) {
358 | const position = piece.info.position; // piece pos
359 | const square = board.filterSquare(position); // select square acccording to its pos
360 | const pieceElement = piece.info.element; // piece image
361 | const squareElement = square.info.element; // and the square element
362 |
363 | piece.square = square; // declare square into piece
364 | square.piece = piece; // declare piece into square
365 |
366 | squareElement.appendChild(pieceElement); // just append the image to the square el
367 | };
368 |
369 | // loop through players and place their pieces
370 | players.forEach((player) => player.data.pieces.forEach(place));
371 | }
372 |
373 | // get all players possibilities
374 | // enemies, moves and castling
375 | getAllPossibilities() {
376 | const players = this.game.data.players; // players
377 | const white = players[0].analyze(); // player 1
378 | const black = players[1].analyze(); // player 2
379 |
380 | return { white, black };
381 | }
382 |
383 | // analyze the board
384 | analyze() {
385 | let status = true; // stat
386 | let turnPlayer = this.game.info.turn;
387 | let AP = this.getAllPossibilities(); // all player possibilities
388 | let entries = Object.entries(AP); // convert as object
389 |
390 | // loop through players and collect their enemies
391 | for (let data of entries) {
392 | const King = this.findPiece(data[1].enemies, "King");
393 | if (King) {
394 | King.player.info.isChecked = true;
395 | // if the turn player role is equal to the king player role
396 | if (turnPlayer.data.role != data[0]) {
397 | status = false; // set as false
398 | King.player.info.isChecked = false;
399 | }
400 | break;
401 | }
402 | }
403 |
404 | return status;
405 | }
406 |
407 | // setting classes and possibilities
408 | setSquarePossibilities(possibilities, insertUI) {
409 | if (!possibilities) return;
410 | let { moves, enemies, castling } = possibilities;
411 |
412 | // reset first
413 | this.resetSquares();
414 |
415 | // then set square properties according to possibilities values
416 | moves.forEach((square) => square.setAs("move", true, insertUI));
417 | enemies.forEach((square) => square.setAs("enemy", true, insertUI));
418 | castling.forEach((square) => square.setAs("castling", true, insertUI));
419 | }
420 |
421 | // remove all class from all squares
422 | resetSquares() {
423 | for (let squares of this.data) {
424 | for (let square of squares) {
425 | square.setAs("move", false, true);
426 | square.setAs("enemy", false, true);
427 | square.setAs("castling", false, true);
428 | square.setAs("from", false, true);
429 | square.setAs("to", false, true);
430 | }
431 | }
432 | }
433 |
434 | setMovedSquare(from, to) {
435 | from.setAs("from", true, true);
436 | to.setAs("to", true, true);
437 | }
438 |
439 | // Check if the x and y position is valid in board
440 | isValidPos(y, x) {
441 | return this.data[y] ? this.data[y][x] : false;
442 | }
443 |
444 | // will convert position square in to a Square object
445 | // e4 => Square
446 | filterSquare(sq) {
447 | // check if it is already an object
448 | if (!sq || typeof sq == "object") return sq;
449 |
450 | // loop in board
451 | for (let squares of this.data) {
452 | // loop through the squares
453 | for (let square of squares) {
454 | // check if square the position is equal to the given pos
455 | if (square.info.position == sq) {
456 | return square;
457 | }
458 | }
459 | }
460 | }
461 |
462 | // will convert piece alias into a Piece object
463 | // wP4 => Piece
464 | filterPiece(player, piece) {
465 | // check if it is already an object
466 | if (!piece || !player || typeof piece == "object") return piece;
467 |
468 | const pieces = player.data.pieces; // player pieces
469 | const alias = piece.substring(0, 2); // alias
470 | const index = piece.charAt(2); // index
471 |
472 | // loop through the pieces
473 | for (let piece of pieces) {
474 | // check if the alias and index is correct
475 | // the return it
476 | if (piece.info.alias == alias) {
477 | if (piece.info.index == index) {
478 | return piece;
479 | }
480 | }
481 | }
482 | }
483 |
484 | // find piece on array of piece or array of squares
485 | findPiece(squares, piece, isPieces) {
486 | if (!squares || !squares.length || !piece) return false;
487 |
488 | // if is not object then just return piece means it is alias or name of piece
489 | piece = this.filterPiece(piece) ?? piece;
490 |
491 | const filter = squares.filter((square) => {
492 | const p = isPieces
493 | ? square
494 | : typeof square == "object"
495 | ? square.piece
496 | : this.filterSquare(square).piece; // the piece
497 | const name = piece.info ? piece.info.name : piece; // piece name
498 | const alias = piece.info ? piece.info.alias : piece; // piece alias
499 | return p.info.name == name || p.info.alias == alias; // find piece where alias or name is equal to the given piece
500 | });
501 |
502 | return (
503 | filter.map((sq) => {
504 | return this.filterSquare(sq).piece ?? sq;
505 | })[0] ?? false
506 | );
507 | }
508 |
509 | }
510 |
511 | // Chess Piece
512 | class Piece {
513 | constructor(pieceObj, player, game) {
514 | this.info = {
515 | ...pieceObj, // piece information
516 | fastpawn: pieceObj.name == "Pawn", // only if pawn
517 | castling: pieceObj.name == "King", // only if king
518 | element: null,
519 | };
520 |
521 | this.data = {}; // just set to an empty * bug
522 | this.player = player; // players
523 | this.game = game; // game
524 |
525 | this.init();
526 | }
527 |
528 | init() {
529 | this.create(); // create new Image element
530 | this.listener(); // some listeners
531 | }
532 |
533 | // when there's piece inside the target square, eat them
534 | eat(piece) {
535 | if (!piece) return;
536 | const piecePlayer = piece.player;
537 | const player = this.player;
538 |
539 | // if element exist, remove the element
540 | piece.info.element && piece.info.element.remove();
541 |
542 | // insert into the target player dropped pieces
543 | piecePlayer.data.dropped.push(piece);
544 | // remove piece into the target player pieces
545 | piecePlayer.data.pieces.splice(piecePlayer.data.pieces.indexOf(piece), 1);
546 | // insert into the player eated pieces
547 | player.data.eated.push(piece);
548 |
549 | return piece;
550 | }
551 |
552 | moveElementTo(square) {
553 | // set fastpawn and castling to false
554 | this.info.fastpawn = false;
555 | this.info.castling = false;
556 |
557 | // append the element into the target square element
558 | square.info.element.appendChild(this.info.element);
559 | }
560 |
561 | // move from current square to the target square
562 | move(square, castling) {
563 | let old = this.square;
564 | // eat piece inside
565 | this.eat(square.piece);
566 | // move piece into the square
567 | this.silentMove(square);
568 | // move the image into the square element
569 | this.moveElementTo(square);
570 |
571 | // trigger, finished moved
572 | this.game.moved(old, square);
573 |
574 | // if the move is castling, then castle
575 | castling && this.castling();
576 | }
577 |
578 | // move in the background
579 | silentMove(square) {
580 | const piece = this;
581 | const board = this.game.data.board;
582 |
583 | // make sure it is Square object
584 | square = board.filterSquare(square)
585 | // set first to false
586 | square.piece = false;
587 | piece.square.piece = false;
588 |
589 | // change data
590 | square.piece = piece;
591 | piece.square = square;
592 | piece.info.position = square.info.position;
593 | piece.square.piece = piece;
594 | }
595 |
596 | // castling
597 | castling() {
598 | // castling only if it is King
599 | if (this.info.name != "King") return false;
600 |
601 | const game = this.game;
602 | const board = game.data.board.data;
603 | const { x, y } = this.square.info.boardPosition;
604 |
605 | const check = function (piece, square, condition) {
606 | // move only if the condition is true
607 | if (!condition) return;
608 |
609 | // move piece into the square
610 | piece.silentMove(square);
611 | // move element into the square element
612 | piece.moveElementTo(square);
613 | };
614 |
615 | // right and left rook
616 | const rr = board[y][x + 1].piece;
617 | const lr = board[y][x - 2].piece;
618 | // console.log(lr)
619 | // console.log("hello")
620 | // console.dir(board[y][x-2].piece)
621 | // check each rook
622 | check(rr, board[y][x - 1], rr && rr.info.name == "Rook");
623 | check(lr, board[y][x + 1], lr && lr.info.name == "Rook");
624 |
625 | }
626 |
627 | create() {
628 | const pieceElement = new Image(); // new Image element
629 | const classname = "chessboard-piece";
630 |
631 | // apply
632 | pieceElement.src = `./assets/media/pieces/${this.info.alias}.png`;
633 | pieceElement.classList.add(classname);
634 |
635 | this.info.element = pieceElement; // store
636 | }
637 |
638 | listener() {
639 | const piece = this; // selected piece
640 | const game = this.game; // the game
641 | const element = this.info.element; // the image of piece
642 | const board = game.data.board; // the board
643 |
644 | // on mousedown event
645 | const mousedown = function (event) {
646 | let current = null; // set as null a target square
647 | let elemBelow, droppableBelow; // squares positioning
648 |
649 | // if player is previewing match history
650 | // return false
651 | if (game.info.preview) return;
652 |
653 | // move the piece towards direction
654 | const move = function (pageX, pageY) {
655 | element.style.cursor = "grabbing"; // set the cursor as grab effect
656 | element.style.left = pageX - element.offsetWidth / 2 + "px";
657 | element.style.top = pageY - element.offsetHeight / 2 + "px";
658 | };
659 |
660 | // when user mousemove
661 | const mousemove = function (event) {
662 | move(event.pageX, event.pageY); // move the piece in mouse position
663 |
664 | element.hidden = true; // hide the element so it will not affect searching point
665 | elemBelow = document.elementFromPoint(event.clientX, event.clientY); // search from point x and y
666 | element.hidden = false; // then show again
667 |
668 | if (!elemBelow) return;
669 |
670 | // find the closest square from the mouse
671 | droppableBelow = elemBelow.closest(".chessboard-square");
672 |
673 | // if it is not the current square
674 | if (current != droppableBelow) current = droppableBelow;
675 | };
676 |
677 | // when the user drop the piece
678 | const drop = function () {
679 | // remove first the mousemove event
680 | document.removeEventListener("mousemove", mousemove);
681 |
682 | // then assign styles to go back to it's position in square
683 | element.removeAttribute("style");
684 |
685 | if (!current) return false;
686 | if (game.info.turn != piece.player) return false;
687 |
688 | piece.player.move(piece, current.getAttribute("data-position"));
689 | };
690 |
691 | // just setting the styles
692 | const setStyle = function () {
693 | // set the position to absolute so the image can drag anywhere on the screen
694 | element.style.position = "absolute";
695 | // set the z index to max so it will go above all elements
696 | element.style.zIndex = 1000;
697 | };
698 |
699 | // just sets some listeners
700 | const manageListener = function () {
701 | // drop on mouseup event
702 | element.onmouseup = drop;
703 |
704 | // disabled dragging
705 | element.ondragstart = function () {
706 | return false;
707 | };
708 |
709 | // add mousemove listener again
710 | document.addEventListener("mousemove", mousemove);
711 | };
712 |
713 | // declaration
714 | setStyle();
715 | manageListener();
716 | move(event.pageX, event.pageY);
717 |
718 | if (game.info.turn != piece.player) return false;
719 | // get the piece possibilities, values(moves(array), enemies(array), castling(array))
720 | // then show circles to all that squares
721 | board.setSquarePossibilities(piece.getPossibleSqOnly(), true);
722 |
723 | piece.player.data.currentPiece = piece;
724 | };
725 |
726 | // add mousedown listener
727 | element.addEventListener("mousedown", mousedown);
728 | }
729 |
730 | // get piece possibilites, move, enemies, castling
731 | getPossibilities() {
732 | const piece = this; // the current piece
733 | const square = this.square; // the current square where piece located
734 | const player = this.player; // the turning player
735 | const role = player.data.role; // player role values(white, black)
736 | const game = this.game; // the game
737 | const gameboard = game.data.board; // game board
738 | const board = gameboard.data; // and the board data
739 | const pos = { moves: [], enemies: [], castling: [] }; // possibilities object
740 | let { x, y } = square.info.boardPosition; // square position into board
741 |
742 | // will check if the piece inside the given square is enemy or not
743 | // then if it is push it into enemies pos
744 | const testEnemy = function (y, x) {
745 | // check if the position is valid
746 | if (!gameboard.isValidPos(y, x)) return false;
747 |
748 | const square = board[y][x]; // target square
749 | const piece = square.piece; // piece inside the target square
750 |
751 | if (!square || !piece) return false;
752 | if (piece.player.data.role == role) return false;
753 |
754 | pos.enemies.push(square);
755 | };
756 |
757 | // test the square when piece can be move or there is enemy
758 | const testSquare = function (y, x) {
759 | // check if the position is valid
760 | if (!gameboard.isValidPos(y, x)) return false;
761 |
762 | const square = board[y][x]; // target square
763 | const sqpiece = square.piece; // piece inside the target square
764 |
765 | if (!square) return false;
766 |
767 | if (sqpiece) {
768 | if (piece.info.name != "Pawn") testEnemy(y, x);
769 | return false;
770 | } else {
771 | pos.moves.push(square);
772 | return true;
773 | }
774 | };
775 |
776 | // test directions and check how long the piece can be move from the board
777 | // yi / xi = y/x need to change
778 | // yo / xo = what operation, true = addition while false = subtration
779 | // un = until (number), how many squares need to check
780 | // is = isKing, then if it is check for castlings
781 | const testLoopSquare = function (yi, yo, xi, xo, un = 8, is) {
782 | for (let i = 1; i < un; i++) {
783 | const ny = yi ? (yo ? y + i : y - i) : y;
784 | const nx = xi ? (xo ? x + i : x - i) : x;
785 |
786 | // check if the position is valid
787 | if (!gameboard.isValidPos(ny, nx)) return false;
788 |
789 | const square = board[ny][nx]; // target square
790 | const sqpiece = square.piece; // piece inside the target square
791 |
792 | if (square) {
793 | if (sqpiece) {
794 | // if not pawn then test if there is enemy
795 | if (piece.info.name != "Pawn") testEnemy(ny, nx);
796 | break;
797 | } else if (is && i == 2) {
798 | // if isKing then check then run as one only in a loop
799 |
800 | const check = function (condition) {
801 | if (condition) pos.castling.push(square);
802 | };
803 |
804 | const rightrook = board[ny][nx + 1].piece;
805 | const leftrook = board[ny][nx - 1].piece;
806 |
807 |
808 | check(rightrook && rightrook.info.name == "Rook");
809 | check(leftrook && leftrook.info.name == "Rook");
810 |
811 | }
812 |
813 | pos.moves.push(square);
814 | }
815 | }
816 | };
817 |
818 | // All Piece move patterns
819 | const Pattern = {
820 | Pawn: function () {
821 | // check if pawn can fastpawn then if it is, increment 1 to it's possible move
822 | let until = piece.info.fastpawn ? 3 : 2;
823 |
824 | // loop through until values
825 | for (let i = 1; i < until; i++) {
826 | if (role == "white") {
827 | // if it is white, subrtact current i value
828 | // so it moves from bottom to top
829 | if (!testSquare(y - i, x)) break;
830 | } else {
831 | // if it is black, add current i value
832 | // so it moves from top to bottom
833 | if (!testSquare(y + i, x)) break;
834 | }
835 | }
836 |
837 | // enemy detection
838 | if (role == "white") {
839 | // (white) check the top left and right square from it's position
840 | testEnemy(y - 1, x - 1);
841 | testEnemy(y - 1, x + 1);
842 | } else {
843 | // (black) check the bottom left and right square from it's position
844 | testEnemy(y + 1, x - 1);
845 | testEnemy(y + 1, x + 1);
846 | }
847 | },
848 |
849 | Rook: function () {
850 | // Top
851 | testLoopSquare(true, false, false, false);
852 | // Bottom
853 | testLoopSquare(true, true, false, false);
854 | // Left
855 | testLoopSquare(false, false, true, false);
856 | // Right
857 | testLoopSquare(false, false, true, true);
858 | },
859 |
860 | Bishop: function () {
861 | // Top left
862 | testLoopSquare(true, false, true, false);
863 | // Bottom Left
864 | testLoopSquare(true, true, true, false);
865 | // Bottom Right
866 | testLoopSquare(true, false, true, true);
867 | // Bottom Right
868 | testLoopSquare(true, true, true, true);
869 | },
870 |
871 | Knight: function () {
872 | // Top
873 | testSquare(y - 2, x - 1);
874 | testSquare(y - 2, x + 1);
875 | // Bottom
876 | testSquare(y + 2, x - 1);
877 | testSquare(y + 2, x + 1);
878 | // Left
879 | testSquare(y - 1, x - 2);
880 | testSquare(y + 1, x - 2);
881 | // Right
882 | testSquare(y - 1, x + 2);
883 | testSquare(y + 1, x + 2);
884 | },
885 |
886 | Queen: function () {
887 | Pattern.Rook(); // can move like a rook
888 | Pattern.Bishop(); // can move like a bishope
889 | },
890 |
891 | King: function () {
892 | // Top
893 | testSquare(y - 1, x);
894 | // Bottom
895 | testSquare(y + 1, x);
896 | // Top Left
897 | testSquare(y - 1, x - 1);
898 | // Top Right
899 | testSquare(y - 1, x + 1);
900 | // Bottom Left
901 | testSquare(y + 1, x - 1);
902 | // Bottom Right
903 | testSquare(y + 1, x + 1);
904 |
905 | if (piece.info.castling) {
906 | testLoopSquare(false, false, true, true, 3, true);
907 | testLoopSquare(false, false, true, false, 3, true);
908 | }
909 | },
910 | };
911 |
912 | // then get the pattern base on their name
913 | // and call it
914 | Pattern[this.info.name].call();
915 |
916 | // return possibilities
917 | return pos;
918 | }
919 |
920 | getPossibleSqOnly() {
921 | let { moves, enemies, castling } = this.getPossibilities();
922 | const game = this.game;
923 |
924 | const filter = (s) => {
925 | return s.filter((sq) => {
926 | return game.testMove(this, sq);
927 | });
928 | };
929 |
930 | game.data.board.resetSquares();
931 | moves = filter(moves);
932 | enemies = filter(enemies);
933 | castling = filter(castling);
934 |
935 | return moves.length || enemies.length || castling.length
936 | ? { moves, enemies, castling }
937 | : false;
938 | }
939 |
940 | getAlias() {
941 | return `${this.info.alias}${this.info.index}`;
942 | }
943 | }
944 |
945 | // Chess Square
946 | class Square {
947 | constructor(boardPosition, position, role, game) {
948 | this.info = {
949 | boardPosition, // square board position
950 | position, // square position
951 | role, // square role
952 | element: null, // square element
953 | isMove: false, // possible move
954 | isEnemy: false, // possible enemy
955 | isCastle: false, // possible castle
956 | };
957 |
958 | this.piece = null; // the piece
959 | this.game = game; // the game
960 |
961 | this.init();
962 | }
963 |
964 | // initialize and ready
965 | init() {
966 | this.create(); // create square element
967 | this.listener(); // some listeners
968 | }
969 |
970 | // create ui
971 | create() {
972 | const squareElement = document.createElement("DIV"); // new Div element
973 | const classname = "chessboard-square"; // element classname
974 |
975 | squareElement.classList.add(classname); // add
976 | squareElement.setAttribute("role", this.info.role); // set role
977 | squareElement.setAttribute("data-position", this.info.position); // and pos
978 |
979 | chessboardParent.appendChild(squareElement); // append to parent
980 | this.info.element = squareElement; // store
981 | }
982 |
983 | listener() {
984 | // action when player clicks on square
985 | const action = function () {
986 | const player = this.game.info.turn;
987 | const info = this.info;
988 | const isQualified = info.isMove || info.isEnemy || info.isCastle;
989 | const currentPiece = player.data.currentPiece;
990 |
991 | if (!isQualified || !currentPiece) return false;
992 |
993 | // move the player on the selected squares
994 | player.move(currentPiece, this);
995 | };
996 |
997 | this.info.element.addEventListener("click", action.bind(this));
998 | }
999 |
1000 | setAs(classname, bool, ui) {
1001 | const element = this.info.element;
1002 |
1003 | this.info.isEnemy = classname == "enemy" && bool; // if there's enemy on the square
1004 | this.info.isMove = classname == "move" && bool; // if can possibly move the piece
1005 | this.info.isCastle = classname == "castling" && bool; // if can castling through that position
1006 |
1007 | if (!ui) return;
1008 | // add class if true and remove if false
1009 | bool
1010 | ? element.classList.add(classname)
1011 | : element.classList.remove(classname);
1012 | }
1013 | }
1014 |
1015 | // Player
1016 | class Player {
1017 | constructor(player) {
1018 | this.info = {
1019 | isTurn: false, // is player turn
1020 | isWinner: false, // is already won
1021 | isStarted: false, // is player started to move
1022 | isTimeout: false, // is player time was ended
1023 | isLeave: false, // is player was leaved
1024 | isChecked: false, // is player was checked
1025 | isReady: false, // is player is ready to go
1026 | };
1027 |
1028 | this.data = {
1029 | ...player, // rewrite player information
1030 | total_moves: 0, // all the moves
1031 | timer: { m: null, s: null },
1032 | piecesData: {}, // data pieces
1033 | pieces: [], // array of pieces
1034 | dropped: [], // all of the pieces that enemy slay
1035 | eated: [], // eated pieces
1036 | moves: [], // total possible moves
1037 | enemies: [], // total possible enemies
1038 | movesHistory: [], // player moves history
1039 | currentPiece: null, // current Piece Holding,
1040 | card: null,
1041 | };
1042 |
1043 | this.game = null; // empty game
1044 | }
1045 |
1046 | // analyze player side
1047 | analyze() {
1048 | this.data.moves = []; // empty the array
1049 | this.data.enemies = []; // empty the array
1050 |
1051 | const game = this.game; // the game
1052 | const turnPlayer = game.info.turn;
1053 | const pieces = this.data.pieces; // player pieces
1054 | const pos = { moves: [], enemies: [], castling: [] }; // store
1055 |
1056 | // loop through the pieces
1057 | for (const piece of pieces) {
1058 | for (const data of Object.entries(piece.getPossibilities())) {
1059 | for (const square of data[1]) {
1060 | if (!square) return;
1061 | if (!pos[data[0]].includes(square.info.position)) {
1062 | pos[data[0]].push(square.info.position);
1063 | }
1064 | }
1065 | }
1066 | }
1067 |
1068 | this.data.moves = pos.moves; // set the moves
1069 | this.data.enemies = pos.enemies; // set the enemies
1070 | this.info.isTurn = turnPlayer.data.username == this.data.username; // if the player is equal to turning player
1071 |
1072 | return pos;
1073 | }
1074 |
1075 | // update ui
1076 | update() {
1077 | const game = this.game;
1078 | const players = game.data.players;
1079 | const pos = players.indexOf(this) + 1;
1080 | const playerCard = document.querySelector(`.player-card.player-${pos}`);
1081 | const isTurn = game.info.turn == this;
1082 |
1083 | if (!playerCard) return;
1084 | const username = playerCard.querySelector(".row-1 .text .headline h4");
1085 | const status = playerCard.querySelector(".row-1 .text .status span");
1086 | const timer = playerCard.querySelector(".row-2 .timer");
1087 |
1088 | username.innerText = this.data.username;
1089 | status.innerText = isTurn ? "Player Turn" : "";
1090 |
1091 | this.data.card = { username, status, timer };
1092 |
1093 | try {
1094 | this.analyze();
1095 | } catch (e) {}
1096 | }
1097 |
1098 | // move target piece to the target square
1099 | move(piece, square) {
1100 | if (!piece || !square) return false;
1101 | const board = this.game.data.board;
1102 | // make sure piece and square is an object
1103 | piece = board.filterPiece(this, piece);
1104 | square = board.filterSquare(square);
1105 |
1106 | const game = this.game; // the game
1107 | const test = game.testMove(piece, square); // test first the move, will return bollean
1108 | const info = square.info; // square information
1109 | const isQualified = info.isMove || info.isEnemy || info.isCastle; // wheater move, enemy or castle
1110 |
1111 | // if the game was not started
1112 | if (!game.isReady()) return false;
1113 |
1114 | // if not ready or not already fetch all the pieces
1115 | if (!this.info.isReady) return false;
1116 |
1117 | // if checked and not correct move
1118 | if (this.info.isChecked) return false;
1119 |
1120 | // if out of time
1121 | if (this.info.isTimeout) return false;
1122 |
1123 | // if not turn
1124 | if (!this.info.isTurn) return false;
1125 |
1126 | // if not qualified, or not possible (move, enemy)
1127 | if (!isQualified) return false;
1128 |
1129 | // if theres no wrong in move, then move
1130 | if (test) piece.move(square, info.isCastle);
1131 |
1132 | return test;
1133 | }
1134 |
1135 | // start the timer
1136 | startTimer() {
1137 | const game = this.game; // the game
1138 | const player = this; // the player
1139 | const card = player.data.card; // playr card element
1140 | const timer = card.timer; // player card timer element
1141 | const span = timer.querySelector("span"); // timer span element
1142 | let { m, s } = player.data.timer; // current data
1143 | let curentduration = parseInt(60 * m) + parseInt(s) ?? 0;
1144 | let duration = m ? curentduration : game.setTime(game.info.timer);
1145 |
1146 | // callback
1147 | const countdownfunction = function () {
1148 | let { minutes, seconds, text } = game.parseTime(duration); // parse time
1149 |
1150 | span.innerText = text; // insert into span
1151 |
1152 | // countdown only if player turn
1153 | if (player.info.isTurn) {
1154 | // if timeout
1155 | if (--duration < 0) {
1156 | timer.classList.add("timeout");
1157 | player.info.isTimeout = true;
1158 | game.info.won = game.switchTurn(player);
1159 | game.winner();
1160 | clearInterval(countdown);
1161 | }
1162 |
1163 | // store current data
1164 | player.data.timer = { m: minutes, s: seconds };
1165 | }
1166 | };
1167 |
1168 | const countdown = setInterval(countdownfunction, 1000);
1169 |
1170 | countdownfunction();
1171 | }
1172 |
1173 | async getPieces() {
1174 | let role = this.data.role; // values ("white", "black")
1175 | let path = `./assets/javascript/json/${role}-pieces.json`; // just a path of json file
1176 | let data = await fetch(path); // get the file content
1177 | this.data.piecesData = await data.json(); // convert data as json
1178 | this.info.isReady = true; // now the player is ready to go
1179 | }
1180 |
1181 | async setPieces() {
1182 | const player = this; // the player
1183 | const game = this.game; // the game
1184 | const pieces = this.data.pieces; // array of class Pieces
1185 | const piecesData = this.data.piecesData; // data of all Chess Pieces
1186 |
1187 | const set = function (setPieceObj) {
1188 | // Get Values
1189 | let { name, length, alias, position } = setPieceObj;
1190 | let { letter: letters, number } = position;
1191 | // Loop through their lengths
1192 | for (let i = 0; i < length; i++) {
1193 | const position = `${letters[i]}${number}`; // get the position
1194 | const obj = { name, alias, position, index: i }; // create piece information
1195 | const piece = new Piece(obj, player, game); // new Piece
1196 | pieces.push(piece); // insert to the array of class Pieces
1197 | }
1198 | };
1199 |
1200 | // Loop through all the data then generate some Piece base on their length
1201 | // And game rules
1202 | piecesData.forEach(set);
1203 | }
1204 |
1205 | async init(game) {
1206 | this.game = game; // initialize the game
1207 |
1208 | await this.getPieces(); // get all data pieces
1209 | await this.setPieces(); // set object pieces to class Pieces
1210 |
1211 | this.update();
1212 | }
1213 | }
1214 | const reset = document.querySelector(".button-64");
1215 | reset.addEventListener("click", resetGame);
1216 |
1217 | function resetGame (){
1218 | location.reload()
1219 | }
1220 |
1221 | const Game = new Chess(); // game
1222 |
1223 | Game.init(function () {
1224 | this.start();
1225 | // this.loadMatchHistory("./assets/javascript/json/matchhistory.json");
1226 | }); // initialize
1227 |
1228 |
--------------------------------------------------------------------------------