From 34d77cd777bd44d7bfe0e3ab3379f9be256ca88b Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Sun, 9 Nov 2025 09:56:59 +0100 Subject: Moved to javascript codebase --- app/__init__.py | 141 --------------------------------- app/config.py | 15 ---- app/controller.py | 194 ---------------------------------------------- app/model.py | 110 -------------------------- app/static/custom.css | 34 -------- app/static/favicon.ico | Bin 15406 -> 0 bytes app/static/file.js | 11 --- app/static/timer.js | 91 ---------------------- app/templates/controls.j2 | 48 ------------ app/templates/games.j2 | 21 ----- app/templates/index.j2 | 16 ---- app/templates/login.html | 23 ------ app/templates/macros.j2 | 45 ----------- app/templates/players.j2 | 40 ---------- app/utilities.py | 172 ---------------------------------------- 15 files changed, 961 deletions(-) delete mode 100644 app/__init__.py delete mode 100644 app/config.py delete mode 100644 app/controller.py delete mode 100644 app/model.py delete mode 100644 app/static/custom.css delete mode 100644 app/static/favicon.ico delete mode 100644 app/static/file.js delete mode 100644 app/static/timer.js delete mode 100644 app/templates/controls.j2 delete mode 100644 app/templates/games.j2 delete mode 100644 app/templates/index.j2 delete mode 100644 app/templates/login.html delete mode 100644 app/templates/macros.j2 delete mode 100644 app/templates/players.j2 delete mode 100644 app/utilities.py (limited to 'app') diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index 0e0ea09..0000000 --- a/app/__init__.py +++ /dev/null @@ -1,141 +0,0 @@ -from pandas import read_csv -from functools import wraps -from app.config import Config -from flask_migrate import Migrate -from flask_sqlalchemy import SQLAlchemy -from flask_simplelogin import SimpleLogin -from werkzeug.utils import secure_filename -from flask_simplelogin import is_logged_in -from flask import ( - Flask, - render_template_string, - render_template, - request, - redirect, - url_for, - Response, - get_template_attribute, -) - -app = Flask(__name__) -app.config.from_object(Config) -db = SQLAlchemy(app) -migrate = Migrate(app, db) -SimpleLogin(app) - - -from app.controller import Session # noqa: E402 - -g = Session() - - -@app.route("/", methods=["GET"]) -def home(): - return render_template("index.j2", url_for=url_for, login_status=is_logged_in()) - - -@app.route("/control", methods=["GET"]) -def get_controls(): - return render_template("controls.j2", courts=g.courts.all()) - - -@app.route("/players", methods=["GET"]) -def get_list_of_players(): - return render_template( - "players.j2", - regulars=g.players.regulars(), - guests=g.players.guests(), - url_for=url_for, - login_status=is_logged_in(), - ) - - -@app.route("/courts", methods=["GET"]) -def get_list_of_courts(): - return render_template("courts.j2", courts=g.courts.all()) - - -def login_required(f): - @wraps(f) - def decorated_function(*args, **kwargs): - if not is_logged_in(): - response = Response("User isn't logged in!") - response.headers["HX-Redirect"] = url_for("simplelogin.login") - return response - return f(*args, **kwargs) - - return decorated_function - - -@app.route("/propose", methods=["POST"]) -@login_required -def get_random_games(): - g.levels = int(request.form["levels"].split()[0]) - g.teams = request.form["team"] == "Teams" - return render_template("games.j2", games=g.propose(), title="Proposal") - - -@app.route("/confirm", methods=["POST"]) -@login_required -def confirm_games(): - return render_template("games.j2", games=g.confirm(), title=f"Round: {g.round()}") - - -@app.route("/clear", methods=["POST"]) -def clear_games(): - return render_template( - "games.j2", games=g.current_round(), title=f"Round: {g.round()}" - ) - - -@app.route("/reset", methods=["POST"]) -@login_required -def reset_games(): - return render_template("games.j2", games=g.reset(), title=f"Round: {g.round()}") - - -@app.route("/player-toggle/", methods=["POST"]) -@login_required -def toggle_player(player_request): - id = int(player_request) - player = g.players.toggle(id) - button = get_template_attribute("macros.j2", "player_button") - return render_template_string("{{button(player)}}", player=player, button=button) - - -@app.route("/reset_players", methods=["POST"]) -@login_required -def reset_all_players(): - g.players.reset() - return redirect(url_for("players")) - - -@app.route("/update_players", methods=["POST"]) -@login_required -def update_all_players(): - file = request.files.get("players") - response = Response("File name or data has issues!", 400) - response.headers["HX-Refresh"] = "true" - if file is None: - return response - elif file.filename is None: - return response - file_name = secure_filename(file.filename) - try: - data = read_csv(file_name) - required_columns = ["id", "first_name", "last_name", "level", "team"] - if not all([item in data.columns for item in required_columns]): - raise ImportError("All columns aren't available in the data.") - except: # noqa: E722 - return response - g.players.update(data) - return redirect(url_for("players")) - - -@app.route("/court-toggle/", methods=["POST"]) -@login_required -def toggle_court(court_number): - id = int(court_number) - court = g.courts.toggle(id) - button = get_template_attribute("macros.j2", "court_button") - return render_template_string("{{button(court)}}", court=court, button=button) diff --git a/app/config.py b/app/config.py deleted file mode 100644 index 5bc65b3..0000000 --- a/app/config.py +++ /dev/null @@ -1,15 +0,0 @@ -from os import environ - - -class Config: - SQLALCHEMY_DATABASE_URI = environ.get("SQLALCHEMY_DATABASE_URI") - SECRET_KEY = environ.get("SECRET_KEY") - GUEST_COUNT = 4 - COURT_COUNT = 12 - MIN_PLAYER_LEVEL = 1 - MAX_PLAYER_LEVEL = 10 - PLAYERS_PER_COURT = 4 - # Default players as dictionary of first names and primary keys - DEFAULTS = {"---": 1, "RESERVED": 2} - # Guest players as dictionary of first names and play level - GUESTS = {"Beginner": 1, "Novice": 3, "Intermediate": 5} diff --git a/app/controller.py b/app/controller.py deleted file mode 100644 index d5bdbcb..0000000 --- a/app/controller.py +++ /dev/null @@ -1,194 +0,0 @@ -from math import floor -from app import app, db -from datetime import date -from typing import Sequence -from pandas import DataFrame -from operator import attrgetter -from sqlalchemy import func, select, not_ -from app.model import ( - Player, - Court, - Game, - DisplayGame, -) -from app.utilities import ( - get_placeholder_games, - get_team_games, - get_shuffle_games, - separate_teams_and_players, - initialize_random_database, - initialize_default_players, -) - - -class Courts: - def __init__(self): - with app.app_context(): - db.session.query(Court).delete() - for _ in range(app.config["COURT_COUNT"]): - db.session.add(Court()) - db.session.commit() - - def get(self, id: int) -> Court: - court = db.session.get(Court, id) - if court is None: - raise ValueError(f"Court not found: {id}") - return court - - def toggle(self, id: int) -> Court: - court = self.get(id) - court.status = not court.status - db.session.commit() - return court - - def active(self): - return db.session.query(Court).where(Court.status).all() - - def inactive(self): - return db.session.query(Court).where(not_(Court.status)).all() - - def all(self) -> list[Court]: - return db.session.query(Court).order_by(Court.id).all() - - -class Players: - def __init__(self) -> None: - with app.app_context(): - if len(self.regulars()) == 0: - initialize_random_database() - - def update(self, data: DataFrame): - initialize_default_players() - for entry in data.to_dict("records"): - args = entry | {"status": False} - db.session.add(Player(**args)) # type: ignore - db.session.commit() - - def get(self, id: int) -> Player: - player = db.session.get(Player, id) - if player is None: - raise ValueError(f"Player not found: {id}") - return player - - def toggle(self, id: int) -> Player: - player = self.get(id) - player.status = not player.status - db.session.commit() - return player - - def regulars(self) -> list[Player]: - filter_list = list(app.config["GUESTS"].keys()) + list( - app.config["DEFAULTS"].keys() - ) - return ( - db.session.query(Player) - .where(Player.id > 1) - .where(Player.first_name.not_in(filter_list)) - .order_by(Player.first_name, Player.last_name) - .all() - ) - - def guests(self) -> list[Player]: - return ( - db.session.query(Player) - .where(Player.id > 1) - .where(Player.first_name.in_(app.config["GUESTS"].keys())) - .order_by(Player.first_name, Player.last_name) - .all() - ) - - def active(self) -> Sequence[Player]: - query = ( - select(Player) - .where(Player.status) - .order_by(Player.first_name, Player.last_name) - ) - return db.session.scalars(query).all() - - def reset(self): - for player in db.session.scalars(select(Player)): - player.status = False - db.session.commit() - - -class Session: - levels: int = 10 - teams: bool = False - proposal: list[DisplayGame] = [] - - def _today(self): - games = ( - db.session.query(Game).filter(func.date(Game.date) == date.today()).all() - ) - return games - - def __init__(self): - with app.app_context(): - db.create_all() - self.courts = Courts() - self.players = Players() - if len(self._today()) == 0: - query = select(Player).where(Player.first_name == "---") - player = db.session.scalars(query).first() - if player is None: - raise ValueError("Database isn't initialized.") - player_data = {f"player_{i + 1}": player.id for i in range(4)} - for court in self.courts.all(): - args = {"round_id": 0, "court_id": court.id} | player_data - db.session.add(Game(**args)) - db.session.commit() - - def round(self) -> int: - games = self._today() - round_id = max([game.round_id for game in games]) - if round_id is None: - raise ValueError("Database isn't initialized") - return round_id - - def current_round(self) -> list[DisplayGame]: - games = ( - db.session.query(Game) - .filter(func.date(Game.date) == date.today()) - .filter(Game.round_id == self.round()) - .order_by(Game.court_id) - .all() - ) - games = [DisplayGame(game) for game in games] - return games - - def propose(self) -> list[DisplayGame]: - reserved_courts = self.courts.inactive() - reserved_games = get_placeholder_games(reserved_courts, "RESERVED") - active_courts = self.courts.active() - active_players = self.players.active() - game_count = floor(len(active_players) / app.config["PLAYERS_PER_COURT"]) - game_count = min(len(active_courts), game_count) - used_courts = active_courts[:game_count] - unused_courts = active_courts[game_count:] - blank_games = get_placeholder_games(unused_courts, "---") - active_teams = {} - if self.teams: - active_teams, active_players = separate_teams_and_players(active_players) - team_courts = used_courts[: len(active_teams)] - shuffle_courts = used_courts[len(active_teams) :] - team_games = get_team_games(team_courts, active_teams) - shuffle_games = get_shuffle_games(shuffle_courts, active_players, self.levels) - self.proposal = shuffle_games + team_games + blank_games + reserved_games - self.proposal.sort(key=attrgetter("court.id")) - return self.proposal - - def confirm(self) -> list[DisplayGame]: - if len(self.proposal) == 0: - return self.current_round() - round_id = self.round() + 1 - for game in self.proposal: - db.session.add(game.to_game(round_id)) - db.session.commit() - self.proposal = [] - return self.current_round() - - def reset(self) -> list[DisplayGame]: - db.session.query(Game).filter(func.date(Game.date) == date.today()).delete() - db.session.commit() - self.__init__() - return self.current_round() diff --git a/app/model.py b/app/model.py deleted file mode 100644 index 22424d4..0000000 --- a/app/model.py +++ /dev/null @@ -1,110 +0,0 @@ -from app import app, db -from sqlalchemy.orm import Mapped -from datetime import date, datetime -from typing import Optional, Annotated -from sqlalchemy.orm import mapped_column as mc -from sqlalchemy import String, Boolean, Integer, ForeignKey - - -class User(db.Model): - id: Mapped[int] = mc(primary_key=True) - username: Mapped[str] = mc(String(64), index=True, unique=True) - password_hash: Mapped[Optional[str]] = mc(String(256)) - - def __repr__(self): - return f"User {self.username}" - - -class Court(db.Model): - id: Mapped[int] = mc(primary_key=True) - status: Mapped[bool] = mc(Boolean, default=True) - - def __repr__(self): - return f"Court {self.id} is {'active' if self.status else 'inactive'}" - - -class Player(db.Model): - id: Mapped[int] = mc(primary_key=True) - first_name: Mapped[str] = mc(String(64), index=True) - last_name: Mapped[str] = mc(String(64), index=True) - team: Mapped[int] = mc(Integer, index=True) - status: Mapped[bool] = mc(Boolean) - level: Mapped[int] = mc(Integer) - - def __init__( - self, - id, - first_name, - last_name, - team=0, - status=False, - level=int(app.config["MIN_PLAYER_LEVEL"]), - ): - self.id = id - self.first_name = first_name - self.last_name = last_name - self.team = team - self.status = status - self.level = level - - def __repr__(self): - return f"{self.first_name} {self.last_name} is {'active' if self.status else 'inactive'}" - - -class Game(db.Model): - id: Mapped[int] = mc(primary_key=True) - round_id: Mapped[int] = mc(Integer, index=True) - court_id: Mapped[int] = mc(ForeignKey(Court.id), index=True) - player_1: Mapped[int] = mc(ForeignKey(Player.id), index=True) - player_2: Mapped[int] = mc(ForeignKey(Player.id), index=True) - player_3: Mapped[int] = mc(ForeignKey(Player.id), index=True) - player_4: Mapped[int] = mc(ForeignKey(Player.id), index=True) - date: Mapped[datetime] = mc(index=True, default=lambda: date.today()) - - def __init__( - self, - round_id: Optional[int], - court_id: int, - player_1: int, - player_2: int, - player_3: int, - player_4: int, - ): - if round_id is not None: - self.round_id = round_id - self.court_id = court_id - self.player_1 = player_1 - self.player_2 = player_2 - self.player_3 = player_3 - self.player_4 = player_4 - - -class DisplayGame: - court: Court - team_1: Annotated[list[Player], 2] - team_2: Annotated[list[Player], 2] - - def __init__(self, game: Game): - court = db.session.get(Court, game.court_id) - if court is None: - raise ValueError("Unknown court") - self.court = court - players = [] - for i in range(4): - player_id = getattr(game, f"player_{i + 1}") - p = db.session.get(Player, player_id) - if p is None: - raise ValueError(f"Player ID: {player_id} mismatch.") - players.append(p) - self.team_1 = players[:2] - self.team_2 = players[2:] - - def to_game(self, round_id) -> Game: - return Game( - round_id, - self.court.id, - self.team_1[0].id, - self.team_1[1].id, - self.team_2[0].id, - self.team_2[1].id, - ) diff --git a/app/static/custom.css b/app/static/custom.css deleted file mode 100644 index a5ca36f..0000000 --- a/app/static/custom.css +++ /dev/null @@ -1,34 +0,0 @@ -button, input, select { - width: 100%; - height: 100%; -} -td { - width: 20%; -} -.grid { - margin-top: 20px; - margin-bottom: 20px; -} -footer { - text-align: center; -} -.parent, .timer { - display: grid; - grid-column-gap: 20px; - grid-row-gap: 20px; - margin-top: 20px; - margin-bottom: 20px; - grid-auto-columns: 1fr; - grid-auto-rows: 1fr; -} -@media (min-width: 900px) { - table { - font-size: xx-large; - } - .parent { - grid-template-columns: repeat(4, 1fr); - } - .timer { - grid-template-columns: 4fr repeat(2, 1fr); - } -} diff --git a/app/static/favicon.ico b/app/static/favicon.ico deleted file mode 100644 index 64eab0a..0000000 Binary files a/app/static/favicon.ico and /dev/null differ diff --git a/app/static/file.js b/app/static/file.js deleted file mode 100644 index 403ddf6..0000000 --- a/app/static/file.js +++ /dev/null @@ -1,11 +0,0 @@ -window.onload = function() { - const fileInput = document.querySelector("#file-js input[type=file]"); - if (fileInput !== null) { - fileInput.onchange = () => { - if (fileInput.files.length > 0) { - const fileName = document.querySelector("#file-js .file-name"); - fileName.textContent = fileInput.files[0].name; - } - } - }; -} \ No newline at end of file diff --git a/app/static/timer.js b/app/static/timer.js deleted file mode 100644 index e8e7854..0000000 --- a/app/static/timer.js +++ /dev/null @@ -1,91 +0,0 @@ -let minsPerRound = Number(sessionStorage.getItem("minsPerRound")) || 12; -let timerRunningFlag = Number(sessionStorage.getItem("timerRunningFlag")) || 0; -let deadline = Number(sessionStorage.getItem("deadline")) || new Date().getTime(); -let clock = 0; - -function setTimer(m, s) { - let timer = document.getElementById("timer-display"); - m = m < 10 ? ("0" + m) : m; - s = s < 10 ? ("0" + s) : s; - timer.value = `${m}:${s}`; -} - -function setSessionData() { - sessionStorage.setItem("deadline", deadline); - sessionStorage.setItem("minsPerRound", minsPerRound); - sessionStorage.setItem("timerRunningFlag", timerRunningFlag); -} - -function resetTimer() { - clearInterval(clock); - setTimer(minsPerRound, 0); - timerRunningFlag = 0; - setSessionData(); -} - -function updateTimer(milliseconds) { - const m = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); - const s = Math.floor((milliseconds % (1000 * 60)) / 1000); - setTimer(m, s); -} - -function refreshTimer() { - const now = new Date().getTime(); - const millisecondsToDeadline = deadline - now; - if (millisecondsToDeadline > 0) { - updateTimer(millisecondsToDeadline); - return; - } - resetTimer(); - const notification = new Notyf({ - duration: 30 * 1000, - position: {x: 'center', y: 'top'}, - types: [{ - type: 'success', - background: 'indianred', - duration: 1000 * 1000, - dismissible: true - }] - }); - notification.success("Round completed"); -} - -function startTimer(fresh) { - if (fresh) { - now = new Date().getTime(); - const millisecondsToDeadline = minsPerRound * 60 * 1000; - deadline = now + millisecondsToDeadline; - } - timerRunningFlag = 1; - setSessionData(); - clock = setInterval(refreshTimer, 1000); -} - -window.onload = function () { - if (timerRunningFlag) - startTimer(0); - else resetTimer(); - - document.getElementById("timer-start").addEventListener('click', ()=> { - if (timerRunningFlag != 0) return; - startTimer(1); - }); - - document.getElementById("timer-stop").addEventListener('click', ()=>{ - resetTimer(); - }); - - document.getElementById("timer-inc").addEventListener('click', ()=> { - if (timerRunningFlag == 0) { - minsPerRound += 1; - resetTimer(); - } - }); - - document.getElementById("timer-dec").addEventListener('click', ()=> { - if (timerRunningFlag == 0 && minsPerRound != 0) { - minsPerRound -= 1; - resetTimer(); - } - }); -} \ No newline at end of file diff --git a/app/templates/controls.j2 b/app/templates/controls.j2 deleted file mode 100644 index 97351bf..0000000 --- a/app/templates/controls.j2 +++ /dev/null @@ -1,48 +0,0 @@ -{% from "macros.j2" import court_button %} -

Timer

-
-
- - - -
- - -
-

Generation

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

Courts

-
-{% for court in courts %} - {{ court_button(court) }} -{% endfor %} -
\ No newline at end of file diff --git a/app/templates/games.j2 b/app/templates/games.j2 deleted file mode 100644 index 00ffb71..0000000 --- a/app/templates/games.j2 +++ /dev/null @@ -1,21 +0,0 @@ -
-

{{ title }}

- - - - - - - - {% for game in games %} - - - {% endfor %} - -
Court Team 1 Team 2
{{ game.court.id }} - {{ game.team_1[0].first_name }}{{ " " + game.team_1[0].last_name }} - {{ game.team_1[1].first_name }}{{ " " + game.team_1[1].last_name }} - {{ game.team_2[0].first_name }}{{ " " + game.team_2[0].last_name }} - {{ game.team_2[1].first_name }}{{ " " + game.team_2[1].last_name }} -
-
\ No newline at end of file diff --git a/app/templates/index.j2 b/app/templates/index.j2 deleted file mode 100644 index af6ca4c..0000000 --- a/app/templates/index.j2 +++ /dev/null @@ -1,16 +0,0 @@ -{% from "macros.j2" import header, footer, navbar %} - - - {{ header(url_for) }} - -
{{ navbar(url_for, login_status) }}
-
-

Games

-
-

Controls

-
-
-
{{ footer() }}
- - - diff --git a/app/templates/login.html b/app/templates/login.html deleted file mode 100644 index e3109b6..0000000 --- a/app/templates/login.html +++ /dev/null @@ -1,23 +0,0 @@ -{% from "macros.j2" import header, footer, navbar %} - - -{{ header(url_for) }} - -
{{ navbar(url_for) }}
-
-
-
- {{ form.csrf_token }} - {{form.username.label(class_="label")}}
{{ form.username(class_="input") }} -

- {{form.password.label(class_="label")}}
{{ form.password(class_="input") }} -

- {% if next %}{% endif %} - - - -
-
{{ footer() }}
- - - \ No newline at end of file diff --git a/app/templates/macros.j2 b/app/templates/macros.j2 deleted file mode 100644 index e82b910..0000000 --- a/app/templates/macros.j2 +++ /dev/null @@ -1,45 +0,0 @@ -{% macro court_button(court) -%} - -{%- endmacro %} - -{% macro player_button(player) -%} - -{%- endmacro %} - -{% macro header(url_for) -%} - - - -Match Up! - - - - - - - - - -{%- endmacro %} - -{% macro footer() -%} -
Made by Karan with
-{%- endmacro %} - - -{% macro navbar(url_for, login_status) -%} - -{%- endmacro %} \ No newline at end of file diff --git a/app/templates/players.j2 b/app/templates/players.j2 deleted file mode 100644 index bacb215..0000000 --- a/app/templates/players.j2 +++ /dev/null @@ -1,40 +0,0 @@ -{% from "macros.j2" import header, navbar, footer, player_button, guest_control %} - - - {{ header(url_for) }} - -
{{ navbar(url_for, login_status) }}
-
-

Members

-
- {% for regular in regulars %} -
{{ player_button(regular) }}
- {% endfor %} -
-

Guests

-
- {% for guest in guests %} -
{{ player_button(guest) }}
- {% endfor %} -
-

Controls

-
- -
-

Player Update

-
-
-
- - -
-
-
-
{{ footer() }}
- -
- - \ No newline at end of file diff --git a/app/utilities.py b/app/utilities.py deleted file mode 100644 index d8bd3da..0000000 --- a/app/utilities.py +++ /dev/null @@ -1,172 +0,0 @@ -from math import floor -from app import app, db -from datetime import date -from sqlalchemy import func -from typing import Tuple, Sequence -from random import shuffle, sample -from random import randint, getrandbits -from names import get_first_name, get_last_name -from app.model import Player, Court, Game, DisplayGame - - -def _shuffle_two_lists(a: list, b: list) -> Tuple[list, list]: - indices = list(range(len(a))) - shuffle(indices) - a = [a[index] for index in indices] - b = [b[index] for index in indices] - return a, b - - -def _pick_from_list_after_sorting_other(a: list, b: list[int]) -> list: - indices = [i[0] for i in sorted(enumerate(b), key=lambda x: x[1])] - return [a[index] for index in indices] - - -def sample_list(indices: list, cost: list[int], count=None) -> list: - if count is None: - count = len(indices) - indices, cost = _shuffle_two_lists(indices, cost) - indices = _pick_from_list_after_sorting_other(indices, cost)[:count] - return indices - - -def _normalize_skill(level: int, new_max: int) -> int: - scaling = (new_max - 1) / int(app.config["MAX_PLAYER_LEVEL"]) - new_level = round(scaling * level) + 1 - return new_level - - -def select_players(players: Sequence[Player], count: int) -> list[Player]: - games = db.session.query(Game).filter(func.date(Game.date) == date.today()).all() - player_identifiers = [] - for game in games: - for i in range(4): - player_identifiers.append(getattr(game, f"player_{i + 1}")) - identifiers = [player.id for player in players] - play_count = [player_identifiers.count(player.id) for player in players] - ids = sample_list(identifiers, play_count, count) - selected_players = [player for player in players if player.id in ids] - return selected_players - - -def group_by_four(players: list[Player], new_max: int) -> list[list[Player]]: - identifiers = [player.id for player in players] - player_levels = [_normalize_skill(int(player.level), new_max) for player in players] - identifiers = sample_list(identifiers, player_levels) - court_players = [] - for i in range(0, len(players), app.config["PLAYERS_PER_COURT"]): - court_ids = identifiers[i : i + app.config["PLAYERS_PER_COURT"]] - selected_players = [player for player in players if player.id in court_ids] - court_players.append(selected_players) - return court_players - - -def separate_teams_and_players( - players: Sequence[Player], -) -> tuple[dict[int, list[Player]], list[Player]]: - unique_team_id = {player.team for player in players} - grouped_players_by_team = {team_id: [] for team_id in unique_team_id} - for player in players: - team = player.team - grouped_players_by_team[team].append(player) - active_teams = {} - players_without_team = [] - for team_id in grouped_players_by_team: - if team_id == 0: - players_without_team += [ - player for player in grouped_players_by_team[team_id] - ] - else: - if len(grouped_players_by_team[team_id]) >= 4: - active_teams[team_id] = grouped_players_by_team[team_id] - else: - players_without_team += [ - player for player in grouped_players_by_team[team_id] - ] - return active_teams, players_without_team - - -def get_placeholder_games(courts: list[Court], name: str) -> list[DisplayGame]: - p = db.session.query(Player).where(Player.first_name == name).first() - if p is None: - raise ValueError("Database isn't initialized.") - player_data = {"round_id": None} | {f"player_{i + 1}": p.id for i in range(4)} - games = [ - DisplayGame(Game(**({"court_id": court.id} | player_data))) # type: ignore - for court in courts - ] - return games - - -def get_team_games( - courts: list[Court], teams: dict[int, list[Player]] -) -> list[DisplayGame]: - games = [] - for court, (_, players) in zip(courts, teams.items()): - players = sample(players, app.config["PLAYERS_PER_COURT"]) - players.sort(key=lambda x: x.level, reverse=True) - game = Game( - None, court.id, players[0].id, players[3].id, players[1].id, players[2].id - ) - games.append(DisplayGame(game)) - return games - - -def get_shuffle_games( - courts: list[Court], players: Sequence[Player], total_levels: int -) -> list[DisplayGame]: - required_player_count = len(courts) * app.config["PLAYERS_PER_COURT"] - selected_players = select_players(players, required_player_count) - court_players = group_by_four(selected_players, total_levels) - games = [] - for court, players in zip(courts, court_players): - game = Game( - None, court.id, players[0].id, players[3].id, players[1].id, players[2].id - ) - games.append(DisplayGame(game)) - return games - - -def initialize_default_players(): - db.session.query(Player).delete() - for k, v in app.config["DEFAULTS"].items(): - db.session.add(Player(**{"id": v, "first_name": k, "last_name": ""})) - guest_offset = 10000 - for i, (k, v) in enumerate(app.config["GUESTS"].items()): - level_offset = app.config["GUEST_COUNT"] * i - for j in range(app.config["GUEST_COUNT"]): - db.session.add( - Player( - **{ - "id": guest_offset + level_offset + j, - "first_name": k, - "last_name": f"{j + 1}", - "level": v, - } - ) - ) - db.session.commit() - - -def initialize_random_database(): - initialize_default_players() - player_count = 80 - total_teams = 5 - players_per_team = 5 - for i in range(player_count): - if i < total_teams * players_per_team: - team = floor(i / players_per_team) + 1 - else: - team = 0 - args = { - "id": 3 + i, - "first_name": get_first_name(), - "last_name": get_last_name(), - "team": team, - "status": bool(getrandbits(1)), - "level": randint( - app.config["MIN_PLAYER_LEVEL"], app.config["MAX_PLAYER_LEVEL"] - ), - } - db.session.add(Player(**args)) - db.session.commit() -- cgit v1.3.1