Skip to content

Commit

Permalink
Unlimited lobby size and a max 8 readied
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeDailey committed Sep 27, 2020
1 parent 69f4837 commit ae9695c
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 47 deletions.
92 changes: 77 additions & 15 deletions __tests__/test_lobby.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from datetime import datetime
from matchmaking.game_data import Game, GameData, Team
from unittest.mock import AsyncMock, patch
from datetime import datetime
from random import choice, randint
from string import digits
from typing import Callable, List
Expand All @@ -9,9 +8,11 @@
from discord import Member, Status, Colour
from discord import TextChannel, VoiceChannel
from discord.ext.commands.bot import Bot
from discord.embeds import Embed

from models.lobby import Lobby
from utils.usage_exception import UsageException
from matchmaking.game_data import Game, GameData, Team
from matchmaking.linear_regression_ranker import get_ranker
from matchmaking.random_shuffler import get_shuffler

Expand All @@ -25,6 +26,7 @@ def channel(name: str = "", topic: str = "") -> TextChannel:
channel.id = id()
channel.name = name
channel.topic = topic
channel.mention = id()
return channel


Expand All @@ -44,6 +46,7 @@ def member(status: Status = Status.online) -> Member:
member = AsyncMock(spec=Member)
member.id = id()
member.display_name = id()
member.mention = id()
member.bot = None
member.status = status
return member
Expand All @@ -53,7 +56,7 @@ async def game_data(_channel) -> GameData:
return GameData(
[
Game(
AsyncMock(spec=datetime),
datetime.now(),
Team([id() for _ in range(4)], randint(1000, 3000)),
Team([id() for _ in range(4)], randint(1000, 3000)),
)
Expand All @@ -62,6 +65,11 @@ async def game_data(_channel) -> GameData:
)


def embed(self, mention: bool = False, title: str = None) -> Embed:
embed = AsyncMock(Embed)
return embed


class TestLobby(AsyncTestCase):
async def test_add_remove(self):
lobby = Lobby(bot(), channel())
Expand All @@ -79,14 +87,24 @@ async def test_add_remove(self):

async def test_full_lobby(self):
lobby = Lobby(bot(), channel())
await lobby.add(member())
assert not lobby.is_full()
await lobby.flyin(first := member())
assert not lobby.is_ready()
for _ in range(7):
await lobby.add(member())
assert lobby.is_full()
await lobby.flyin(member())
assert lobby.is_ready()

await lobby.add(alternate := member())
with self.assertRaises(UsageException):
await lobby.add(member())
await lobby.ready(alternate)

await lobby.remove(first)
assert not lobby.is_ready()

await lobby.ready(alternate)
assert lobby.is_ready()

with self.assertRaises(UsageException):
await lobby.ready(first)

async def test_leavers(self):
lobby = Lobby(bot(), channel())
Expand Down Expand Up @@ -126,43 +144,87 @@ async def test_ready_unready(self):
async def test_lobby_embed(self):
lobby = Lobby(bot(), channel())
embed = lobby.get_lobby_message()
assert embed.title == "Lobby (0)"
assert embed.footer.text == "There are 8 spots remaining!"
assert embed.colour == Colour.orange()

await lobby.add(player := member())
embed = lobby.get_lobby_message()
assert embed.footer.text == "There are 7 spots remaining!"
assert embed.title == "Lobby (1)"
assert embed.footer.text == "There are 8 spots remaining!"
assert embed.colour == Colour.orange()
assert len(embed.fields) == 1
assert embed.fields[0].name == ":x: Not Ready"
assert embed.fields[0].name == "Alternates (1)"
assert str(player.display_name) in embed.fields[0].value

embed = lobby.get_lobby_message(mention=True)
assert str(player.display_name) in embed.fields[0].value

await lobby.ready(player)
embed = lobby.get_lobby_message()
assert embed.title == "Lobby (1)"
assert embed.footer.text == "There are 7 spots remaining!"
assert embed.colour == Colour.orange()
assert len(embed.fields) == 1
assert embed.fields[0].name == ":white_check_mark: Ready!"
assert embed.fields[0].name == "Players (1)"
assert str(player.display_name) in embed.fields[0].value

embed = lobby.get_lobby_message(mention=True)
assert str(player.mention) in embed.fields[0].value

for _ in range(7):
await lobby.flyin(member())
embed = lobby.get_lobby_message()
assert embed.footer.text == "This lobby is full!"
assert embed.title == "Lobby (8)"
assert "Use `?shuffle` or `?ranked`" in embed.footer.text
assert embed.colour == Colour.green()
assert len(embed.fields) == 1
assert embed.fields[0].name == ":white_check_mark: Ready!"
assert embed.fields[0].name == "Players (8)"

await lobby.unready(player)
embed = lobby.get_lobby_message()
assert embed.footer.text == "This lobby is full!"
assert embed.colour == Colour.green()
assert embed.title == "Lobby (8)"
assert embed.footer.text == "There's one spot remaining!"
assert embed.colour == Colour.orange()
assert len(embed.fields) == 2
assert embed.fields[0].name == "Players (7)"
assert embed.fields[1].name == "Alternates (1)"

await lobby.remove(player)
embed = lobby.get_lobby_message()
assert embed.title == "Lobby (7)"
assert embed.footer.text == "There's one spot remaining!"
assert embed.colour == Colour.orange()
assert len(embed.fields) == 1
assert embed.fields[0].name == "Players (7)"

for _ in range(100):
await lobby.add(member())
embed = lobby.get_lobby_message()
assert embed.title == "Lobby (107)"
assert embed.footer.text == "There's one spot remaining!"
assert embed.colour == Colour.orange()
assert len(embed.fields) == 2
assert embed.fields[1].name == "Alternates (100)"

await lobby.flyin(member())
embed = lobby.get_lobby_message()
assert embed.title == "Lobby (108)"
assert "Use `?shuffle` or `?ranked`" in embed.footer.text
assert embed.colour == Colour.green()
assert len(embed.fields) == 2
assert embed.fields[0].name == "Players (8)"
assert embed.fields[1].name == "Alternates (100)"

async def test_game_start_message(self):
lobby = Lobby(bot(), ctx := channel())
lobby.get_lobby_message = AsyncMock()
for _ in range(8):
await lobby.flyin(member())
lobby.get_lobby_message.assert_called_with(
mention=True,
title=f"Game starting in ({ctx.mention}))",
)

async def test_numbers(self):
discord_bot = bot()
Expand Down
74 changes: 43 additions & 31 deletions models/lobby.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from functools import reduce
import asyncio
import re
from typing import Callable, List, Dict, Optional
from typing import Callable, List, Dict, Optional, Tuple

from discord import File, Embed, Colour
from discord import Member, Status
Expand Down Expand Up @@ -33,17 +33,13 @@ async def add(self, user: Member, author: Optional[Member] = None) -> None:
if self.has_left_before(Player(user)):
raise UsageException.leaver_not_added(self.channel)

if self.is_full():
raise UsageException.game_is_full(self.channel)

player = Player(user)
if self.has_joined(player):
raise UsageException.already_joined(
self.channel, author is not None
)

self.players.append(player)
self.reset_orderings()

if self.ready_count() == 7:
await self.broadcast_game_almost_full()
Expand All @@ -61,7 +57,6 @@ async def remove(self, user: Member, author: Optional[Member] = None):
self.leavers.append(player)

self.players.remove(player)
self.reset_orderings()

await self.channel.send(
f"Succesfully removed: {player.get_name()} from the game."
Expand Down Expand Up @@ -119,30 +114,48 @@ async def ready(self, user: Member) -> None:
if player.is_ready():
raise UsageException.already_ready(self.channel)

if self.is_ready():
raise UsageException.game_is_full(self.channel)

ind = self.players.index(player)
self.players[ind].set_ready()
self.reset_orderings()
await self.channel.send(
f"{player.get_name()} ready!. :white_check_mark:",
)

if self.is_ready():
title = f"Game starting in ({self.channel.mention}))"
embed = self.get_lobby_message(mention=True, title=title)
await self.channel.send(embed=embed)

async def unready(self, user: Member) -> None:
player = Player(user)
if player not in self.players:
raise UsageException.join_the_lobby_first(self.channel)

ind = self.players.index(player)
self.players[ind].set_unready()
self.reset_orderings()
await self.channel.send(f"{player.get_name()} unreadied!. :x:")

async def flyin(self, user):
await self.add(user)
await self.ready(user)

def get_players(self) -> Tuple[List[Player], List[Player]]:
ready = []
alternates = []
for p in self.players:
(ready if p.is_ready() else alternates).append(p)

return ready, alternates

async def get_next_match(self, order: Callable) -> Optional[Match]:
if order.__name__ not in self.orderings:
self.orderings[order.__name__] = await MatchFinder.new(
self.players, order
)
ready, _ = self.get_players()
match_finder = await MatchFinder.new(ready, order)
self.orderings[order.__name__] = match_finder

return self.orderings[order.__name__].get_next_match()

Expand Down Expand Up @@ -190,36 +203,35 @@ async def show_ranking(self, filter_lobby: bool):

await self.channel.send(embed=embed)

def get_lobby_message(self) -> Embed:
color = Colour.green() if self.is_full() else Colour.orange()
def get_lobby_message(
self,
mention: bool = False,
title: Optional[str] = None,
) -> Embed:
color = Colour.green() if self.is_ready() else Colour.orange()
embed = Embed(colour=color)
embed.title = f"Left 4 Dead Lobby ({len(self.players)}/8)"
embed.title = title or f"Lobby ({len(self.players)})"

if self.ready_count() != 0:
ready = ""
for player in self.players:
if player.is_ready():
ready += f"• {player.get_name()}\n"
ready, alternates = self.get_players()

if ready:
print = "get_mention" if mention else "get_name"
embed.add_field(
name=":white_check_mark: Ready!",
value=ready,
name=f"Players ({len(ready)})",
value="".join([f"• {(getattr(p, print))()}\n" for p in ready]),
inline=False,
)

if self.ready_count() != len(self.players):
not_ready = ""
for player in self.players:
if not player.is_ready():
not_ready += f"• {player.get_name()}\n"
if alternates:
embed.add_field(
name=":x: Not Ready",
value=not_ready,
name=f"Alternates ({len(alternates)})",
value="".join([f"• {p.get_name()}\n" for p in alternates]),
inline=False,
)

remaining_spots = 8 - len(self.players)
remaining_spots = 8 - self.ready_count()
if remaining_spots == 0:
text = "This lobby is full!"
text = "Use `?shuffle` or `?ranked` to start building teams."
elif remaining_spots == 1:
text = "There's one spot remaining!"
else:
Expand All @@ -228,9 +240,6 @@ def get_lobby_message(self) -> Embed:
embed.set_footer(text=text)
return embed

def is_full(self) -> bool:
return len(self.players) == 8

def has_joined(self, player: Player) -> bool:
return player in self.players

Expand All @@ -244,6 +253,9 @@ def ready_count(self) -> int:
0,
)

def is_ready(self) -> bool:
return self.ready_count() == 8

def reset_orderings(self) -> None:
self.orderings = {}

Expand Down
2 changes: 1 addition & 1 deletion utils/usage_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def leaver_not_added(channel: TextChannel):
def game_is_full(channel: TextChannel):
return UsageException(
channel,
"Sorry the game is full. Please wait for someone to leave.",
"Sorry the game is full. Please wait for someone to unready.",
)

@staticmethod
Expand Down

0 comments on commit ae9695c

Please sign in to comment.