Simulate Monster Team Battles
Company: Meta
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Technical Screen
Quick Answer: This question evaluates a candidate's ability to model stateful simulations and implement deterministic battle mechanics with clean data structures and object-oriented design, covering damage calculation, turn sequencing, tie-breaking rules, default multiplier handling, and comprehensive event logging.
Constraints
- 0 <= number of monsters in each team <= 100
- Each monster has at least 1 attack
- 0 <= initial health <= 10^6
- 1 <= base attack damage <= 10^6
- All provided type multipliers are positive finite numbers
Examples
Input: ({'name': 'Alpha', 'monsters': [{'name': 'Blaze', 'type': 'fire', 'health': 30, 'attacks': [{'name': 'Flame', 'type': 'fire', 'damage': 10}, {'name': 'Bite', 'type': 'normal', 'damage': 8}]}, {'name': 'Aqua', 'type': 'water', 'health': 25, 'attacks': [{'name': 'Splash', 'type': 'water', 'damage': 9}]}]}, {'name': 'Beta', 'monsters': [{'name': 'Leafy', 'type': 'grass', 'health': 30, 'attacks': [{'name': 'Vine', 'type': 'grass', 'damage': 7}]}, {'name': 'Rocko', 'type': 'rock', 'health': 20, 'attacks': [{'name': 'Toss', 'type': 'rock', 'damage': 6}]}]}, {('fire', 'grass'): 2.0, ('grass', 'fire'): 0.5, ('water', 'rock'): 2.0, ('rock', 'water'): 0.5, ('fire', 'rock'): 0.5})
Expected Output: {'winner': 'team_a', 'battle_log': [{'event': 'attack', 'attacker_team': 'Alpha', 'attacker': 'Blaze', 'defender_team': 'Beta', 'defender': 'Leafy', 'attack': 'Flame', 'damage': 20.0, 'defender_health': 10.0}, {'event': 'attack', 'attacker_team': 'Beta', 'attacker': 'Leafy', 'defender_team': 'Alpha', 'defender': 'Blaze', 'attack': 'Vine', 'damage': 3.5, 'defender_health': 26.5}, {'event': 'attack', 'attacker_team': 'Alpha', 'attacker': 'Blaze', 'defender_team': 'Beta', 'defender': 'Leafy', 'attack': 'Flame', 'damage': 20.0, 'defender_health': 0.0}, {'event': 'faint', 'team': 'Beta', 'monster': 'Leafy'}, {'event': 'attack', 'attacker_team': 'Alpha', 'attacker': 'Blaze', 'defender_team': 'Beta', 'defender': 'Rocko', 'attack': 'Bite', 'damage': 8.0, 'defender_health': 12.0}, {'event': 'attack', 'attacker_team': 'Beta', 'attacker': 'Rocko', 'defender_team': 'Alpha', 'defender': 'Blaze', 'attack': 'Toss', 'damage': 6.0, 'defender_health': 20.5}, {'event': 'attack', 'attacker_team': 'Alpha', 'attacker': 'Blaze', 'defender_team': 'Beta', 'defender': 'Rocko', 'attack': 'Bite', 'damage': 8.0, 'defender_health': 4.0}, {'event': 'attack', 'attacker_team': 'Beta', 'attacker': 'Rocko', 'defender_team': 'Alpha', 'defender': 'Blaze', 'attack': 'Toss', 'damage': 6.0, 'defender_health': 14.5}, {'event': 'attack', 'attacker_team': 'Alpha', 'attacker': 'Blaze', 'defender_team': 'Beta', 'defender': 'Rocko', 'attack': 'Bite', 'damage': 8.0, 'defender_health': 0.0}, {'event': 'faint', 'team': 'Beta', 'monster': 'Rocko'}]}
Explanation: Blaze uses Flame against Leafy because of the 2.0 fire-vs-grass multiplier. After Leafy faints, Blaze starts the new matchup against Rocko and chooses Bite because Flame is reduced to 5.0 by the 0.5 fire-vs-rock multiplier.
Input: ({'name': 'Gamma', 'monsters': [{'name': 'Dual', 'type': 'normal', 'health': 20, 'attacks': [{'name': 'Jab', 'type': 'fire', 'damage': 10}, {'name': 'Kick', 'type': 'water', 'damage': 10}]}]}, {'name': 'Delta', 'monsters': [{'name': 'Slime', 'type': 'grass', 'health': 15, 'attacks': [{'name': 'Peck', 'type': 'normal', 'damage': 4}]}]}, {('fire', 'grass'): 1.0, ('water', 'grass'): 1.0})
Expected Output: {'winner': 'team_a', 'battle_log': [{'event': 'attack', 'attacker_team': 'Gamma', 'attacker': 'Dual', 'defender_team': 'Delta', 'defender': 'Slime', 'attack': 'Jab', 'damage': 10.0, 'defender_health': 5.0}, {'event': 'attack', 'attacker_team': 'Delta', 'attacker': 'Slime', 'defender_team': 'Gamma', 'defender': 'Dual', 'attack': 'Peck', 'damage': 4.0, 'defender_health': 16.0}, {'event': 'attack', 'attacker_team': 'Gamma', 'attacker': 'Dual', 'defender_team': 'Delta', 'defender': 'Slime', 'attack': 'Jab', 'damage': 10.0, 'defender_health': 0.0}, {'event': 'faint', 'team': 'Delta', 'monster': 'Slime'}]}
Explanation: Both Jab and Kick deal the same effective damage, so the simulator must choose the earlier attack in the list: Jab.
Input: ({'name': 'Epsilon', 'monsters': [{'name': 'Ghost', 'type': 'fire', 'health': 0, 'attacks': [{'name': 'Spark', 'type': 'fire', 'damage': 5}]}, {'name': 'Seed', 'type': 'grass', 'health': 12, 'attacks': [{'name': 'Needle', 'type': 'grass', 'damage': 5}]}]}, {'name': 'Zeta', 'monsters': [{'name': 'Stone', 'type': 'rock', 'health': 18, 'attacks': [{'name': 'Crash', 'type': 'rock', 'damage': 7}]}]}, {})
Expected Output: {'winner': 'team_b', 'battle_log': [{'event': 'attack', 'attacker_team': 'Epsilon', 'attacker': 'Seed', 'defender_team': 'Zeta', 'defender': 'Stone', 'attack': 'Needle', 'damage': 5.0, 'defender_health': 13.0}, {'event': 'attack', 'attacker_team': 'Zeta', 'attacker': 'Stone', 'defender_team': 'Epsilon', 'defender': 'Seed', 'attack': 'Crash', 'damage': 7.0, 'defender_health': 5.0}, {'event': 'attack', 'attacker_team': 'Epsilon', 'attacker': 'Seed', 'defender_team': 'Zeta', 'defender': 'Stone', 'attack': 'Needle', 'damage': 5.0, 'defender_health': 8.0}, {'event': 'attack', 'attacker_team': 'Zeta', 'attacker': 'Stone', 'defender_team': 'Epsilon', 'defender': 'Seed', 'attack': 'Crash', 'damage': 7.0, 'defender_health': 0.0}, {'event': 'faint', 'team': 'Epsilon', 'monster': 'Seed'}]}
Explanation: Ghost starts with 0 health, so Seed is the first alive monster. Because the type chart is empty, every multiplier defaults to 1.0.
Input: ({'name': 'VoidA', 'monsters': []}, {'name': 'VoidB', 'monsters': [{'name': 'Dormant', 'type': 'rock', 'health': 0, 'attacks': [{'name': 'Tap', 'type': 'rock', 'damage': 1}]}]}, {})
Expected Output: {'winner': 'draw', 'battle_log': []}
Explanation: Neither team has any alive monster at the start, so the result is a draw and no battle events occur.
Hints
- Keep a pointer to the current alive monster for each team, and advance it only when that monster's health reaches 0.
- For each turn, scan the attack list once to find the maximum effective damage; use '>' instead of '>=' so tied attacks keep the earliest choice.