Skip to content

Commit

Permalink
🛠️ Add (package): Behavioral Template Method pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
jpcadena committed Oct 8, 2023
1 parent 12f97e3 commit de3b48f
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default_language_version:
python: python3.11
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
Expand All @@ -22,7 +22,7 @@ repos:
args: [ --markdown-linebreak-ext=md ]

- repo: https://github.com/asottile/pyupgrade
rev: v3.14.0
rev: v3.15.0
hooks:
- id: pyupgrade
args:
Expand Down
7 changes: 7 additions & 0 deletions gamma_categorization/behavioral/template_method/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
"""
Template method pattern package initialization
"""

# Algorithms decomposed into parts + specifics
# Algorithm at high level is the template
# Inheritance instead composition. Override abstract members

# Define skeleton of algorithm with concrete implementations defined in
# subclasses
105 changes: 105 additions & 0 deletions gamma_categorization/behavioral/template_method/exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
A module for exercise in the gamma categorization.behavioral.template
method package.
"""
from abc import ABC, abstractmethod


class Creature:
"""
Creature class representation
"""

def __init__(self, attack: int, health: int):
self.initial_health: int = health # For temporary damage reset
self.health: int = health
self.attack: int = attack


class CardGame(ABC):
"""
Card Game representation from Abstract Base Class
"""

def __init__(self, creatures: list[Creature]):
self.creatures: list[Creature] = creatures

def combat(self, c1_index: int, c2_index: int) -> int:
"""
Combat in the card game
:param c1_index: The index of the first creature
:type c1_index: int
:param c2_index: The index of the second creature
:type c2_index: int
:return: The index of the living survivor creature;
-1 if both are alive
:rtype: int
"""
first: Creature = self.creatures[c1_index]
second: Creature = self.creatures[c2_index]
self.hit(first, second)
self.hit(second, first)
is_first_alive = first.health > 0
is_second_alive = second.health > 0
# Reset health for temporary damage games
self.reset_health(first)
self.reset_health(second)
if is_first_alive == is_second_alive:
return -1
return c1_index if is_first_alive else c2_index

@abstractmethod
def hit(self, attacker: Creature, defender: Creature) -> None:
"""
Hit abstract method for the creatures
:param attacker: The creature that attacks
:type attacker: Creature
:param defender: The creature that defense
:type defender: Creature
:return: None
:rtype: NoneType
"""
pass

@abstractmethod
def reset_health(self, creature: Creature) -> None:
"""
Reset health abstract method for a creature
:param creature: The creature that will be reset the health
:type creature: Creature
:return: None
:rtype: NoneType
"""
pass


class TemporaryDamageCardGame(CardGame):
def hit(self, attacker: Creature, defender: Creature) -> None:
defender.health -= attacker.attack

def reset_health(self, creature: Creature) -> None:
creature.health = creature.initial_health


class PermanentDamageCardGame(CardGame):
def hit(self, attacker: Creature, defender: Creature) -> None:
defender.health -= attacker.attack

def reset_health(self, creature: Creature) -> None:
pass # No need to reset health in permanent damage game


# Example usage
if __name__ == "__main__":
creature1: Creature = Creature(1, 2)
creature2: Creature = Creature(1, 2)
game: CardGame = TemporaryDamageCardGame([creature1, creature2])
print(
game.combat(0, 1)
) # Output will be -1 because both creatures will be alive
creature1 = Creature(1, 1)
creature2 = Creature(2, 2)
game = PermanentDamageCardGame([creature1, creature2])
print(
game.combat(0, 1)
) # Output will be 1 because the second creature wins
92 changes: 92 additions & 0 deletions gamma_categorization/behavioral/template_method/template_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
A module for template in the gamma categorization.behavioral.template method
package.
"""
from abc import ABC, abstractmethod


class Game(ABC):
"""
Game representation from Abstract Base Class
"""

def __init__(self, number_of_players: int) -> None:
self.number_of_players: int = number_of_players
self.current_player: int = 0

def run(self) -> None:
"""
Run the game
:return: None
:rtype: NoneType
"""
self.start()
while not self.have_winner:
self.take_turn()
print(f'Player {self.winning_player} wins!')

def start(self) -> None:
"""
Start the game
:return: None
:rtype: NoneType
"""
pass

@abstractmethod
@property
def have_winner(self) -> bool:
"""
Have a winner from the game
:return: True if there's a winner; False if tied
:rtype: bool
"""
pass

def take_turn(self) -> None:
"""
Take turns in the game
:return: None
:rtype: NoneType
"""
pass

@abstractmethod
@property
def winning_player(self) -> int:
"""
Winning player
:return: The identifier for the winning player
:rtype: int
"""
pass


class Chess(Game):
def __init__(self) -> None:
super().__init__(2)
self.max_turns: int = 10
self.turn: int = 1

def start(self) -> None:
print(
f'Starting a game of chess with {self.number_of_players} players.'
)

@property
def have_winner(self) -> bool:
return self.turn == self.max_turns

def take_turn(self) -> None:
print(f'Turn {self.turn} taken by player {self.current_player}')
self.turn += 1
self.current_player = 1 - self.current_player

@property
def winning_player(self) -> int:
return self.current_player


if __name__ == '__main__':
chess: Chess = Chess()
chess.run()

0 comments on commit de3b48f

Please sign in to comment.