Card 08/ 09

ExerciseDifficulty: Advanced3 min

Practice: Finding the Template Method in a Game Loop

A base Game class with play(), and chess and draughts subclasses.

A game loop is a natural Template Method, and this exercise asks you to find every part of the pattern in one. Read the listing, then answer the questions before opening the solution.

py
from abc import ABC, abstractmethod


class Game(ABC):
    def play(self) -> None:
        self.set_up()
        turn = 0
        while not self.is_over(turn):
            self.before_turn(turn)
            self.take_turn(turn)
            turn += 1
        print(f"{type(self).__name__}: {self.winner(turn)} wins after {turn} turns")

    @abstractmethod
    def set_up(self) -> None: ...

    @abstractmethod
    def take_turn(self, turn: int) -> None: ...

    @abstractmethod
    def is_over(self, turn: int) -> bool: ...

    def before_turn(self, turn: int) -> None:
        pass

    def winner(self, turn: int) -> str:
        return "player 1" if turn % 2 else "player 2"


class Chess(Game):
    def set_up(self) -> None:
        print("Chess: 32 pieces placed")

    def take_turn(self, turn: int) -> None:
        print(f"  move {turn + 1}")

    def is_over(self, turn: int) -> bool:
        return turn == 4


class Draughts(Game):
    def set_up(self) -> None:
        print("Draughts: 24 pieces placed")

    def before_turn(self, turn: int) -> None:
        print(f"  checking captures before move {turn + 1}")

    def take_turn(self, turn: int) -> None:
        print(f"  move {turn + 1}")

    def is_over(self, turn: int) -> bool:
        return turn == 3


Chess().play()
Draughts().play()
A base `Game` and two games.
  1. Which method is the template method?
  2. Which methods are abstract steps, and which are hooks?
  3. Which class overrides a hook, and what does it add?
  4. What does each game print?
Show a solution
Each method of Game and its role
MethodRoleWhy
playTemplate methodIt fixes the order: set up, then turns until the game is over, then the winner
set_up, take_turn, is_overAbstract stepsMarked @abstractmethod, so every game must write them
before_turnHookIt has a default that does nothing
winnerHookIt has a default, and no game here overrides it
py
from abc import ABC, abstractmethod


class Game(ABC):
    def play(self) -> None:
        self.set_up()
        turn = 0
        while not self.is_over(turn):
            self.before_turn(turn)
            self.take_turn(turn)
            turn += 1
        print(f"{type(self).__name__}: {self.winner(turn)} wins after {turn} turns")

    @abstractmethod
    def set_up(self) -> None: ...

    @abstractmethod
    def take_turn(self, turn: int) -> None: ...

    @abstractmethod
    def is_over(self, turn: int) -> bool: ...

    def before_turn(self, turn: int) -> None:
        pass

    def winner(self, turn: int) -> str:
        return "player 1" if turn % 2 else "player 2"


class Chess(Game):
    def set_up(self) -> None:
        print("Chess: 32 pieces placed")

    def take_turn(self, turn: int) -> None:
        print(f"  move {turn + 1}")

    def is_over(self, turn: int) -> bool:
        return turn == 4


class Draughts(Game):
    def set_up(self) -> None:
        print("Draughts: 24 pieces placed")

    def before_turn(self, turn: int) -> None:
        print(f"  checking captures before move {turn + 1}")

    def take_turn(self, turn: int) -> None:
        print(f"  move {turn + 1}")

    def is_over(self, turn: int) -> bool:
        return turn == 3


Chess().play()
Draughts().play()
OutputRan with python:3.12-slim (Python 3.12.13)
Chess: 32 pieces placed
  move 1
  move 2
  move 3
  move 4
Chess: player 2 wins after 4 turns
Draughts: 24 pieces placed
  checking captures before move 1
  move 1
  checking captures before move 2
  move 2
  checking captures before move 3
  move 3
Draughts: player 1 wins after 3 turns
Only draughts prints the hook's line before each move.

Draughts overrides the before_turn hook to check for captures before each move, and Chess leaves it alone. Chess ends after four turns and draughts after three, and both use the default winner. Neither game contains the loop: it is written once, in play.