0
0
LLDsystem_design~15 mins

Piece movement rules (polymorphism) in LLD - Deep Dive

Choose your learning style9 modes available
Overview - Piece movement rules (polymorphism)
What is it?
Piece movement rules (polymorphism) is a design approach where different game pieces follow their own movement rules through a shared interface. Each piece type implements its own logic for valid moves, but the system treats them uniformly. This allows easy extension and clear organization of movement behaviors without mixing all rules in one place.
Why it matters
Without polymorphism, all piece movement rules would be tangled in one place, making the code hard to read, change, or add new pieces. Polymorphism solves this by letting each piece control its own moves, making the system flexible and scalable. This approach prevents bugs and saves time when updating or expanding the game.
Where it fits
Before learning this, you should understand basic object-oriented programming concepts like classes and inheritance. After this, you can explore design patterns like Strategy or Visitor that further organize behavior, or learn how to integrate these rules into a full game engine with UI and networking.
Mental Model
Core Idea
Each piece knows how it moves by having its own movement method, but all pieces share the same interface so the game treats them equally.
Think of it like...
Imagine a group of vehicles where each vehicle type (car, bike, airplane) has its own way to move, but all have a 'drive' button. Pressing 'drive' makes each vehicle move according to its own rules, even though you use the same button for all.
┌───────────────┐
│   Piece (base)│
│  ┌─────────┐  │
│  │move()   │  │
│  └─────────┘  │
└──────┬────────┘
       │
  ┌────┴─────┐  ┌───────┐  ┌───────┐
  │Pawn      │  │Knight │  │Bishop │
  │move()    │  │move() │  │move() │
  └──────────┘  └───────┘  └───────┘
Build-Up - 7 Steps
1
FoundationUnderstanding basic piece movement
🤔
Concept: Learn what piece movement rules mean in a game context.
In board games like chess, each piece moves differently. For example, a pawn moves forward one step, a knight moves in an L shape. These rules define how pieces can move on the board.
Result
You understand that each piece has unique movement rules that must be enforced by the game.
Knowing that movement rules differ per piece is the foundation for organizing code to handle these differences clearly.
2
FoundationIntroduction to polymorphism concept
🤔
Concept: Polymorphism lets different objects respond to the same method call in their own way.
Polymorphism means 'many forms'. In programming, it allows objects of different types to be treated the same way but behave differently. For example, calling move() on any piece runs that piece's specific move logic.
Result
You grasp that polymorphism enables uniform handling of diverse behaviors.
Understanding polymorphism is key to designing flexible systems where behavior varies by type but usage stays consistent.
3
IntermediateDesigning a base Piece interface
🤔Before reading on: do you think the base Piece class should implement move logic or just define the method? Commit to your answer.
Concept: Create a base class or interface that declares a move method without implementing it.
Define a Piece interface with a move() method. This method will be overridden by each piece type. The base class does not know how to move; it only promises that all pieces can move.
Result
You have a common contract that all pieces follow, enabling polymorphism.
Knowing to separate interface from implementation prevents mixing all movement logic in one place and supports extension.
4
IntermediateImplementing specific piece movement rules
🤔Before reading on: do you think each piece class should check all other pieces' rules or only its own? Commit to your answer.
Concept: Each piece class implements its own move method with its unique rules.
Create classes like Pawn, Knight, Bishop that inherit from Piece. Each overrides move() to return valid moves according to its rules. For example, Pawn.move() returns one step forward, Knight.move() returns L-shaped moves.
Result
Each piece knows how to move independently, making the system modular.
Understanding that encapsulating movement logic in each piece reduces complexity and improves maintainability.
5
IntermediateUsing polymorphism to handle moves uniformly
🤔
Concept: The game engine calls move() on any piece without knowing its type.
When the game needs to get valid moves, it calls piece.move() on each piece object. Polymorphism ensures the correct move method runs based on the piece's class, so the engine treats all pieces the same way.
Result
The game can handle any piece type seamlessly, supporting new pieces easily.
Knowing this uniform interface simplifies game logic and supports scalability.
6
AdvancedExtending movement rules with new pieces
🤔Before reading on: do you think adding a new piece requires changing existing code or just adding a new class? Commit to your answer.
Concept: Adding new pieces means creating new classes implementing move(), without changing existing code.
To add a new piece like Queen, create a Queen class inheriting Piece and implement move() with queen's rules. The rest of the system works without modification because it relies on the Piece interface.
Result
The system is open for extension but closed for modification, following a key design principle.
Understanding this principle prevents bugs and reduces regression risks when expanding the game.
7
ExpertHandling complex move validation and state
🤔Before reading on: do you think move() should only return possible moves or also check game state like check or pins? Commit to your answer.
Concept: Advanced move rules require move() to consider game state and constraints beyond basic movement patterns.
In production, move() may need access to the board state to exclude moves that put the king in check or violate pins. This requires passing context or using additional methods to validate moves after generation.
Result
Move logic becomes more complex but remains encapsulated per piece, maintaining polymorphism benefits.
Knowing how to integrate state-aware validation prevents illegal moves and ensures game correctness without breaking design.
Under the Hood
At runtime, when the game calls move() on a piece object, the program looks up the actual class of the object and executes that class's move method. This is called dynamic dispatch. The base Piece class defines the interface, but the concrete subclass provides the implementation. This allows the same method call to behave differently depending on the piece type.
Why designed this way?
This design was chosen to separate concerns: the game engine handles pieces uniformly, while each piece handles its own movement logic. Alternatives like a big switch-case for piece types were harder to maintain and extend. Polymorphism supports the open/closed principle, making the system scalable and less error-prone.
┌───────────────┐
│ Game Engine   │
│ calls move()  │
└──────┬────────┘
       │
┌──────▼───────┐
│ Piece Object │
│ (dynamic)   │
└──────┬───────┘
       │
┌──────▼───────┐  ┌─────────────┐  ┌─────────────┐
│ Pawn.move()  │  │ Knight.move()│  │ Bishop.move()│
│ (overridden) │  │ (overridden) │  │ (overridden) │
└──────────────┘  └─────────────┘  └─────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does polymorphism mean all pieces share the same move logic? Commit yes or no.
Common Belief:Polymorphism means all pieces use the same movement code.
Tap to reveal reality
Reality:Polymorphism means pieces share the same method name but each has its own unique implementation.
Why it matters:Believing this causes confusion and leads to trying to write one complex move method for all pieces, making code messy and error-prone.
Quick: Should the base Piece class implement detailed move rules? Commit yes or no.
Common Belief:The base Piece class should implement all movement rules to avoid duplication.
Tap to reveal reality
Reality:The base class only defines the interface; detailed rules belong in subclasses to keep code organized and flexible.
Why it matters:Putting all rules in the base class breaks modularity and makes adding new pieces difficult.
Quick: Can polymorphism alone handle all move validation including game state? Commit yes or no.
Common Belief:Polymorphism by itself handles all move validation perfectly.
Tap to reveal reality
Reality:Polymorphism handles movement logic per piece, but game state checks (like check or pins) require additional validation layers.
Why it matters:Ignoring this leads to illegal moves slipping through, causing bugs and incorrect gameplay.
Quick: Does adding a new piece require changing existing piece classes? Commit yes or no.
Common Belief:Adding a new piece means modifying existing piece classes to accommodate it.
Tap to reveal reality
Reality:You only add a new class for the new piece; existing classes remain unchanged.
Why it matters:Misunderstanding this leads to fragile code and higher risk of introducing bugs.
Expert Zone
1
Polymorphism combined with composition can separate movement patterns from piece types for even more flexible designs.
2
Caching valid moves per piece can optimize performance but requires careful invalidation when the board state changes.
3
Using double dispatch or visitor patterns can help when move validation depends on interactions between pieces.
When NOT to use
Polymorphism is less suitable if movement rules are extremely simple and uniform, where a data-driven approach or lookup tables might be more efficient. Also, if performance is critical and dynamic dispatch overhead is unacceptable, alternative designs like static polymorphism or procedural code may be preferred.
Production Patterns
In real games, polymorphism is used alongside state validation layers, event-driven updates, and AI move generators. Pieces often have helper classes for move generation and validation. The system supports hot-swapping piece rules for variants or custom games without changing core engine code.
Connections
Strategy Pattern
Builds-on
Understanding polymorphism helps grasp the Strategy pattern, where behavior is encapsulated in interchangeable objects, allowing dynamic changes in algorithms.
Dynamic Dispatch in Programming Languages
Same pattern
Polymorphism relies on dynamic dispatch, a core concept in many languages that enables method calls to resolve to the correct implementation at runtime.
Human Roles in a Team
Analogy to real-world roles
Just like team members have different skills but respond to the same task request, polymorphic objects respond to the same method call with different behaviors, showing how software models real-world diversity.
Common Pitfalls
#1Trying to put all piece movement logic in one big method with many if-else checks.
Wrong approach:function move(piece) { if (piece.type === 'Pawn') { /* pawn moves */ } else if (piece.type === 'Knight') { /* knight moves */ } else if (piece.type === 'Bishop') { /* bishop moves */ } // and so on... }
Correct approach:class Piece { move() { throw 'Not implemented'; } } class Pawn extends Piece { move() { /* pawn moves */ } } class Knight extends Piece { move() { /* knight moves */ } }
Root cause:Misunderstanding polymorphism leads to procedural code that is hard to maintain and extend.
#2Implementing move logic only in the base Piece class and not overriding in subclasses.
Wrong approach:class Piece { move() { return allPossibleMovesForAllPieces(); } }
Correct approach:class Piece { move() { throw 'Not implemented'; } } class Pawn extends Piece { move() { return pawnMoves(); } }
Root cause:Confusing interface definition with implementation causes loss of polymorphism benefits.
#3Ignoring game state when generating moves, allowing illegal moves like moving into check.
Wrong approach:class Pawn extends Piece { move() { return allForwardMoves(); /* no check validation */ } }
Correct approach:class Pawn extends Piece { move(board) { let moves = allForwardMoves(); return moves.filter(move => !board.isKingInCheckAfter(move)); } }
Root cause:Not integrating game state with movement logic leads to invalid gameplay.
Key Takeaways
Polymorphism lets each piece define its own movement rules while sharing a common interface, simplifying game logic.
Separating movement logic into subclasses follows good design principles, making the system easier to extend and maintain.
Dynamic dispatch ensures the correct move method runs based on the piece type at runtime.
Advanced move validation requires combining polymorphism with game state checks to prevent illegal moves.
Misusing polymorphism by mixing all rules or ignoring state leads to fragile and buggy systems.