1. Factory Pattern - For Creating Chess Pieces
Purpose: To create different chess pieces (King, Queen, Bishop, etc.) based on specific parameters (e.g., color).
Application: Use a
PieceFactory
class to encapsulate the logic of creating instances of specific chess pieces. This pattern simplifies object creation and reduces coupling by centralizing the piece creation logic.public class PieceFactory { public static Piece createPiece(String type, boolean isWhite) { switch (type.toLowerCase()) { case "king": return new King(isWhite); case "queen": return new Queen(isWhite); case "bishop": return new Bishop(isWhite); case "knight": return new Knight(isWhite); case "rook": return new Rook(isWhite); case "pawn": return new Pawn(isWhite); default: throw new IllegalArgumentException("Unknown piece type"); } } }
Reason behind using static in factory class?
A static method allows direct access to the factory method without needing to instantiate the factory class itself.
This can make the code cleaner, especially when the factory only provides creation methods.
Piece king = PieceFactory.createPiece("King", true);
2. Strategy Pattern - For Different Piece Movements
Purpose: To encapsulate the movement logic of different pieces, which each have unique rules.
Application: Create a
MovementStrategy
interface with different implementations for each piece type (KingMovement
,QueenMovement
,PawnMovement
, etc.). Each piece class can then use an appropriate movement strategy instance to determine valid moves.interface MovementStrategy { List<Move> getValidMoves(Board board, Square currentSquare); } class KingMovement implements MovementStrategy { @Override public List<Move> getValidMoves(Board board, Square currentSquare) { // Implement king-specific move logic } } class Piece { private MovementStrategy movementStrategy; public Piece(MovementStrategy movementStrategy) { this.movementStrategy = movementStrategy; } public List<Move> getValidMoves(Board board, Square currentSquare) { return movementStrategy.getValidMoves(board, currentSquare); } }
3. Command Pattern - For Move and Undo Operations
Purpose: To encapsulate a move as an object, allowing for undoing, redoing, and replaying moves.
Application: Use a
MoveCommand
class to represent a move, containing information about the starting square, destination square, and piece involved. This command can be executed, undone, and stored in a history list for replay or undo functionality.interface Command { void execute(); void undo(); } class MoveCommand implements Command { private Piece piece; private Square startSquare; private Square endSquare; private Piece capturedPiece; public MoveCommand(Piece piece, Square startSquare, Square endSquare) { this.piece = piece; this.startSquare = startSquare; this.endSquare = endSquare; } @Override public void execute() { // Move piece to endSquare } @Override public void undo() { // Move piece back to startSquare and restore any captured piece } }
4. Observer Pattern - For Game State Changes and Notifications
Purpose: To notify various parts of the system about game state changes (e.g., check, checkmate, player turns, or move completion).
Application: Implement an observer pattern where
Game
acts as theSubject
and notifiesObservers
like a UI component, a game logger, or a score tracker when significant events occur.interface Observer { void update(GameEvent event); } class Game { private List<Observer> observers = new ArrayList<>(); public void addObserver(Observer observer) { observers.add(observer); } public void notifyObservers(GameEvent event) { for (Observer observer : observers) { observer.update(event); } } public void makeMove(Move move) { // Execute move and notify observers of any game events (check, checkmate, etc.) notifyObservers(new GameEvent("moveCompleted", move)); } }
5. Singleton Pattern - For Game or Board Instance
Purpose: Ensure there is only one instance of the
Game
orBoard
classes, as typically only one instance of each is needed during a game.Application: Use the singleton pattern for
Game
orBoard
if you want to restrict instantiation and provide a global access point.public class Game { private static volatile Game instance; private Game() { // Private constructor to prevent instantiation } public static Game getInstance() { if (instance == null) { // First check (no locking) synchronized (Game.class) { if (instance == null) { // Second check (with locking) instance = new Game(); } } } return instance; } }
6. State Pattern - For Managing Game Phases (e.g., Active, Checkmate, Stalemate)
Purpose: To represent and manage different states of the game, such as active play, check, checkmate, and stalemate.
Application: Define a
GameState
interface with methods relevant to each game state (likemakeMove
orcheckEndCondition
). Implement classes likeActiveState
,CheckState
, andCheckmateState
that handle behavior specific to each game state.interface GameState { void makeMove(Game game, Move move); boolean checkEndCondition(Game game); } class ActiveState implements GameState { @Override public void makeMove(Game game, Move move) { // Handle a regular move } @Override public boolean checkEndCondition(Game game) { // Check for check, checkmate, or stalemate } } class CheckmateState implements GameState { @Override public void makeMove(Game game, Move move) { throw new IllegalStateException("Game is over due to checkmate."); } @Override public boolean checkEndCondition(Game game) { return true; } }