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 --- model.py | 96 ++++++++++++++++++++++++++++++---------------------------------- 1 file changed, 45 insertions(+), 51 deletions(-) (limited to 'model.py') diff --git a/model.py b/model.py index c83bcfd..bfaa9d5 100644 --- a/model.py +++ b/model.py @@ -4,36 +4,28 @@ from pathlib import Path from random import sample from operator import attrgetter from dataclasses import dataclass - - -@dataclass -class Court: - number: int - status: bool +from typing import Tuple @dataclass class CourtList: total: int - def __post_init__(self): - self.courts = [Court(i + 1, True) for i in range(self.total)] + def __post_init__(self) -> None: + self.courts = {i + 1: True for i in range(self.total)} - def get_court(self, court_number: int): - return [court for court in self.courts if court.number == court_number] + def get_court_status(self, court_number: int) -> bool: + return self.courts[court_number] - def toggle_court(self, court_number: int): - self.courts = [ - ( - Court( - number=court_number, - status=not court.status, - ) - if court_number == court.number - else court - ) - for court in self.courts - ] + 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 @@ -96,7 +88,7 @@ class Team: @dataclass class Game: - court: Court + court: int team1: Team team2: Team @@ -105,36 +97,33 @@ DUMMY_PLAYER = Player(name="-") DUMMY_TEAM = Team(player1=DUMMY_PLAYER, player2=DUMMY_PLAYER) -def create_dummy_games(courts: list[Court]): - return [Game(court, DUMMY_TEAM, DUMMY_TEAM) for court in courts] +def create_dummy_games(courts: dict): + return [Game(court, DUMMY_TEAM, DUMMY_TEAM) for court, _ in courts.items()] RESERVED_PLAYER = Player(name="Reserved") RESERVED_TEAM = Team(player1=RESERVED_PLAYER, player2=RESERVED_PLAYER) -def create_reserved_games(courts: list[Court]): - return [Game(court, RESERVED_TEAM, RESERVED_TEAM) for court in courts] +def create_reserved_games(courts: dict): + return [Game(court, RESERVED_TEAM, RESERVED_TEAM) for court, _ in courts.items()] PLAYER_PER_COURT = 4 -def create_single_game(court: Court, players: list[Player]): +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 create_possible_games(court_list: list[Court], players: list[Player]): +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, len(players), PLAYER_PER_COURT) - ] - games = [ - create_single_game(court, clump) - for court, clump in zip(court_list, 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 @@ -142,42 +131,47 @@ def create_possible_games(court_list: list[Court], players: list[Player]): class GameList: def clear(self, court_list: CourtList): - courts = court_list.courts - active_courts = [court for court in courts if court.status] - inactive_courts = [court for court in courts if not court.status] + 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.number")) + games.sort(key=attrgetter("court")) self.games = games - def create_random_games(self, court_list: CourtList, player_list: PlayerList): + def create_random_games(self, courts: CourtList, player_list: PlayerList): active_players = player_list.get_active_players() - active_courts = [court for court in court_list.courts if court.status] - inactive_courts = [court for court in court_list.courts if not court.status] + 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( - active_courts[:possible_games], current_players + dict(list(active_courts.items())[:possible_games]), current_players + ) + dummy_games = create_dummy_games( + dict(list(active_courts.items())[possible_games:]) ) - dummy_games = create_dummy_games(active_courts[possible_games:]) reserved_games = create_reserved_games(inactive_courts) games = active_games + dummy_games + reserved_games - games.sort(key=attrgetter("court.number")) + games.sort(key=attrgetter("court")) self.games = games - def create_skilled_games(self, court_list: CourtList, player_list: PlayerList): + def create_skilled_games(self, courts: CourtList, player_list: PlayerList): active_players = player_list.get_active_players() - active_courts = [court for court in court_list.courts if court.status] - inactive_courts = [court for court in court_list.courts if not court.status] + 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( - active_courts[:possible_games], current_players + dict(list(active_courts.items())[:possible_games]), current_players + ) + dummy_games = create_dummy_games( + dict(list(active_courts.items())[possible_games:]) ) - dummy_games = create_dummy_games(active_courts[possible_games:]) reserved_games = create_reserved_games(inactive_courts) games = active_games + dummy_games + reserved_games - games.sort(key=attrgetter("court.number")) + games.sort(key=attrgetter("court")) self.games = games -- 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 'model.py') 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 'model.py') 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

-
+
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + +
+
+
+ +
+
+
+ +
+
+ +
+
+
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