Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fffb607b16 | |||
| 02658aa049 | |||
| 1137a9e7c7 | |||
| b6ef2b5995 |
@@ -0,0 +1,144 @@
|
||||
-- Apply this patch after using create_database.sql to extend the schema to support tournaments from version 0.2.0
|
||||
-- WARNING: Executing this on a post 0.2.0 database will delete all data related to tournaments !!!
|
||||
|
||||
DROP TABLE IF EXISTS `game_titles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `game_titles` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
web_link VARCHAR(512) NOT NULL,
|
||||
image_name VARCHAR(255) NOT NULL,
|
||||
UNIQUE KEY uq_game_title_name (name)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `tournaments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tournaments` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
game_title_id INT NOT NULL,
|
||||
format VARCHAR(20) NOT NULL, -- SE_BO1, DE_BO3, ...
|
||||
start_time DATETIME NOT NULL,
|
||||
status VARCHAR(20) NOT NULL, -- OPEN, CLOSED, ONGOING, ...
|
||||
max_participants INT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_tournament_game
|
||||
FOREIGN KEY (game_title_id)
|
||||
REFERENCES game_titles(id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_tournaments_game_title
|
||||
ON tournaments(game_title_id);
|
||||
|
||||
DROP TABLE IF EXISTS `tournament_participants`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tournament_participants` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tournament_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
participant_type VARCHAR(10) NOT NULL DEFAULT 'PLAYER',
|
||||
seed INT NULL,
|
||||
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE KEY uq_tournament_user (tournament_id, user_id),
|
||||
|
||||
CONSTRAINT fk_tp_tournament
|
||||
FOREIGN KEY (tournament_id)
|
||||
REFERENCES tournaments(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_tp_user
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES users(user_id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_tp_tournament
|
||||
ON tournament_participants(tournament_id);
|
||||
CREATE INDEX idx_tp_user
|
||||
ON tournament_participants(user_id);
|
||||
|
||||
DROP TABLE IF EXISTS `tournament_rounds`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tournament_rounds` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tournament_id INT NOT NULL,
|
||||
bracket VARCHAR(10) NOT NULL, -- UPPER, LOWER, FINAL
|
||||
round_index INT NOT NULL,
|
||||
|
||||
UNIQUE KEY uq_round (tournament_id, bracket, round_index),
|
||||
|
||||
CONSTRAINT fk_round_tournament
|
||||
FOREIGN KEY (tournament_id)
|
||||
REFERENCES tournaments(id)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_rounds_tournament
|
||||
ON tournament_rounds(tournament_id);
|
||||
|
||||
DROP TABLE IF EXISTS `matches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `matches` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tournament_id INT NOT NULL,
|
||||
round_id INT NOT NULL,
|
||||
match_index INT NOT NULL,
|
||||
status VARCHAR(15) NOT NULL, -- WAITING, PENDING, COMPLETED, ...
|
||||
best_of INT NOT NULL, -- 1, 3, 5
|
||||
scheduled_time DATETIME NULL,
|
||||
completed_at DATETIME NULL,
|
||||
|
||||
UNIQUE KEY uq_match (round_id, match_index),
|
||||
|
||||
CONSTRAINT fk_match_tournament
|
||||
FOREIGN KEY (tournament_id)
|
||||
REFERENCES tournaments(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_match_round
|
||||
FOREIGN KEY (round_id)
|
||||
REFERENCES tournament_rounds(id)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_matches_tournament
|
||||
ON matches(tournament_id);
|
||||
|
||||
CREATE INDEX idx_matches_round
|
||||
ON matches(round_id);
|
||||
|
||||
DROP TABLE IF EXISTS `match_participants`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `match_participants` (
|
||||
match_id INT NOT NULL,
|
||||
participant_id INT NOT NULL,
|
||||
score INT NULL,
|
||||
is_winner TINYINT(1) NULL,
|
||||
|
||||
PRIMARY KEY (match_id, participant_id),
|
||||
|
||||
CONSTRAINT fk_mp_match
|
||||
FOREIGN KEY (match_id)
|
||||
REFERENCES matches(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_mp_participant
|
||||
FOREIGN KEY (participant_id)
|
||||
REFERENCES tournament_participants(id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
+12
-2
@@ -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,16 @@ if __name__ == "__main__":
|
||||
name="DbErrorPage",
|
||||
url_segment="db-error",
|
||||
build=pages.DbErrorPage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="TournamentDetailsPage",
|
||||
url_segment="tournament",
|
||||
build=pages.TournamentDetailsPage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="TournamentRulesPage",
|
||||
url_segment="tournament-rules",
|
||||
build=pages.TournamentRulesPage,
|
||||
)
|
||||
],
|
||||
theme=theme,
|
||||
@@ -188,5 +198,5 @@ if __name__ == "__main__":
|
||||
|
||||
sys.exit(app.run_as_web_server(
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
port=8001,
|
||||
))
|
||||
|
||||
@@ -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,211 @@
|
||||
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
|
||||
|
||||
async def tree_button_clicked(self) -> None:
|
||||
pass # ToDo: Implement tournament tree view
|
||||
|
||||
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
|
||||
tree_button = Spacer(grow_x=False, grow_y=False)
|
||||
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 or self.tournament.status == TournamentStatus.COMPLETED:
|
||||
tournament_status_color = self.session.theme.warning_color
|
||||
tree_button = Button(
|
||||
content="Turnierbaum anzeigen",
|
||||
shape="rectangle",
|
||||
style="minor",
|
||||
color="hud",
|
||||
margin_left=4,
|
||||
margin_right=4,
|
||||
margin_top=1,
|
||||
on_press=self.tree_button_clicked
|
||||
)
|
||||
|
||||
# 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"), "./tournament-rules", open_in_new_tab=True)
|
||||
)
|
||||
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
|
||||
),
|
||||
tree_button,
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
from rio import Column, Component, event, TextStyle, Text, Spacer
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
RULES: list[str] = [
|
||||
"Den Anweisungen der Turnierleitung ist stets Folge zu leisten.",
|
||||
"Teilnehmer müssen aktiv dafür sorgen, dass Spiele ohne Verzögerungen stattfinden.",
|
||||
"Unvollständige Teams werden ggf. zum Turnierstart entfernt.",
|
||||
"Verzögerungen und Ausfälle sind er Turnierleitung sofort zu melden.",
|
||||
"Jeder Spieler erstellt Screenshots am Rundenende zur Ergebnisdokumentation.",
|
||||
"Der Verlierer trägt das Ergebnis ein, der Gewinner überprüft es.",
|
||||
"Bei fehlendem oder falschem Ergebnis, ist sofort die Turnierorganisation zu informieren.",
|
||||
"Von 02:00–11:00 Uhr besteht keine Spielpflicht",
|
||||
"Täuschung, Falschangaben sowie Bugusing und Cheaten führen zur sofortigen Disqualifikation."
|
||||
]
|
||||
|
||||
class TournamentRulesPage(Component):
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turnierregeln")
|
||||
|
||||
def build(self) -> Component:
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
Column(
|
||||
Text(
|
||||
text="Turnierregeln",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
margin_top=2,
|
||||
margin_bottom=2,
|
||||
align_x=0.5
|
||||
),
|
||||
*[Text(
|
||||
f"{idx + 1}. {rule}",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=0.9
|
||||
),
|
||||
margin_bottom=0.8,
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
overflow="wrap"
|
||||
) for idx, rule in enumerate(RULES)],
|
||||
Spacer(min_height=1)
|
||||
)
|
||||
),
|
||||
Spacer(grow_y=True)
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -20,3 +20,5 @@ from .ManageUsersPage import ManageUsersPage
|
||||
from .ManageCateringPage import ManageCateringPage
|
||||
from .ManageTournamentsPage import ManageTournamentsPage
|
||||
from .OverviewPage import OverviewPage
|
||||
from .TournamentDetailsPage import TournamentDetailsPage
|
||||
from .TournamentRulesPage import TournamentRulesPage
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
|
||||
from datetime import date, datetime
|
||||
from pprint import pprint
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
|
||||
@@ -11,8 +12,11 @@ from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, Cateri
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount, CateringOrderStatus
|
||||
from src.ezgg_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
|
||||
from src.ezgg_lan_manager.types.News import News
|
||||
from src.ezgg_lan_manager.types.Participant import Participant
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ezgg_lan_manager.types.Ticket import Ticket
|
||||
from src.ezgg_lan_manager.types.Tournament import Tournament
|
||||
from src.ezgg_lan_manager.types.TournamentBase import GameTitle, TournamentFormat, TournamentStatus, ParticipantType
|
||||
from src.ezgg_lan_manager.types.Transaction import Transaction
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
|
||||
@@ -81,6 +85,52 @@ class DatabaseService:
|
||||
last_updated_at=data[11]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parse_tournament_format(format_as_string: str) -> TournamentFormat:
|
||||
if format_as_string == "SE_BO_1":
|
||||
return TournamentFormat.SINGLE_ELIMINATION_BO_1
|
||||
elif format_as_string == "SE_BO_3":
|
||||
return TournamentFormat.SINGLE_ELIMINATION_BO_3
|
||||
elif format_as_string == "SE_BO_5":
|
||||
return TournamentFormat.SINGLE_ELIMINATION_BO_5
|
||||
elif format_as_string == "DE_BO_1":
|
||||
return TournamentFormat.DOUBLE_ELIMINATION_BO_1
|
||||
elif format_as_string == "DE_BO_3":
|
||||
return TournamentFormat.DOUBLE_ELIMINATION_BO_3
|
||||
elif format_as_string == "DE_BO_5":
|
||||
return TournamentFormat.DOUBLE_ELIMINATION_BO_5
|
||||
else:
|
||||
# If this happens, database is FUBAR
|
||||
raise RuntimeError(f"Unknown TournamentFormat: {format_as_string}")
|
||||
|
||||
@staticmethod
|
||||
def _parse_tournament_status(status_as_string: str) -> TournamentStatus:
|
||||
if status_as_string == "CLOSED":
|
||||
return TournamentStatus.CLOSED
|
||||
elif status_as_string == "OPEN":
|
||||
return TournamentStatus.OPEN
|
||||
elif status_as_string == "COMPLETED":
|
||||
return TournamentStatus.COMPLETED
|
||||
elif status_as_string == "CANCELED":
|
||||
return TournamentStatus.CANCELED
|
||||
elif status_as_string == "INVITE_ONLY":
|
||||
return TournamentStatus.INVITE_ONLY
|
||||
elif status_as_string == "ONGOING":
|
||||
return TournamentStatus.ONGOING
|
||||
else:
|
||||
# If this happens, database is FUBAR
|
||||
raise RuntimeError(f"Unknown TournamentStatus: {status_as_string}")
|
||||
|
||||
@staticmethod
|
||||
def _parse_participant_type(participant_type_as_string: str) -> ParticipantType:
|
||||
if participant_type_as_string == "PLAYER":
|
||||
return ParticipantType.PLAYER
|
||||
elif participant_type_as_string == "TEAM":
|
||||
return ParticipantType.TEAM
|
||||
else:
|
||||
# If this happens, database is FUBAR
|
||||
raise RuntimeError(f"Unknown ParticipantType: {participant_type_as_string}")
|
||||
|
||||
async def get_user_by_name(self, user_name: str) -> Optional[User]:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
@@ -787,3 +837,97 @@ class DatabaseService:
|
||||
return await self.remove_profile_picture(user_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error deleting user profile picture: {e}")
|
||||
|
||||
async def get_all_tournaments(self) -> list[Tournament]:
|
||||
logger.info(f"Polling Tournaments...")
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"""
|
||||
SELECT
|
||||
/* =======================
|
||||
Tournament
|
||||
======================= */
|
||||
t.id AS tournament_id,
|
||||
t.name AS tournament_name,
|
||||
t.description AS tournament_description,
|
||||
t.format AS tournament_format,
|
||||
t.start_time,
|
||||
t.status AS tournament_status,
|
||||
t.max_participants,
|
||||
t.created_at,
|
||||
|
||||
/* =======================
|
||||
Game Title
|
||||
======================= */
|
||||
gt.id AS game_title_id,
|
||||
gt.name AS game_title_name,
|
||||
gt.description AS game_title_description,
|
||||
gt.web_link AS game_title_web_link,
|
||||
gt.image_name AS game_title_image_name,
|
||||
|
||||
/* =======================
|
||||
Tournament Participant
|
||||
======================= */
|
||||
tp.id AS participant_id,
|
||||
tp.user_id,
|
||||
tp.participant_type,
|
||||
tp.seed,
|
||||
tp.joined_at
|
||||
|
||||
FROM tournaments t
|
||||
JOIN game_titles gt
|
||||
ON gt.id = t.game_title_id
|
||||
|
||||
LEFT JOIN tournament_participants tp
|
||||
ON tp.tournament_id = t.id
|
||||
|
||||
ORDER BY
|
||||
t.id,
|
||||
tp.seed IS NULL,
|
||||
tp.seed;
|
||||
|
||||
"""
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.get_all_tournaments()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting tournaments: {e}")
|
||||
|
||||
tournaments = []
|
||||
current_tournament: Optional[Tournament] = None
|
||||
for row in await cursor.fetchall():
|
||||
if current_tournament is None or current_tournament.id != row["tournament_id"]:
|
||||
if current_tournament is not None:
|
||||
tournaments.append(current_tournament)
|
||||
current_tournament = Tournament(
|
||||
id_=row["tournament_id"],
|
||||
name=row["tournament_name"],
|
||||
description=row["tournament_description"],
|
||||
game_title=GameTitle(
|
||||
name=row["game_title_name"],
|
||||
description=row["game_title_description"],
|
||||
web_link=row["game_title_web_link"],
|
||||
image_name=row["game_title_image_name"]
|
||||
),
|
||||
format_=self._parse_tournament_format(row["tournament_format"]),
|
||||
start_time=row["start_time"],
|
||||
status=self._parse_tournament_status(row["tournament_status"]),
|
||||
participants=[Participant(id_=row["user_id"], participant_type=self._parse_participant_type(row["participant_type"]))],
|
||||
matches=None, # ToDo: Implement
|
||||
rounds=[], # ToDo: Implement
|
||||
max_participants=row["max_participants"]
|
||||
)
|
||||
else:
|
||||
current_tournament.add_participant(
|
||||
Participant(id_=row["user_id"], participant_type=self._parse_participant_type(row["participant_type"]))
|
||||
)
|
||||
else:
|
||||
tournaments.append(current_tournament)
|
||||
|
||||
return tournaments
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
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
|
||||
self._cache: dict[int, Tournament] = {}
|
||||
self._cache_dirty: bool = True # Setting this flag invokes cache update on next read
|
||||
|
||||
# This overrides the database access and is meant for easy development.
|
||||
# Set to None before merging back into main.
|
||||
self._dev_data = [
|
||||
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, 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 _update_cache(self) -> None:
|
||||
tournaments = await self._db_service.get_all_tournaments()
|
||||
for tournament in tournaments:
|
||||
self._cache[tournament.id] = tournament
|
||||
self._cache_dirty = False
|
||||
|
||||
|
||||
async def get_tournaments(self) -> list[Tournament]:
|
||||
if self._cache_dirty:
|
||||
await self._update_cache()
|
||||
return list(self._cache.values())
|
||||
|
||||
async def get_tournament_by_id(self, tournament_id: int) -> Optional[Tournament]:
|
||||
if self._cache_dirty:
|
||||
await self._update_cache()
|
||||
return self._cache.get(tournament_id, None)
|
||||
@@ -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"
|
||||
@@ -2,10 +2,9 @@ from src.ezgg_lan_manager.types.TournamentBase import ParticipantType
|
||||
|
||||
|
||||
class Participant:
|
||||
def __init__(self, id_: int, display_name: str, participant_type: ParticipantType) -> None:
|
||||
def __init__(self, id_: int, participant_type: ParticipantType) -> None:
|
||||
self._id = id_
|
||||
self._participant_type = participant_type
|
||||
self._display_name = display_name
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
@@ -14,7 +13,3 @@ class Participant:
|
||||
@property
|
||||
def participant_type(self) -> ParticipantType:
|
||||
return self._participant_type
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
return self._display_name
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -9,29 +9,30 @@ 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
|
||||
|
||||
# Generic Participants
|
||||
self.participant_a = Participant(1, "CoolUserName", ParticipantType.PLAYER)
|
||||
self.participant_b = Participant(2, "CrazyUserName", ParticipantType.PLAYER)
|
||||
self.participant_c = Participant(3, "FunnyUserName", ParticipantType.PLAYER)
|
||||
self.participant_a = Participant(1, ParticipantType.PLAYER)
|
||||
self.participant_b = Participant(2, ParticipantType.PLAYER)
|
||||
self.participant_c = Participant(3, 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)
|
||||
|
||||
Reference in New Issue
Block a user