Bird
Raised Fist0
LLDsystem_design~7 mins

Piece movement rules (polymorphism) in LLD - System Design Guide

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Problem Statement
When implementing a board game like chess, hardcoding movement rules for each piece in a single class leads to complex, tangled code that is hard to maintain and extend. Adding new piece types or changing rules requires modifying existing code, increasing the risk of bugs and slowing development.
Solution
Use polymorphism by defining a base Piece class with a method for movement rules, then create subclasses for each piece type that override this method with their specific movement logic. This separates concerns, making the code easier to read, maintain, and extend without changing existing classes.
Architecture
Piece
───────────
Pawn
───────────

This diagram shows a base Piece class with a move method, and subclasses Pawn, Knight, and Bishop each implementing their own move method to define specific movement rules.

Trade-offs
✓ Pros
Separates movement logic for each piece, improving code clarity and maintainability.
Makes it easy to add new piece types without modifying existing code.
Supports polymorphic behavior, allowing uniform handling of different pieces.
✗ Cons
May introduce more classes, increasing the number of files and complexity in small projects.
Requires understanding of object-oriented principles, which might be challenging for beginners.
Overhead of dynamic dispatch can slightly impact performance in very tight loops.
Use when the system has multiple piece types with distinct movement rules and you expect to add or modify pieces over time, especially in games like chess or checkers with complex rules.
Avoid when the game has very few piece types with simple, similar movement rules or when performance is critical and the overhead of polymorphism is unacceptable.
Real World Examples
Chess.com
Uses polymorphism to implement different chess piece movement rules, allowing easy updates and extensions to game logic.
Lichess
Separates piece movement logic into classes to maintain clean code and support variants with different piece behaviors.
Ubisoft
In strategy games, uses polymorphism to handle unit movement rules, enabling diverse unit types with unique behaviors.
Code Example
The before code uses a single class with conditional statements to handle movement rules, making it hard to maintain and extend. The after code uses polymorphism by defining a base Piece class and subclasses for each piece type, each implementing its own move method. This design is cleaner, easier to extend, and follows object-oriented principles.
LLD
### Before: Without polymorphism (all logic in one class)
class Piece:
    def __init__(self, type):
        self.type = type

    def move(self, position):
        if self.type == 'pawn':
            # pawn movement logic
            return 'pawn moves'
        elif self.type == 'knight':
            # knight movement logic
            return 'knight moves'
        # more conditions for other pieces


### After: With polymorphism (each piece has its own class)
class Piece:
    def move(self, position):
        raise NotImplementedError()

class Pawn(Piece):
    def move(self, position):
        return 'pawn moves'

class Knight(Piece):
    def move(self, position):
        return 'knight moves'

# Usage
pieces = [Pawn(), Knight()]
for p in pieces:
    print(p.move(None))
OutputSuccess
Alternatives
Procedural conditional logic
Implements all movement rules in a single function using conditionals to check piece type.
Use when: Choose when the number of piece types is very small and unlikely to change, and simplicity is preferred over extensibility.
Data-driven movement rules
Stores movement rules in data structures (e.g., arrays or config files) instead of code, interpreted at runtime.
Use when: Choose when you want to allow non-developers to modify rules without code changes or support many variants dynamically.
Summary
Hardcoding piece movement rules in one place leads to tangled, hard-to-maintain code.
Polymorphism lets each piece type define its own movement logic in separate classes.
This approach improves code clarity, extensibility, and supports adding new pieces easily.

Practice

(1/5)
1. What is the main benefit of using polymorphism for piece movement rules in a game design?
easy
A. It allows each piece to have its own move logic without type checks.
B. It forces all pieces to share the same move logic.
C. It requires manual checking of piece types before moving.
D. It prevents pieces from moving on the board.

Solution

  1. Step 1: Understand polymorphism concept

    Polymorphism allows different objects to be treated through a common interface while having their own behavior.
  2. Step 2: Apply to piece movement

    Each piece class implements its own move() method, so no need to check piece type before moving.
  3. Final Answer:

    It allows each piece to have its own move logic without type checks. -> Option A
  4. Quick Check:

    Polymorphism = own move logic without type checks [OK]
Hint: Polymorphism means no type checks for moves [OK]
Common Mistakes:
  • Thinking all pieces share the same move logic
  • Believing manual type checks are needed
  • Confusing polymorphism with inheritance only
2. Which of the following is the correct way to declare a base class method for piece movement in a polymorphic design?
easy
A. move(self): pass
B. def move(self): pass
C. def move(): pass
D. def move(self, board): return

Solution

  1. Step 1: Recall method declaration syntax in Python

    Instance methods must have self as the first parameter.
  2. Step 2: Identify correct method signature

    def move(self): pass correctly declares a method with self and no implementation.
  3. Final Answer:

    def move(self): pass -> Option B
  4. Quick Check:

    Method with self parameter = def move(self): pass [OK]
Hint: Instance methods always start with self parameter [OK]
Common Mistakes:
  • Omitting self parameter in method
  • Using incorrect syntax without def keyword
  • Adding unnecessary parameters without context
3. Given the following code, what will be the output?
class Piece:
    def move(self):
        return "Base move"

class Knight(Piece):
    def move(self):
        return "L-shaped move"

pieces = [Piece(), Knight()]
for p in pieces:
    print(p.move())
medium
A. Base move\nL-shaped move
B. L-shaped move\nL-shaped move
C. Error: move() not implemented
D. Base move\nBase move

Solution

  1. Step 1: Understand method overriding

    Subclass Knight overrides move() to return "L-shaped move".
  2. Step 2: Trace the loop output

    First object is Piece, prints "Base move"; second is Knight, prints "L-shaped move".
  3. Final Answer:

    Base move\nL-shaped move -> Option A
  4. Quick Check:

    Base class and overridden subclass moves printed [OK]
Hint: Subclass method overrides base method output [OK]
Common Mistakes:
  • Assuming base method always runs
  • Expecting same output for all pieces
  • Confusing method overriding with overloading
4. Identify the error in the following polymorphic piece movement code:
class Piece:
    def move(self):
        pass

class Bishop(Piece):
    def move():
        print("Diagonal move")

b = Bishop()
b.move()
medium
A. Cannot instantiate Bishop directly
B. Piece.move() should return a value
C. Bishop.move() missing self parameter
D. print statement syntax error

Solution

  1. Step 1: Check method signatures

    Bishop.move() lacks self parameter, so it is not a proper instance method.
  2. Step 2: Understand call context

    Calling b.move() passes self automatically, causing a TypeError due to missing parameter.
  3. Final Answer:

    Bishop.move() missing self parameter -> Option C
  4. Quick Check:

    Instance methods must have self parameter [OK]
Hint: Instance methods always need self parameter [OK]
Common Mistakes:
  • Ignoring missing self in subclass method
  • Thinking base class method must return value
  • Assuming print syntax is wrong
5. You are designing a chess game using polymorphism for piece movement. How should you structure your classes to allow easy addition of new piece types without changing existing code?
hard
A. Write a single move() function with if-else for each piece type.
B. Implement move logic only in the base class and override rarely.
C. Use global variables to track piece types and moves.
D. Create a base Piece class with an abstract move() method; each piece subclass implements move().

Solution

  1. Step 1: Apply polymorphism design principle

    Use a base class with an abstract or empty move() method to define interface.
  2. Step 2: Implement subclasses for each piece

    Each piece subclass provides its own move() logic, enabling extension without modifying base code.
  3. Final Answer:

    Create a base Piece class with an abstract move() method; each piece subclass implements move(). -> Option D
  4. Quick Check:

    Base class + subclass move() = scalable design [OK]
Hint: Base class with abstract move() enables easy extension [OK]
Common Mistakes:
  • Using if-else instead of polymorphism
  • Relying on global variables for logic
  • Putting all move logic in base class only