Skip to content

Commit

Permalink
feat: add sequence strategy
Browse files Browse the repository at this point in the history
Add a play strategy that iterates through a pre-defined sequence of plays.

It is available via command line option, though for now just uses the default sequence when specified via command line.
  • Loading branch information
AndrewADev committed Aug 20, 2023
1 parent 03ad373 commit 2d09a46
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 7 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
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

0 comments on commit 2d09a46

Please sign in to comment.