├── Game.js ├── README.md ├── chess.css ├── chess.html ├── img ├── blackBishop.png ├── blackKing.png ├── blackKnight.png ├── blackPawn.png ├── blackQueen.png ├── blackRook.png ├── whiteBishop.png ├── whiteKing.png ├── whiteKnight.png ├── whitePawn.png ├── whiteQueen.png └── whiteRook.png └── pieces ├── Bishop.js ├── King.js ├── Knight.js ├── Pawn.js ├── Piece.js ├── Queen.js └── Rook.js /Game.js: -------------------------------------------------------------------------------- 1 | class Game { 2 | constructor(pieces) { 3 | this.board = document.getElementById('board'); 4 | this.squares = this.board.querySelectorAll('.square'); 5 | this.pieces = pieces; 6 | this.turn = 'white'; 7 | this.turnSign = document.getElementById('turn'); 8 | this.clickedPiece = null; 9 | this.allowedMoves = null; 10 | this.addEventListeners(); 11 | this.whiteSematary = document.getElementById('whiteSematary'); 12 | this.blackSematary = document.getElementById('blackSematary'); 13 | } 14 | 15 | addEventListeners() { 16 | this.pieces.forEach( piece => { 17 | piece.img.addEventListener("click", this.pieceMove.bind(this)); 18 | piece.img.addEventListener("dragstart", this.pieceMove.bind(this)); 19 | piece.img.addEventListener("drop", this.pieceMove.bind(this)); 20 | }); 21 | this.squares.forEach( square => { 22 | square.addEventListener("click", this.movePiece.bind(this)); 23 | square.addEventListener("dragover", function(event){ 24 | event.preventDefault(); 25 | }); 26 | square.addEventListener("drop", this.movePiece.bind(this)); 27 | }); 28 | } 29 | 30 | pieceMove(event) { 31 | const name = event.target.getAttribute('id'); 32 | const allowedMoves = this.getPieceAllowedMoves(event, name); 33 | if (allowedMoves) { 34 | const position = this.getPieceByName(name).position; 35 | const clickedSquare = document.getElementById(position); 36 | 37 | /*if (event.type == 'click' && this.clickedPiece && this.clickedPiece.name == name) { 38 | this.setClickedPiece(null); 39 | return this.clearSquares(); 40 | }*/ 41 | clickedSquare.classList.add('clicked-square'); 42 | 43 | allowedMoves.forEach( allowedMove => { 44 | if (document.body.contains(document.getElementById(allowedMove))) { 45 | document.getElementById(allowedMove).classList.add('allowed'); 46 | } 47 | }); 48 | } 49 | else{ 50 | this.clearSquares(); 51 | } 52 | } 53 | 54 | changeTurn() { 55 | if (this.turn == 'white') { 56 | this.turn = 'black'; 57 | this.turnSign.innerHTML = "Black's Turn"; 58 | } 59 | else{ 60 | this.turn = 'white'; 61 | this.turnSign.innerHTML = "White's Turn"; 62 | } 63 | } 64 | 65 | getPiecesByColor(color) { 66 | return this.pieces.filter(obj => { 67 | return obj.color === color 68 | }); 69 | } 70 | 71 | getPlayerPositions(color){ 72 | const pieces = this.getPiecesByColor(color); 73 | return pieces.map( a => parseInt(a.position)); 74 | } 75 | 76 | filterPositions(positions) { 77 | return positions.filter(pos => { 78 | return pos > 10 && pos < 89 79 | }); 80 | }; 81 | 82 | unblockedPositions(allowedPositions=[], position, color, checking=true){ 83 | position = parseInt(position); 84 | const unblockedPositions = []; 85 | 86 | if (color == 'white') { 87 | var myBlockedPositions = this.getPlayerPositions('white'); 88 | var otherBlockedPositions = this.getPlayerPositions('black'); 89 | } 90 | else{ 91 | var myBlockedPositions = this.getPlayerPositions('black'); 92 | var otherBlockedPositions = this.getPlayerPositions('white'); 93 | } 94 | 95 | if (this.clickedPiece.hasRank('pawn')) { 96 | for (const move of allowedPositions[0]) { //attacking moves 97 | if (checking && this.myKingChecked(move)) continue; 98 | if (otherBlockedPositions.indexOf(move) != -1) unblockedPositions.push(move); 99 | } 100 | const blockedPositions = myBlockedPositions + otherBlockedPositions; 101 | for (const move of allowedPositions[1]) { //moving moves 102 | if (blockedPositions.indexOf(move) != -1) break; 103 | else if (checking && this.myKingChecked(move, false)) continue; 104 | unblockedPositions.push(move); 105 | } 106 | } 107 | else{ 108 | allowedPositions.forEach( allowedPositionsGroup => { 109 | for (const move of allowedPositionsGroup) { 110 | if (myBlockedPositions.indexOf(move) != -1) { 111 | break; 112 | } 113 | else if ( checking && this.myKingChecked(move) ) { 114 | continue; 115 | } 116 | unblockedPositions.push(move); 117 | if (otherBlockedPositions.indexOf(move) != -1) break; 118 | } 119 | }); 120 | } 121 | 122 | return this.filterPositions(unblockedPositions); 123 | } 124 | 125 | getPieceAllowedMoves(event, pieceName){ 126 | const piece = this.getPieceByName(pieceName); 127 | if(this.turn == piece.color){ 128 | this.clearSquares(); 129 | this.setClickedPiece(piece); 130 | if (event.type == 'dragstart') { 131 | event.dataTransfer.setData("text", event.target.id); 132 | } 133 | 134 | let pieceAllowedMoves = piece.getAllowedMoves(); 135 | if (piece.rank == 'king') { 136 | pieceAllowedMoves = this.getCastlingSquares(pieceAllowedMoves); 137 | } 138 | 139 | const allowedMoves = this.unblockedPositions( pieceAllowedMoves, piece.position, piece.color, true ); 140 | this.allowedMoves = allowedMoves; 141 | return allowedMoves; 142 | } 143 | else if (this.clickedPiece && this.turn == this.clickedPiece.color && this.allowedMoves && this.allowedMoves.indexOf(piece.position) != -1) { 144 | this.kill(piece); 145 | } 146 | else{ 147 | return 0; 148 | } 149 | } 150 | 151 | getCastlingSquares(allowedMoves) { 152 | if ( !this.clickedPiece.ableToCastle || this.king_checked(this.turn) ) return allowedMoves; 153 | const rook1 = this.getPieceByName(this.turn+'Rook1'); 154 | const rook2 = this.getPieceByName(this.turn+'Rook2'); 155 | if (rook1 && rook1.ableToCastle) { 156 | const castlingPosition = rook1.position + 2 157 | if( 158 | !this.positionHasExistingPiece(castlingPosition - 1) && 159 | !this.positionHasExistingPiece(castlingPosition) && !this.myKingChecked(castlingPosition, true) && 160 | !this.positionHasExistingPiece(castlingPosition + 1) && !this.myKingChecked(castlingPosition + 1, true) 161 | ) 162 | allowedMoves[1].push(castlingPosition); 163 | } 164 | if (rook2 && rook2.ableToCastle) { 165 | const castlingPosition = rook2.position - 1; 166 | if( 167 | !this.positionHasExistingPiece(castlingPosition - 1) && !this.myKingChecked(castlingPosition - 1, true) && 168 | !this.positionHasExistingPiece(castlingPosition) && !this.myKingChecked(castlingPosition, true) 169 | ) 170 | allowedMoves[0].push(castlingPosition); 171 | } 172 | return allowedMoves; 173 | } 174 | 175 | getPieceByName(piecename) { 176 | return this.pieces.filter( obj => obj.name === piecename )[0]; 177 | } 178 | 179 | getPieceByPos(piecePosition) { 180 | return this.pieces.filter(obj => obj.position === piecePosition )[0]; 181 | } 182 | 183 | positionHasExistingPiece(position) { 184 | return this.getPieceByPos(position) != undefined; 185 | } 186 | 187 | setClickedPiece(piece) { 188 | this.clickedPiece = piece; 189 | } 190 | 191 | movePiece(event, square='') { 192 | square = square || event.target; 193 | if (square.classList.contains('allowed')) { 194 | const clickedPiece = this.clickedPiece; 195 | if (clickedPiece) { 196 | const newPosition = square.getAttribute('id'); 197 | if (clickedPiece.hasRank('king') || clickedPiece.hasRank('pawn')) 198 | clickedPiece.changePosition(newPosition, true); 199 | else 200 | clickedPiece.changePosition(newPosition); 201 | square.append(clickedPiece.img); 202 | this.clearSquares(); 203 | this.changeTurn(); 204 | if (this.king_checked(this.turn)) { 205 | if (this.king_dead(this.turn)) { 206 | this.checkmate(clickedPiece.color); 207 | } 208 | else{ 209 | // alert('check'); 210 | } 211 | } 212 | } 213 | else{ 214 | return 0; 215 | } 216 | } 217 | if (event) event.preventDefault(); 218 | } 219 | 220 | kill(piece) { 221 | piece.img.parentNode.removeChild(piece.img); 222 | piece.img.className = ''; 223 | 224 | if (piece.color == 'white') this.whiteSematary.querySelector('.'+piece.rank).append(piece.img); 225 | else this.blackSematary.querySelector('.'+piece.rank).append(piece.img); 226 | 227 | const chosenSquare = document.getElementById(piece.position); 228 | this.pieces.splice(this.pieces.indexOf(piece), 1); 229 | this.movePiece('', chosenSquare); 230 | } 231 | 232 | castleRook(rookName) { 233 | const rook = this.getPieceByName(rookName); 234 | const newPosition = rookName.indexOf('Rook2') != -1 ? rook.position - 2 : rook.position + 3; 235 | 236 | this.setClickedPiece(rook); 237 | const chosenSquare = document.getElementById(newPosition); 238 | chosenSquare.classList.add('allowed'); 239 | 240 | this.movePiece('', chosenSquare ); 241 | this.changeTurn(); 242 | } 243 | 244 | promote(pawn) { 245 | const queenName = pawn.name.replace('Pawn', 'Queen'); 246 | const image = pawn.img; 247 | image.id = queenName; 248 | image.src = image.src.replace('Pawn', 'Queen'); 249 | this.pieces.splice(this.pieces.indexOf(pawn), 1); 250 | this.pieces.push( new Queen(pawn.position, queenName) ); 251 | } 252 | 253 | myKingChecked(pos, kill=true){ 254 | const piece = this.clickedPiece; 255 | const originalPosition = piece.position; 256 | const otherPiece = this.getPieceByPos(pos); 257 | const should_kill_other_piece = kill && otherPiece && otherPiece.rank != 'king'; 258 | piece.changePosition(pos); 259 | if (should_kill_other_piece) this.pieces.splice(this.pieces.indexOf(otherPiece), 1); 260 | if (this.king_checked(piece.color)) { 261 | piece.changePosition(originalPosition); 262 | if (should_kill_other_piece) this.pieces.push(otherPiece); 263 | return 1; 264 | } 265 | else{ 266 | piece.changePosition(originalPosition); 267 | if (should_kill_other_piece) this.pieces.push(otherPiece); 268 | return 0; 269 | } 270 | } 271 | 272 | king_dead(color) { 273 | const pieces = this.getPiecesByColor(color); 274 | for (const piece of pieces) { 275 | this.setClickedPiece(piece); 276 | const allowedMoves = this.unblockedPositions( piece.getAllowedMoves(), piece.position, piece.color, true ); 277 | if (allowedMoves.length) { 278 | this.setClickedPiece(null); 279 | return 0; 280 | } 281 | } 282 | this.setClickedPiece(null); 283 | return 1; 284 | } 285 | 286 | king_checked(color) { 287 | const piece = this.clickedPiece; 288 | const king = this.getPieceByName(color + 'King'); 289 | const enemyColor = (color == 'white') ? 'black' : 'white'; 290 | const enemyPieces = this.getPiecesByColor(enemyColor); 291 | for (const enemyPiece of enemyPieces) { 292 | this.setClickedPiece(enemyPiece); 293 | const allowedMoves = this.unblockedPositions( enemyPiece.getAllowedMoves(), enemyPiece.position, enemyColor, false ); 294 | if (allowedMoves.indexOf(king.position) != -1) { 295 | this.setClickedPiece(piece); 296 | return 1; 297 | } 298 | } 299 | this.setClickedPiece(piece); 300 | return 0; 301 | } 302 | 303 | clearSquares(){ 304 | this.allowedMoves = null; 305 | const allowedSquares = this.board.querySelectorAll('.allowed'); 306 | allowedSquares.forEach( allowedSquare => allowedSquare.classList.remove('allowed') ); 307 | const cllickedSquare = document.getElementsByClassName('clicked-square')[0]; 308 | if (cllickedSquare) cllickedSquare.classList.remove('clicked-square'); 309 | } 310 | 311 | checkmate(color){ 312 | const endScene = document.getElementById('endscene'); 313 | endScene.getElementsByClassName('winning-sign')[0].innerHTML = color + ' Wins'; 314 | endScene.classList.add('show'); 315 | } 316 | } 317 | 318 | const pieces = [ 319 | new Rook(11, 'whiteRook1'), 320 | new Knight(12, 'whiteKnight1'), 321 | new Bishop(13, 'whiteBishop1'), 322 | new Queen(14, 'whiteQueen'), 323 | new King(15, 'whiteKing'), 324 | new Bishop(16, 'whiteBishop2'), 325 | new Knight(17, 'whiteKnight2'), 326 | new Rook(18, 'whiteRook2'), 327 | new Pawn(21, 'whitePawn1'), 328 | new Pawn(22, 'whitePawn2'), 329 | new Pawn(23, 'whitePawn3'), 330 | new Pawn(24, 'whitePawn4'), 331 | new Pawn(25, 'whitePawn5'), 332 | new Pawn(26, 'whitePawn6'), 333 | new Pawn(27, 'whitePawn7'), 334 | new Pawn(28, 'whitePawn8'), 335 | 336 | new Pawn(71, 'blackPawn1'), 337 | new Pawn(72, 'blackPawn2'), 338 | new Pawn(73, 'blackPawn3'), 339 | new Pawn(74, 'blackPawn4'), 340 | new Pawn(75, 'blackPawn5'), 341 | new Pawn(76, 'blackPawn6'), 342 | new Pawn(77, 'blackPawn7'), 343 | new Pawn(78, 'blackPawn8'), 344 | new Rook(81, 'blackRook1'), 345 | new Knight(82, 'blackKnight1'), 346 | new Bishop(83, 'blackBishop1'), 347 | new Queen(84, 'blackQueen'), 348 | new King(85, 'blackKing'), 349 | new Bishop(86, 'blackBishop2'), 350 | new Knight(87, 'blackKnight2'), 351 | new Rook(88, 'blackRook2') 352 | ]; 353 | 354 | const game = new Game(pieces); -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /chess.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | body{ 6 | background: #ddd; 7 | } 8 | #board{ 9 | width: 600px; 10 | height: 600px; 11 | border:3px #333 solid; 12 | } 13 | #board div div{ 14 | float: left; 15 | width: 75px; 16 | height: 75px; 17 | box-sizing:border-box; 18 | border: #000 solid .01cm; 19 | } 20 | 21 | #board .even div:nth-child(even){ 22 | background: #ccd; 23 | } 24 | 25 | #board .even div:nth-child(odd){ 26 | background: rgb(112,112,112);/*621700*/ 27 | } 28 | 29 | #board .odd div:nth-child(even){ 30 | background: rgb(112,112,112);/*621700*/ 31 | } 32 | 33 | #board .odd div:nth-child(odd){ 34 | background: #ccd; 35 | } 36 | 37 | .animate{ 38 | animation: rotateBoard 1s ease-out; 39 | animation-fill-mode: both; 40 | } 41 | @keyframes rotateBoard { 42 | 0% { 43 | transform: rotateZ(0); 44 | } 45 | 100%{ 46 | transform: rotateZ(-180deg); 47 | } 48 | 49 | } 50 | .forward{ 51 | transform: rotateZ(-180deg); 52 | } 53 | .backward{ 54 | transform: rotateZ(0); 55 | } 56 | .animate-backward{ 57 | animation: rotateBoardBackward 1s ease-out; 58 | animation-fill-mode: both; 59 | } 60 | @keyframes rotateBoardBackward { 61 | 0% { 62 | transform: rotateZ(-180deg); 63 | } 64 | 100%{ 65 | transform: rotateZ(0); 66 | } 67 | 68 | } 69 | img{ 70 | width: 75px; 71 | height: 75px; 72 | } 73 | .allowed{ 74 | opacity: .8; 75 | background: radial-gradient(#333,#222 )!important; 76 | /*-webkit-box-shadow: inset 1px -4px 92px 0px rgba(0,0,0,0.75); 77 | -moz-box-shadow: inset 1px -4px 92px 0px rgba(0,0,0,0.75); 78 | box-shadow: inset 1px -4px 92px 0px rgba(0,0,0,0.75);*/ 79 | border:1px solid black !important; 80 | } 81 | .clicked-square{ 82 | background: radial-gradient(#333,#222 )!important; 83 | border:1px solid black !important; 84 | } 85 | #sematary img{ 86 | transform: rotateZ(0); 87 | } 88 | #whiteSematary{ 89 | position: absolute; 90 | top: 0; 91 | left: 610px; 92 | width: 180px; 93 | } 94 | #blackSematary{ 95 | position: absolute; 96 | top: 0; 97 | left: -190px; 98 | width: 180px; 99 | } 100 | #blackSematary div{ 101 | overflow-y: auto; 102 | margin-bottom: 2px; 103 | } 104 | #blackSematary img{ 105 | float: right; 106 | } 107 | #sematary img{ 108 | width: 60px; 109 | height: 60px; 110 | } 111 | #endscene{ 112 | position: relative; 113 | display: none; 114 | opacity: 0; 115 | z-index: 1; 116 | } 117 | .overlay{ 118 | position: fixed; 119 | width: 100%; 120 | height: 100%; 121 | background: #000; 122 | opacity: .5; 123 | } 124 | #endscene p{ 125 | position: fixed; 126 | color:#fff; 127 | z-index: 2; 128 | width: 100%; 129 | text-align: center; 130 | margin-top: 40vh; 131 | font-size: 40px; 132 | } 133 | .show{ 134 | display: block !important; 135 | animation: showMessage 1s ease-out; 136 | animation-fill-mode: both; 137 | } 138 | @keyframes showMessage { 139 | 0% { 140 | opacity: 0; 141 | } 142 | 100%{ 143 | opacity: 1; 144 | } 145 | 146 | } 147 | #turn{ 148 | text-align: center; 149 | font-size: 18px; 150 | } 151 | .winning-sign:first-letter{ 152 | text-transform: uppercase; 153 | } 154 | 155 | .flip-board{ 156 | padding: 10px 20px; 157 | border-radius: 5px !important; 158 | outline: 0; 159 | background: #7f979e; 160 | color: white; 161 | border: 0; 162 | } -------------------------------------------------------------------------------- /chess.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |white wins
11 | 13 |White's Turn
184 |