mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-12 15:50:20 -08:00
Boxing (Python): Split configuration from logic
This commit is contained in:
@@ -1,7 +1,70 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Tuple, Dict, NamedTuple, Literal
|
||||||
|
|
||||||
|
|
||||||
|
class HitStats(NamedTuple):
|
||||||
|
choices: int
|
||||||
|
threshold: int
|
||||||
|
hit_damage: int
|
||||||
|
block_damage: int
|
||||||
|
|
||||||
|
pre_msg: str
|
||||||
|
hit_msg: str
|
||||||
|
blocked_msg: str
|
||||||
|
|
||||||
|
knockout_possible: bool = False
|
||||||
|
|
||||||
|
def is_hit(self) -> bool:
|
||||||
|
return random.randint(1, self.choices) <= self.threshold
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Player:
|
||||||
|
name: str
|
||||||
|
best: int # TODO: This is never used!
|
||||||
|
is_computer: bool
|
||||||
|
weakness: int
|
||||||
|
|
||||||
|
# for each of the 4 punch types, we have a probability of hitting
|
||||||
|
hit_stats: Dict[Literal[1, 2, 3, 4], HitStats]
|
||||||
|
|
||||||
|
damage: int = 0
|
||||||
|
score: int = 0
|
||||||
|
knockedout: bool = False
|
||||||
|
|
||||||
|
def get_punch_choice(self) -> Literal[1, 2, 3, 4]:
|
||||||
|
if self.is_computer:
|
||||||
|
return random.randint(1, 4) # type: ignore
|
||||||
|
else:
|
||||||
|
punch = -1
|
||||||
|
while punch not in [1, 2, 3, 4]:
|
||||||
|
print(f"{self.name}'S PUNCH", end="? ")
|
||||||
|
punch = int(input())
|
||||||
|
return punch # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
KNOCKOUT_THRESHOLD = 35
|
||||||
|
|
||||||
|
# Texts
|
||||||
QUESTION_PROMPT = "? "
|
QUESTION_PROMPT = "? "
|
||||||
|
KNOCKED_COLD = "{loser} IS KNOCKED COLD AND {winner} IS THE WINNER AND CHAMP"
|
||||||
|
|
||||||
|
|
||||||
|
def get_vulnerability() -> int:
|
||||||
|
print("WHAT IS HIS VULNERABILITY", end=QUESTION_PROMPT)
|
||||||
|
vulnerability = int(input())
|
||||||
|
return vulnerability
|
||||||
|
|
||||||
|
|
||||||
|
def get_opponent_stats() -> Tuple[int, int]:
|
||||||
|
opponent_best = 0
|
||||||
|
opponent_weakness = 0
|
||||||
|
while opponent_best == opponent_weakness:
|
||||||
|
opponent_best = random.randint(1, 4)
|
||||||
|
opponent_weakness = random.randint(1, 4)
|
||||||
|
return opponent_best, opponent_weakness
|
||||||
|
|
||||||
|
|
||||||
def play() -> None:
|
def play() -> None:
|
||||||
@@ -10,15 +73,6 @@ def play() -> None:
|
|||||||
print("\n\n")
|
print("\n\n")
|
||||||
print("BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)")
|
print("BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)")
|
||||||
|
|
||||||
opponent_score = 0
|
|
||||||
player_score = 0
|
|
||||||
|
|
||||||
opponent_damage = 0
|
|
||||||
player_damage = 0
|
|
||||||
|
|
||||||
opponent_knockedout = False
|
|
||||||
player_knockedout = False
|
|
||||||
|
|
||||||
print("WHAT IS YOUR OPPONENT'S NAME", end=QUESTION_PROMPT)
|
print("WHAT IS YOUR OPPONENT'S NAME", end=QUESTION_PROMPT)
|
||||||
opponent_name = input()
|
opponent_name = input()
|
||||||
print("WHAT IS YOUR MAN'S NAME", end=QUESTION_PROMPT)
|
print("WHAT IS YOUR MAN'S NAME", end=QUESTION_PROMPT)
|
||||||
@@ -27,147 +81,164 @@ def play() -> None:
|
|||||||
print("DIFFERENT PUNCHES ARE 1 FULL SWING 2 HOOK 3 UPPERCUT 4 JAB")
|
print("DIFFERENT PUNCHES ARE 1 FULL SWING 2 HOOK 3 UPPERCUT 4 JAB")
|
||||||
print("WHAT IS YOUR MAN'S BEST", end=QUESTION_PROMPT)
|
print("WHAT IS YOUR MAN'S BEST", end=QUESTION_PROMPT)
|
||||||
player_best = int(input()) # noqa: TODO - this likely is a bug!
|
player_best = int(input()) # noqa: TODO - this likely is a bug!
|
||||||
|
player_weakness = get_vulnerability()
|
||||||
print("WHAT IS HIS VULNERABILITY", end=QUESTION_PROMPT)
|
player = Player(
|
||||||
player_weakness = int(input())
|
name=player_name,
|
||||||
|
best=player_best,
|
||||||
opponent_best = 0
|
weakness=player_weakness,
|
||||||
opponent_weakness = 0
|
is_computer=False,
|
||||||
while opponent_best == opponent_weakness:
|
hit_stats={
|
||||||
opponent_best = random.randint(1, 4)
|
1: HitStats( # FULL SWING
|
||||||
opponent_weakness = random.randint(1, 4)
|
choices=30,
|
||||||
|
threshold=10,
|
||||||
print(
|
hit_damage=15,
|
||||||
"{}'S ADVANTAGE is {} AND VULNERABILITY IS SECRET.".format(
|
block_damage=0,
|
||||||
opponent_name, opponent_weakness
|
pre_msg="{active.name} SWINGS AND",
|
||||||
)
|
hit_msg="HE CONNECTS!",
|
||||||
|
blocked_msg="HE MISSES",
|
||||||
|
),
|
||||||
|
2: HitStats( # HOOK
|
||||||
|
choices=2,
|
||||||
|
threshold=1,
|
||||||
|
hit_damage=7,
|
||||||
|
block_damage=0,
|
||||||
|
pre_msg="{active.name} GIVES THE HOOK...",
|
||||||
|
hit_msg="",
|
||||||
|
blocked_msg="",
|
||||||
|
),
|
||||||
|
3: HitStats( # UPPERCUT
|
||||||
|
choices=100,
|
||||||
|
threshold=50,
|
||||||
|
hit_damage=4,
|
||||||
|
block_damage=0,
|
||||||
|
pre_msg="{player_name} TRIES AN UPPERCUT",
|
||||||
|
hit_msg="AND HE CONNECTS!",
|
||||||
|
blocked_msg="AND IT'S BLOCKED (LUCKY BLOCK!)",
|
||||||
|
),
|
||||||
|
4: HitStats( # JAB
|
||||||
|
choices=8,
|
||||||
|
threshold=3,
|
||||||
|
hit_damage=3,
|
||||||
|
block_damage=0,
|
||||||
|
pre_msg="{active.name} JABS AT {passive.name}'S HEAD",
|
||||||
|
hit_msg="AND HE CONNECTS!",
|
||||||
|
blocked_msg="AND IT'S BLOCKED (LUCKY BLOCK!)",
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
for round in (1, 2, 3):
|
opponent_best, opponent_weakness = get_opponent_stats()
|
||||||
print(f"ROUND {round} BEGINS...\n")
|
opponent = Player(
|
||||||
if opponent_score >= 2 or player_score >= 2:
|
name=opponent_name,
|
||||||
break
|
best=opponent_best,
|
||||||
|
weakness=opponent_weakness,
|
||||||
|
is_computer=True,
|
||||||
|
hit_stats={
|
||||||
|
1: HitStats( # FULL SWING
|
||||||
|
choices=60,
|
||||||
|
threshold=29,
|
||||||
|
hit_damage=15,
|
||||||
|
block_damage=0,
|
||||||
|
knockout_possible=True,
|
||||||
|
pre_msg="{active.name} TAKES A FULL SWING",
|
||||||
|
hit_msg="AND POW!!!! HE HITS HIM RIGHT IN THE FACE!",
|
||||||
|
blocked_msg="BUT IT'S BLOCKED!",
|
||||||
|
),
|
||||||
|
2: HitStats( # HOOK
|
||||||
|
choices=1,
|
||||||
|
threshold=1,
|
||||||
|
hit_damage=12,
|
||||||
|
block_damage=0,
|
||||||
|
knockout_possible=True,
|
||||||
|
pre_msg="{active.name} GETS {passive.name} IN THE JAW (OUCH!)....AND AGAIN",
|
||||||
|
hit_msg="CONNECTS...",
|
||||||
|
blocked_msg="BUT IT'S BLOCKED!!!!!!!!!!!!!",
|
||||||
|
),
|
||||||
|
3: HitStats( # UPPERCUT
|
||||||
|
choices=200,
|
||||||
|
threshold=125,
|
||||||
|
hit_damage=8,
|
||||||
|
block_damage=5,
|
||||||
|
pre_msg="{passive.name} IS ATTACKED BY AN UPPERCUT (OH,OH)",
|
||||||
|
hit_msg="AND {active.name} CONNECTS...",
|
||||||
|
blocked_msg="{passive.name} BLOCKS AND HITS {active.name} WITH A HOOK",
|
||||||
|
),
|
||||||
|
4: HitStats( # JAB
|
||||||
|
choices=7,
|
||||||
|
threshold=3,
|
||||||
|
hit_damage=3,
|
||||||
|
block_damage=0,
|
||||||
|
pre_msg="{active.name} JABS AND",
|
||||||
|
hit_msg="BLOOD SPILLS !!!",
|
||||||
|
blocked_msg="AND IT'S BLOCKED (LUCKY BLOCK!)",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
for _action in range(7):
|
print(
|
||||||
if random.randint(1, 10) > 5:
|
f"{opponent.name}'S ADVANTAGE is {opponent.weakness} AND VULNERABILITY IS SECRET."
|
||||||
# opponent swings
|
)
|
||||||
punch = random.randint(1, 4)
|
|
||||||
|
|
||||||
if punch == player_weakness:
|
for round_number in (1, 2, 3):
|
||||||
player_damage += 2
|
play_round(round_number, player, opponent)
|
||||||
|
|
||||||
if punch == 1:
|
if player.knockedout:
|
||||||
print(f"{opponent_name} TAKES A FULL SWING AND", end=" ")
|
print(KNOCKED_COLD.format(loser=player.name, winner=opponent.name))
|
||||||
if player_weakness == 1 or random.randint(1, 60) < 30:
|
elif opponent.knockedout:
|
||||||
print("POW!!!! HE HITS HIM RIGHT IN THE FACE!")
|
print(KNOCKED_COLD.format(loser=opponent.name, winner=player.name))
|
||||||
if player_damage > 35:
|
elif opponent.score > player.score:
|
||||||
player_knockedout = True
|
print(f"{opponent.name} WINS (NICE GOING), {player.name}")
|
||||||
break
|
|
||||||
player_damage += 15
|
|
||||||
else:
|
|
||||||
print("BUT IT'S BLOCKED!")
|
|
||||||
elif punch == 2:
|
|
||||||
print(
|
|
||||||
"{} GETS {} IN THE JAW (OUCH!)".format(
|
|
||||||
opponent_name, player_name
|
|
||||||
),
|
|
||||||
end=" ",
|
|
||||||
)
|
|
||||||
player_damage += 7
|
|
||||||
print("....AND AGAIN")
|
|
||||||
if player_damage > 35:
|
|
||||||
player_knockedout = True
|
|
||||||
break
|
|
||||||
player_damage += 5
|
|
||||||
elif punch == 3:
|
|
||||||
print(f"{player_name} IS ATTACKED BY AN UPPERCUT (OH,OH)")
|
|
||||||
if player_weakness == 3 or random.randint(1, 200) > 75:
|
|
||||||
print(
|
|
||||||
"{} BLOCKS AND HITS {} WITH A HOOK".format(
|
|
||||||
player_name, opponent_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
opponent_damage += 5
|
|
||||||
else:
|
|
||||||
print(f"AND {opponent_name} CONNECTS...")
|
|
||||||
player_damage += 8
|
|
||||||
else:
|
|
||||||
print(f"{opponent_name} JABS AND", end=" ")
|
|
||||||
if player_weakness == 4 or random.randint(1, 7) > 4:
|
|
||||||
print("BLOOD SPILLS !!!")
|
|
||||||
player_damage += 3
|
|
||||||
else:
|
|
||||||
print("AND IT'S BLOCKED (LUCKY BLOCK!)")
|
|
||||||
else:
|
|
||||||
print(f"{player_name}'S PUNCH", end="? ")
|
|
||||||
punch = int(input())
|
|
||||||
|
|
||||||
if punch == opponent_weakness:
|
|
||||||
opponent_damage += 2
|
|
||||||
|
|
||||||
if punch == 1:
|
|
||||||
print(f"{player_name} SWINGS AND", end=" ")
|
|
||||||
if opponent_weakness == 1 or random.randint(1, 30) < 10:
|
|
||||||
print("HE CONNECTS!")
|
|
||||||
if opponent_damage > 35:
|
|
||||||
opponent_knockedout = True
|
|
||||||
break
|
|
||||||
opponent_damage += 15
|
|
||||||
else:
|
|
||||||
print("HE MISSES")
|
|
||||||
elif punch == 2:
|
|
||||||
print(f"{player_name} GIVES THE HOOK...", end=" ")
|
|
||||||
if opponent_weakness == 2 or random.randint(1, 2) == 1:
|
|
||||||
print("CONNECTS...")
|
|
||||||
opponent_damage += 7
|
|
||||||
else:
|
|
||||||
print("BUT IT'S BLOCKED!!!!!!!!!!!!!")
|
|
||||||
elif punch == 3:
|
|
||||||
print(f"{player_name} TRIES AN UPPERCUT", end=" ")
|
|
||||||
if opponent_weakness == 3 or random.randint(1, 100) < 51:
|
|
||||||
print("AND HE CONNECTS!")
|
|
||||||
opponent_damage += 4
|
|
||||||
else:
|
|
||||||
print("AND IT'S BLOCKED (LUCKY BLOCK!)")
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
f"{player_name} JABS AT {opponent_name}'S HEAD",
|
|
||||||
end=" ",
|
|
||||||
)
|
|
||||||
if opponent_weakness == 4 or random.randint(1, 8) < 4:
|
|
||||||
print("AND HE CONNECTS!")
|
|
||||||
opponent_damage += 3
|
|
||||||
else:
|
|
||||||
print("AND IT'S BLOCKED (LUCKY BLOCK!)")
|
|
||||||
|
|
||||||
if player_knockedout or opponent_knockedout:
|
|
||||||
break
|
|
||||||
elif player_damage > opponent_damage:
|
|
||||||
print(f"{opponent_name} WINS ROUND {round}")
|
|
||||||
opponent_score += 1
|
|
||||||
else:
|
|
||||||
print(f"{player_name} WINS ROUND {round}")
|
|
||||||
player_score += 1
|
|
||||||
|
|
||||||
if player_knockedout:
|
|
||||||
print(
|
|
||||||
"{} IS KNOCKED COLD AND {} IS THE WINNER AND CHAMP".format(
|
|
||||||
player_name, opponent_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif opponent_knockedout:
|
|
||||||
print(
|
|
||||||
"{} IS KNOCKED COLD AND {} IS THE WINNER AND CHAMP".format(
|
|
||||||
opponent_name, player_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif opponent_score > player_score:
|
|
||||||
print(f"{opponent_name} WINS (NICE GOING), {player_name}")
|
|
||||||
else:
|
else:
|
||||||
print(f"{player_name} AMAZINGLY WINS")
|
print(f"{player.name} AMAZINGLY WINS")
|
||||||
|
|
||||||
print("\n\nAND NOW GOODBYE FROM THE OLYMPIC ARENA.")
|
print("\n\nAND NOW GOODBYE FROM THE OLYMPIC ARENA.")
|
||||||
|
|
||||||
|
|
||||||
|
def is_opponents_turn() -> bool:
|
||||||
|
return random.randint(1, 10) > 5
|
||||||
|
|
||||||
|
|
||||||
|
def play_round(round_number: int, player: Player, opponent: Player) -> None:
|
||||||
|
print(f"ROUND {round_number} BEGINS...\n")
|
||||||
|
if opponent.score >= 2 or player.score >= 2:
|
||||||
|
return
|
||||||
|
|
||||||
|
for _action in range(7):
|
||||||
|
if is_opponents_turn():
|
||||||
|
punch = opponent.get_punch_choice()
|
||||||
|
active = opponent
|
||||||
|
passive = player
|
||||||
|
else:
|
||||||
|
punch = player.get_punch_choice()
|
||||||
|
active = player
|
||||||
|
passive = opponent
|
||||||
|
|
||||||
|
# Load the hit characteristics of the current player's punch
|
||||||
|
hit_stats = active.hit_stats[punch]
|
||||||
|
|
||||||
|
if punch == passive.weakness:
|
||||||
|
passive.damage += 2
|
||||||
|
|
||||||
|
print(hit_stats.pre_msg.format(active=active, passive=passive), end=" ")
|
||||||
|
if passive.weakness == punch or hit_stats.is_hit():
|
||||||
|
print(hit_stats.hit_msg.format(active=active, passive=passive))
|
||||||
|
if hit_stats.knockout_possible and passive.damage > KNOCKOUT_THRESHOLD:
|
||||||
|
passive.knockedout = True
|
||||||
|
break
|
||||||
|
passive.damage += hit_stats.hit_damage
|
||||||
|
else:
|
||||||
|
print(hit_stats.blocked_msg.format(active=active, passive=passive))
|
||||||
|
active.damage += hit_stats.block_damage
|
||||||
|
|
||||||
|
if player.knockedout or opponent.knockedout:
|
||||||
|
return
|
||||||
|
elif player.damage > opponent.damage:
|
||||||
|
print(f"{opponent.name} WINS ROUND {round_number}")
|
||||||
|
opponent.score += 1
|
||||||
|
else:
|
||||||
|
print(f"{player.name} WINS ROUND {round_number}")
|
||||||
|
player.score += 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
play()
|
play()
|
||||||
|
|||||||
62
15_Boxing/python/test_boxing.py
Normal file
62
15_Boxing/python/test_boxing.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
from _pytest.capture import CaptureFixture
|
||||||
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
|
|
||||||
|
from boxing import play
|
||||||
|
|
||||||
|
|
||||||
|
def test_boxing_bad_opponent(
|
||||||
|
monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setattr("boxing.Player.get_punch_choice", lambda self: 1)
|
||||||
|
monkeypatch.setattr("boxing.get_opponent_stats", lambda: (2, 1))
|
||||||
|
monkeypatch.setattr("boxing.is_opponents_turn", lambda: False)
|
||||||
|
|
||||||
|
opponent = "Anna"
|
||||||
|
my_man = "Bob"
|
||||||
|
strength = "1"
|
||||||
|
weakness = "2"
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"sys.stdin",
|
||||||
|
io.StringIO(f"{opponent}\n{my_man}\n{strength}\n{weakness}\n1\n1\n1"),
|
||||||
|
)
|
||||||
|
play()
|
||||||
|
actual = capsys.readouterr().out
|
||||||
|
expected = """BOXING
|
||||||
|
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)
|
||||||
|
WHAT IS YOUR OPPONENT'S NAME? WHAT IS YOUR MAN'S NAME? DIFFERENT PUNCHES ARE 1 FULL SWING 2 HOOK 3 UPPERCUT 4 JAB
|
||||||
|
WHAT IS YOUR MAN'S BEST? WHAT IS HIS VULNERABILITY? Anna'S ADVANTAGE is 1 AND VULNERABILITY IS SECRET.
|
||||||
|
ROUND 1 BEGINS...
|
||||||
|
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob WINS ROUND 1
|
||||||
|
ROUND 2 BEGINS...
|
||||||
|
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob SWINGS AND HE CONNECTS!
|
||||||
|
Bob WINS ROUND 2
|
||||||
|
ROUND 3 BEGINS...
|
||||||
|
|
||||||
|
Bob AMAZINGLY WINS
|
||||||
|
|
||||||
|
|
||||||
|
AND NOW GOODBYE FROM THE OLYMPIC ARENA.
|
||||||
|
"""
|
||||||
|
assert actual.split("\n") == expected.split("\n")
|
||||||
Reference in New Issue
Block a user