Skip to content

Commit

Permalink
Merge pull request #50 from lesteenman/feature-43-numeric-games
Browse files Browse the repository at this point in the history
Fixes #43: add an optional min or max to games while creating.
  • Loading branch information
lesteenman authored Apr 29, 2021
2 parents f356897 + 51c81bf commit cd52b26
Show file tree
Hide file tree
Showing 15 changed files with 329 additions and 43 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ DISCORD_APP_DIR=discord_app
ERROR_PARSER_DIR=error_parser_function
INFRA_DIR=infra

autopep:
python -m autopep8 --recursive --in-place --aggressive discord_app/eternal_guesses
python -m autopep8 --recursive --in-place --aggressive discord_app/tests
python -m autopep8 --recursive --in-place --aggressive error_parser_function/
python -m autopep8 --recursive --in-place --aggressive infra/app.py
python -m autopep8 --recursive --in-place --aggressive infra/infra/

quality:
cd ${DISCORD_APP_DIR} && poetry run flake8 --config ../.flake8 ./tests ./eternal_guesses ../error_parser_function ../infra

Expand Down
12 changes: 12 additions & 0 deletions bin/register_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ def register(config: Dict):
"type": COMMAND_OPTION_TYPE_STRING,
"required": False,
},
{
"name": "min",
"description": "The lowest guess allowed.",
"type": COMMAND_OPTION_TYPE_INTEGER,
"required": False,
},
{
"name": "max",
"description": "The highest guess allowed.",
"type": COMMAND_OPTION_TYPE_INTEGER,
"required": False,
},
],
},
{
Expand Down
8 changes: 7 additions & 1 deletion discord_app/eternal_guesses/model/data/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Game:
def __init__(self, guild_id: int = None, game_id: str = None, created_by: int = None, closed: bool = None,
guesses: Mapping[int, GameGuess] = None, channel_messages: List[ChannelMessage] = None,
create_datetime: datetime = None, close_datetime: Optional[datetime] = None, title: str = None,
description: str = None):
description: str = None, min_guess: int = None, max_guess: int = None):
self.guild_id = guild_id
self.game_id = game_id
self.title = title
Expand All @@ -22,8 +22,14 @@ def __init__(self, guild_id: int = None, game_id: str = None, created_by: int =
self.guesses = guesses
self.channel_messages = channel_messages

self.min_guess = min_guess
self.max_guess = max_guess

if channel_messages is None:
self.channel_messages = []

if guesses is None:
self.guesses = {}

def is_numeric(self):
return self.min_guess is not None or self.max_guess is not None
4 changes: 4 additions & 0 deletions discord_app/eternal_guesses/model/data/game_guess.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ def __init__(self, user_id: int = None, guess: str = None, nickname: str = None,
self.guess = guess
self.nickname = nickname
self.timestamp = timestamp

def __repr__(self):
return f"<GameGuess user_id={self.user_id}, guess={self.guess}, nickname={self.nickname}, " \
f"timestamp={self.timestamp}"
2 changes: 2 additions & 0 deletions discord_app/eternal_guesses/repositories/dynamodb_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class Meta:
guesses = UnicodeAttribute(null=True)
title = UnicodeAttribute(null=True)
description = UnicodeAttribute(null=True)
min_guess = NumberAttribute(null=True)
max_guess = NumberAttribute(null=True)

# GuildConfig
management_roles = ListAttribute(of=NumberAttribute, null=True)
Expand Down
51 changes: 29 additions & 22 deletions discord_app/eternal_guesses/repositories/games_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from pynamodb.exceptions import DoesNotExist

from eternal_guesses.model.data.game import Game
from eternal_guesses.model.data.channel_message import ChannelMessage
from eternal_guesses.model.data.game import Game
from eternal_guesses.model.data.game_guess import GameGuess
from eternal_guesses.repositories.dynamodb_models import EternalGuessesTable, ChannelMessageMap

Expand Down Expand Up @@ -43,17 +43,11 @@ def _game_from_model(model: EternalGuessesTable) -> Game:
if model.close_datetime is not None:
close_datetime = datetime.fromisoformat(model.close_datetime)

closed = False
if model.closed is not None:
closed = model.closed

title = ""
if model.title is not None:
title = model.title

description = ""
if model.description is not None:
description = model.description
closed = model.closed or False
title = model.title or ""
description = model.description or ""
min_guess = model.min_guess
max_guess = model.max_guess

channel_messages = []
if model.channel_messages is not None:
Expand All @@ -69,12 +63,14 @@ def _game_from_model(model: EternalGuessesTable) -> Game:
game_id=game_id,
title=title,
description=description,
min_guess=min_guess,
max_guess=max_guess,
created_by=model.created_by,
create_datetime=create_datetime,
close_datetime=close_datetime,
closed=closed,
channel_messages=channel_messages,
guesses=guesses
guesses=guesses,
)


Expand Down Expand Up @@ -142,22 +138,20 @@ def save(self, game: Game):
if game.description:
model.description = game.description

if game.min_guess:
model.min_guess = game.min_guess

if game.max_guess:
model.max_guess = game.max_guess

if game.create_datetime is not None:
model.create_datetime = game.create_datetime.isoformat()

if game.close_datetime is not None:
model.close_datetime = game.close_datetime.isoformat()

if game.guesses is not None:
guesses = {}
for user_id, game_guess in game.guesses.items():
guesses[user_id] = {
'user_id': game_guess.user_id,
'nickname': game_guess.nickname,
'guess': game_guess.guess,
'timestamp': game_guess.timestamp.isoformat(),
}
model.guesses = json.dumps(guesses)
model.guesses = json.dumps(self._guesses(game))

if game.channel_messages is not None:
channel_messages = []
Expand All @@ -166,3 +160,16 @@ def save(self, game: Game):
model.channel_messages = channel_messages

model.save()

def _guesses(self, game):
guesses = {}

for user_id, game_guess in game.guesses.items():
guesses[user_id] = {
'user_id': game_guess.user_id,
'nickname': game_guess.nickname,
'guess': game_guess.guess,
'timestamp': game_guess.timestamp.isoformat(),
}

return guesses
6 changes: 5 additions & 1 deletion discord_app/eternal_guesses/routes/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ async def call(self, event: DiscordEvent) -> DiscordResponse:
game_id = event.command.options.get('game-id')
title = event.command.options.get('title')
description = event.command.options.get('description')
min_guess = event.command.options.get('min')
max_guess = event.command.options.get('max')

if game_id is None:
game_id = self._generate_game_id(guild_id)
Expand All @@ -41,10 +43,12 @@ async def call(self, event: DiscordEvent) -> DiscordResponse:
game_id=game_id,
title=title,
description=description,
min_guess=min_guess,
max_guess=max_guess,
created_by=event.member.user_id,
create_datetime=datetime.now(),
close_datetime=None,
closed=False
closed=False,
)
self.games_repository.save(game)

Expand Down
25 changes: 25 additions & 0 deletions discord_app/eternal_guesses/routes/guess.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime

from eternal_guesses.model.data.game import Game
from eternal_guesses.model.data.game_guess import GameGuess
from eternal_guesses.model.discord.discord_event import DiscordEvent
from eternal_guesses.model.discord.discord_response import DiscordResponse
Expand Down Expand Up @@ -38,6 +39,11 @@ async def call(self, event: DiscordEvent) -> DiscordResponse:
error_message = self.message_provider.error_guess_on_closed_game(game_id)
return DiscordResponse.ephemeral_channel_message(error_message)

if game.is_numeric():
if not self.validate_guess(game=game, guess=guess):
error_message = self.message_provider.invalid_guess(game)
return DiscordResponse.ephemeral_channel_message(error_message)

game_guess = GameGuess()
game_guess.user_id = user_id
game_guess.user_nickname = user_nickname
Expand All @@ -51,3 +57,22 @@ async def call(self, event: DiscordEvent) -> DiscordResponse:

guess_added_message = self.message_provider.guess_added(game_id, guess)
return DiscordResponse.ephemeral_channel_message(content=guess_added_message)

def validate_guess(self, game: Game, guess: str):
if not self.is_numeric(guess):
return False

if game.max_guess is not None and int(guess) > game.max_guess:
return False

if game.min_guess is not None and int(guess) < game.min_guess:
return False

return True

def is_numeric(self, guess: str):
try:
int(guess)
return True
except ValueError:
return False
22 changes: 21 additions & 1 deletion discord_app/eternal_guesses/util/message_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ def guess_edited(self) -> str:
def guess_deleted(self) -> str:
raise NotImplementedError()

def invalid_guess(self, game: Game) -> str:
raise NotImplementedError()


class MessageProviderImpl(MessageProvider):
def game_post_embed(self, game: Game) -> discord.Embed:
Expand All @@ -105,7 +108,7 @@ def game_post_embed(self, game: Game) -> discord.Embed:
description += "Guesses:\n\n"

guess_list = []
for user_id, guess in sorted(game.guesses.items(), key=lambda i: i[1].guess):
for user_id, guess in _sorted_guesses(game):
guess_list.append(f"<@{user_id}>: {guess.guess}")

if len(guess_list) > 0:
Expand Down Expand Up @@ -232,3 +235,20 @@ def guess_edited(self) -> str:

def guess_deleted(self) -> str:
return "The guess has been deleted."

def invalid_guess(self, game: Game) -> str:
if game.min_guess is not None and game.max_guess is not None:
return f"The guess must be a number between {game.min_guess} and {game.max_guess}"
elif game.min_guess is not None:
return f"The guess must be a number higher than {game.min_guess}"
elif game.max_guess is not None:
return f"The guess must be a number lower than {game.max_guess}"

raise NotImplementedError()


def _sorted_guesses(game):
if game.is_numeric():
return sorted(game.guesses.items(), key=lambda i: int(i[1].guess))
else:
return sorted(game.guesses.items(), key=lambda i: i[1].guess)
14 changes: 13 additions & 1 deletion discord_app/tests/integration/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def make_discord_guess_event(guild_id: int, game_id: str, guess: str, user_id: i
def make_discord_create_event(guild_id: int, game_id: str, game_title: str = None, game_description: str = None,
channel_id: int = DEFAULT_CHANNEL_ID, user_id: int = DEFAULT_USER_ID,
member_nickname: str = DEFAULT_MEMBER_NICK, user_name: str = DEFAULT_USER_NAME,
role_id: int = DEFAULT_ROLE_ID):
role_id: int = DEFAULT_ROLE_ID, min_guess: int = None, max_guess: int = None):
event_body = _base_event_body(guild_id=guild_id, channel_id=channel_id, user_id=user_id,
member_nickname=member_nickname, user_name=user_name, role_id=role_id, is_admin=False)

Expand All @@ -66,6 +66,18 @@ def make_discord_create_event(guild_id: int, game_id: str, game_title: str = Non
"value": game_description,
})

if min_guess is not None:
options.append({
"name": "min",
"value": min_guess,
})

if max_guess is not None:
options.append({
"name": "max",
"value": max_guess,
})

event_body['data'] = {
"id": "2001",
"name": "eternal-guess",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def test_get_minimal_game():
# Then
assert game.guild_id == retrieved_game.guild_id
assert game.game_id == retrieved_game.game_id
assert not game.is_numeric()


def test_get_game():
Expand All @@ -57,6 +58,8 @@ def test_get_game():
closed = True
title = 'A mockery of fools'
description = 'A tale as old as time'
min_guess = 1
max_guess = 500

game = Game(
guild_id=guild_id,
Expand All @@ -67,6 +70,8 @@ def test_get_game():
game_id=game_id,
title=title,
description=description,
min_guess=min_guess,
max_guess=max_guess,
guesses={
user_id: GameGuess(
user_id=user_id,
Expand Down Expand Up @@ -95,6 +100,10 @@ def test_get_game():
assert retrieved_game.title == title
assert retrieved_game.description == description

assert retrieved_game.min_guess == min_guess
assert retrieved_game.max_guess == max_guess
assert retrieved_game.is_numeric()

assert user_id in retrieved_game.guesses

game_guess = retrieved_game.guesses[user_id]
Expand Down
22 changes: 20 additions & 2 deletions discord_app/tests/integration/test_integration_create_and_vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def test_integration_full_flow():
game_id=game_id,
title=game_title,
description=game_description,
min_guess=50,
max_guess=5000,
channel_id=management_channel)

game = games_repository.get(guild_id, game_id)
Expand Down Expand Up @@ -61,7 +63,7 @@ def test_integration_full_flow():
assert len(game.guesses) == 2

# Change a member's guess as a management user
new_guess = "5600"
new_guess = "4900"
change_guess(guild_id=guild_id, game_id=game_id, guessing_user_id=user_id, new_guess=new_guess,
channel_id=management_channel)

Expand All @@ -75,6 +77,19 @@ def test_integration_full_flow():
assert user_id not in game.guesses
assert another_user_id in game.guesses

# Place a guess that's below the min
one_more_user_id = 102
guess_on_game(guild_id=guild_id, game_id=game_id, guess="49", user_id=one_more_user_id)

game = games_repository.get(guild_id, game_id)
assert one_more_user_id not in game.guesses

# Place a guess that's above the max
guess_on_game(guild_id=guild_id, game_id=game_id, guess="5001", user_id=one_more_user_id)

game = games_repository.get(guild_id, game_id)
assert one_more_user_id not in game.guesses


def guess_on_game(guild_id: int, game_id: str, guess: str, user_id: int):
response = handler.handle_lambda(
Expand All @@ -85,14 +100,17 @@ def guess_on_game(guild_id: int, game_id: str, guess: str, user_id: int):
assert response['statusCode'] == 200


def create_new_game(guild_id: int, game_id: str, title: str, description: str, channel_id: int):
def create_new_game(guild_id: int, game_id: str, title: str, description: str, channel_id: int,
min_guess: int, max_guess: int):
response = handler.handle_lambda(
make_discord_create_event(
guild_id=guild_id,
game_id=game_id,
game_title=title,
game_description=description,
channel_id=channel_id,
min_guess=min_guess,
max_guess=max_guess,
),
create_context()
)
Expand Down
Loading

0 comments on commit cd52b26

Please sign in to comment.