by Haris Chatzianastasiou
Unlike most chess projects that rely on existing libraries, SKG Chess implements its own chess engine from scratch in Java. This gives us complete control over the game logic and allows for deep optimization. Let's see how this works!
The heart of the engine
At the heart of the engine lies the Board class, which follows an immutable design pattern. The important thing to understand is that each time a move is played, a new Board is created, hoding the information of the tiles (empty or occupied -- which piece is on it -- ) and the players (current and opponent, with their pieces and list of moves they can play). Here's the Board class describing the structure of the board:
Board (Immutable Class) │ ├── tiles: List of Tile │ | 64 tiles │ | (0-63) │ │ │ └── Tile (Abstract Class) │ ├── tileCoordinate: int │ │ // 0 = a1, 1 = b1... │ │ │ └── tileAlliance: Alliance │ // WHITE or BLACK │ │ │ └── Subclasses: │ ├── EmptyTile │ │ // Empty square │ │ │ └── OccupiedTile │ // Has a piece │ ├── currentPlayer: Player │ // Player whose turn it is │ // Manages active player's pieces │ // and moves │ │ │ └── Player (Abstract Class) │ ├── pieces: Collection│ │ │ ├── moves: Collection │ │ │ └── alliance: Alliance │ // WHITE or BLACK │ ├── opponentPlayer: Player | // Player waiting for their turn | // Same structure | // as currentPlayer | │ | └── Player (Abstract Class) | ├── pieces: Collection | │ | ├── moves: Collection | │ | └── alliance: Alliance | // WHITE or BLACK Key Points: 1. Board is immutable - new instance created for each move 2. Each tile knows its position and alliance 3. Each tile knows if it is occupied or empty 4. Players manage their own pieces and legal moves 5. Players switch roles after each move, the new current player is the color of the opponent and vice versa. 6. All state changes create new objects (immutable design)
Pieces and Moves
Each player has a collection of pieces and a collections of moves they can make.Let's see how each piece on the board is represented by a Piece object.
Piece (Abstract Class) │ ├── pieceAlliance: Alliance │ // WHITE or BLACK │ ├── pieceCoordinate: int │ // Current position (0-63) │ ├── isFirstMove: boolean │ // Tracks if piece has moved │ // Important for: │ // - Pawns (first move can be 2 squares) │ // - Kings (castling rights) │ // - Rooks (castling rights) │ └── pieceSymbol: PieceSymbol // Visual representation // Used for display and notation │ └── Subclasses: ├── Pawn │ / Moves 1 or 2 squares │ // Captures diagonally │ // Can promote at end │ of board │ // (PawnPromotionMove class) │ ├── Knight │ // Moves in L-shape │ // Can jump over pieces │ ├── Bishop │ // Moves diagonally │ // Cannot jump | over pieces │ ├── Rook │ // Moves horizontally | or vertically │ // Cannot jump | over pieces │ ├── Queen │ // Combines Bishop | and Rook movement │ // Most powerful piece │ └── King // Moves one square in any direction // Special move: Castling // Must be protected from check Key Points: 1. Each piece knows its position and alliance 2. First move tracking enables special moves 3. Each piece type has unique movement patterns 4. Pieces are owned by players 5. Piece positions are updated after each move
Let's also see how we represent a move with the Move class.
Move (Abstract Class) │ ├── boardTiles: List│ // Current board state │ // Used to validate and | execute moves │ ├── sourceCoordinate: int │ // Starting position (0-63) │ // Where the piece is moving from │ ├── targetCoordinate: int │ // Ending position (0-63) │ // Where the piece is moving to │ └── pieceToMove: Piece | // Piece being moved | // Contains piece-specific logic | │ | └── Subclasses: | | ├── CapturingMove | | │ // Represents a capture | | │ // Removes captured piece | | │ │ | | │ └── Subclasses: | | │ ├── PawnPromotion | | | | CapturingMove | | │ │ | | │ └── PawnEn | | | PassantAttack | | │ | | └── NonCapturingMove | | | // Represents a normal move | | | // No piece is captured | | | │ | | | └── Subclasses: | | | ├── PawnPromotionMove | | | │ | | | ├── QueenSideCastleMove | | | │ | | | ├── KingSideCastleMove | | | │ | | | ├── PawnJumpMove | | | │ // Pawn's first move | | | │ // Moves 2 squares | | | └── PawnMove | | | │ // Moves 1 square Key Points: 1. Each move type handles specific scenarios 2. Moves are validated before execution 3. Special moves have unique logic 4. Moves create new board state
Warning : Technical content ahead! If you thought the left section was complex, buckle up - we're about to dive into the chess engine's brain! 🧠♟️
Step 1 : Game Initialization
When a new game starts, a new Game entity is created in the database. The initial board state is created using the Builder pattern through the createStandardBoard() method:
public static IBoard createStandardBoard() { final Builder builder = new Builder(); // Set up Black pieces builder.setPiece(new Rook (0, Alliance.BLACK)); builder.setPiece(new Knight(1, Alliance.BLACK)); builder.setPiece(new Bishop(2, Alliance.BLACK)); builder.setPiece(new Queen (3, Alliance.BLACK)); builder.setPiece(new King (4, Alliance.BLACK)); builder.setPiece(new Bishop(5, Alliance.BLACK)); builder.setPiece(new Knight(6, Alliance.BLACK)); builder.setPiece(new Rook (7, Alliance.BLACK)); for(int coordinate = 8; coordinate < 16; coordinate++) { builder.setPiece(new Pawn(coordinate, Alliance.BLACK)); } // Set up White pieces builder.setPiece(new Rook (56, Alliance.WHITE)); builder.setPiece(new Knight(57, Alliance.WHITE)); builder.setPiece(new Bishop(58, Alliance.WHITE)); builder.setPiece(new Queen (59, Alliance.WHITE)); builder.setPiece(new King (60, Alliance.WHITE)); builder.setPiece(new Bishop(61, Alliance.WHITE)); builder.setPiece(new Knight(62, Alliance.WHITE)); builder.setPiece(new Rook (63, Alliance.WHITE)); for(int coordinate = 48; coordinate < 56; coordinate++) { builder.setPiece(new Pawn(coordinate, Alliance.WHITE)); } // Set initial player to WHITE builder.setcurrentPlayerAlliance(Alliance.WHITE); return createBoard(builder); }
Step 2 : User clicks a piece from position x to position y
When a player clicks a piece, the following sequence occurs:
- Frontend captures the tile coordinate of the clicked piece
- Frontend sends a POST request to
/api/game/{gameId}/move
with:- sourceCoordinate: The tile coordinate of the clicked piece
- targetCoordinate: The tile coordinate where the piece will move
Step 3 : The move is processed
The move is processed through several steps:
- GameController receives the move request and calls GameService
- GameService retrieves the current game state from the database
- The board is deserialized from the stored JSON:
IBoard currentBoard = IBoard.deserialize(game.getBoard(), game.getLastMoveData());
- The current player's legal moves are retrieved:
Player player = currentBoard.getCurrentPlayer(); Move move = player.getMoves().stream() .filter(m -> m.getSourceCoordinate() == sourceCoordinate && m.getTargetCoordinate() == targetCoordinate) .findFirst() .orElseThrow(() -> new InvalidMoveException("Invalid move"));
Step 4 : Board Creation & Player Updates
If the move played is in the list of legal moves, a new Board is created with updated tiles and players.
private Board(final Builder builder, final Move lastMove) { // Create new tiles this.tiles = createTiles(builder); // Create new opponent player (previous current player) this.opponentPlayer = Player.createPlayer(tiles, builder.currentPlayerAlliance.getOpposite(), null, null); // Create new current player (previous opponent) this.currentPlayer = Player.createPlayer(tiles, builder.currentPlayerAlliance, this.opponentPlayer, lastMove); }
For each player, the following happens:
1. All pieces of the player's color are collected from the tiles
2. Legal moves are calculated for each piece through a complex process:
a. For each piece, calculate all possible destination coordinates based on its movement pattern:
// Example for a Bishop's move offsets private static final int[] CANDIDATE_MOVE_COORDINATES = { -9, -7, 7, 9 }; // These represent diagonal directions: up-left, up-right, down-left, down-right
b. Apply multiple validation layers for each potential move:
• Check validations (king safety, pins)
• Special move validations (castling, en passant)
c. For sliding pieces (Bishop, Rook, Queen), continue checking in each direction until:
• Hitting a piece (stop or capture)
• Failing a validation
d. For fixed-distance pieces (Pawn, Knight, King):
• Validate special conditions (first move, castling rights)
3. The player is created with:
• Collection of legal moves
• Player's alliance (WHITE/BLACK)
Step 5 : Game Update
After the move is executed:
- The new board state is serialized to JSON
- The game entity is updated in the database
- The new state is broadcast to both players via WebSocket
- The frontend updates the board display
Because We're Not Savages - We Use Spring Boot! 🌱
Ah yes, Spring Boot! The framework that makes Java developers feel like they're not living in the stone age. SKG Chess proudly rides the Spring Boot train, because who doesn't love auto-configuration and not having to write XML files until 3 AM? 🚂
Our Fancy Application Layers
Application Layers (AKA The Organized Chaos) ├── Controllers (REST API) │ ├── GameController // Where chess moves come to party 🎮 │ ├── UserController // The bouncer of our application 🚫 │ └── MainController // The page router and view manager 🗺️ ├── Services │ ├── GameService // The brain behind chess operations 🧠 │ ├── UserService // User management wizardry 🧙 │ └── RateLimiterService // Keeps the spammers at bay 🛡️ ├── Repositories │ ├── GameRepository // Database whisperer for games 📚 │ └── UserRepository // User data hoarder 🗄️ └── Domain Models ├── Game // The game state container 🎲 └── User // Player information holder 👤
Our Tech Stack (AKA The Cool Kids' Table)
- WebSocket - For those sweet, sweet real-time updates (no carrier pigeons here!) 📡
- PostgreSQL - Our reliable data butler, always ready to serve 🎩
- Thymeleaf - Our trusty template engine for server-side rendering 🎨
P.S. Can't wait to finish this project and go back to my life 😐
The "We Had To" Part
Look, we're here for the backend magic, but since you asked... 🤷♂️
SKG Chess uses the holy trinity of web development:
- HTML - For structure (because we're not savages)
- CSS - To make it not look like it's from 1995
- JavaScript - To make things move and stuff
And because we're fancy, we threw in Thymeleaf with its cool fragments feature. It's like copy-paste but for grown-ups! 🎭
Now, can we get to the fun stuff? What does SKG Chess really offers to chess players? 🎪
SKG Chess Package
Alright, let's talk about what makes SKG Chess special!
Thessaloniki Vibes 🏛️
Ever wondered why it's called SKG Chess? Well, we borrowed the name from Thessaloniki's airport code (SKG) because, you know, we're fancy like that! ✈️
Are you from Thessaloniki? Great! You'll see your city in the background. Not from Thessaloniki? Welcome to the club! Time to experience the magic of the Queen of the North! 👑
Entertainment Package 🎮
- Chess moves that'll make your opponent go "Wait, what?" 🤔
- Daily wisdom quotes (because why not?) 📚
- Background music to set the mood (because silence is awkward) 🎵
Fancy Features
- Two themes to switch between (because one is boring) 🎨
- Game history collection (to show off your wins) 🏆
- Chat with your opponent (trash talk included, free of charge) 💬
- Save your games (because memory is overrated) 💾
The Fine Print (AKA The Not-So-Fun Part) 📝
- Yes, you need an account (we're not savages, we need to know who you are) 👤
- Matchmaking is manual (copy-paste-share-play, because we're old school) 🔗
- No AI opponents (yet) - we're keeping it human for now 🤖
P.S. If you're still reading this, you're probably procrastinating. Go play some chess! ♟️