From ccdf8fcb51a47b79d5a1c0042699b0d60c3c304c Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Mon, 14 Oct 2024 12:00:14 +0200 Subject: Changed the courts to dictionaries --- templates/court.j2 | 2 +- templates/courts.j2 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'templates') diff --git a/templates/court.j2 b/templates/court.j2 index 425e7d5..5eeb65a 100644 --- a/templates/court.j2 +++ b/templates/court.j2 @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/templates/courts.j2 b/templates/courts.j2 index 64fd44a..4e3cb3a 100644 --- a/templates/courts.j2 +++ b/templates/courts.j2 @@ -1,8 +1,8 @@
-{% for court in courts %} +{% for key, value in courts.items() %}
- +
{% endfor %}
-- cgit v1.3.1 From 600b6afff3066d5fc9c4eca659f969322e670a77 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Tue, 15 Oct 2024 12:07:42 +0200 Subject: Cleaned up the code significantly --- app.py | 58 ++++++---------- controller.py | 102 ++++++++++++++++++++++++++++ model.py | 185 ++++++++++++--------------------------------------- templates/courts.j2 | 5 +- templates/games.j2 | 19 +++--- templates/index.j2 | 115 ++++++++++++++++++-------------- templates/player.j2 | 7 +- templates/players.j2 | 4 +- 8 files changed, 251 insertions(+), 244 deletions(-) create mode 100644 controller.py (limited to 'templates') diff --git a/app.py b/app.py index 5c39c0d..85dd4f0 100644 --- a/app.py +++ b/app.py @@ -1,20 +1,18 @@ from os import getenv +from controller import Session from dotenv import load_dotenv -from flask import Flask, abort, render_template -from model import CourtList, PlayerList, GameList +from model import CourtList, PlayerList +from flask import Flask, render_template, request + load_dotenv() -PLAYERS = PlayerList(location="test_data_large.csv") -COURTS = CourtList(12) -GAME_GENERATOR = GameList() -GAME_GENERATOR.clear(COURTS) +S = Session(courts=CourtList(12), players=PlayerList("test_data_large.csv")) app = Flask("Match Up! Backend") -env_config = getenv("PROD_APP_SETTINGS", "config.DevelopmentConfig") -app.config.from_object(env_config) +app.config.from_object(getenv("PROD_APP_SETTINGS", "config.DevelopmentConfig")) @app.route("/", methods=["GET"]) @@ -22,54 +20,36 @@ def home(): return render_template("index.j2") -@app.route("/random-games", methods=["POST"]) +@app.route("/propose", methods=["POST"]) def get_random_games(): - print("Creating new games...") - GAME_GENERATOR.create_random_games(COURTS, PLAYERS) - return render_template("games.j2", games=GAME_GENERATOR.games) - - -@app.route("/skilled-games", methods=["POST"]) -def get_skilled_games(): - print("Creating new games...") - GAME_GENERATOR.create_skilled_games(COURTS, PLAYERS) - return render_template("games.j2", games=GAME_GENERATOR.games) + levels = int(request.form["type"].split()[0]) + return render_template("games.j2", title="Proposal", games=S.propose_round(levels)) -@app.route("/clear-games", methods=["POST"]) +@app.route("/clear", methods=["POST"]) def clear_games(): - print("Clearing games...") - GAME_GENERATOR.clear(COURTS) - return render_template("games.j2", games=GAME_GENERATOR.games) - - -@app.route("/game-list", methods=["GET"]) -def get_list_of_games(): - return render_template("games.j2", games=GAME_GENERATOR.games) + return render_template("games.j2", title=f"Round: {S.round}", games=S.clear_round()) @app.route("/player-toggle/", methods=["POST"]) def toggle_player(player_request): - selection = PLAYERS.get_player(player_request) - if len(selection) != 1: - abort(404) - PLAYERS.toggle_player(player_request) - selection = PLAYERS.get_player(player_request) - return render_template("player.j2", player=selection[0]) + id = int(player_request) + status, name = S.players.toggle_player_status(id) + return render_template("player.j2", id=id, status=status, name=name) -@app.route("/player-list", methods=["GET"]) +@app.route("/players", methods=["GET"]) def get_list_of_players(): - return render_template("players.j2", players=PLAYERS.players) + return render_template("players.j2", players=S.players.get_players()) @app.route("/court-toggle/", methods=["POST"]) def toggle_court(court_number): id = int(court_number) - status = COURTS.toggle_court_status(id) + status = S.courts.toggle_court_status(id) return render_template("court.j2", id=id, status=status) -@app.route("/court-list", methods=["GET"]) +@app.route("/courts", methods=["GET"]) def get_list_of_courts(): - return render_template("courts.j2", courts=COURTS.courts) + return render_template("courts.j2", courts=S.courts.get_courts()) diff --git a/controller.py b/controller.py new file mode 100644 index 0000000..45ae2bf --- /dev/null +++ b/controller.py @@ -0,0 +1,102 @@ +from math import floor +from random import sample +from operator import attrgetter +from dataclasses import dataclass, field +from model import Team, Game, PlayerList, CourtList + +PLAYER_PER_COURT = 4 + + +def _create_placeholders(courts: dict, name: str): + dummy_team = Team(player1=name, player2=name) + return [Game(court, dummy_team, dummy_team) for court in courts] + + +def create_game(court: int, players: dict): + ids = list(players.keys()) + team1 = Team(player1=players[ids[0]].name, player2=players[ids[3]].name) + team2 = Team(player1=players[ids[1]].name, player2=players[ids[2]].name) + return Game(court, team1, team2) + + +def select_players(players: dict, count: int): + identifiers = sample(sorted(players), count) + return {id: players[id] for id in identifiers} + + +def get_random_combinations(players: dict) -> list[dict]: + ids = list(players.keys()) + clumps = [ + ids[i : i + PLAYER_PER_COURT] for i in range(0, len(players), PLAYER_PER_COURT) + ] + combinations = [{id: players[id] for id in clump} for clump in clumps] + return combinations + + +def create_random_games(courts: CourtList, players: PlayerList): + active_courts, reserved_courts = courts.separate_courts() + + # Block the courts for other activities + reserved_games = _create_placeholders(reserved_courts, "RESERVED") + + # Check if there are too many courts + possible_players = players.separate_players()[0] + possible_games = floor(len(possible_players) / PLAYER_PER_COURT) + + # Create the games + selected_players = select_players( + possible_players, possible_games * PLAYER_PER_COURT + ) + combinations = get_random_combinations(selected_players) + + used_courts = dict(list(active_courts.items())[:possible_games]) + active_games = [ + create_game(court, clump) for court, clump in zip(used_courts, combinations) + ] + + # Create dummy games if there are empty courts + dummy_games = [] + if possible_games < len(active_courts): + unused_courts = dict(list(active_courts.items())[possible_games:]) + dummy_games = _create_placeholders(unused_courts, "---") + + # Return the list of games + games = active_games + dummy_games + reserved_games + games.sort(key=attrgetter("court")) + games = {idx: value for idx, value in enumerate(games)} + return games + + +@dataclass +class Session: + courts: CourtList + players: PlayerList + round: int = 0 + games: dict = field(default_factory=dict) + proposal: dict = field(default_factory=dict) + history: list[dict] = field(default_factory=list) + + def __post_init__(self): + self.history = [] + active_courts, inactive_courts = self.courts.separate_courts() + dummy_games = _create_placeholders(active_courts, "---") + reserved_games = _create_placeholders(inactive_courts, "RESERVED") + games = dummy_games + reserved_games + games.sort(key=attrgetter("court")) + games = {idx: value for idx, value in enumerate(games)} + self.games = games + + def clear_round(self): + self.proposal = {} + return self.games + + def propose_round(self, levels: int = 10): + self.proposal = create_random_games(self.courts, self.players) + return self.proposal + + def confirm_round(self): + self.games = self.proposal + self.history.append(self.proposal) + self.proposal = {} + self.round += 1 + return self.games diff --git a/model.py b/model.py index bfaa9d5..c0b9dd3 100644 --- a/model.py +++ b/model.py @@ -1,89 +1,20 @@ -from math import floor from csv import reader +from typing import Tuple from pathlib import Path -from random import sample -from operator import attrgetter from dataclasses import dataclass -from typing import Tuple - - -@dataclass -class CourtList: - total: int - - def __post_init__(self) -> None: - self.courts = {i + 1: True for i in range(self.total)} - - def get_court_status(self, court_number: int) -> bool: - return self.courts[court_number] - - def toggle_court_status(self, court_number: int) -> None: - current_status = self.courts[court_number] - self.courts[court_number] = not current_status - return not current_status - - def separate_courts(self) -> Tuple[dict, dict]: - active_courts = {k: v for k, v in self.courts.items() if v} - inactive_courts = {k: v for k, v in self.courts.items() if not v} - return active_courts, inactive_courts @dataclass class Player: name: str skill: int = 0 - status: bool = True - - -def create_player_from_row(row: list[str]) -> Player: - return Player(name=row[0], skill=row[1], status=(row[2] == "True")) - - -class PlayerList: - - def __init__(self, location: Path = "players.csv"): - with open(location, newline="") as file: - data_reader = reader(file) - next(data_reader, None) - players = [create_player_from_row(row) for row in data_reader] - players.sort(key=attrgetter("name")) - self.players = players - - def get_player(self, player_request: str): - players = [player for player in self.players if player.name == player_request] - players.sort(key=attrgetter("name")) - return players - - def get_active_players(self): - players = [player for player in self.players if player.status] - players.sort(key=attrgetter("name")) - return players - - def toggle_player(self, player_request: Player): - self.players = [ - ( - Player( - name=player.name, - skill=player.skill, - status=not player.status, - ) - if player.name == player_request - else player - ) - for player in self.players - ] - - def __str__(self): - display_string = "" - for player in self.players: - display_string += player.__str__() + "\n" - return display_string + status: bool = False @dataclass class Team: - player1: Player - player2: Player + player1: str + player2: str @dataclass @@ -93,85 +24,53 @@ class Game: team2: Team -DUMMY_PLAYER = Player(name="-") -DUMMY_TEAM = Team(player1=DUMMY_PLAYER, player2=DUMMY_PLAYER) - - -def create_dummy_games(courts: dict): - return [Game(court, DUMMY_TEAM, DUMMY_TEAM) for court, _ in courts.items()] +@dataclass +class CourtList: + total: int + def __post_init__(self) -> None: + self.courts = {i + 1: True for i in range(self.total)} -RESERVED_PLAYER = Player(name="Reserved") -RESERVED_TEAM = Team(player1=RESERVED_PLAYER, player2=RESERVED_PLAYER) + def get_court_status(self, court_number: int) -> bool: + return self.courts[court_number] + def toggle_court_status(self, court_number: int) -> None: + new_status = not self.courts[court_number] + self.courts[court_number] = new_status + return new_status -def create_reserved_games(courts: dict): - return [Game(court, RESERVED_TEAM, RESERVED_TEAM) for court, _ in courts.items()] + def get_courts(self) -> dict: + return self.courts + def separate_courts(self) -> Tuple[dict, dict]: + active_courts = {k: v for k, v in self.courts.items() if v} + inactive_courts = {k: v for k, v in self.courts.items() if not v} + return active_courts, inactive_courts -PLAYER_PER_COURT = 4 +@dataclass +class PlayerList: + location: Path -def create_game(court: int, players: list[Player]): - team1 = Team(player1=players[0], player2=players[3]) - team2 = Team(player1=players[1], player2=players[2]) - return Game(court, team1, team2) + def __post_init__(self) -> None: + with open(self.location, newline="") as file: + pointer = reader(file) + next(pointer, None) + players = {id: Player(row[0], row[1]) for id, row in enumerate(pointer)} + self.players = players + def get_player_status(self, player_number: int) -> bool: + return self.players[player_number].status -def create_possible_games(courts: dict, players: list[Player]): - count = len(players) - clumped_players = [ - players[i : i + PLAYER_PER_COURT] for i in range(0, count, PLAYER_PER_COURT) - ] - games = [create_game(court, clump) for court, clump in zip(courts, clumped_players)] - return games + def toggle_player_status(self, player_number: int) -> Tuple[bool, str]: + new_status = not self.players[player_number].status + self.players[player_number].status = new_status + return new_status, self.players[player_number].name + def get_players(self) -> dict: + return self.players -@dataclass -class GameList: - - def clear(self, court_list: CourtList): - active_courts, inactive_courts = court_list.separate_courts() - dummy_games = create_dummy_games(active_courts) - reserved_games = create_reserved_games(inactive_courts) - games = dummy_games + reserved_games - games.sort(key=attrgetter("court")) - self.games = games - - def create_random_games(self, courts: CourtList, player_list: PlayerList): - active_players = player_list.get_active_players() - active_courts, inactive_courts = courts.separate_courts() - possible_games = floor(len(active_players) / PLAYER_PER_COURT) - if possible_games > len(active_courts): - possible_games = len(active_courts) - current_players = sample(active_players, possible_games * PLAYER_PER_COURT) - active_games = create_possible_games( - dict(list(active_courts.items())[:possible_games]), current_players - ) - dummy_games = create_dummy_games( - dict(list(active_courts.items())[possible_games:]) - ) - reserved_games = create_reserved_games(inactive_courts) - games = active_games + dummy_games + reserved_games - games.sort(key=attrgetter("court")) - self.games = games - - def create_skilled_games(self, courts: CourtList, player_list: PlayerList): - active_players = player_list.get_active_players() - active_courts, inactive_courts = courts.separate_courts() - possible_games = floor(len(active_players) / PLAYER_PER_COURT) - if possible_games > len(active_courts): - possible_games = len(active_courts) - current_players = sample(active_players, possible_games * PLAYER_PER_COURT) - current_players.sort(key=attrgetter("skill")) - - active_games = create_possible_games( - dict(list(active_courts.items())[:possible_games]), current_players - ) - dummy_games = create_dummy_games( - dict(list(active_courts.items())[possible_games:]) - ) - reserved_games = create_reserved_games(inactive_courts) - games = active_games + dummy_games + reserved_games - games.sort(key=attrgetter("court")) - self.games = games + def separate_players(self) -> Tuple[dict, dict]: + active_players = {k: v for k, v in self.players.items() if v.status} + inactive_players = {k: v for k, v in self.players.items() if not v.status} + return active_players, inactive_players diff --git a/templates/courts.j2 b/templates/courts.j2 index 4e3cb3a..5dc64f9 100644 --- a/templates/courts.j2 +++ b/templates/courts.j2 @@ -2,7 +2,10 @@
{% for key, value in courts.items() %}
- +
{% endfor %}
diff --git a/templates/games.j2 b/templates/games.j2 index b070b16..55cabf4 100644 --- a/templates/games.j2 +++ b/templates/games.j2 @@ -1,17 +1,20 @@ + + + - - + + -{% for game in games %} +{% for _, game in games.items() %} - - + {% endfor %} diff --git a/templates/index.j2 b/templates/index.j2 index cfd32b5..3a2118b 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -1,52 +1,67 @@ - - - - 🏸Match Up! - - - - - - -
-
-

🏸The Smashing Fellows

-
-
-
-

Games

-
-
-
-
-
-
-
-
-
-
-
-

Courts

-
-
-
-

Players

-
-
- - - \ No newline at end of file + + + + 🏸Match Up! + + + + + + +
+
+

🏸The Smashing Fellows

+
+
+
+

Games

+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+

Courts

+
+
+
+

Players

+
+
+ + + diff --git a/templates/player.j2 b/templates/player.j2 index 5b70df6..68bea2a 100644 --- a/templates/player.j2 +++ b/templates/player.j2 @@ -1 +1,6 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/templates/players.j2 b/templates/players.j2 index 6179dbb..7a13672 100644 --- a/templates/players.j2 +++ b/templates/players.j2 @@ -1,8 +1,8 @@
-{% for player in players %} +{% for id, player in players.items() %}
- +
{% endfor %}
\ No newline at end of file -- cgit v1.3.1 From d304a470e5a9fae6fc0c8975f050cb7aa290f74a Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Wed, 16 Oct 2024 11:08:15 +0200 Subject: First working version with history --- app.py | 21 ++++--- controller.py | 161 +++++++++++++++++++++++++++------------------------ model.py | 19 ++++-- templates/court.j2 | 2 +- templates/courts.j2 | 2 +- templates/games.j2 | 6 +- templates/index.j2 | 15 +++-- templates/player.j2 | 2 +- templates/players.j2 | 2 +- utilities.py | 23 ++++++++ 10 files changed, 151 insertions(+), 102 deletions(-) create mode 100644 utilities.py (limited to 'templates') diff --git a/app.py b/app.py index 85dd4f0..87151fe 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ from os import getenv -from controller import Session from dotenv import load_dotenv +from controller import RoundGenerator from model import CourtList, PlayerList from flask import Flask, render_template, request @@ -8,7 +8,7 @@ from flask import Flask, render_template, request load_dotenv() -S = Session(courts=CourtList(12), players=PlayerList("test_data_large.csv")) +rg = RoundGenerator(courts=CourtList(12), players=PlayerList("test_data_large.csv")) app = Flask("Match Up! Backend") @@ -23,33 +23,38 @@ def home(): @app.route("/propose", methods=["POST"]) def get_random_games(): levels = int(request.form["type"].split()[0]) - return render_template("games.j2", title="Proposal", games=S.propose_round(levels)) + return render_template("games.j2", title="Proposal", games=rg.propose(levels)) + + +@app.route("/confirm", methods=["POST"]) +def confirm_games(): + return render_template("games.j2", title=f"Round: {rg.round}", games=rg.confirm()) @app.route("/clear", methods=["POST"]) def clear_games(): - return render_template("games.j2", title=f"Round: {S.round}", games=S.clear_round()) + return render_template("games.j2", title=f"Round: {rg.round}", games=rg.clear()) @app.route("/player-toggle/", methods=["POST"]) def toggle_player(player_request): id = int(player_request) - status, name = S.players.toggle_player_status(id) + status, name = rg.players.toggle_player_status(id) return render_template("player.j2", id=id, status=status, name=name) @app.route("/players", methods=["GET"]) def get_list_of_players(): - return render_template("players.j2", players=S.players.get_players()) + return render_template("players.j2", players=rg.players.get_players()) @app.route("/court-toggle/", methods=["POST"]) def toggle_court(court_number): id = int(court_number) - status = S.courts.toggle_court_status(id) + status = rg.courts.toggle_court_status(id) return render_template("court.j2", id=id, status=status) @app.route("/courts", methods=["GET"]) def get_list_of_courts(): - return render_template("courts.j2", courts=S.courts.get_courts()) + return render_template("courts.j2", courts=rg.courts.get_courts()) diff --git a/controller.py b/controller.py index 45ae2bf..751586e 100644 --- a/controller.py +++ b/controller.py @@ -1,102 +1,109 @@ -from math import floor -from random import sample +from typing import Tuple from operator import attrgetter -from dataclasses import dataclass, field -from model import Team, Game, PlayerList, CourtList +from dataclasses import dataclass +from utilities import sample_list +from model import Team, Game, PlayerList, CourtList, MAX_LEVEL, PLAYER_PER_COURT -PLAYER_PER_COURT = 4 - -def _create_placeholders(courts: dict, name: str): +def _create_placeholders(courts: dict, name: str) -> list[Game]: dummy_team = Team(player1=name, player2=name) - return [Game(court, dummy_team, dummy_team) for court in courts] - - -def create_game(court: int, players: dict): - ids = list(players.keys()) - team1 = Team(player1=players[ids[0]].name, player2=players[ids[3]].name) - team2 = Team(player1=players[ids[1]].name, player2=players[ids[2]].name) - return Game(court, team1, team2) + games = [Game(court, dummy_team, dummy_team) for court in courts] + return games -def select_players(players: dict, count: int): - identifiers = sample(sorted(players), count) - return {id: players[id] for id in identifiers} +def _normalize_skill(players: dict, new_max: int) -> int: + player_levels = [] + for _, player in players.items(): + skill_level = player.skill + new_level = round((new_max - 1) * (skill_level / MAX_LEVEL)) + 1 + player_levels.append(new_level) + return player_levels -def get_random_combinations(players: dict) -> list[dict]: - ids = list(players.keys()) - clumps = [ - ids[i : i + PLAYER_PER_COURT] for i in range(0, len(players), PLAYER_PER_COURT) - ] +def _create_groups_of_four_players(players: dict, new_max: int) -> list[dict]: + player_levels = _normalize_skill(players, new_max) + identifiers = sample_list([*players], player_levels) + clumps = [] + for i in range(0, len(players), PLAYER_PER_COURT): + clumps.append(identifiers[i : i + PLAYER_PER_COURT]) combinations = [{id: players[id] for id in clump} for clump in clumps] return combinations -def create_random_games(courts: CourtList, players: PlayerList): - active_courts, reserved_courts = courts.separate_courts() - - # Block the courts for other activities - reserved_games = _create_placeholders(reserved_courts, "RESERVED") - - # Check if there are too many courts - possible_players = players.separate_players()[0] - possible_games = floor(len(possible_players) / PLAYER_PER_COURT) - - # Create the games - selected_players = select_players( - possible_players, possible_games * PLAYER_PER_COURT - ) - combinations = get_random_combinations(selected_players) - - used_courts = dict(list(active_courts.items())[:possible_games]) - active_games = [ - create_game(court, clump) for court, clump in zip(used_courts, combinations) - ] - - # Create dummy games if there are empty courts - dummy_games = [] - if possible_games < len(active_courts): - unused_courts = dict(list(active_courts.items())[possible_games:]) - dummy_games = _create_placeholders(unused_courts, "---") - - # Return the list of games - games = active_games + dummy_games + reserved_games - games.sort(key=attrgetter("court")) - games = {idx: value for idx, value in enumerate(games)} +def _create_games(courts: dict, players: list[dict], levels: int): + grouped_players = _create_groups_of_four_players(players, levels) + games = [] + for court, players in zip(courts, grouped_players): + ids = [*players] + team1 = Team(player1=players[ids[0]].name, player2=players[ids[3]].name) + team2 = Team(player1=players[ids[1]].name, player2=players[ids[2]].name) + games.append(Game(court, team1, team2)) return games @dataclass -class Session: +class RoundGenerator: courts: CourtList players: PlayerList round: int = 0 - games: dict = field(default_factory=dict) - proposal: dict = field(default_factory=dict) - history: list[dict] = field(default_factory=list) - - def __post_init__(self): - self.history = [] - active_courts, inactive_courts = self.courts.separate_courts() - dummy_games = _create_placeholders(active_courts, "---") - reserved_games = _create_placeholders(inactive_courts, "RESERVED") - games = dummy_games + reserved_games + proposal: tuple = tuple() + + def __post_init__(self) -> None: + self.history = {key: 0 for key, _ in self.players.get_players().items()} + self.games = self._generate_proposal(MAX_LEVEL)[0] + + def _separate_courts(self, courts: dict) -> Tuple[dict, dict]: + possible_games = min(len(courts), self.players.get_possible_game_count()) + courts_as_list = list(courts.items()) + used_courts = dict(courts_as_list[:possible_games]) + unused_courts = dict(courts_as_list[possible_games:]) + return used_courts, unused_courts + + def _select_players(self, count: int) -> dict: + active_players = self.players.get_active_players() + identifiers = [*active_players] + play_count = [self.history[player_id] for player_id in active_players] + ids = sample_list(identifiers, play_count, count) + return {player_id: active_players[player_id] for player_id in ids} + + def _create_inactive_games(self) -> list[Game]: + active_courts, reserved_courts = self.courts.separate_courts() + reserved_games = _create_placeholders(reserved_courts, "RESERVED") + _, unused_courts = self._separate_courts(active_courts) + blank_games = _create_placeholders(unused_courts, "---") + games = reserved_games + blank_games + return games + + def _create_active_games(self, total_levels: int): + active_courts, _ = self.courts.separate_courts() + used_courts, _ = self._separate_courts(active_courts) + required_player_count = len(used_courts) * PLAYER_PER_COURT + selected_players = self._select_players(required_player_count) + games = _create_games(used_courts, selected_players, total_levels) + return games, selected_players + + def _generate_proposal(self, total_levels: int) -> Tuple[list[Game], dict]: + inactive_games = self._create_inactive_games() + active_games, selected_players = self._create_active_games(total_levels) + games = active_games + inactive_games games.sort(key=attrgetter("court")) - games = {idx: value for idx, value in enumerate(games)} - self.games = games + return games, selected_players - def clear_round(self): - self.proposal = {} + def clear(self) -> list[Game]: + self.proposal = tuple() return self.games - def propose_round(self, levels: int = 10): - self.proposal = create_random_games(self.courts, self.players) - return self.proposal - - def confirm_round(self): - self.games = self.proposal - self.history.append(self.proposal) - self.proposal = {} + def propose(self, levels: int) -> list[Game]: + self.proposal = self._generate_proposal(levels) + return self.proposal[0] + + def confirm(self) -> list[Game]: + proposed_games, proposed_players = self.proposal + if self.games == proposed_games or len(proposed_games) == 0: + return self.games + self.games = proposed_games + for player_id in proposed_players: + self.history[player_id] = self.history[player_id] + 1 + self.proposal = tuple() self.round += 1 return self.games diff --git a/model.py b/model.py index c0b9dd3..3e76085 100644 --- a/model.py +++ b/model.py @@ -1,8 +1,13 @@ +from math import floor from csv import reader from typing import Tuple from pathlib import Path from dataclasses import dataclass +MIN_LEVEL = 1 +MAX_LEVEL = 10 +PLAYER_PER_COURT = 4 + @dataclass class Player: @@ -56,7 +61,9 @@ class PlayerList: with open(self.location, newline="") as file: pointer = reader(file) next(pointer, None) - players = {id: Player(row[0], row[1]) for id, row in enumerate(pointer)} + players = { + id: Player(row[0], int(row[1])) for id, row in enumerate(pointer) + } self.players = players def get_player_status(self, player_number: int) -> bool: @@ -70,7 +77,11 @@ class PlayerList: def get_players(self) -> dict: return self.players - def separate_players(self) -> Tuple[dict, dict]: + def get_possible_game_count(self) -> int: + active_player_count = len(self.get_active_players()) + possible_games = floor(active_player_count / PLAYER_PER_COURT) + return possible_games + + def get_active_players(self) -> Tuple[dict, dict]: active_players = {k: v for k, v in self.players.items() if v.status} - inactive_players = {k: v for k, v in self.players.items() if not v.status} - return active_players, inactive_players + return active_players diff --git a/templates/court.j2 b/templates/court.j2 index 5eeb65a..859c942 100644 --- a/templates/court.j2 +++ b/templates/court.j2 @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/templates/courts.j2 b/templates/courts.j2 index 5dc64f9..2bc8a95 100644 --- a/templates/courts.j2 +++ b/templates/courts.j2 @@ -2,7 +2,7 @@
{% for key, value in courts.items() %}
- diff --git a/templates/games.j2 b/templates/games.j2 index 55cabf4..50e1087 100644 --- a/templates/games.j2 +++ b/templates/games.j2 @@ -3,14 +3,14 @@
- + -{% for _, game in games.items() %} +{% for game in games %} - + {% for game in games %} - +
{{ title }}
Court Team 1 Team 2 Team 1 Team 2
{{ game.court.number }}{{ game.team1.player1.name }} - {{ game.team1.player2.name }} - {{ game.team2.player1.name }} - {{ game.team2.player2.name }} + {{ game.court }}{{ game.team1.player1 }} + {{ game.team1.player2 }} + {{ game.team2.player1 }} + {{ game.team2.player2 }}
{{ title }}
Court Court Team 1 Team 2
{{ game.court }}{{ game.court }} {{ game.team1.player1 }} {{ game.team1.player2 }} {{ game.team2.player1 }} diff --git a/templates/index.j2 b/templates/index.j2 index 3a2118b..40d0eb1 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -9,10 +9,13 @@ @@ -26,7 +29,7 @@

Games

-
+
+
\ No newline at end of file diff --git a/templates/index.j2 b/templates/index.j2 index 40d0eb1..42fc43e 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -29,7 +29,18 @@

Games

-
+
+
+ +
+
+
+ +
+
+
+
+
"+n+"
",1);case"col":return s(""+n+"
",2);case"tr":return s(""+n+"
",2);case"td":case"th":return s(""+n+"
",3);case"script":case"style":return s("
"+n+"
",1);default:return s(n,0)}}function ie(e){if(e){e()}}function I(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return I(e,"Function")}function P(e){return I(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function M(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function D(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){b(e);return null}}function U(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function B(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function t(e){return Tr(re().body,function(){return eval(e)})}function F(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function j(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function _(e,t){e=p(e);if(t){setTimeout(function(){_(e);e=null},t)}else{e.parentElement.removeChild(e)}}function z(e,t,r){e=p(e);if(r){setTimeout(function(){z(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=p(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function $(e,t){e=p(e);e.classList.toggle(t)}function W(e,t){e=p(e);oe(e.parentElement.children,function(e){n(e,t)});z(e,t)}function v(e,t){e=p(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function g(e,t){return e.substring(0,t.length)===t}function G(e,t){return e.substring(e.length-t.length)===t}function J(e){var t=e.trim();if(g(t,"<")&&G(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function Z(e,t){if(t.indexOf("closest ")===0){return[v(e,J(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,J(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[K(e,J(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[Y(e,J(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(J(t))}}var K=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return Z(e,t)[0]}else{return Z(re().body,e)[0]}}function p(e){if(I(e,"String")){return C(e)}else{return e}}function ve(e,t,r){if(k(t)){return{target:re().body,event:e,listener:t}}else{return{target:p(e),event:t,listener:r}}}function de(t,r,n){jr(function(){var e=ve(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=k(r);return e?r:n}function ge(t,r,n){jr(function(){var e=ve(t,r,n);e.target.removeEventListener(e.event,e.listener)});return k(r)?r:n}var pe=re().createElement("output");function me(e,t){var r=ne(e,t);if(r){if(r==="this"){return[xe(e,t)]}else{var n=Z(e,r);if(n.length===0){b('The selector "'+r+'" on '+t+" returned no matches!");return[pe]}else{return n}}}}function xe(e,t){return c(e,function(e){return te(e,t)!=null})}function ye(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return xe(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function be(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!Se(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){Fe(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function Ce(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(var a=0;a0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();we(e,i);s.tasks.push(function(){we(e,a)})}}})}function Oe(e){return function(){n(e,Q.config.addedClass);zt(e);Nt(e);qe(e);ce(e,"htmx:load")}}function qe(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function a(e,t,r,n){Te(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;z(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Oe(i))}}}function He(e,t){var r=0;while(r-1){var t=e.replace(H,"");var r=t.match(q);if(r){return r[2]}}}function je(e,t,r,n,i,a){i.title=Ve(n);var o=l(n);if(o){Ce(r,o,i);o=Be(r,o,a);Re(o);return Fe(e,r,t,o,i)}}function _e(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!P(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=Tr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Qe(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!t.test(e[0])){r+=e.shift()}return r}function tt(e){var t;if(e.length>0&&Ze.test(e[0])){e.shift();t=y(e,Ke).trim();e.shift()}else{t=y(e,x)}return t}var rt="input, textarea, select";function nt(e,t,r){var n=[];var i=Ye(t);do{y(i,Je);var a=i.length;var o=y(i,/[,\[\s]/);if(o!==""){if(o==="every"){var s={trigger:"every"};y(i,Je);s.pollInterval=d(y(i,/[,\[\s]/));y(i,Je);var l=et(e,i,"event");if(l){s.eventFilter=l}n.push(s)}else if(o.indexOf("sse:")===0){n.push({trigger:"sse",sseEvent:o.substr(4)})}else{var u={trigger:o};var l=et(e,i,"event");if(l){u.eventFilter=l}while(i.length>0&&i[0]!==","){y(i,Je);var f=i.shift();if(f==="changed"){u.changed=true}else if(f==="once"){u.once=true}else if(f==="consume"){u.consume=true}else if(f==="delay"&&i[0]===":"){i.shift();u.delay=d(y(i,x))}else if(f==="from"&&i[0]===":"){i.shift();if(Ze.test(i[0])){var c=tt(i)}else{var c=y(i,x);if(c==="closest"||c==="find"||c==="next"||c==="previous"){i.shift();var h=tt(i);if(h.length>0){c+=" "+h}}}u.from=c}else if(f==="target"&&i[0]===":"){i.shift();u.target=tt(i)}else if(f==="throttle"&&i[0]===":"){i.shift();u.throttle=d(y(i,x))}else if(f==="queue"&&i[0]===":"){i.shift();u.queue=y(i,x)}else if(f==="root"&&i[0]===":"){i.shift();u[f]=tt(i)}else if(f==="threshold"&&i[0]===":"){i.shift();u[f]=y(i,x)}else{fe(e,"htmx:syntax:error",{token:i.shift()})}}n.push(u)}}if(i.length===a){fe(e,"htmx:syntax:error",{token:i.shift()})}y(i,Je)}while(i[0]===","&&i.shift());if(r){r[t]=n}return n}function it(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Q.config.triggerSpecsCache;r=n&&n[t]||nt(e,t,n)}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,rt)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function at(e){ae(e).cancelled=true}function ot(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ct(r,e,Wt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}ot(e,t,r)}},r.pollInterval)}function st(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function lt(t,r,e){if(t.tagName==="A"&&st(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){ht(t,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(n,i,e,t)},r,e,true)})}}function ut(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function ft(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ct(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function ht(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=Z(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(ft(a,e)){return}if(l||ut(e,a)){e.preventDefault()}if(ct(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle>0){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay>0){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var vt=false;var dt=null;function gt(){if(!dt){dt=function(){vt=true};window.addEventListener("scroll",dt);setInterval(function(){if(vt){vt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){pt(e)})}},200)}}function pt(t){if(!o(t,"data-hx-revealed")&&X(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function mt(e,t,r){var n=D(r);for(var i=0;i=0){var t=wt(n);setTimeout(function(){xt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(yt(s)){return}var t=e.data;R(s,function(e){t=e.transformResponse(t,null,s)});var r=T(s);var n=l(t);var i=M(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(ut(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function wt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(e,t,r){var n=D(r);for(var i=0;i0){setTimeout(i,n)}else{i()}}function Ht(t,i,e){var a=false;oe(w,function(r){if(o(t,"hx-"+r)){var n=te(t,"hx-"+r);a=true;i.path=n;i.verb=r;e.forEach(function(e){Lt(t,e,i,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(r,n,e,t)})})}});return a}function Lt(n,e,t,r){if(e.sseEvent){Rt(n,r,e.sseEvent)}else if(e.trigger==="revealed"){gt();ht(n,r,t,e);pt(n)}else if(e.trigger==="intersect"){var i={};if(e.root){i.root=ue(n,e.root)}if(e.threshold){i.threshold=parseFloat(e.threshold)}var a=new IntersectionObserver(function(e){for(var t=0;t0){t.polling=true;ot(n,r,e)}else{ht(n,r,t,e)}}function At(e){if(!e.htmxExecuted&&Q.config.allowScriptTags&&(e.type==="text/javascript"||e.type==="module"||e.type==="")){var t=re().createElement("script");oe(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}var r=e.parentElement;try{r.insertBefore(t,e)}catch(e){b(e)}finally{if(e.parentElement){e.parentElement.removeChild(e)}}}}function Nt(e){if(h(e,"script")){At(e)}oe(f(e,"script"),function(e){At(e)})}function It(e){var t=e.attributes;if(!t){return false}for(var r=0;r0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Bt(o)}for(var l in r){Ft(e,l,r[l])}}}function jt(e){Ae(e);for(var t=0;tQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Yt(e){if(!U()){return null}e=B(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Zt();var r=T(t);var n=Ve(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Ue(t,e,r);nr(r.tasks);Jt=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function ar(e){er();e=e||location.pathname+location.search;var t=Yt(e);if(t){var r=l(t.content);var n=Zt();var i=T(n);Ue(n,r,i);nr(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);Jt=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{ir(e)}}}function or(e){var t=me(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function sr(e){var t=me(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function lr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ur(e,t){for(var r=0;r=0}function wr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!br(e)){n["show"]="top"}if(r){var i=D(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{b("Unknown modifier in hx-swap: "+o)}}}}return n}function Sr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function Er(t,r,n){var i=null;R(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(Sr(r)){return mr(n)}else{return pr(n)}}}function T(e){return{tasks:[],elts:[e]}}function Cr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Rr(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=Tr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return Rr(u(e),t,r,n)}function Tr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Or(e,t){return Rr(e,"hx-vars",true,t)}function qr(e,t){return Rr(e,"hx-vals",false,t)}function Hr(e){return le(Or(e),qr(e))}function Lr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Ar(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return t.test(e.getAllResponseHeaders())}function Nr(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||I(r,"String")){return he(e,t,null,null,{targetOverride:p(r),returnPromise:true})}else{return he(e,t,p(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:p(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Ir(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function kr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=g(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Mr;var X=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ye(n);if(u==null||u==pe){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var D=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:D,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var p=ne(n,"hx-sync");var m=null;var x=false;if(p){var B=p.split(":");var F=B[0].trim();if(F==="this"){g=xe(n,"hx-sync")}else{g=ue(n,F)}p=(B[1]||"drop").trim();f=ae(g);if(p==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(p==="abort"){if(f.xhr){ie(o);return l}else{x=true}}else if(p==="replace"){ce(g,"htmx:abort")}else if(p.indexOf("queue")===0){var V=p.split(" ");m=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(m==null){if(i){var y=ae(i);if(y&&y.triggerSpec&&y.triggerSpec.queue){m=y.triggerSpec.queue}}if(m==null){m="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(m==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=x;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=xr(n,u,S);if(t!=="get"&&!Sr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=dr(n,t);var C=_.errors;var R=_.values;if(a.values){R=le(R,a.values)}var z=Hr(n);var $=le(R,z);var T=yr($,n);if(Q.config.getCacheBusterParam&&t==="get"){T["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=Rr(n,"hx-request");var W=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:W,useUrlParams:q,parameters:T,unfilteredParameters:$,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;T=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(T).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=pr(T);if(L){A+="#"+L}}}if(!kr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Lr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:W,select:X,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=Ir(n);I.pathInfo.responsePath=Ar(b);M(n,I);lr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=or(n);var P=sr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:Er(b,n,T);b.send(Y);return l}function Pr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Mr(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){_e(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){er();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Nr("GET",r,v).then(function(){tr(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){if(f.getResponseHeader("HX-Retarget")==="this"){u.target=l}else{u.target=ue(l,f.getResponseHeader("HX-Retarget"))}}var d=Pr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var p=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:p},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;p=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){at(l)}R(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){er()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=wr(l,s);if(v.hasOwnProperty("ignoreTitle")){p=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var m=null;var x=null;var y=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){tr(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{rr(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=T(c);je(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}_e(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!p){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}Cr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}_e(f,"HX-Trigger-After-Settle",r)}ie(m)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(x);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){m=e;x=t});var S=y;y=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(y,v.swapDelay)}else{y()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Xr={};function Dr(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Ur(e,t){if(t.init){t.init(r)}Xr[e]=le(Dr(),t)}function Br(e){delete Xr[e]}function Fr(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Xr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Fr(u(e),r,n)}var Vr=false;re().addEventListener("DOMContentLoaded",function(){Vr=true});function jr(e){if(Vr||re().readyState==="complete"){e()}else{re().addEventListener("DOMContentLoaded",e)}}function _r(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b81df6c Binary files /dev/null and b/static/logo.png differ diff --git a/templates/court.j2 b/templates/court.j2 index 859c942..d7aec86 100644 --- a/templates/court.j2 +++ b/templates/court.j2 @@ -1 +1 @@ - \ No newline at end of file +{% from "macros.j2" import court %}{{ court(key, value) }} \ No newline at end of file diff --git a/templates/courts.j2 b/templates/courts.j2 index 2bc8a95..d650298 100644 --- a/templates/courts.j2 +++ b/templates/courts.j2 @@ -1,12 +1,8 @@ +{% from "macros.j2" import court %}
{% for key, value in courts.items() %} -
- -
+ {{ court(key, value) }} {% endfor %}
\ No newline at end of file diff --git a/templates/guests.j2 b/templates/guests.j2 index b5d251f..b84a2fd 100644 --- a/templates/guests.j2 +++ b/templates/guests.j2 @@ -1,3 +1,3 @@
- +
\ No newline at end of file diff --git a/templates/index.j2 b/templates/index.j2 index 42fc43e..4ccdf92 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -3,10 +3,10 @@ - 🏸Match Up! - - - + Match Up! + + + -
-
-

🏸The Smashing Fellows

+
+

Games

@@ -31,11 +46,19 @@
- +
- +
@@ -52,14 +75,21 @@
- +
- +
- +
diff --git a/templates/macros.j2 b/templates/macros.j2 new file mode 100644 index 0000000..b4767be --- /dev/null +++ b/templates/macros.j2 @@ -0,0 +1,11 @@ +{% macro court(id, status) -%} + +{%- endmacro %} + +{% macro player(id, status, name) -%} + +{%- endmacro %} \ No newline at end of file diff --git a/templates/player.j2 b/templates/player.j2 index 7e80285..68a1ea4 100644 --- a/templates/player.j2 +++ b/templates/player.j2 @@ -1,6 +1 @@ - \ No newline at end of file +{% from "macros.j2" import player %}{{ player(id, status, name) }} \ No newline at end of file diff --git a/templates/players.j2 b/templates/players.j2 index 080c03a..4eedc54 100644 --- a/templates/players.j2 +++ b/templates/players.j2 @@ -1,8 +1,9 @@ +{% from "macros.j2" import player %}
-{% for id, player in players.items() %} +{% for id, value in players.items() %}
- + {{ player(id, value.status, value.name) }}
{% endfor %}
\ No newline at end of file -- cgit v1.3.1 From 156056e9c31468564fb53ca284996a5de41471fb Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Wed, 16 Oct 2024 23:28:46 +0200 Subject: Working version with all functionality --- app.py | 12 ++++++-- controller.py | 7 +++-- model.py | 33 +++++++++++++++++++++ static/custom.css | 15 ++++++++++ templates/controls.j2 | 51 +++++++++++++++++++++++++++++++++ templates/court.j2 | 1 - templates/games.j2 | 2 +- templates/index.j2 | 79 ++++++--------------------------------------------- templates/player.j2 | 1 - templates/time.j2 | 3 ++ 10 files changed, 125 insertions(+), 79 deletions(-) create mode 100644 static/custom.css create mode 100644 templates/controls.j2 delete mode 100644 templates/court.j2 delete mode 100644 templates/player.j2 create mode 100644 templates/time.j2 (limited to 'templates') diff --git a/app.py b/app.py index 261c76b..22f6767 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,9 @@ from flask import Flask, render_template, request load_dotenv() app = Flask("Match Up! Backend") app.config.from_object(getenv("APP_SETTINGS")) -rg = RoundGenerator(courts=CourtList(12), players=PlayerList(getenv("DATABASE"))) +rg = RoundGenerator( + courts=CourtList(int(getenv("COURTS"))), players=PlayerList(getenv("DATABASE")) +) re = Environment(loader=FileSystemLoader("templates")) @@ -18,6 +20,12 @@ def home(): return render_template("index.j2") +@app.route("/time", methods=["GET"]) +def get_time(): + mins, secs = rg.timer.get() + return render_template("time.j2", mins=mins, secs=secs) + + @app.route("/guests", methods=["GET"]) def get_guests(): return render_template("guests.j2", guests=rg.players.guests) @@ -68,9 +76,7 @@ def get_list_of_players(): def toggle_court(court_number): id = int(court_number) status = rg.courts.toggle_court_status(id) - print(status) t = re.from_string('{% from "macros.j2" import court %}{{ court(id, status) }}') - print(render_template(t, id=id, status=status)) return render_template(t, id=id, status=status) diff --git a/controller.py b/controller.py index 1116443..2e09e9f 100644 --- a/controller.py +++ b/controller.py @@ -2,7 +2,7 @@ from typing import Tuple from operator import attrgetter from dataclasses import dataclass from utilities import sample_list -from model import Team, Game, PlayerList, CourtList, MAX_LEVEL, PLAYER_PER_COURT +from model import Team, Game, PlayerList, CourtList, Timer, MAX_LEVEL, PLAYER_PER_COURT def _create_placeholders(courts: dict, name: str) -> list[Game]: @@ -49,6 +49,7 @@ class RoundGenerator: proposal: tuple = tuple() def __post_init__(self) -> None: + self.timer = Timer() self.history = {key: 0 for key, _ in self.players.get_players().items()} self.games = self._generate_proposal(MAX_LEVEL)[0] @@ -110,7 +111,9 @@ class RoundGenerator: return self.games self.games = proposed_games for player_id in proposed_players: - self.history[player_id] = self.history[player_id] + 1 + if player_id < self.players.guest_id_start: + self.history[player_id] = self.history[player_id] + 1 self.proposal = tuple() self.round += 1 + self.timer.start() return self.games diff --git a/model.py b/model.py index f768d0f..c69ba48 100644 --- a/model.py +++ b/model.py @@ -1,6 +1,7 @@ from math import floor from csv import reader from typing import Tuple +from time import time, strftime from dataclasses import dataclass MIN_LEVEL = 1 @@ -101,3 +102,35 @@ class PlayerList: } players.update(guests) return players + + +@dataclass +class Timer: + ref: float = None + running: bool = False + max_mins: int = 1 + default: str = ("00", "00") + + def __post_init__(self): + self.max_seconds = self.max_mins * 60 + + def start(self): + self.ref = time() + self.max_seconds + self.running = True + + def stop(self): + self.ref = None + self.running = False + + def get(self): + if not self.running: + return self.default + total_seconds = self.ref - time() + if total_seconds < 0: + self.stop() + return self.default + mins = f"{int(total_seconds // 60):02d}" + secs = f"{int(total_seconds % 60):02d}" + print(mins) + print(secs) + return mins, secs diff --git a/static/custom.css b/static/custom.css new file mode 100644 index 0000000..3e57091 --- /dev/null +++ b/static/custom.css @@ -0,0 +1,15 @@ +button, input, .select, .select select { + width: 100%; +} +table { + font-size: 30px; +} +td { + width: 20%; +} +img { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); +} \ No newline at end of file diff --git a/templates/controls.j2 b/templates/controls.j2 new file mode 100644 index 0000000..1799e01 --- /dev/null +++ b/templates/controls.j2 @@ -0,0 +1,51 @@ +
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
diff --git a/templates/court.j2 b/templates/court.j2 deleted file mode 100644 index d7aec86..0000000 --- a/templates/court.j2 +++ /dev/null @@ -1 +0,0 @@ -{% from "macros.j2" import court %}{{ court(key, value) }} \ No newline at end of file diff --git a/templates/games.j2 b/templates/games.j2 index 50e1087..0285de1 100644 --- a/templates/games.j2 +++ b/templates/games.j2 @@ -9,7 +9,7 @@
{{ game.court }} {{ game.team1.player1 }} {{ game.team1.player2 }} diff --git a/templates/index.j2 b/templates/index.j2 index 4ccdf92..fcea4e3 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -6,24 +6,8 @@ Match Up! + -
@@ -36,63 +20,16 @@
-

Today's Games

+

Match Up!

+
+
+
-

Games

-
-
-
-
- -
-
-
- -
-
-
-
-
- -
-
-
- -
-
-
- -
-
- -
-
-
+

Games

+ {% include 'controls.j2' %}

Courts

@@ -104,7 +41,7 @@
diff --git a/templates/player.j2 b/templates/player.j2 deleted file mode 100644 index 68a1ea4..0000000 --- a/templates/player.j2 +++ /dev/null @@ -1 +0,0 @@ -{% from "macros.j2" import player %}{{ player(id, status, name) }} \ No newline at end of file diff --git a/templates/time.j2 b/templates/time.j2 new file mode 100644 index 0000000..d006fc4 --- /dev/null +++ b/templates/time.j2 @@ -0,0 +1,3 @@ +
+

Timer: {{mins}}:{{secs}}

+
\ No newline at end of file -- cgit v1.3.1 From 310c54caffb137af593e710b2437c2639863574d Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Wed, 16 Oct 2024 23:36:56 +0200 Subject: Added a few more j2 macros --- app.py | 6 ++++-- templates/macros.j2 | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'templates') diff --git a/app.py b/app.py index 22f6767..f145354 100644 --- a/app.py +++ b/app.py @@ -23,12 +23,14 @@ def home(): @app.route("/time", methods=["GET"]) def get_time(): mins, secs = rg.timer.get() - return render_template("time.j2", mins=mins, secs=secs) + t = re.from_string('{% from "macros.j2" import timer %}{{timer(mins, secs)}}') + return render_template(t, mins=mins, secs=secs) @app.route("/guests", methods=["GET"]) def get_guests(): - return render_template("guests.j2", guests=rg.players.guests) + t = re.from_string('{% from "macros.j2" import guests %}{{guests(count)}}') + return render_template(t, count=rg.players.guests) @app.route("/decrement", methods=["POST"]) diff --git a/templates/macros.j2 b/templates/macros.j2 index b4767be..7117d11 100644 --- a/templates/macros.j2 +++ b/templates/macros.j2 @@ -8,4 +8,16 @@ +{%- endmacro %} + +{% macro guests(count) -%} +
+ +
+{%- endmacro %} + +{% macro timer(mins, secs) -%} +
+

Timer: {{mins}}:{{secs}}

+
{%- endmacro %} \ No newline at end of file -- cgit v1.3.1 From f95286e325c214bf5fe61c4d93d34ae03692783c Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Wed, 16 Oct 2024 23:38:00 +0200 Subject: Removed unnecessary templates --- templates/guests.j2 | 3 --- templates/time.j2 | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 templates/guests.j2 delete mode 100644 templates/time.j2 (limited to 'templates') diff --git a/templates/guests.j2 b/templates/guests.j2 deleted file mode 100644 index b84a2fd..0000000 --- a/templates/guests.j2 +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/templates/time.j2 b/templates/time.j2 deleted file mode 100644 index d006fc4..0000000 --- a/templates/time.j2 +++ /dev/null @@ -1,3 +0,0 @@ -
-

Timer: {{mins}}:{{secs}}

-
\ No newline at end of file -- cgit v1.3.1