Design and implement a battle simulation system where two teams of monsters fight each other in a turn-based combat. Each team has a list of monsters, and each monster has health points and attack power. Teams alternate attacking until one team is completely eliminated. Your system should output an event log describing each action in the battle.
This is an Object-Oriented Design problem.
Implement a battle system with the following specifications:
Monster: Each monster has:
A name (string identifier)
Health points (HP) - positive integer
Attack power - positive integer
Team: Each team has:
A team name
A list of monsters
Battle Rules:
Teams take turns attacking, starting with Team A
On each turn, the first alive monster in the attacking team attacks the first alive monster in the defending team
When monster A attacks monster B: B.health = B.health - A.attack
The attacker does NOT receive counter-damage
A monster is eliminated when its health drops to 0 or below
The battle ends when all monsters on one team are eliminated
Event Log: Output a log of events including:
Each attack (who attacked whom, damage dealt)
Monster eliminations
Battle outcome (which team won)
class Monster:
def __init__(self, name: str, health: int, attack: int):
"""
Create a monster with given name, health points, and attack power.
"""
pass
def is_alive(self) -> bool:
"""Return True if the monster's health is above 0."""
pass
def take_damage(self, damage: int) -> None:
"""Reduce health by the damage amount."""
pass
class Team:
def __init__(self, name: str, monsters: list[Monster]):
"""Create a team with a name and list of monsters."""
pass
def get_first_alive(self) -> Monster | None:
"""Return the first monster that is still alive, or None if all are eliminated."""
pass
def is_defeated(self) -> bool:
"""Return True if all monsters are eliminated."""
pass
def battle(team_a: Team, team_b: Team) -> list[str]:
"""
Simulate a battle between two teams.
Returns a list of event log strings describing each action.
"""
pass
# Create monsters for Team A
dragon = Monster("Dragon", health=100, attack=25)
griffin = Monster("Griffin", health=80, attack=20)
team_a = Team("Heroes", [dragon, griffin])
# Create monsters for Team B
goblin = Monster("Goblin", health=30, attack=10)
orc = Monster("Orc", health=50, attack=15)
troll = Monster("Troll", health=70, attack=12)
team_b = Team("Monsters", [goblin, orc, troll])
# Run the battle
event_log = battle(team_a, team_b)
# Expected event log (example format):
# [
# "Battle begins: Heroes vs Monsters",
# "Dragon attacks Goblin for 25 damage. Goblin has 5 HP remaining.",
# "Goblin attacks Dragon for 10 damage. Dragon has 90 HP remaining.",
# "Dragon attacks Goblin for 25 damage. Goblin is eliminated!",
# "Orc attacks Dragon for 15 damage. Dragon has 75 HP remaining.",
# "Dragon attacks Orc for 25 damage. Orc has 25 HP remaining.",
# "Orc attacks Dragon for 15 damage. Dragon has 60 HP remaining.",
# "Dragon attacks Orc for 25 damage. Orc is eliminated!",
# "Troll attacks Dragon for 12 damage. Dragon has 48 HP remaining.",
# "Dragon attacks Troll for 25 damage. Troll has 45 HP remaining.",
# "Troll attacks Dragon for 12 damage. Dragon has 36 HP remaining.",
# "Dragon attacks Troll for 25 damage. Troll has 20 HP remaining.",
# "Troll attacks Dragon for 12 damage. Dragon has 24 HP remaining.",
# "Dragon attacks Troll for 25 damage. Troll is eliminated!",
# "Battle ends: Heroes wins!"
# ]
class Monster:
def init(self, name: str, health: int, attack: int):
self.name = name
self.health = health
self.attack = attack
def is_alive(self) -> bool:
return self.health > 0
def take_damage(self, damage: int) -> None:
self.health -= damage
class Team:
def init(self, name: str, monsters: list[Monster]):
self.name = name
self.monsters = monsters
def get_first_alive(self) -> Monster | None:
for monster in self.monsters:
if monster.is_alive():
return monster
return None
def is_defeated(self) -> bool:
return all(not monster.is_alive() for monster in self.monsters)
def battle(team_a: Team, team_b: Team) -> list[str]:
events = []
events.append(f"Battle begins: {team_a.name} vs {team_b.name}")
current_attacker = team_a
current_defender = team_b
while not team_a.is_defeated() and not team_b.is_defeated():
attacker = current_attacker.get_first_alive()
defender_monster = current_defender.get_first_alive()
if attacker is None or defender_monster is None:
break
damage = attacker.attack
defender_monster.take_damage(damage)
if defender_monster.is_alive():
events.append(
f"{attacker.name} attacks {defender_monster.name} for {damage} damage. "
f"{defender_monster.name} has {defender_monster.health} HP remaining."
)
else:
events.append(
f"{attacker.name} attacks {defender_monster.name} for {damage} damage. "
f"{defender_monster.name} is eliminated!"
)
current_attacker, current_defender = current_defender, current_attacker
if team_b.is_defeated():
events.append(f"Battle ends: {team_a.name} wins!")
else:
events.append(f"Battle ends: {team_b.name} wins!")
return events
Extend the battle system to support monster types with damage modifiers. Different types interact with each other to deal bonus or reduced damage.
Monster Types: Each monster now has a type (e.g., Fire, Water, Grass, Electric)
Type Effectiveness:
Some types deal double damage (2x) against certain types
Some types deal half damage (0.5x) against certain types
Neutral matchups deal normal damage (1x)
Example Type Chart:
Fire -> Grass: 2x damage
Fire -> Water: 0.5x damage
Water -> Fire: 2x damage
Water -> Grass: 0.5x damage
Grass -> Water: 2x damage
Grass -> Fire: 0.5x damage
Electric -> Water: 2x damage
All other matchups: 1x damage
Attack Order: Monsters still attack in list order (first alive attacks first alive)
from enum import Enum
class MonsterType(Enum):
FIRE = "Fire"
WATER = "Water"
GRASS = "Grass"
ELECTRIC = "Electric"
class Monster:
def init(self, name: str, health: int, attack: int, monster_type: MonsterType):
self.name = name
self.health = health
self.attack = attack
self.monster_type = monster_type
def calculate_damage(self, defender: 'Monster') -> int:
"""Calculate damage considering type effectiveness."""
pass
# Type effectiveness example
fire_dragon = Monster("FireDragon", health=100, attack=20, monster_type=MonsterType.FIRE)
water_serpent = Monster("WaterSerpent", health=80, attack=15, monster_type=MonsterType.WATER)
# FireDragon attacks WaterSerpent: 20 * 0.5 = 10 damage (Fire weak against Water)
# WaterSerpent attacks FireDragon: 15 * 2.0 = 30 damage (Water strong against Fire)
from enum import Enum
class MonsterType(Enum):
FIRE = "Fire"
WATER = "Water"
GRASS = "Grass"
ELECTRIC = "Electric"
TYPE_CHART = {
(MonsterType.FIRE, MonsterType.GRASS): 2.0,
(MonsterType.FIRE, MonsterType.WATER): 0.5,
(MonsterType.WATER, MonsterType.FIRE): 2.0,
(MonsterType.WATER, MonsterType.GRASS): 0.5,
(MonsterType.GRASS, MonsterType.WATER): 2.0,
(MonsterType.GRASS, MonsterType.FIRE): 0.5,
(MonsterType.ELECTRIC, MonsterType.WATER): 2.0,
}
class Monster:
def init(self, name: str, health: int, attack: int, monster_type: MonsterType):
self.name = name
self.health = health
self.attack = attack
self.monster_type = monster_type
def is_alive(self) -> bool:
return self.health > 0
def take_damage(self, damage: int) -> None:
self.health -= damage
def calculate_damage(self, defender: 'Monster') -> int:
"""Calculate damage with type effectiveness."""
multiplier = TYPE_CHART.get(
(self.monster_type, defender.monster_type),
1.0 # Default to neutral damage
)
return int(self.attack * multiplier)
class Team:
def init(self, name: str, monsters: list[Monster]):
self.name = name
self.monsters = monsters
def get_first_alive(self) -> Monster | None:
for monster in self.monsters:
if monster.is_alive():
return monster
return None
def is_defeated(self) -> bool:
return all(not monster.is_alive() for monster in self.monsters)
def battle(team_a: Team, team_b: Team) -> list[str]:
events = []
events.append(f"Battle begins: {team_a.name} vs {team_b.name}")
current_attacker = team_a
current_defender = team_b
while not team_a.is_defeated() and not team_b.is_defeated():
attacker = current_attacker.get_first_alive()
defender_monster = current_defender.get_first_alive()
if attacker is None or defender_monster is None:
break
damage = attacker.calculate_damage(defender_monster)
defender_monster.take_damage(damage)
base_damage = attacker.attack
if damage > base_damage:
effectiveness = " (Super effective!)"
elif damage < base_damage:
effectiveness = " (Not very effective...)"
else:
effectiveness = ""
if defender_monster.is_alive():
events.append(
f"{attacker.name} attacks {defender_monster.name} for {damage} damage{effectiveness}. "
f"{defender_monster.name} has {defender_monster.health} HP remaining."
)
else:
events.append(
f"{attacker.name} attacks {defender_monster.name} for {damage} damage{effectiveness}. "
f"{defender_monster.name} is eliminated!"
)
current_attacker, current_defender = current_defender, current_attacker
if team_b.is_defeated():
events.append(f"Battle ends: {team_a.name} wins!")
else:
events.append(f"Battle ends: {team_b.name} wins!")
return events
Extend the battle system so that on each turn, instead of using the first alive monster, the attacking team selects the monster that can deal the maximum damage to the first alive monster on the defending team.
Attacker Selection: On each turn:
Identify the defending team's first alive monster
Among all alive monsters on the attacking team, select the one that would deal the most damage to that defender
If multiple monsters deal the same maximum damage, choose the one that appears first in the list
Defender Selection: The defender is still the first alive monster on the defending team (unchanged from Part 1)
Type Effectiveness: Continue using the type chart from Part 2
# Team A has:
# - FireDragon (attack=20, FIRE type)
# - ElectricEel (attack=15, ELECTRIC type)
# Team B's first alive monster is:
# - WaterSerpent (WATER type)
# Damage calculations:
# - FireDragon vs WaterSerpent: 20 * 0.5 = 10 (Fire weak to Water)
# - ElectricEel vs WaterSerpent: 15 * 2.0 = 30 (Electric strong to Water)
# ElectricEel is selected as the attacker (deals 30 damage vs 10)
class Team:
def init(self, name: str, monsters: list[Monster]):
self.name = name
self.monsters = monsters
def get_first_alive(self) -> Monster | None:
for monster in self.monsters:
if monster.is_alive():
return monster
return None
def get_best_attacker(self, defender: Monster) -> Monster | None:
"""
Select the alive monster that deals maximum damage to the defender.
If tied, return the one that appears first in the list.
"""
best_attacker = None
best_damage = -1
for monster in self.monsters:
if monster.is_alive():
damage = monster.calculate_damage(defender)
if damage > best_damage:
best_damage = damage
best_attacker = monster
return best_attacker
def is_defeated(self) -> bool:
return all(not monster.is_alive() for monster in self.monsters)
def battle(team_a: Team, team_b: Team) -> list[str]:
events = []
events.append(f"Battle begins: {team_a.name} vs {team_b.name}")
current_attacker_team = team_a
current_defender_team = team_b
while not team_a.is_defeated() and not team_b.is_defeated():
defender_monster = current_defender_team.get_first_alive()
if defender_monster is None:
break
attacker = current_attacker_team.get_best_attacker(defender_monster)
if attacker is None:
break
damage = attacker.calculate_damage(defender_monster)
defender_monster.take_damage(damage)
base_damage = attacker.attack
if damage > base_damage:
effectiveness = " (Super effective!)"
elif damage < base_damage:
effectiveness = " (Not very effective...)"
else:
effectiveness = ""
if defender_monster.is_alive():
events.append(
f"{attacker.name} attacks {defender_monster.name} for {damage} damage{effectiveness}. "
f"{defender_monster.name} has {defender_monster.health} HP remaining."
)
else:
events.append(
f"{attacker.name} attacks {defender_monster.name} for {damage} damage{effectiveness}. "
f"{defender_monster.name} is eliminated!"
)
current_attacker_team, current_defender_team = current_defender_team, current_attacker_team
if team_b.is_defeated():
events.append(f"Battle ends: {team_a.name} wins!")
else:
events.append(f"Battle ends: {team_b.name} wins!")
return events
# Create Team A with mixed types
fire_dragon = Monster("FireDragon", health=100, attack=20, monster_type=MonsterType.FIRE)
electric_eel = Monster("ElectricEel", health=60, attack=15, monster_type=MonsterType.ELECTRIC)
grass_golem = Monster("GrassGolem", health=120, attack=18, monster_type=MonsterType.GRASS)
team_a = Team("Elements", [fire_dragon, electric_eel, grass_golem])
# Create Team B with water types
water_serpent = Monster("WaterSerpent", health=80, attack=15, monster_type=MonsterType.WATER)
water_sprite = Monster("WaterSprite", health=50, attack=12, monster_type=MonsterType.WATER)
team_b = Team("Aquatics", [water_serpent, water_sprite])
event_log = battle(team_a, team_b)
# With smart targeting, ElectricEel (30 damage) attacks WaterSerpent instead of
# FireDragon (10 damage), maximizing damage output
Encapsulation: Monster state (health) is modified only through methods (take_damage)
Single Responsibility: Each class has one clear purpose
Open/Closed: Type chart can be extended without modifying monster logic
Separation of Concerns: Battle logic is separate from entity definitions
def test_basic_battle():
m1 = Monster("A", 50, 30, MonsterType.FIRE)
m2 = Monster("B", 100, 10, MonsterType.WATER)
team_a = Team("TeamA", [m1])
team_b = Team("TeamB", [m2])
log = battle(team_a, team_b)
def test_smart_targeting():
fire = Monster("Fire", 50, 20, MonsterType.FIRE)
elec = Monster("Electric", 50, 15, MonsterType.ELECTRIC)
water = Monster("Water", 100, 10, MonsterType.WATER)
team_a = Team("A", [fire, elec])
team_b = Team("B", [water])
log = battle(team_a, team_b)
assert "Electric attacks Water" in log[1]