Audio Player Fragment

Loading...

Source: Free Music Archive / Incompetech

Log in to Chess

or
Don't have an account? Sign up
Forgot your password?
To Skaki
LOG IN SIGN UP
GR / EN
SKG Chess Logo ixlstudio logo

Core Architecture
Deep Dive
Welcome to SKG Chess
Just the beginning LinkedIn GitHub Email
Custom Chess Engine!

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

But how does it work?

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:

  1. Frontend captures the tile coordinate of the clicked piece
  2. 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:

  1. GameController receives the move request and calls GameService
  2. GameService retrieves the current game state from the database
  3. The board is deserialized from the stored JSON:
    IBoard currentBoard = IBoard.deserialize(game.getBoard(), game.getLastMoveData());
  4. 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:

• Basic validations (bounds, alliance, pattern)
• 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 the board edge
• Hitting a piece (stop or capture)
• Failing a validation

d. For fixed-distance pieces (Pawn, Knight, King):

• Check each possible destination once
• Validate special conditions (first move, castling rights)

3. The player is created with:

• Collection of pieces
• Collection of legal moves
• Player's alliance (WHITE/BLACK)


Step 5 : Game Update

After the move is executed:

  1. The new board state is serialized to JSON
  2. The game entity is updated in the database
  3. The new state is broadcast to both players via WebSocket
  4. The frontend updates the board display
Spring Boot Integration

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 😐

Frontend

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? 🎪

What's in the Box?

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! ♟️

SKGChess Logo