problemmediumoodood-for-othello-gameood for othello gameoodforothellogamedesign-othello-reversi-gamedesign othello reversi gamedesignothelloreversigameothello-game-designothello game designothellogamedesign

OOD - Othello (Reversi) Game

MediumUpdated: Jan 1, 2026

1. Problem Statement

Othello (also known as Reversi) is a classic two-player strategy board game played on an 8×8 grid where each game piece has two sides: one black and one white. The game begins with four pieces placed in the center of the board forming a square pattern—two white pieces diagonally opposite each other and two black pieces occupying the remaining center squares. Players alternate turns placing pieces of their color on empty squares, and critically, each move must result in capturing at least one opponent's piece by bracketing a continuous line of opponent pieces between the newly placed piece and another piece of the player's color already on the board. When opponent pieces are captured, they are flipped to reveal the capturing player's color, fundamentally changing the board dynamics.

The bracketing (or flanking) mechanism operates across eight possible directions from any placed piece: horizontally left and right, vertically up and down, and diagonally in all four diagonal directions. A single move can simultaneously capture opponent pieces in multiple directions if valid bracketing exists in more than one line from the newly placed piece. This multi-directional capture rule creates complex tactical situations where a single well-placed piece can flip numerous opponent pieces across several lines simultaneously. The game's strategic depth emerges from players needing to balance immediate piece count gains against long-term positional advantages, as control of key squares (particularly corners and edges) provides stable pieces that cannot be flipped by opponents.

The game concludes when neither player has any legal moves remaining, which typically occurs when the board is completely filled but can also happen earlier if one player has no valid moves and passing their turn results in the opponent also having no valid moves. Victory is determined by simple piece majority: the player with more pieces of their color visible on the board wins, with ties occurring when both players have exactly 32 pieces. The implementation challenge involves accurately detecting all eight directions for potential captures, validating move legality by checking if at least one opponent piece would be flipped, efficiently flipping all captured pieces in multiple directions simultaneously, maintaining correct piece counts for both players throughout the game, detecting the end-game condition when no valid moves exist for either player, and providing clear visual feedback of the current board state and valid move options to guide player decisions.

othello-game-viz1.excalidraw

2. System Requirements

Functional Requirements:

  1. Initialize an 8×8 game board with the standard starting position (4 pieces in center)
  2. Support two players (Black and White) alternating turns
  3. Allow players to place pieces on valid empty squares
  4. Validate each move to ensure it captures at least one opponent piece
  5. Detect captures in all 8 directions (horizontal, vertical, diagonal)
  6. Flip all captured opponent pieces when a valid move is played
  7. Update piece counts for both players after each move
  8. Display the current board state with clear piece visualization
  9. Detect when a player has no valid moves and automatically pass their turn
  10. Detect end-of-game when neither player has valid moves
  11. Determine the winner by counting pieces of each color
  12. Handle edge cases: invalid coordinates, occupied squares, no-capture moves

Non-Functional Requirements:

  1. Correctness: Move validation and piece flipping must strictly follow official Othello rules
  2. Performance: Move validation should complete within 100ms even for complex board states
  3. Usability: Clear board display with row/column labels for easy coordinate input
  4. Testability: Core game logic separated from I/O for unit testing
  5. Extensibility: Support adding AI player, undo/redo, move hints in future versions

Assumptions:

  • Standard 8×8 board (not configurable board size)
  • Two human players (AI opponent is future enhancement)
  • No time limits per move
  • No move history or game replay functionality
  • Simple console-based I/O (no GUI)

3. Use Case Diagram

Actors:

  • Player (Black): Makes moves with black pieces
  • Player (White): Makes moves with white pieces
  • Game System: Manages game state, validates moves, detects end conditions

Core Use Cases:

  • Start New Game
  • Place Piece
  • Validate Move
  • Flip Captured Pieces
  • Pass Turn (when no valid moves)
  • End Game
  • Declare Winner
graph TB
    subgraph OthelloGame["Othello Game System"]
        UC1["Start New Game"]
        UC2["Place Piece"]
        UC3["Validate Move"]
        UC4["Flip Pieces"]
        UC5["Pass Turn"]
        UC6["View Board"]
        UC7["End Game"]
        UC8["Declare Winner"]
    end
    
    BlackPlayer([Black Player])
    WhitePlayer([White Player])
    System([Game System])
    
    BlackPlayer --> UC2
    WhitePlayer --> UC2
    BlackPlayer --> UC6
    WhitePlayer --> UC6
    
    System --> UC1
    System --> UC3
    System --> UC4
    System --> UC5
    System --> UC7
    System --> UC8
    
    UC2 -.->|triggers| UC3
    UC3 -.->|if valid| UC4
    UC4 -.->|updates| UC6
    UC5 -.->|checks| UC7
    UC7 -.->|triggers| UC8
    
    style BlackPlayer fill:#333,color:#fff
    style WhitePlayer fill:#fff,color:#000,stroke:#000
    style System fill:#e8f5e9

4. Class Diagram

Core Classes:

  • Position: Value object representing (row, col) coordinates on the board
  • Direction: Enum for 8 movement directions (N, NE, E, SE, S, SW, W, NW)
  • Piece: Enum for piece colors (BLACK, WHITE, EMPTY)
  • Player: Represents a player with color, piece count, and move validation
  • Cell: Represents a single board cell with its piece state
  • Board: Manages the 8×8 grid, validates moves, flips pieces
  • Game: Orchestrates game flow, turn management, win detection
  • MoveValidator: Encapsulates move validation logic for all directions
  • GameState: Enum for game status (IN_PROGRESS, BLACK_WINS, WHITE_WINS, TIE)
classDiagram
    class Position {
        -int row
        -int col
        +Position(int, int)
        +isValid() boolean
        +move(Direction) Position
        +equals(Object) boolean
    }
    
    class Direction {
        <<enumeration>>
        NORTH(-1, 0)
        NORTHEAST(-1, 1)
        EAST(0, 1)
        SOUTHEAST(1, 1)
        SOUTH(1, 0)
        SOUTHWEST(1, -1)
        WEST(0, -1)
        NORTHWEST(-1, -1)
        -int rowDelta
        -int colDelta
    }
    
    class Piece {
        <<enumeration>>
        BLACK
        WHITE
        EMPTY
        +opposite() Piece
    }
    
    class Cell {
        -Position position
        -Piece piece
        +Cell(Position)
        +setPiece(Piece) void
        +getPiece() Piece
        +isEmpty() boolean
        +flip() void
    }
    
    class Player {
        -Piece color
        -String name
        -int pieceCount
        +Player(Piece, String)
        +incrementPieces() void
        +decrementPieces() void
        +getPieceCount() int
    }
    
    class Board {
        -Cell[][] grid
        -int size
        +Board(int)
        +getCell(Position) Cell
        +placePiece(Position, Piece) void
        +flipPiece(Position) void
        +isValidPosition(Position) boolean
        +countPieces(Piece) int
        +display() void
    }
    
    class MoveValidator {
        -Board board
        +MoveValidator(Board)
        +isValidMove(Position, Piece) boolean
        +getFlippablePositions(Position, Piece) List~Position~
        +checkDirection(Position, Piece, Direction) List~Position~
    }
    
    class Game {
        -Board board
        -Player blackPlayer
        -Player whitePlayer
        -Player currentPlayer
        -MoveValidator validator
        -GameState state
        +Game()
        +start() void
        +makeMove(Position) boolean
        +switchTurn() void
        +hasValidMoves(Player) boolean
        +isGameOver() boolean
        +getWinner() Player
    }
    
    class GameState {
        <<enumeration>>
        IN_PROGRESS
        BLACK_WINS
        WHITE_WINS
        TIE
    }
    
    Board "1" *-- "64" Cell
    Cell "1" --> "1" Position
    Cell "1" --> "1" Piece
    Game "1" --> "1" Board
    Game "1" --> "2" Player
    Game "1" --> "1" MoveValidator
    Game "1" --> "1" GameState
    Player "1" --> "1" Piece
    MoveValidator "1" --> "1" Board
    Position ..> Direction : uses

5. Activity Diagrams

Making a Move

graph TD
    A([Player inputs coordinates]) --> B{Position valid?}
    B -->|No| C[Display error: Invalid coordinates]
    C --> A
    B -->|Yes| D{Cell empty?}
    D -->|No| E[Display error: Cell occupied]
    E --> A
    D -->|Yes| F[Check all 8 directions for captures]
    F --> G{At least one capture?}
    G -->|No| H[Display error: Must capture pieces]
    H --> A
    G -->|Yes| I[Place piece at position]
    I --> J[Flip all captured pieces in valid directions]
    J --> K[Update piece counts]
    K --> L[Display updated board]
    L --> M([Switch to next player])

Game Flow

graph TD
    Start([Start Game]) --> Init[Initialize board with 4 center pieces]
    Init --> SetBlack[Set current player to Black]
    SetBlack --> Turn{Current player has valid moves?}
    Turn -->|Yes| Input[Get player move input]
    Input --> Validate[Validate and execute move]
    Validate --> Switch[Switch current player]
    Switch --> BothCheck{Both players have no moves?}
    
    Turn -->|No| Pass[Pass turn to opponent]
    Pass --> BothCheck
    
    BothCheck -->|No| Turn
    BothCheck -->|Yes| Count[Count pieces for both players]
    Count --> Winner{Compare counts}
    Winner -->|Black > White| WinB[Black Wins]
    Winner -->|White > Black| WinW[White Wins]
    Winner -->|Equal| Tie[Tie Game]
    WinB --> End([End Game])
    WinW --> End
    Tie --> End

6. Java Implementation

Enums and Value Objects

/**
 * Represents the 8 possible directions on an Othello board
 */
public enum Direction {
    NORTH(-1, 0),
    NORTHEAST(-1, 1),
    EAST(0, 1),
    SOUTHEAST(1, 1),
    SOUTH(1, 0),
    SOUTHWEST(1, -1),
    WEST(0, -1),
    NORTHWEST(-1, -1);
    
    private final int rowDelta;
    private final int colDelta;
    
    Direction(int rowDelta, int colDelta) {
        this.rowDelta = rowDelta;
        this.colDelta = colDelta;
    }
    
    public int getRowDelta() {
        return rowDelta;
    }
    
    public int getColDelta() {
        return colDelta;
    }
}

/**
 * Represents piece colors and empty cells
 */
public enum Piece {
    BLACK('●'),
    WHITE('○'),
    EMPTY('·');
    
    private final char symbol;
    
    Piece(char symbol) {
        this.symbol = symbol;
    }
    
    public char getSymbol() {
        return symbol;
    }
    
    public Piece opposite() {
        if (this == BLACK) return WHITE;
        if (this == WHITE) return BLACK;
        return EMPTY;
    }
    
    @Override
    public String toString() {
        return String.valueOf(symbol);
    }
}

/**
 * Represents game end states
 */
public enum GameState {
    IN_PROGRESS,
    BLACK_WINS,
    WHITE_WINS,
    TIE
}

Position Class

import java.util.Objects;

/**
 * Immutable value object representing board coordinates
 */
public class Position {
    private final int row;
    private final int col;
    
    public Position(int row, int col) {
        this.row = row;
        this.col = col;
    }
    
    public int getRow() {
        return row;
    }
    
    public int getCol() {
        return col;
    }
    
    /**
     * Check if position is within 8x8 board bounds
     */
    public boolean isValid(int boardSize) {
        return row >= 0 && row < boardSize && col >= 0 && col < boardSize;
    }
    
    /**
     * Create new position by moving in a direction
     */
    public Position move(Direction direction) {
        return new Position(
            row + direction.getRowDelta(),
            col + direction.getColDelta()
        );
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Position)) return false;
        Position position = (Position) o;
        return row == position.row && col == position.col;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(row, col);
    }
    
    @Override
    public String toString() {
        return String.format("(%d,%d)", row, col);
    }
}

Cell and Board Classes

/**
 * Represents a single cell on the board
 */
public class Cell {
    private final Position position;
    private Piece piece;
    
    public Cell(Position position) {
        this.position = position;
        this.piece = Piece.EMPTY;
    }
    
    public void setPiece(Piece piece) {
        this.piece = piece;
    }
    
    public Piece getPiece() {
        return piece;
    }
    
    public boolean isEmpty() {
        return piece == Piece.EMPTY;
    }
    
    /**
     * Flip piece to opposite color
     */
    public void flip() {
        if (piece != Piece.EMPTY) {
            piece = piece.opposite();
        }
    }
    
    public Position getPosition() {
        return position;
    }
}

/**
 * Represents the game board
 */
public class Board {
    private static final int BOARD_SIZE = 8;
    private final Cell[][] grid;
    
    public Board() {
        grid = new Cell[BOARD_SIZE][BOARD_SIZE];
        initializeBoard();
    }
    
    private void initializeBoard() {
        // Create all cells
        for (int row = 0; row < BOARD_SIZE; row++) {
            for (int col = 0; col < BOARD_SIZE; col++) {
                grid[row][col] = new Cell(new Position(row, col));
            }
        }
        
        // Set up initial 4 pieces in center
        int mid = BOARD_SIZE / 2;
        grid[mid - 1][mid - 1].setPiece(Piece.WHITE);
        grid[mid - 1][mid].setPiece(Piece.BLACK);
        grid[mid][mid - 1].setPiece(Piece.BLACK);
        grid[mid][mid].setPiece(Piece.WHITE);
    }
    
    public Cell getCell(Position position) {
        if (!isValidPosition(position)) {
            throw new IllegalArgumentException("Invalid position: " + position);
        }
        return grid[position.getRow()][position.getCol()];
    }
    
    public void placePiece(Position position, Piece piece) {
        getCell(position).setPiece(piece);
    }
    
    public void flipPiece(Position position) {
        getCell(position).flip();
    }
    
    public boolean isValidPosition(Position position) {
        return position.isValid(BOARD_SIZE);
    }
    
    public int getBoardSize() {
        return BOARD_SIZE;
    }
    
    /**
     * Count total pieces of a specific color on the board
     */
    public int countPieces(Piece piece) {
        int count = 0;
        for (int row = 0; row < BOARD_SIZE; row++) {
            for (int col = 0; col < BOARD_SIZE; col++) {
                if (grid[row][col].getPiece() == piece) {
                    count++;
                }
            }
        }
        return count;
    }
    
    /**
     * Display board with row/column labels
     */
    public void display() {
        System.out.println("\n  0 1 2 3 4 5 6 7");
        System.out.println("  ----------------");
        
        for (int row = 0; row < BOARD_SIZE; row++) {
            System.out.print(row + "|");
            for (int col = 0; col < BOARD_SIZE; col++) {
                System.out.print(grid[row][col].getPiece() + " ");
            }
            System.out.println("|" + row);
        }
        
        System.out.println("  ----------------");
        System.out.println("  0 1 2 3 4 5 6 7\n");
    }
}

Move Validator

import java.util.ArrayList;
import java.util.List;

/**
 * Validates moves and identifies flippable pieces
 */
public class MoveValidator {
    private final Board board;
    
    public MoveValidator(Board board) {
        this.board = board;
    }
    
    /**
     * Check if placing a piece at position is a valid move
     */
    public boolean isValidMove(Position position, Piece playerColor) {
        // Cell must be empty
        if (!board.isValidPosition(position) || !board.getCell(position).isEmpty()) {
            return false;
        }
        
        // Must capture at least one opponent piece
        return !getFlippablePositions(position, playerColor).isEmpty();
    }
    
    /**
     * Get all positions that would be flipped by this move
     */
    public List<Position> getFlippablePositions(Position position, Piece playerColor) {
        List<Position> flippable = new ArrayList<>();
        
        // Check all 8 directions
        for (Direction direction : Direction.values()) {
            List<Position> captured = checkDirection(position, playerColor, direction);
            flippable.addAll(captured);
        }
        
        return flippable;
    }
    
    /**
     * Check one direction for capturable opponent pieces
     * Returns list of positions to flip, or empty list if no capture
     */
    private List<Position> checkDirection(Position start, Piece playerColor, Direction direction) {
        List<Position> toFlip = new ArrayList<>();
        Position current = start.move(direction);
        Piece opponentColor = playerColor.opposite();
        
        // First, we must encounter at least one opponent piece
        while (board.isValidPosition(current) && 
               board.getCell(current).getPiece() == opponentColor) {
            toFlip.add(current);
            current = current.move(direction);
        }
        
        // Then we must encounter our own piece (not empty, not out of bounds)
        if (toFlip.isEmpty() || 
            !board.isValidPosition(current) || 
            board.getCell(current).getPiece() != playerColor) {
            return new ArrayList<>(); // No valid capture in this direction
        }
        
        return toFlip;
    }
    
    /**
     * Get all valid moves for a player
     */
    public List<Position> getValidMoves(Piece playerColor) {
        List<Position> validMoves = new ArrayList<>();
        int size = board.getBoardSize();
        
        for (int row = 0; row < size; row++) {
            for (int col = 0; col < size; col++) {
                Position pos = new Position(row, col);
                if (isValidMove(pos, playerColor)) {
                    validMoves.add(pos);
                }
            }
        }
        
        return validMoves;
    }
}

Player Class

/**
 * Represents a player in the game
 */
public class Player {
    private final Piece color;
    private final String name;
    private int pieceCount;
    
    public Player(Piece color, String name) {
        if (color == Piece.EMPTY) {
            throw new IllegalArgumentException("Player color cannot be EMPTY");
        }
        this.color = color;
        this.name = name;
        this.pieceCount = 2; // Each player starts with 2 pieces
    }
    
    public Piece getColor() {
        return color;
    }
    
    public String getName() {
        return name;
    }
    
    public int getPieceCount() {
        return pieceCount;
    }
    
    public void setPieceCount(int count) {
        this.pieceCount = count;
    }
    
    @Override
    public String toString() {
        return name + " (" + color + ")";
    }
}

Game Class

import java.util.List;
import java.util.Scanner;

/**
 * Main game controller orchestrating the Othello game flow
 */
public class Game {
    private final Board board;
    private final Player blackPlayer;
    private final Player whitePlayer;
    private Player currentPlayer;
    private final MoveValidator validator;
    private GameState state;
    
    public Game() {
        this.board = new Board();
        this.blackPlayer = new Player(Piece.BLACK, "Black");
        this.whitePlayer = new Player(Piece.WHITE, "White");
        this.currentPlayer = blackPlayer; // Black moves first
        this.validator = new MoveValidator(board);
        this.state = GameState.IN_PROGRESS;
    }
    
    /**
     * Main game loop
     */
    public void start() {
        Scanner scanner = new Scanner(System.in);
        
        System.out.println("=== OTHELLO GAME ===");
        System.out.println("Black (●) moves first");
        System.out.println("Enter moves as: row col (e.g., '3 4')");
        System.out.println("Enter 'quit' to exit\n");
        
        while (state == GameState.IN_PROGRESS) {
            board.display();
            displayScore();
            
            // Check if current player has valid moves
            if (!hasValidMoves(currentPlayer)) {
                System.out.println(currentPlayer.getName() + " has no valid moves. Passing turn...");
                switchTurn();
                
                // If next player also has no moves, game is over
                if (!hasValidMoves(currentPlayer)) {
                    endGame();
                    break;
                }
                continue;
            }
            
            System.out.print(currentPlayer.getName() + "'s turn. Enter move: ");
            String input = scanner.nextLine().trim();
            
            if (input.equalsIgnoreCase("quit")) {
                System.out.println("Game ended by player.");
                break;
            }
            
            try {
                String[] parts = input.split("\\s+");
                if (parts.length != 2) {
                    System.out.println("Invalid input. Use format: row col");
                    continue;
                }
                
                int row = Integer.parseInt(parts[0]);
                int col = Integer.parseInt(parts[1]);
                Position position = new Position(row, col);
                
                if (makeMove(position)) {
                    switchTurn();
                }
            } catch (NumberFormatException e) {
                System.out.println("Invalid coordinates. Please enter numbers.");
            } catch (IllegalArgumentException e) {
                System.out.println("Error: " + e.getMessage());
            }
        }
        
        scanner.close();
    }
    
    /**
     * Attempt to make a move at the specified position
     * @return true if move was valid and executed
     */
    public boolean makeMove(Position position) {
        Piece playerColor = currentPlayer.getColor();
        
        if (!validator.isValidMove(position, playerColor)) {
            System.out.println("Invalid move! Must be empty cell that captures opponent pieces.");
            return false;
        }
        
        // Place the piece
        board.placePiece(position, playerColor);
        
        // Flip all captured pieces
        List<Position> toFlip = validator.getFlippablePositions(position, playerColor);
        for (Position pos : toFlip) {
            board.flipPiece(pos);
        }
        
        // Update piece counts
        updatePieceCounts();
        
        System.out.println("Move executed! Flipped " + toFlip.size() + " piece(s).\n");
        return true;
    }
    
    /**
     * Switch to the other player
     */
    private void switchTurn() {
        currentPlayer = (currentPlayer == blackPlayer) ? whitePlayer : blackPlayer;
    }
    
    /**
     * Check if player has any valid moves
     */
    public boolean hasValidMoves(Player player) {
        return !validator.getValidMoves(player.getColor()).isEmpty();
    }
    
    /**
     * Update piece counts by counting board pieces
     */
    private void updatePieceCounts() {
        blackPlayer.setPieceCount(board.countPieces(Piece.BLACK));
        whitePlayer.setPieceCount(board.countPieces(Piece.WHITE));
    }
    
    /**
     * Display current score
     */
    private void displayScore() {
        System.out.println("Score - Black: " + blackPlayer.getPieceCount() + 
                         " | White: " + whitePlayer.getPieceCount());
    }
    
    /**
     * End game and determine winner
     */
    private void endGame() {
        updatePieceCounts();
        
        System.out.println("\n=== GAME OVER ===");
        board.display();
        displayScore();
        
        int blackCount = blackPlayer.getPieceCount();
        int whiteCount = whitePlayer.getPieceCount();
        
        if (blackCount > whiteCount) {
            state = GameState.BLACK_WINS;
            System.out.println("🏆 Black wins!");
        } else if (whiteCount > blackCount) {
            state = GameState.WHITE_WINS;
            System.out.println("🏆 White wins!");
        } else {
            state = GameState.TIE;
            System.out.println("🤝 Tie game!");
        }
    }
    
    public GameState getState() {
        return state;
    }
    
    public Player getWinner() {
        if (state == GameState.BLACK_WINS) return blackPlayer;
        if (state == GameState.WHITE_WINS) return whitePlayer;
        return null;
    }
}

Main Application

/**
 * Entry point for Othello game
 */
public class OthelloApp {
    public static void main(String[] args) {
        Game game = new Game();
        game.start();
    }
}

7. Design Patterns Applied

1. Immutable Value Object (Position)

Intent: Represent board coordinates as immutable objects that can be safely shared.

Implementation:

  • Position class has final fields
  • No setters, only getters
  • Factory method move() returns new Position instead of mutating

Benefits:

  • Thread-safe by default
  • Can be used as HashMap keys
  • Prevents accidental coordinate changes
  • Simplifies reasoning about state
Position start = new Position(3, 4);
Position next = start.move(Direction.NORTH); // Returns new Position
// start remains unchanged

2. Strategy Pattern (Move Validation)

Intent: Encapsulate move validation logic separately from game flow.

Implementation:

  • MoveValidator class handles all validation rules
  • Game class delegates validation without knowing implementation details
  • Easy to modify validation rules without changing Game

Benefits:

  • Single Responsibility: validation logic isolated
  • Easy to add new validation rules (e.g., tournament time rules)
  • Testable independently from Game class
MoveValidator validator = new MoveValidator(board);
if (validator.isValidMove(position, playerColor)) {
    // Execute move
}

3. Enum Pattern (Direction, Piece, GameState)

Intent: Type-safe constants with associated behavior.

Implementation:

  • Direction enum encapsulates row/col deltas
  • Piece enum has display symbols and opposite() method
  • GameState enum represents finite game states

Benefits:

  • Compile-time safety (no invalid directions/pieces)
  • Self-documenting code
  • Can add methods to enums (e.g., Piece.opposite())
for (Direction dir : Direction.values()) {
    Position next = current.move(dir);
}

4. Model-View-Controller (MVC) Architecture

Intent: Separate game logic, display, and user interaction.

Implementation:

  • Model: Board, Cell, Player, Position (game state)
  • View: Board.display(), console output (presentation)
  • Controller: Game class (orchestrates flow, handles input)

Benefits:

  • Easy to swap console UI for GUI
  • Game logic testable without UI
  • Clear separation of concerns

5. Encapsulation (Cell State Management)

Intent: Hide piece state behind cell abstraction.

Implementation:

  • Cell class owns piece state
  • Provides flip() method that handles logic internally
  • Client code doesn't manipulate piece state directly

Benefits:

  • Board grid operations are atomic
  • Flipping logic centralized in Cell
  • Easy to add cell-level features (e.g., highlighting)
cell.flip(); // Internally handles piece.opposite()

6. Factory Method Pattern (Board Initialization)

Intent: Encapsulate object creation logic.

Current Implementation:

private void initializeBoard() {
    // Creates 64 Cell objects with proper positions
    // Sets up initial 4-piece configuration
}

Benefits:

  • Board construction logic centralized
  • Easy to modify starting configuration
  • Could support alternate game modes (different initial positions)

8. Key Design Decisions

Decision 1: Immutable Position Class

Rationale: Coordinates shouldn't change after creation. The move() method returns a new Position rather than mutating the original.

Benefits:

  • No defensive copying needed
  • Safe to use as HashMap keys
  • Thread-safe
  • Clearer semantics (positions don't "move", you get new positions)

Trade-off: Slightly more memory allocation, but modern JVMs handle this efficiently.

Decision 2: Separate MoveValidator Class

Rationale: Move validation is complex (8 directions, multiple flips) and deserves its own class.

Benefits:

  • Single Responsibility Principle
  • Easy to unit test validation logic
  • Game class stays focused on orchestration
  • Could swap validators for different rule variants

Alternative Considered: Putting validation in Board class would bloat it with complex logic.

Decision 3: Direction Enum with Deltas

Rationale: Each direction has associated row/column deltas. Storing them in the enum eliminates repetitive switch statements.

Benefits:

  • DRY: deltas defined once
  • Easy to iterate all directions: for (Direction dir : Direction.values())
  • Type-safe direction references

Code Elimination:

// Without enum approach (repetitive):
if (direction == "NORTH") { nextRow = row - 1; nextCol = col; }
else if (direction == "SOUTH") { nextRow = row + 1; nextCol = col; }
// ... 6 more cases

// With enum:
Position next = current.move(direction); // Clean!

Decision 4: Cell-Based Board Representation

Rationale: Each square is a Cell object containing position and piece state, rather than just a 2D array of enums.

Benefits:

  • Cells can have additional properties (e.g., highlighted, last move)
  • Encapsulates piece state and flip logic
  • Easier to add cell-specific behavior later

Trade-off: Slightly more memory than raw Piece[][] array, but worth it for extensibility.

Decision 5: Piece Count Recalculation

Rationale: Instead of tracking piece counts incrementally, we recalculate by scanning the board after each move.

Benefits:

  • Source of truth is always the board state
  • No risk of count desynchronization bugs
  • Simpler state management

Trade-off: O(64) scan per move, but 64 is small enough to be negligible.

Alternative Considered: Incremental tracking would be O(1) but more error-prone with flips affecting both players.

Decision 6: Validator Returns Flippable List

Rationale: getFlippablePositions() returns all positions that would be flipped, allowing Game to execute flips.

Benefits:

  • Separation: validator identifies targets, Game executes
  • Validator is pure function (no side effects)
  • Easy to preview moves (show what would flip)
  • Testable without affecting board state
List<Position> toFlip = validator.getFlippablePositions(position, color);
// Preview: "This move will flip 5 pieces"
for (Position pos : toFlip) {
    board.flipPiece(pos);
}

Decision 7: No Undo/Redo Initially

Rationale: Simplified scope to focus on core game mechanics.

Future Enhancement: Could add Command pattern for move history:

interface Command {
    void execute();
    void undo();
}

class PlacePieceCommand implements Command {
    private Position position;
    private List<Position> flipped;
    // ... execute/undo implementation
}

Decision 8: Console-Based UI

Rationale: Simple text interface allows focusing on OOD without GUI complexity.

Benefits:

  • Easy to test via automated input
  • Cross-platform (no GUI dependencies)
  • Clear demonstration of MVC separation

Future GUI Enhancement:

interface BoardDisplay {
    void display(Board board);
}

class ConsoleDisplay implements BoardDisplay { ... }
class SwingGUIDisplay implements BoardDisplay { ... }

Decision 9: Pass Turn When No Moves

Rationale: Othello rules require passing if you have no valid moves. Implementation checks both players before ending game.

Logic:

if (!hasValidMoves(currentPlayer)) {
    switchTurn();
    if (!hasValidMoves(currentPlayer)) {
        endGame(); // Both players stuck
    }
}

Edge Case Handled: One player has no moves but opponent does—game continues.

Decision 10: Piece.EMPTY vs null

Rationale: Using Piece.EMPTY enum value instead of null for empty cells.

Benefits:

  • No null pointer exceptions
  • Can iterate all cells without null checks
  • Piece enum is complete domain model

Example Safety:

// Safe without null check
char symbol = cell.getPiece().getSymbol();

// vs. with null:
if (cell.getPiece() != null) {
    char symbol = cell.getPiece().getSymbol();
}

This design creates a clean, maintainable Othello implementation that strictly follows game rules while demonstrating solid object-oriented principles through clear class responsibilities, immutable value objects, and proper encapsulation.

Comments