2 Commits

Author SHA1 Message Date
David Rodenkirchen 1137a9e7c7 implement Details View UI for tournaments 2026-02-02 08:52:38 +01:00
David Rodenkirchen b6ef2b5995 Add tournaments UI - Prototype #1 2026-01-31 18:33:28 +01:00
15 changed files with 515 additions and 24 deletions
+7 -2
View File
@@ -30,7 +30,7 @@ if __name__ == "__main__":
corner_radius_large=0,
font=Font(from_root("src/ezgg_lan_manager/assets/fonts/joystix.otf"))
)
default_attachments = [LocalData()]
default_attachments: list = [LocalData()]
default_attachments.extend(init_services())
lan_info = default_attachments[3].get_lan_info()
@@ -161,6 +161,11 @@ if __name__ == "__main__":
name="DbErrorPage",
url_segment="db-error",
build=pages.DbErrorPage,
),
ComponentPage(
name="TournamentDetailsPage",
url_segment="tournament",
build=pages.TournamentDetailsPage,
)
],
theme=theme,
@@ -188,5 +193,5 @@ if __name__ == "__main__":
sys.exit(app.run_as_web_server(
host="0.0.0.0",
port=8000,
port=8001,
))
+4 -2
View File
@@ -13,11 +13,12 @@ from src.ezgg_lan_manager.services.NewsService import NewsService
from src.ezgg_lan_manager.services.ReceiptPrintingService import ReceiptPrintingService
from src.ezgg_lan_manager.services.SeatingService import SeatingService
from src.ezgg_lan_manager.services.TicketingService import TicketingService
from src.ezgg_lan_manager.services.TournamentService import TournamentService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types import *
# Inits services in the correct order
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService, ReceiptPrintingService]:
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService, ReceiptPrintingService, TournamentService]:
logging.basicConfig(level=logging.DEBUG)
configuration_service = ConfigurationService(from_root("config.toml"))
db_service = DatabaseService(configuration_service.get_database_configuration())
@@ -30,6 +31,7 @@ def init_services() -> tuple[AccountingService, CateringService, ConfigurationSe
receipt_printing_service = ReceiptPrintingService(seating_service, configuration_service.get_receipt_printing_configuration(), configuration_service.DEV_MODE_ACTIVE)
catering_service = CateringService(db_service, accounting_service, user_service, receipt_printing_service)
local_data_service = LocalDataService()
tournament_service = TournamentService(db_service, user_service)
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service, receipt_printing_service
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service, receipt_printing_service, tournament_service
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

@@ -0,0 +1,33 @@
from typing import Optional
from rio import Component, Row, Text, TextStyle, Color
class TournamentDetailsInfoRow(Component):
key: str
value: str
color: Optional[Color] = None
def build(self) -> Component:
return Row(
Text(
text=self.key,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1
),
margin_bottom=0.5,
align_x=0
),
Text(
text=self.value,
style=TextStyle(
fill=self.color if self.color is not None else self.session.theme.background_color,
font_size=1
),
margin_bottom=0.5,
align_x=1
),
margin_left=4,
margin_right=4
)
@@ -0,0 +1,60 @@
from typing import Literal, Callable
from rio import Component, PointerEventListener, Rectangle, Image, Text, Tooltip, TextStyle, Color, Icon, Row, PointerEvent
from from_root import from_root
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus
class TournamentPageRow(Component):
tournament_id: int
tournament_name: str
game_image_name: str
current_participants: int
max_participants: int
tournament_status: TournamentStatus
clicked_cb: Callable
def handle_click(self, _: PointerEvent) -> None:
self.clicked_cb(self.tournament_id)
def determine_tournament_status_icon_color_and_text(self) -> tuple[str, Literal["success", "warning", "danger"], str]:
if self.tournament_status == TournamentStatus.OPEN:
return "material/lock_open", "success", "Anmeldung geöffnet"
elif self.tournament_status == TournamentStatus.CLOSED:
return "material/lock", "danger", "Anmeldung geschlossen"
elif self.tournament_status == TournamentStatus.ONGOING:
return "material/autoplay", "warning", "Turnier läuft"
elif self.tournament_status == TournamentStatus.COMPLETED:
return "material/check_circle", "success", "Turnier beendet"
elif self.tournament_status == TournamentStatus.CANCELED:
return "material/cancel", "danger", "Turnier abgesagt"
elif self.tournament_status == TournamentStatus.INVITE_ONLY:
return "material/person_cancel", "warning", "Teilnahme nur per Einladung"
else:
raise RuntimeError(f"Unknown tournament status: {str(self.tournament_status)}")
def build(self) -> Component:
icon_name, color, text = self.determine_tournament_status_icon_color_and_text()
return PointerEventListener(
content=Rectangle(
content=Row(
Image(image=from_root(f"src/ezgg_lan_manager/assets/img/games/{self.game_image_name}")),
Text(self.tournament_name, style=TextStyle(fill=self.session.theme.background_color, font_size=1)),
Text(f"{self.current_participants}/{self.max_participants}", style=TextStyle(fill=self.session.theme.background_color, font_size=1), justify="right", margin_right=0.5),
Tooltip(anchor=Icon(icon_name, min_width=1, min_height=1, fill=color), position="top",
tip=Text(text, style=TextStyle(fill=self.session.theme.background_color, font_size=0.7))),
proportions=[1, 4, 1, 1],
margin=.5
),
fill=self.session.theme.hud_color,
margin=1,
margin_bottom=0,
stroke_color=Color.TRANSPARENT,
stroke_width=0.2,
hover_stroke_color=self.session.theme.background_color,
cursor="pointer"
),
on_press=self.handle_click
)
@@ -0,0 +1,197 @@
from typing import Optional, Union, Literal
from from_root import from_root
from rio import Column, Component, event, TextStyle, Text, Row, Image, Spacer, ProgressCircle, Button, Checkbox, ThemeContextSwitcher, Link
from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow
from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts
from src.ezgg_lan_manager.types.User import User
class TournamentDetailsPage(Component):
tournament: Optional[Union[Tournament, str]] = None
rules_accepted: bool = False
user: Optional[User] = None
loading: bool = False
# State for message above register button
message: str = ""
is_success: bool = False
@event.on_populate
async def on_populate(self) -> None:
try:
tournament_id = int(self.session.active_page_url.query_string.split("id=")[-1])
except (ValueError, AttributeError, TypeError):
tournament_id = None
if tournament_id is not None:
self.tournament = await self.session[TournamentService].get_tournament_by_id(tournament_id)
if self.tournament is not None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - {self.tournament.name}")
else:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turniere")
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.loading_done()
async def register_pressed(self) -> None:
self.loading = True
if not self.user:
return
self.is_success = True
self.message = f"Erfolgreich angemeldet!" # ToDo: Hook into Tournament Service
self.loading = False
async def unregister_pressed(self) -> None:
self.loading = True
if not self.user:
return
self.is_success = True
self.message = f"Erfolgreich abgemeldet!" # ToDo: Hook into Tournament Service
self.loading = False
def loading_done(self) -> None:
if self.tournament is None:
self.tournament = "Turnier konnte nicht gefunden werden"
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(str(self.__class__), self.on_populate)
def build(self) -> Component:
if self.tournament is None:
content = Column(
ProgressCircle(
color="secondary",
align_x=0.5,
margin_top=0,
margin_bottom=0
),
min_height=10
)
elif isinstance(self.tournament, str):
content = Row(
Text(
text=self.tournament,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1
),
margin_top=2,
margin_bottom=2,
align_x=0.5
)
)
else:
tournament_status_color = self.session.theme.background_color
if self.tournament.status == TournamentStatus.OPEN:
tournament_status_color = self.session.theme.success_color
elif self.tournament.status == TournamentStatus.CLOSED:
tournament_status_color = self.session.theme.danger_color
elif self.tournament.status == TournamentStatus.ONGOING:
tournament_status_color = self.session.theme.warning_color
# ToDo: Integrate Teams logic
ids_of_participants = [p.id for p in self.tournament.participants]
color_key: Literal["hud", "danger"] = "hud"
on_press_function = self.register_pressed
if self.user and self.user.user_id in ids_of_participants: # User already registered for tournament
button_text = "Abmelden"
button_sensitive_hook = True # User has already accepted the rules previously
color_key = "danger"
on_press_function = self.unregister_pressed
elif self.user and self.user.user_id not in ids_of_participants:
button_text = "Anmelden"
button_sensitive_hook = self.rules_accepted
else:
# This should NEVER happen
button_text = "Anmelden"
button_sensitive_hook = False
if self.tournament.status != TournamentStatus.OPEN or self.tournament.is_full:
button_sensitive_hook = False # Override button controls if tournament is not open anymore or full
if self.user:
accept_rules_row = Row(
ThemeContextSwitcher(content=Checkbox(is_on=self.bind().rules_accepted, margin_left=4), color=self.session.theme.hud_color),
Text("Ich akzeptiere die ", margin_left=1, style=TextStyle(fill=self.session.theme.background_color, font_size=0.8), overflow="nowrap", justify="right"),
Link(Text("Turnierregeln", margin_right=4, style=TextStyle(fill=self.session.theme.background_color, font_size=0.8, italic=True), overflow="nowrap", justify="left"), "https://google.de") # ToDo: Add rules
)
button = Button(
content=button_text,
shape="rectangle",
style="major",
color=color_key,
margin_left=2,
margin_right=2,
is_sensitive=button_sensitive_hook,
on_press=on_press_function,
is_loading=self.loading
)
else:
# No UI here if user not logged in
accept_rules_row, button = Spacer(), Spacer()
content = Column(
Row(
Image(image=from_root(f"src/ezgg_lan_manager/assets/img/games/{self.tournament.game_title.image_name}")),
Text(
text=self.tournament.name,
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=1,
margin_bottom=1,
align_x=0.5
),
margin_right=6,
margin_left=6
),
Spacer(min_height=1),
TournamentDetailsInfoRow("Status", tournament_status_to_display_text(self.tournament.status), tournament_status_color),
TournamentDetailsInfoRow("Startzeit", f"{weekday_to_display_text(self.tournament.start_time.weekday())}, {self.tournament.start_time.strftime('%H:%M')} Uhr"),
TournamentDetailsInfoRow("Format", tournament_format_to_display_texts(self.tournament.format)[0]),
TournamentDetailsInfoRow("Best of", tournament_format_to_display_texts(self.tournament.format)[1]),
TournamentDetailsInfoRow(
"Teilnehmer",
f"{len(self.tournament.participants)} / {self.tournament.max_participants}",
self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color
),
Row(
Text(
text="Info",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=1.2
),
margin_top=1,
margin_bottom=1,
align_x=0.5
)
),
# FixMe: Use rio.Markdown with correct TextStyle instead to allow basic text formatting from DB-side.
Text(self.tournament.description, margin_left=2, margin_right=2, style=TextStyle(fill=self.session.theme.background_color, font_size=1), overflow="wrap"),
Spacer(min_height=2),
accept_rules_row,
Spacer(min_height=0.5),
Text(self.message, margin_left=2, margin_right=2, style=TextStyle(fill=self.session.theme.success_color if self.is_success else self.session.theme.danger_color, font_size=1), overflow="wrap", justify="center"),
Spacer(min_height=0.5),
button
)
return Column(
MainViewContentBox(
Column(
Spacer(min_height=1),
content,
Spacer(min_height=1)
)
),
align_y=0
)
+40 -12
View File
@@ -1,15 +1,50 @@
from rio import Column, Component, event, TextStyle, Text
from rio import Column, Component, event, TextStyle, Text, Spacer, ProgressCircle
from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager import ConfigurationService, TournamentService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TournamentPageRow import TournamentPageRow
from src.ezgg_lan_manager.types.Tournament import Tournament
class TournamentsPage(Component):
tournament_data: list[Tournament] = []
@event.on_populate
async def on_populate(self) -> None:
self.tournament_data = await self.session[TournamentService].get_tournaments()
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turniere")
def tournament_clicked(self, tournament_id: int) -> None:
self.session.navigate_to(f"tournament?id={tournament_id}")
def build(self) -> Component:
tournament_page_rows = []
for tournament in self.tournament_data:
tournament_page_rows.append(
TournamentPageRow(
tournament.id,
tournament.name,
tournament.game_title.image_name,
len(tournament.participants),
tournament.max_participants,
tournament.status,
self.tournament_clicked
)
)
if len(self.tournament_data) == 0:
content = [Column(
ProgressCircle(
color="secondary",
align_x=0.5,
margin_top=0,
margin_bottom=0
),
min_height=10
)]
else:
content = tournament_page_rows
return Column(
MainViewContentBox(
Column(
@@ -20,18 +55,11 @@ class TournamentsPage(Component):
font_size=1.2
),
margin_top=2,
margin_bottom=0,
margin_bottom=2,
align_x=0.5
),
Text(
text="Aktuell ist noch kein Turnierplan hinterlegt.",
style=TextStyle(
fill=self.session.theme.background_color,
font_size=0.9
),
margin=1,
overflow="wrap"
)
*content,
Spacer(min_height=1)
)
),
align_y=0
+1
View File
@@ -20,3 +20,4 @@ from .ManageUsersPage import ManageUsersPage
from .ManageCateringPage import ManageCateringPage
from .ManageTournamentsPage import ManageTournamentsPage
from .OverviewPage import OverviewPage
from .TournamentDetailsPage import TournamentDetailsPage
@@ -0,0 +1,98 @@
from asyncio import sleep
from datetime import datetime
from typing import Optional
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import GameTitle, TournamentFormat, TournamentStatus, ParticipantType
DEV_LOREM_IPSUM = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Dieses LAN-Turnier bringt Spieler aus verschiedenen Regionen zusammen, um gemeinsam spannende Matches zu erleben. Tastaturen klappern, Monitore leuchten und die Stimmung ist von Anfang an von Vorfreude und Ehrgeiz geprägt.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. In intensiven Partien zählen Strategie, Reaktion und Teamarbeit. Zwischen den Spielen wird gefachsimpelt, gelacht und neue Kontakte entstehen, während Server stabil laufen und das Netzwerk dauerhaft gefordert ist.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Wenn die Finalspiele beginnen, steigt die Spannung spürbar. Am Ende bleiben faire Wettkämpfe, gemeinsame Erinnerungen und das Gefühl, Teil einer starken Community gewesen zu sein.
"""
class TournamentService:
def __init__(self, db_service: DatabaseService, user_service: UserService) -> None:
self._db_service = db_service
self._user_service = user_service
# This overrides the database access and is meant for easy development.
# Set to None before merging back into main.
self._dev_data = [
Tournament(
0,
"Teeworlds 2vs2",
DEV_LOREM_IPSUM,
GameTitle(
"Teeworlds",
"Teeworlds is a free online multiplayer game, available for all major operating systems. Battle with up to 16 players in a variety of game modes.",
"https://store.steampowered.com/app/380840/Teeworlds/",
"teeworlds.png"
),
TournamentFormat.SINGLE_ELIMINATION_BO_3,
datetime(2026, 5, 8, 18, 0, 0),
TournamentStatus.OPEN,
[],
None,
[],
32
),
Tournament(
1,
"Rocket League 3vs3",
DEV_LOREM_IPSUM,
GameTitle(
"Rocket League",
"Rocket League is a high-powered hybrid of arcade-style soccer and vehicular mayhem with easy-to-understand controls and fluid, physics-driven competition.",
"https://steamcommunity.com/app/252950",
"rl.png"
),
TournamentFormat.SINGLE_ELIMINATION_BO_3,
datetime(2026, 5, 8, 18, 0, 0),
TournamentStatus.OPEN,
[Participant(30, "Typhus", ParticipantType.PLAYER)],
None,
[],
8
),
Tournament(
2,
"Worms Armageddon 1vs1",
DEV_LOREM_IPSUM,
GameTitle(
"Worms Armageddon",
"2D turn-based artillery strategy game.",
"https://store.steampowered.com/app/217200/Worms_Armageddon/",
"worms.png"
),
TournamentFormat.SINGLE_ELIMINATION_BO_1,
datetime(2026, 5, 8, 18, 30, 0),
TournamentStatus.CLOSED,
[],
None,
[],
16
)
]
async def get_tournaments(self) -> list[Tournament]:
# Fake DB lookup delay
await sleep(1)
if self._dev_data is not None:
return self._dev_data
return [] # ToDo: Implement database polling
async def get_tournament_by_id(self, tournament_id: int) -> Optional[Tournament]:
await sleep(1)
try:
return self._dev_data[tournament_id]
except IndexError:
return None
+15
View File
@@ -0,0 +1,15 @@
def weekday_to_display_text(weekday: int) -> str:
if weekday == 0:
return "Montag"
elif weekday == 1:
return "Dienstag"
elif weekday == 2:
return "Mittwoch"
elif weekday == 3:
return "Donnerstag"
elif weekday == 4:
return "Freitag"
elif weekday == 5:
return "Samstag"
else:
return "Sonntag"
+21 -3
View File
@@ -12,15 +12,18 @@ class Tournament:
def __init__(self,
id_: int,
name: str,
description: str,
game_title: GameTitle,
format_: TournamentFormat,
start_time: datetime,
status: TournamentStatus,
participants: list[Participant],
matches: Optional[tuple[Match]],
rounds: list[list[Match]]) -> None:
rounds: list[list[Match]],
max_participants: int) -> None:
self._id = id_
self._name = name
self._description = description
self._game_title = game_title
self._format = format_
self._start_time = start_time
@@ -28,6 +31,7 @@ class Tournament:
self._participants = participants
self._matches = matches
self._rounds = rounds
self._max_participants = max_participants
@property
def id(self) -> int:
@@ -69,6 +73,18 @@ class Tournament:
def matches(self) -> list[Match]:
return self._matches if self._matches else []
@property
def max_participants(self) -> int:
return self._max_participants
@property
def description(self) -> str:
return self._description
@property
def is_full(self) -> bool:
return len(self._participants) >= self._max_participants
def add_participant(self, participant: Participant) -> None:
if participant.id in (p.id for p in self._participants):
raise TournamentError(f"Participant with ID {participant.id} already registered for tournament")
@@ -304,16 +320,18 @@ class Tournament:
match.check_completion()
def generate_new_tournament(name: str, game_title: GameTitle, format_: TournamentFormat, start_time: datetime, initial_status: TournamentStatus = TournamentStatus.CLOSED) -> Tournament:
def generate_new_tournament(name: str, description: str, game_title: GameTitle, format_: TournamentFormat, start_time: datetime, max_participants: int, initial_status: TournamentStatus = TournamentStatus.CLOSED) -> Tournament:
id_ = uuid.uuid4().int
return Tournament(
id_,
name,
description,
game_title,
format_,
start_time,
initial_status,
list(),
None,
list()
list(),
max_participants
)
+34 -1
View File
@@ -7,7 +7,7 @@ class GameTitle:
name: str
description: str
web_link: str
image_name: str # Name of the image in assets/img/games
class TournamentFormat(Enum):
SINGLE_ELIMINATION_BO_1 = 1
@@ -17,6 +17,23 @@ class TournamentFormat(Enum):
DOUBLE_ELIMINATION_BO_3 = 5
DOUBLE_ELIMINATION_BO_5 = 6
def tournament_format_to_display_texts(tournament_format: TournamentFormat) -> tuple[str, str]:
""" Returns tuple where idx 0 is SE/DE string and idx 1 is match count """
if tournament_format == TournamentFormat.SINGLE_ELIMINATION_BO_1:
return "Single Elimination", "1"
elif tournament_format == TournamentFormat.SINGLE_ELIMINATION_BO_3:
return "Single Elimination", "3"
elif tournament_format == TournamentFormat.SINGLE_ELIMINATION_BO_5:
return "Single Elimination", "5"
elif tournament_format == TournamentFormat.DOUBLE_ELIMINATION_BO_1:
return "Double Elimination", "1"
elif tournament_format == TournamentFormat.DOUBLE_ELIMINATION_BO_3:
return "Double Elimination", "3"
elif tournament_format == TournamentFormat.DOUBLE_ELIMINATION_BO_5:
return "Double Elimination", "5"
else:
raise RuntimeError(f"Unknown tournament status: {str(tournament_format)}")
class TournamentStatus(Enum):
CLOSED = 1
@@ -26,6 +43,22 @@ class TournamentStatus(Enum):
INVITE_ONLY = 5 # For Show-matches
ONGOING = 6
def tournament_status_to_display_text(tournament_status: TournamentStatus) -> str:
if tournament_status == TournamentStatus.OPEN:
return "Offen"
elif tournament_status == TournamentStatus.CLOSED:
return "Geschlossen"
elif tournament_status == TournamentStatus.ONGOING:
return "Läuft"
elif tournament_status == TournamentStatus.COMPLETED:
return "Abgeschlossen"
elif tournament_status == TournamentStatus.CANCELED:
return "Abgesagt"
elif tournament_status == TournamentStatus.INVITE_ONLY:
return "Invite-only"
else:
raise RuntimeError(f"Unknown tournament status: {str(tournament_status)}")
class TournamentError(Exception):
def __init__(self, message: str) -> None:
+5 -4
View File
@@ -9,7 +9,8 @@ class TournamentDomainTests(unittest.TestCase):
def setUp(self):
# Generic Tournament config
self.name = "Tetris 1vs1"
self.game_title = GameTitle("Tetris99", "Some Description", "https://de.wikipedia.org/wiki/Tetris_99")
self.description = "Just play Tetris, yo"
self.game_title = GameTitle("Tetris99", "Some Description", "https://de.wikipedia.org/wiki/Tetris_99", "tetris.png")
self.format_ = TournamentFormat.SINGLE_ELIMINATION_BO_3
self.start_time = datetime(year=2100, month=6, day=23, hour=16, minute=30, second=0)
self.initial_status = TournamentStatus.CLOSED
@@ -20,18 +21,18 @@ class TournamentDomainTests(unittest.TestCase):
self.participant_c = Participant(3, "FunnyUserName", ParticipantType.PLAYER)
def test_tournament_without_participants_can_not_be_started(self) -> None:
tournament_under_test = generate_new_tournament(self.name, self.game_title, self.format_, self.start_time, self.initial_status)
tournament_under_test = generate_new_tournament(self.name, self.description, self.game_title, self.format_, self.start_time, 32, self.initial_status)
with self.assertRaises(TournamentError):
tournament_under_test.start()
def test_adding_the_same_participant_twice_leads_to_exception(self) -> None:
tournament_under_test = generate_new_tournament(self.name, self.game_title, self.format_, self.start_time, self.initial_status)
tournament_under_test = generate_new_tournament(self.name, self.description, self.game_title, self.format_, self.start_time, 32, self.initial_status)
tournament_under_test.add_participant(self.participant_a)
with self.assertRaises(TournamentError):
tournament_under_test.add_participant(self.participant_a)
def test_single_elimination_bo3_tournament_gets_generated_correctly(self) -> None:
tournament_under_test = generate_new_tournament(self.name, self.game_title, self.format_, self.start_time, self.initial_status)
tournament_under_test = generate_new_tournament(self.name, self.description, self.game_title, self.format_, self.start_time, 32, self.initial_status)
tournament_under_test.add_participant(self.participant_a)
tournament_under_test.add_participant(self.participant_b)