Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sequence strategy #40

Merged
merged 1 commit into from
Aug 20, 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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# RPS Sim

A small console program that 'simulates' games of rock paper scissors.
A small console program that simulates games of rock paper scissors.

## Installing

Expand Down Expand Up @@ -39,10 +39,16 @@ Once installed, you should be able to run it with:
$ python ./rps-sim.py
```

You can specify the number of games that should be played via the `--games` argument, e.g.:
You can specify the number of games that should be played via the `--games` option, e.g.:
```shell
$ python ./rps-sim.py --games 7
```

You can specify the strategy that a player will use via the `--p1-strategy`/`--p2-strategy` option, e.g.:
```shell
$ python ./rps-sim.py --p1-strategy 'sequence'
```

Run `python ./rps-sim.py --help` for a complete list of options

## Tests
Expand Down
23 changes: 19 additions & 4 deletions rps-sim.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Literal
import click
from rps.player import BotPlayer
from rps.play import human_readable
from rps.strategy import RandomPlayStrategy
from rps.strategy import get_strategy, Strategy
from rps.game import play_games


Expand All @@ -14,9 +15,23 @@
)
@click.option("--p1-name", default="Player 1", help="Name of first player")
@click.option("--p2-name", default="Player 2", help="Name of second player")
def simple_sim(games: int, p1_name: str, p2_name: str) -> None:
player1 = BotPlayer(RandomPlayStrategy(), p1_name)
player2 = BotPlayer(RandomPlayStrategy(), p2_name)
@click.option(
"--p1-strategy",
default=Strategy.RANDOM,
type=click.Choice([Strategy.RANDOM, Strategy.SEQUENCE]),
help="Strategy for player 1",
)
@click.option(
"--p2-strategy",
default=Strategy.RANDOM,
type=click.Choice([Strategy.RANDOM, Strategy.SEQUENCE]),
help="Strategy for player 2",
)
def simple_sim(
games: int, p1_name: str, p2_name: str, p1_strategy: Strategy, p2_strategy: Strategy
) -> None:
player1 = BotPlayer(get_strategy(p1_strategy), p1_name)
player2 = BotPlayer(get_strategy(p2_strategy), p2_name)
results = play_games(player1, player2, games)

for result in results:
Expand Down
28 changes: 28 additions & 0 deletions rps/strategy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import abc
from random import randrange
from typing import Any, Sequence
from .play import Play
from enum import auto, StrEnum


class PlaySelectionStrategy(object):
Expand All @@ -11,6 +13,32 @@ def play(self) -> Play:
"""Required method"""


class Strategy(StrEnum):
RANDOM = auto()
SEQUENCE = auto()


class RandomPlayStrategy(PlaySelectionStrategy):
def play(self) -> Play:
return Play(randrange(Play.min(), Play.max() + 1))


default_sequence = [Play.ROCK, Play.PAPER, Play.SCISSORS]


class SequencePlayStrategy(PlaySelectionStrategy):
def __init__(self, sequence: Sequence[Play] = default_sequence):
self._sequence = sequence if sequence is not None else default_sequence
self._idx = 0

def play(self) -> Play:
item = self._sequence[self._idx]
self._idx = (self._idx + 1) % len(self._sequence)
return item


def get_strategy(strategy: Strategy, args: Any = None) -> PlaySelectionStrategy:
if strategy is Strategy.RANDOM:
return RandomPlayStrategy()
elif strategy is Strategy.SEQUENCE:
return SequencePlayStrategy(sequence=args)
58 changes: 56 additions & 2 deletions test/test_strategy.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,62 @@
from rps.strategy import RandomPlayStrategy
from rps.strategy import (
RandomPlayStrategy,
SequencePlayStrategy,
Strategy,
get_strategy,
)
from rps.play import Play


class TestStrategy:
def test_RandomPlayStrategy_returns_play(self):
def test_RandomPlayStrategy_returns_play(self) -> None:
sut = RandomPlayStrategy()
assert type(sut.play()) is Play

def test_SequencePlayStrategy_returns_sequence(self) -> None:
sequence = [Play.PAPER, Play.ROCK, Play.SCISSORS]
sut = SequencePlayStrategy(sequence)
first_play = sut.play()

assert first_play == sequence[0]
assert sut.play() == sequence[1]
assert sut.play() == sequence[2]

def test_SequencePlayStrategy_repeats_sequence_on_exhaustion(self) -> None:
sequence = [Play.PAPER, Play.ROCK]
sut = SequencePlayStrategy(sequence)
first_play = sut.play()

assert first_play == sequence[0]
assert sut.play() == sequence[1]
assert sut.play() == sequence[0]
assert sut.play() == sequence[1]
assert sut.play() == sequence[0]
assert sut.play() == sequence[1]

def test_SequencePlayStrategy_has_default_sequence(self):
sut = SequencePlayStrategy()
first_play = sut.play()
assert first_play is not None
second_play = sut.play()
assert second_play is not None
assert second_play != first_play
third_play = sut.play()
assert third_play is not None
assert third_play != second_play
assert third_play != first_play

def test_get_strategy_returns_strategy(self):
returned = get_strategy(Strategy.RANDOM)
assert type(returned) is RandomPlayStrategy

def test_get_strategy_returns_strategy_with_args(self):
returned = get_strategy(Strategy.SEQUENCE, args=[Play.ROCK, Play.SCISSORS])
assert type(returned) is SequencePlayStrategy
first_play = returned.play()
assert first_play == Play.ROCK

def test_get_strategy_returns_strategy_with_args_when_defer_defaults(self):
returned = get_strategy(Strategy.SEQUENCE)
assert type(returned) is SequencePlayStrategy
first_play = returned.play()
assert first_play == Play.ROCK