Two-player chess that runs entirely in the browser. No server, no build step, no libraries — open index.html and play. All the rules are implemented: castling, en passant, promotion, check, checkmate and stalemate.
Download or clone the repo and open index.html in any browser.
git clone https://github.com/tsarumar/Online-Chess-Game.git
cd Online-Chess-Game
Then double-click index.html. That's it — there is nothing to install.
Click a piece to select it and the legal moves appear as dots. Click one to move, or drag the piece where you want it. Both work. New game resets, Flip board turns it around so black is at the bottom.
Starting position:
A knight selected, with its legal moves marked. Captures show as a ring around the piece:
Checkmate. The king in check turns red and the move list records it as Qxf7#:
Each class has one job, and the layers only talk downward.
js/piece.js— aPiecebase class and one subclass per piece type. Each one knows how it moves and nothing else.Pawnalso overridesgetAttacks()because a pawn moves forward but captures diagonally, and check detection has to know the difference.js/board.js—Boardholds the 8x8 grid, applies moves to itself, and answers questions like "is this square attacked" and "is this king in check". It has no idea whose turn it is.js/game.js—Gameowns the rules. It filters the pieces' moves down to the genuinely legal ones, handles castling, tracks the history and captures, and works out check, checkmate and stalemate.js/ui.js—ChessUIdraws the board and handles clicks and drag-and-drop. It never decides whether a move is allowed, it asksGameand renders the answer.js/main.js— creates aGame, hands it to the UI.
In two passes, which is what keeps the piece classes simple.
First each piece generates its pseudo-legal moves — squares it could reach following its own movement pattern, ignoring the king entirely. A rook slides until something blocks it, a knight jumps to eight fixed offsets.
Then Game filters them. For every candidate it clones the board, plays the move on the copy, and asks whether the mover's own king is now attacked. If it is, the move is thrown away. That single check handles pins, moving into check, and being obliged to answer a check, without any of those cases needing their own code.
Checkmate falls out of the same machinery: if the side to move is in check and has zero legal moves, it is mate. If it is not in check and still has zero legal moves, it is stalemate.
Three rules do not fit the "each piece knows its own moves" pattern, so they are handled specially.
Castling lives in Game, not King. It depends on whether squares are attacked, and asking that question from inside move generation would recurse forever — generating the king's moves would need to know if a square is attacked, which would need to generate the enemy's moves, which would need to check castling, and so on. Keeping it a level up breaks the loop.
En passant needs memory of the previous move, which a piece does not have. Board stores an enPassantTarget square when a pawn double-steps, and clears it after any other move, so the window is exactly one move wide as the rules require.
Promotion needs a decision from the player, so the move is generated with a default of queen and the UI opens a dialog to override it before the move is actually played.
The move generator was checked with perft — the standard method, where you count every possible sequence of moves to a given depth and compare against published values. Getting these exactly right is difficult unless castling, en passant, promotion and pinned pieces are all correct, so matching them is strong evidence the rules are right.
| Position | Depth | Expected | Result |
|---|---|---|---|
| Starting position | 4 | 197,281 | match |
| Kiwipete (castling and en passant heavy) | 3 | 97,862 | match |
| Pawn endgame | 4 | 43,238 | match |
On top of that: Scholar's mate and Fool's mate both register as checkmate, a known stalemate position registers as stalemate, castling produces O-O and O-O-O and is correctly refused when the king would pass through an attacked square, en passant removes the right pawn, underpromotion to a knight works, and a pinned piece reports zero legal moves.
Pieces are Unicode characters styled with CSS rather than image files, so the repo has no assets and nothing to load. The board is generated by JavaScript into an empty <div> — the HTML contains no squares.
Flipping the board only changes where each square is drawn. The underlying grid never moves, which means none of the rules code has to know or care which way round the display is.


