Last active
November 7, 2021 06:09
-
-
Save Pagliacii/7969cd9edc4a93b79d7428bd93e44283 to your computer and use it in GitHub Desktop.
An easy action adventure game based on text.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding:utf-8 -*- | |
"""An easy action adventure game based on text.""" | |
import random | |
import time | |
import uuid | |
from typing import Dict, Generator, List, Protocol | |
class WeaponBehavior(Protocol): | |
"""A behavior that a weapon could be performed.""" | |
attack: int | |
article: str | |
name: str | |
def use_weapon(self, enemy: "Character") -> None: | |
"""Uses a weapon to attack an enemy""" | |
print(f"Attacking with {self.article} {self.name}: -{self.attack}") | |
enemy.hurt(self.attack) | |
class KnifeBehavior(WeaponBehavior): | |
"""A knife""" | |
attack = 5 | |
article = "a" | |
name = "knife" | |
class BowAndArrowBehavior(WeaponBehavior): | |
"""A bow and arrows""" | |
attack = 8 | |
article = "a" | |
name = "bow and arrows" | |
class AxeBehavior(WeaponBehavior): | |
"""An axe""" | |
attack = 15 | |
article = "an" | |
name = "axe" | |
class SwordBehavior(WeaponBehavior): | |
"""A sword""" | |
attack = 10 | |
article = "a" | |
name = "sword" | |
class Character(Protocol): | |
"""A game character""" | |
cid: str | |
weapon: WeaponBehavior | |
name: str | |
health: int = 10 | |
speed: int | |
shield: int | |
_hit_rate: float | |
_dodge_rate: float | |
def __new__(cls): | |
cls.cid = uuid.uuid4().hex | |
cls._hit_rate = random.uniform(0.08, 1.0) | |
cls._dodge_rate = random.uniform(0.0, 0.2) | |
return super().__new__(cls) | |
def fight(self, enemy: "Character") -> None: | |
"""Fights an enemy with a weapon""" | |
if random.random() <= self._hit_rate: | |
print(f"{self.name}'s attack is missing!") | |
return | |
self.weapon.use_weapon(enemy) | |
def hurt(self, attack) -> None: | |
"""Hurts by an attack""" | |
if random.random() <= self._dodge_rate: | |
print(f"{self.name} is dodging!") | |
return | |
self.health -= attack - self.shield | |
print( | |
f"Hurt > {self.name}: Health = {self.health}, Shield = {self.shield}" | |
) | |
def death(self) -> bool: | |
"""This character is dead.""" | |
return self.health <= 0 | |
class King(Character): | |
"""The king of a kingdom""" | |
name = "King" | |
health = 10 | |
speed = 8 | |
shield = 8 | |
weapon = BowAndArrowBehavior() | |
class Queen(Character): | |
"""The queen""" | |
name = "Queen" | |
health = 8 | |
speed = 10 | |
shield = 8 | |
weapon = KnifeBehavior() | |
class Knight(Character): | |
"""The knight""" | |
name = "Knight" | |
health = 12 | |
speed = 10 | |
shield = 8 | |
weapon = SwordBehavior() | |
class Troll(Character): | |
"""The troll""" | |
name = "Troll" | |
health = 32 | |
speed = 6 | |
shield = 2 | |
weapon = AxeBehavior() | |
class Goblin(Character): | |
"""The goblin""" | |
name = "Goblin" | |
health = 6 | |
speed = 12 | |
shield = 4 | |
weapon = KnifeBehavior() | |
class Game(object): | |
"""The game""" | |
_heroes: Dict[str, Character] = { | |
c.cid: c for c in (King(), Queen(), Knight()) | |
} | |
_enemies: Dict[str, Character] = {c.cid: c for c in (Troll(), Goblin())} | |
_round: int = 0 | |
_round_ticks: int = 16 | |
_wait_time: float = 1.0 | |
_finished: bool = False | |
def all_characters(self) -> Generator[Character, None, None]: | |
"""A generator to yield all characters""" | |
characters: List[Character] = [] | |
characters.extend(self._heroes.values()) | |
characters.extend(self._enemies.values()) | |
characters.sort(key=lambda c: c.speed, reverse=True) | |
for character in characters: | |
yield character | |
def run(self) -> None: | |
"""Let's play the game.""" | |
random.seed() | |
print("Fight!!!") | |
while True: | |
self._round += 1 | |
print("=" * 16 + f"Round {self._round}" + "=" * 16) | |
self.activate_round() | |
if self._finished: | |
break | |
time.sleep(self._wait_time) | |
print("=" * 39) | |
print("Game over.") | |
def attack_enemy(self, character: Character) -> Character: | |
"""Controls the character to attack a random enemy | |
Args: | |
character (Character): Which one will do an attack. | |
Returns: | |
Character: which enemy has been attacked. | |
""" | |
enemy: Character = random.choice( | |
list( | |
( | |
self._enemies | |
if character.cid in self._heroes | |
else self._heroes | |
).values() | |
) | |
) | |
print(f"{character.name} turn") | |
character.fight(enemy) | |
return enemy | |
def activate_round(self): | |
"""Activates a round""" | |
tick: int = self._round_ticks | |
while not self._finished and tick > 0: | |
for character in self.all_characters(): | |
if character.speed != tick: | |
continue | |
enemy: Character = self.attack_enemy(character) | |
if enemy.death(): | |
print(f"{enemy.name} is dead.") | |
self._heroes.pop(enemy.cid, None) | |
self._enemies.pop(enemy.cid, None) | |
if not (self._heroes and self._enemies): | |
self._finished = True | |
if self._heroes: | |
print("Heroes win.") | |
else: | |
print("Enemies win.") | |
break | |
tick -= 1 | |
if __name__ == "__main__": | |
game = Game() | |
game.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: