Skip to content
This repository was archived by the owner on Jul 2, 2024. It is now read-only.

WRW Update #41

Merged
merged 7 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ packages = [
{ include = "sequences", from = "src" },
{ include = "sorting", from = "src" },
{ include = "primes", from = "src" },
{ include = "wrw_game", from = "src" },
{ include = "wtk", from = "src" },
]

[tool.poetry.dependencies]
Expand All @@ -58,7 +58,7 @@ pylint = "^2.15.5"
tox = "^4.0"

[tool.poetry.scripts]
wrw_game = "wrw_game.__main__:run"
wtk = "wtk.__main__:run"

[tool.pytest.ini_options]
addopts = "--cov=src --verbose"
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/wrw_game/__main__.py → src/wtk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import logging

from wrw_game import engine
from wrw_game.loggers import stream_handler
from wtk import engine
from wtk.loggers import stream_handler

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
Expand Down
119 changes: 50 additions & 69 deletions src/wrw_game/challenge.rst → src/wtk/challenge.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
###############################################################################
WARRIORS, ROBBERS AND WIZARDS GAME
Wizards, Thiefs and Knights
###############################################################################

Warriors, robbers and wizards (WRW) game is a "Paper, Rock and Scissors" clone,
"Wizards, Thiefs and Knights" (WTK) game is a "Paper, Rock and Scissors" clone,
but in a fantasy setting. It comes with a simple command line interface where
the use must type in his or her choice. The enemy is controlled by the script.
The player's goal is to gain as many score points, as it possible.

*****************
Code Organization
Code organization
*****************

Use separate modules to maintain your code base. For example:
Expand All @@ -22,25 +22,25 @@ Use separate modules to maintain your code base. For example:
|-- settings.py

******************************
General Playground Description
General playground description
******************************

The game process is divided into rounds. Each round consists of **attack** and
**defence** stages. Rounds are repeated, until player is defeated.

Fight Rules
Fight rules
===========

It's simple...

- **Warrior** beats **Robber**
- **Robber** beats **Wizard**
- **Wizard** beats **Warrior**
- **Knight** beats **Thief**
- **Thief** beats **Wizard**
- **Wizard** beats **Knight**

Attack Stage
Attack stage
============

Player selects the choice to attack from **warrior**, **robber** or **wizard**,
Player selects the choice to attack from **knight**, **thief** or **wizard**,
enemy selects the choice for defence from the same options by random. If the
attack is successful:

Expand All @@ -53,10 +53,10 @@ In case enemy is defeated:
- player gains some extra score points
- next defence stage is skipped, and player attacks again

Defence Stage
Defence stage
=============

Player selects the choice to defend from **warrior**, **robber** or **wizard**,
Player selects the choice to defend from **knight**, **thief** or **wizard**,
enemy selects the choice to attack from the same options by random. If the
attack is successful:

Expand All @@ -71,100 +71,76 @@ If player is defeated:
Exceptions
**********

Enemy Down
Enemy down
==========

This is an exceptional scenario when enemy is defeated. A custom exception
``EnemyDown`` should be used to track these cases. Exception should provide
the details on the enemy's instance, especially its level.

Game Over
.. autoclass:: wtk.exceptions.EnemyDown

Game over
=========

This is an exceptional scenario when player is defeated. A custom exception
``GameOver`` should be used to track these cases. Exception should provide
the details on the player's instance, especially its score points.

.. autoclass:: wtk.exceptions.GameOver

******
Models
******

Enemy
=====

Represents the playing enemy-bot. All choices made by this model are random.
The model should implement methods:
.. autoclass:: wtk.models.Enemy
:members: decrease_health, select_attack, select_defence
:special-members: __init__

:``__init__``:
Initialize enemy instance. Initializer should receive one argument of
integer type - ``level: int``. Health points value should be set equal
to level value.
You are free to implement other methods you like, if needed.

:``descrease_health``:
Method decreases the health points value by 1 (one). If this value becomes
less that 1 (one) the ``EnemyDown`` exception is raised.
Player
======

:``select_attack``:
Return a random attack choice from valid choices.
.. autoclass:: wtk.models.Player
:members: decrease_health, select_attack, select_defence,
fight, attack, defence
:special-members: __init__

:``select_defence``:
Return a random defence choice from valid choices.
********
Settings
********

You are free to implement other methods you like, if needed.
Settings module contains constants values for the game.

Player
======
For example,

This model is controlled by the user. It represents a playing user. All choices
are controlled by the user. The model should implement methods:
.. py:data:: INITIAL_PLAYER_NAME

:``__init__``:
Initialize player instance. Initializer should receive player's name as
an argument - ``name: str``. Health points are to be set from settings.
Score points should be initialized with 0 (zero).
Initial health meter value for a player instance

:``decrease_health``:
Method decreases the health points value by 1 (one). If this value becomes
less that 1 (one) the ``GameOver`` exception is raised.
:type: int

:``select_attack``:
Return a fight choice made by the user. Performs choice validation.
.. py:data:: INITIAL_ENEMY_LEVEL

:``select_defence``:
Return a fight choice made by the user. Performs choice validation.
Indicates the level to initialize the first enemy instance.

:``fight``:
Static method to perform a fight. Takes two arguments representing attack
and defence choices. Performs fight result calculation and return it back.
:type: int

:``attack``:
Perform attack on an enemy instance. This method takes an enemy instance as
an argument. After that, it takes attack choice from the player model and
the defence choice from an enemy model. After fight result calculation
required operation are to be performed (decrease enemy health, assign
score points etc.). Based on fight result should print out a message:
.. py:data:: SCORE_SUCCESS_ATTACK

- ``"YOUR ATTACK IS SUCCESSFUL!"``
- ``"YOUR ATTACK IS FAILED!"``
- ``IT'S A DRAW!"``
Set the score value to assign when an attack is successful

:``defence``:
Perform defence from an enemy attack. This method takes an enemy instance
as an argument. After that, it takes defence choice from the player model
and the attack choice from an enemy model. After fight result calculation
required operation are to be performed (decrease player health). Based on
fight result should print out a message:
:type: int

- ``"YOUR DEFENCE IS SUCCESSFUL!"``
- ``"YOUR DEFENCE IS FAILED!"``
- ``IT'S A DRAW!"``
.. py:data:: SCORE_ENEMY_DOWN

********
Settings
********
Set the score value to assign when an enemy is defeated

**settings.py** module contains constants values for the game (e.g.
``INITIAL_PLAYER_HEALTH = 5``).
:type: int

******
Engine
Expand All @@ -182,6 +158,8 @@ Asks the user to type in his or her name and return it back.
Leading and trailing whitespaces are to be trimmed.
Name should contain at least one character.

.. autofunction:: wtk.engine.get_player_name

Play
====

Expand All @@ -194,6 +172,8 @@ terminal.
``KeyboardInterrupt`` should be handled as well - it's behavior is similar
to "Game Over" event, but "game over" message should be omitted.

.. autofunction:: wtk.engine.play

*********************
Optional Enhancements
*********************
Expand All @@ -205,3 +185,4 @@ Optional Enhancements
AVAILABLE MENU CHOICES: PLAY, SCORES, EXIT
TYPE YOUR CHOICE HERE:

#. Store score table to the database instead of using text file.
28 changes: 21 additions & 7 deletions src/wrw_game/engine.py → src/wtk/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@

import logging

from wrw_game.exceptions import EnemyDown, GameOver
from wrw_game.loggers import stream_handler
from wrw_game.models import Enemy, Player
from wtk.exceptions import EnemyDown, GameOver
from wtk.loggers import stream_handler
from wtk.models import Enemy, Player

logger = logging.getLogger("engine")
logger.setLevel(logging.INFO)
logger.addHandler(stream_handler)


def get_player_name() -> str:
"""Return a player's name from the user prompt"""
"""
Return a player's name from the user prompt

A validation process is performed as well. The player name cannot be
an empty string.

:return: a player defined name

"""

player_name: str = ""

Expand All @@ -26,7 +34,14 @@ def get_player_name() -> str:


def play() -> None:
"""Play the game"""
"""
Run the game

The function initializes player and enemy instances.
After that it runs the game process in an endless loop.
Once the player is defeated - it stops the execution.

"""

player_name = get_player_name()
player = Player(player_name)
Expand All @@ -40,9 +55,8 @@ def play() -> None:
logger.info(exc)
enemy = Enemy(enemy.level + 1)
except GameOver as exc:
msg_score_points = f"SCORE POINTS: {player.score}"
logger.info(exc)
logger.info(msg_score_points)
logger.info("SCORE POINTS: %s", player.score)
break


Expand Down
17 changes: 9 additions & 8 deletions src/wrw_game/enums.py → src/wtk/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def __str__(self) -> str:
class FightChoice(enum.Enum):
"""Fight choice enumeration model"""

WARRIOR = enum.auto()
ROBBER = enum.auto()
KNIGHT = enum.auto()
THIEF = enum.auto()
WIZARD = enum.auto()

def __str__(self) -> str:
Expand Down Expand Up @@ -58,20 +58,21 @@ def get_fight_result(attack: FightChoice, defence: FightChoice) -> FightResult:

# perform type validation
if not isinstance(attack, FightChoice) or \
not isinstance(defence, FightChoice):
not isinstance(defence, FightChoice):
attack_cls = attack.__class__.__name__
defence_cls = defence.__class__.__name__
raise TypeError(
f"unsupported argument type(s): "
f"'{attack.__class__.__name__}' and '{defence.__class__.__name__}'"
f"unsupported argument type(s): '{attack_cls}' and '{defence_cls}'"
)

# calculate result
if attack == defence:
return FightResult.DRAW

successful_attacks = (
(FightChoice.WARRIOR, FightChoice.ROBBER),
(FightChoice.ROBBER, FightChoice.WIZARD),
(FightChoice.WIZARD, FightChoice.WARRIOR),
(FightChoice.KNIGHT, FightChoice.THIEF),
(FightChoice.THIEF, FightChoice.WIZARD),
(FightChoice.WIZARD, FightChoice.KNIGHT),
)
if (attack, defence) in successful_attacks:
return FightResult.SUCCESS
Expand Down
7 changes: 3 additions & 4 deletions src/wrw_game/exceptions.py → src/wtk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

if TYPE_CHECKING:
# noinspection PyProtectedMember
from wrw_game.models import _AbstractModel
from wtk.models import _AbstractModel # pragma: no cover

__all__ = ["EnemyDown", "GameOver"]


class _GameModelException(Exception):
Expand All @@ -32,6 +34,3 @@ class EnemyDown(_GameModelException):

class GameOver(_GameModelException):
"""Raised when a player is defeated"""


__all__ = ["EnemyDown", "GameOver"]
File renamed without changes.
Loading