From 5de6c7dc16a496ec43662196ef0f88875e08d173 Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Wed, 23 Mar 2022 12:13:58 +0100 Subject: [PATCH 1/4] Boxing (Python): Split configuration from logic --- 15_Boxing/python/boxing.py | 353 +++++++++++++++++++------------- 15_Boxing/python/test_boxing.py | 62 ++++++ 2 files changed, 274 insertions(+), 141 deletions(-) create mode 100644 15_Boxing/python/test_boxing.py diff --git a/15_Boxing/python/boxing.py b/15_Boxing/python/boxing.py index 2f38ddd0..15754326 100755 --- a/15_Boxing/python/boxing.py +++ b/15_Boxing/python/boxing.py @@ -1,7 +1,70 @@ #!/usr/bin/env python3 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 = "? " +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: @@ -10,15 +73,6 @@ def play() -> None: print("\n\n") 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) opponent_name = input() 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("WHAT IS YOUR MAN'S BEST", end=QUESTION_PROMPT) player_best = int(input()) # noqa: TODO - this likely is a bug! - - print("WHAT IS HIS VULNERABILITY", end=QUESTION_PROMPT) - player_weakness = int(input()) - - opponent_best = 0 - opponent_weakness = 0 - while opponent_best == opponent_weakness: - opponent_best = random.randint(1, 4) - opponent_weakness = random.randint(1, 4) - - print( - "{}'S ADVANTAGE is {} AND VULNERABILITY IS SECRET.".format( - opponent_name, opponent_weakness - ) + player_weakness = get_vulnerability() + player = Player( + name=player_name, + best=player_best, + weakness=player_weakness, + is_computer=False, + hit_stats={ + 1: HitStats( # FULL SWING + choices=30, + threshold=10, + hit_damage=15, + block_damage=0, + 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): - print(f"ROUND {round} BEGINS...\n") - if opponent_score >= 2 or player_score >= 2: - break + opponent_best, opponent_weakness = get_opponent_stats() + opponent = Player( + name=opponent_name, + 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): - if random.randint(1, 10) > 5: - # opponent swings - punch = random.randint(1, 4) + print( + f"{opponent.name}'S ADVANTAGE is {opponent.weakness} AND VULNERABILITY IS SECRET." + ) - if punch == player_weakness: - player_damage += 2 + for round_number in (1, 2, 3): + play_round(round_number, player, opponent) - if punch == 1: - print(f"{opponent_name} TAKES A FULL SWING AND", end=" ") - if player_weakness == 1 or random.randint(1, 60) < 30: - print("POW!!!! HE HITS HIM RIGHT IN THE FACE!") - if player_damage > 35: - player_knockedout = True - 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}") + if player.knockedout: + print(KNOCKED_COLD.format(loser=player.name, winner=opponent.name)) + elif opponent.knockedout: + print(KNOCKED_COLD.format(loser=opponent.name, winner=player.name)) + elif opponent.score > player.score: + print(f"{opponent.name} WINS (NICE GOING), {player.name}") else: - print(f"{player_name} AMAZINGLY WINS") + print(f"{player.name} AMAZINGLY WINS") 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__": play() diff --git a/15_Boxing/python/test_boxing.py b/15_Boxing/python/test_boxing.py new file mode 100644 index 00000000..200b4d6b --- /dev/null +++ b/15_Boxing/python/test_boxing.py @@ -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") From b074548e4277cf2f6edbc3a5fb1d5fea2f047f2b Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Wed, 23 Mar 2022 12:26:07 +0100 Subject: [PATCH 2/4] BUG: Fix best in Python boxing --- 15_Boxing/python/boxing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/15_Boxing/python/boxing.py b/15_Boxing/python/boxing.py index 15754326..c47c7279 100755 --- a/15_Boxing/python/boxing.py +++ b/15_Boxing/python/boxing.py @@ -23,9 +23,9 @@ class HitStats(NamedTuple): @dataclass class Player: name: str - best: int # TODO: This is never used! + best: int # this hit guarantees 2 damage on opponent + weakness: int # you're always hit when your opponent uses this punch 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] @@ -216,7 +216,7 @@ def play_round(round_number: int, player: Player, opponent: Player) -> None: # Load the hit characteristics of the current player's punch hit_stats = active.hit_stats[punch] - if punch == passive.weakness: + if punch == active.best: passive.damage += 2 print(hit_stats.pre_msg.format(active=active, passive=passive), end=" ") From 7e7e0b1a0c14cad804defbccf3ff7126fc8e5f58 Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Wed, 23 Mar 2022 21:26:24 +0100 Subject: [PATCH 3/4] Boxing (Python): Move configuration to JSON files --- 15_Boxing/python/boxing.py | 94 ++++---------------------- 15_Boxing/python/opponent-profile.json | 40 +++++++++++ 15_Boxing/python/player-profile.json | 38 +++++++++++ 15_Boxing/python/test_boxing.py | 1 - 4 files changed, 92 insertions(+), 81 deletions(-) create mode 100644 15_Boxing/python/opponent-profile.json create mode 100644 15_Boxing/python/player-profile.json diff --git a/15_Boxing/python/boxing.py b/15_Boxing/python/boxing.py index c47c7279..7b4c22d4 100755 --- a/15_Boxing/python/boxing.py +++ b/15_Boxing/python/boxing.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 +import json import random from dataclasses import dataclass -from typing import Tuple, Dict, NamedTuple, Literal +from pathlib import Path +from typing import Dict, Literal, NamedTuple, Tuple class HitStats(NamedTuple): @@ -47,7 +49,6 @@ class Player: KNOCKOUT_THRESHOLD = 35 -# Texts QUESTION_PROMPT = "? " KNOCKED_COLD = "{loser} IS KNOCKED COLD AND {winner} IS THE WINNER AND CHAMP" @@ -67,6 +68,15 @@ def get_opponent_stats() -> Tuple[int, int]: return opponent_best, opponent_weakness +def read_hit_stats(filepath: Path) -> Dict[Literal[1, 2, 3, 4], HitStats]: + with open(filepath) as f: + hit_stats_dict = json.load(f) + result = {} + for key, value in hit_stats_dict.items(): + result[int(key)] = HitStats(**value) + return result # type: ignore + + def play() -> None: print("BOXING") print("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") @@ -87,44 +97,7 @@ def play() -> None: best=player_best, weakness=player_weakness, is_computer=False, - hit_stats={ - 1: HitStats( # FULL SWING - choices=30, - threshold=10, - hit_damage=15, - block_damage=0, - 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!)", - ), - }, + hit_stats=read_hit_stats(Path(__file__).parent / "player-profile.json"), ) opponent_best, opponent_weakness = get_opponent_stats() @@ -133,46 +106,7 @@ def play() -> None: 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!)", - ), - }, + hit_stats=read_hit_stats(Path(__file__).parent / "opponent-profile.json"), ) print( diff --git a/15_Boxing/python/opponent-profile.json b/15_Boxing/python/opponent-profile.json new file mode 100644 index 00000000..83cc77d8 --- /dev/null +++ b/15_Boxing/python/opponent-profile.json @@ -0,0 +1,40 @@ +{ + "1": { + "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": { + "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": { + "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": { + "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!)" + } +} diff --git a/15_Boxing/python/player-profile.json b/15_Boxing/python/player-profile.json new file mode 100644 index 00000000..9272bfa0 --- /dev/null +++ b/15_Boxing/python/player-profile.json @@ -0,0 +1,38 @@ +{ + "1": { + "choices": 30, + "threshold": 10, + "hit_damage": 15, + "block_damage": 0, + "pre_msg": "{active.name} SWINGS AND", + "hit_msg": "HE CONNECTS!", + "blocked_msg": "HE MISSES" + }, + "2": { + "choices": 2, + "threshold": 1, + "hit_damage": 7, + "block_damage": 0, + "pre_msg": "{active.name} GIVES THE HOOK...", + "hit_msg": "CONNECTS...", + "blocked_msg": "BUT IT'S BLOCKED!!!!!!!!!!!!!" + }, + "3": { + "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": { + "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!)" + } +} diff --git a/15_Boxing/python/test_boxing.py b/15_Boxing/python/test_boxing.py index 200b4d6b..71b2a6a9 100644 --- a/15_Boxing/python/test_boxing.py +++ b/15_Boxing/python/test_boxing.py @@ -2,7 +2,6 @@ import io from _pytest.capture import CaptureFixture from _pytest.monkeypatch import MonkeyPatch - from boxing import play From 3d929fb9ab476a7207876e5ba4c86f5ec1d7454b Mon Sep 17 00:00:00 2001 From: Martin Thoma Date: Wed, 23 Mar 2022 21:36:24 +0100 Subject: [PATCH 4/4] Boxing (Python): Rename methods for clarity --- 15_Boxing/python/boxing.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/15_Boxing/python/boxing.py b/15_Boxing/python/boxing.py index 7b4c22d4..f1c2ae59 100755 --- a/15_Boxing/python/boxing.py +++ b/15_Boxing/python/boxing.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Dict, Literal, NamedTuple, Tuple -class HitStats(NamedTuple): +class PunchProfile(NamedTuple): choices: int threshold: int hit_damage: int @@ -30,7 +30,7 @@ class Player: is_computer: bool # for each of the 4 punch types, we have a probability of hitting - hit_stats: Dict[Literal[1, 2, 3, 4], HitStats] + punch_profiles: Dict[Literal[1, 2, 3, 4], PunchProfile] damage: int = 0 score: int = 0 @@ -68,12 +68,12 @@ def get_opponent_stats() -> Tuple[int, int]: return opponent_best, opponent_weakness -def read_hit_stats(filepath: Path) -> Dict[Literal[1, 2, 3, 4], HitStats]: +def read_punch_profiles(filepath: Path) -> Dict[Literal[1, 2, 3, 4], PunchProfile]: with open(filepath) as f: - hit_stats_dict = json.load(f) + punch_profile_dict = json.load(f) result = {} - for key, value in hit_stats_dict.items(): - result[int(key)] = HitStats(**value) + for key, value in punch_profile_dict.items(): + result[int(key)] = PunchProfile(**value) return result # type: ignore @@ -97,7 +97,9 @@ def play() -> None: best=player_best, weakness=player_weakness, is_computer=False, - hit_stats=read_hit_stats(Path(__file__).parent / "player-profile.json"), + punch_profiles=read_punch_profiles( + Path(__file__).parent / "player-profile.json" + ), ) opponent_best, opponent_weakness = get_opponent_stats() @@ -106,7 +108,9 @@ def play() -> None: best=opponent_best, weakness=opponent_weakness, is_computer=True, - hit_stats=read_hit_stats(Path(__file__).parent / "opponent-profile.json"), + punch_profiles=read_punch_profiles( + Path(__file__).parent / "opponent-profile.json" + ), ) print( @@ -148,21 +152,21 @@ def play_round(round_number: int, player: Player, opponent: Player) -> None: passive = opponent # Load the hit characteristics of the current player's punch - hit_stats = active.hit_stats[punch] + punch_profile = active.punch_profiles[punch] if punch == active.best: 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: + print(punch_profile.pre_msg.format(active=active, passive=passive), end=" ") + if passive.weakness == punch or punch_profile.is_hit(): + print(punch_profile.hit_msg.format(active=active, passive=passive)) + if punch_profile.knockout_possible and passive.damage > KNOCKOUT_THRESHOLD: passive.knockedout = True break - passive.damage += hit_stats.hit_damage + passive.damage += punch_profile.hit_damage else: - print(hit_stats.blocked_msg.format(active=active, passive=passive)) - active.damage += hit_stats.block_damage + print(punch_profile.blocked_msg.format(active=active, passive=passive)) + active.damage += punch_profile.block_damage if player.knockedout or opponent.knockedout: return