diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/match_up/controller.py | 56 | ||||
| -rw-r--r-- | src/match_up/data.py | 8 | ||||
| -rw-r--r-- | src/match_up/model.py | 100 | ||||
| -rw-r--r-- | src/match_up/routes.py | 33 | ||||
| -rw-r--r-- | src/match_up/templates/controls.j2 | 12 | ||||
| -rw-r--r-- | src/match_up/templates/players.j2 | 14 | ||||
| -rw-r--r-- | src/match_up/utilities.py | 117 |
7 files changed, 186 insertions, 154 deletions
diff --git a/src/match_up/controller.py b/src/match_up/controller.py index 0e08cae..b00bf6b 100644 --- a/src/match_up/controller.py +++ b/src/match_up/controller.py @@ -2,7 +2,14 @@ from typing import Tuple from operator import attrgetter from match_up.data import Game, PLAYER_PER_COURT from match_up.model import Player, PlayerList, CourtList -from match_up.utilities import _create_placeholders, _select_players, _create_games +from match_up.utilities import ( + _create_placeholders, + _select_players, + _create_shuffle_games, + _create_team_games, + separate_teams_and_players, + split_courts, +) class RoundGenerator: @@ -30,21 +37,31 @@ class RoundGenerator: return games def _create_active_games( - self, total_levels: int + self, total_levels: int, group_by_team: bool ) -> Tuple[list[Game], list[Player]]: - active_courts, _ = self.courts.separate_courts() - used_courts, _ = self._separate_courts(active_courts) + active_courts = self.courts.separate_courts()[0] + used_courts = self._separate_courts(active_courts)[0] required_player_count = len(used_courts) * PLAYER_PER_COURT active_players = self.players.get_players() - selected_players = _select_players(active_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], list[Player]]: - 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")) + if group_by_team: + active_teams, team_less_players = separate_teams_and_players(active_players) + regular_court_count = len(used_courts) - len(active_teams) + regular_courts, team_courts = split_courts(used_courts, regular_court_count) + new_player_count = required_player_count - ( + PLAYER_PER_COURT * len(active_teams) + ) + regular_players = _select_players(team_less_players, new_player_count) + regular_games = _create_shuffle_games( + regular_courts, regular_players, total_levels + ) + team_games, selected_team_players = _create_team_games( + team_courts, active_teams + ) + selected_players = regular_players + selected_team_players + games = regular_games + team_games + else: + selected_players = _select_players(active_players, required_player_count) + games = _create_shuffle_games(used_courts, selected_players, total_levels) return games, selected_players def update_player_list(self, csv_location): @@ -55,10 +72,15 @@ class RoundGenerator: self.proposed_players = [] return self.games - def propose(self, levels: int) -> list[Game]: - proposal = self._generate_proposal(levels) - self.proposed_games = proposal[0] - self.proposed_players = proposal[1] + def propose(self, levels: int, group_by_team: bool) -> list[Game]: + inactive_games = self._create_inactive_games() + active_games, selected_players = self._create_active_games( + levels, group_by_team + ) + games = active_games + inactive_games + games.sort(key=attrgetter("court")) + self.proposed_games = games + self.proposed_players = selected_players return self.proposed_games def confirm(self) -> list[Game]: diff --git a/src/match_up/data.py b/src/match_up/data.py index 62aa5d8..c00dde2 100644 --- a/src/match_up/data.py +++ b/src/match_up/data.py @@ -6,6 +6,8 @@ from sqlalchemy.ext.declarative import declarative_base MIN_LEVEL = 1 MAX_LEVEL = 10 PLAYER_PER_COURT = 4 +GUEST_LEVELS = [1, 3, 5] +GUESTS_PER_LEVEL = 4 BASE = declarative_base() @@ -16,6 +18,7 @@ class Player(BASE): first = Column("first", String) last = Column("last", String) level = Column("level", Integer) + team = Column("team", Integer) status = Column("status", Integer) count = Column("count", Integer) @@ -25,6 +28,7 @@ class Player(BASE): first: str = "", last: str = "", level: int = 0, + team: int = 0, status: bool = True, count: int = 0, ): @@ -32,12 +36,14 @@ class Player(BASE): self.first = first self.last = last self.status = status + self.team = team self.level = level self.count = count def __repr__(self): + team = f" of Team {self.team}" if self.team else "" status = "active" if self.status else "inactive" - return f"{self.first} {self.last} (Level {self.level}) is {status} and has played {self.count} games" + return f"{self.first} {self.last} (Level {self.level}){team} is {status} and has played {self.count} games" class DisplayPlayer: diff --git a/src/match_up/model.py b/src/match_up/model.py index c42823b..467310b 100644 --- a/src/match_up/model.py +++ b/src/match_up/model.py @@ -1,21 +1,22 @@ from math import floor from typing import Tuple from os.path import isfile +from pandas import read_csv from dataclasses import dataclass from importlib.resources import path +from random import getrandbits, randint from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine, not_ -from match_up.utilities import ( - generate_guests, - _init_database_from_csv, - _init_random_database, - _textualize_guest, -) +from names import get_first_name, get_last_name from match_up.data import ( - Player, - DisplayPlayer, + MIN_LEVEL, + MAX_LEVEL, PLAYER_PER_COURT, BASE, + GUEST_LEVELS, + GUESTS_PER_LEVEL, + Player, + DisplayPlayer, ) @@ -43,8 +44,63 @@ class CourtList: return active_courts, inactive_courts +def _init_guests(session) -> None: + for level_id, level in enumerate(GUEST_LEVELS): + for player_id in range(GUESTS_PER_LEVEL): + unique_id = (level_id * GUESTS_PER_LEVEL) + player_id + 1 + player = Player( + id=-unique_id, + first="Guest", + last=str(unique_id), + level=level, + status=False, + count=0, + ) + session.add(player) + session.commit() + + +def _init_database_from_csv(session, csv_location: str) -> None: + data = read_csv(csv_location) + session.query(Player).delete() + for _, entry in data.iterrows(): + player = Player( + id=entry["id"], + first=entry["first"], + last=entry["last"], + level=entry["skill"], + team=entry["team"], + status=False, + count=0, + ) + session.add(player) + session.commit() + _init_guests(session) + + +def _init_random_database(session, player_count: int = 80) -> None: + 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 + player = Player( + id=i, + first=get_first_name(), + last=get_last_name(), + level=randint(MIN_LEVEL, MAX_LEVEL), + team=team, + status=bool(getrandbits(1)), + count=0, + ) + session.add(player) + session.commit() + _init_guests(session) + + class PlayerList: - guest_count: dict = {1: 0, 3: 0, 5: 0} def __init__(self, csv_location=None) -> None: with path("match_up", "players.db") as p: @@ -64,16 +120,6 @@ class PlayerList: BASE.metadata.create_all(bind=engine) self.session = sessionmaker(bind=engine)() - def increment_guests(self, level: int): - self.guest_count[level] = self.guest_count[level] + 1 - return _textualize_guest(level, self.guest_count[level]) - - def decrement_guests(self, level: int): - if self.guest_count[level] == 0: - return 0 - self.guest_count[level] = self.guest_count[level] - 1 - return _textualize_guest(level, self.guest_count[level]) - def get_player_status(self, id: int) -> bool: return self.session.query(Player).filter(Player.id == id).first().status @@ -83,14 +129,17 @@ class PlayerList: self.session.commit() return DisplayPlayer(player) - def get_all_players(self) -> list[DisplayPlayer]: + def get_all_players(self) -> Tuple[list[DisplayPlayer], list[DisplayPlayer]]: all_players = [ DisplayPlayer(p) for p in self.session.query(Player) .order_by(Player.first, Player.last) .all() ] - return all_players + regulars = [player for player in all_players if player.first != "Guest"] + guests = [player for player in all_players if player.first == "Guest"] + guests.sort(key=lambda x: int(x.last)) + return regulars, guests def get_possible_game_count(self) -> int: total_players = len(self.get_players()) @@ -104,14 +153,7 @@ class PlayerList: .filter(not_(Player.status == 0)) .all() ) - guests = generate_guests(self.guest_count) - return active_players + guests - - def get_guests(self): - return { - level: _textualize_guest(level, self.guest_count[level]) - for level in self.guest_count - } + return active_players def increment_game_count(self, player_ids: list[int]): players = self.session.query(Player).filter(Player.id.in_(player_ids)) diff --git a/src/match_up/routes.py b/src/match_up/routes.py index c0e77aa..e5a3b99 100644 --- a/src/match_up/routes.py +++ b/src/match_up/routes.py @@ -15,31 +15,13 @@ def get_controls(): return render_template("controls.j2", courts=_rg.courts.get_courts()) -@_app.route("/increment_guest", methods=["POST"]) -def increment_guests(): - level = int(request.form["level"].split()[0]) - t = re.from_string( - '{% from "macros.j2" import guest_control %}{{guest_control(level, value)}}' - ) - value = _rg.players.increment_guests(level) - return render_template(t, level=level, value=value) - - -@_app.route("/decrement_guest", methods=["POST"]) -def decrement_guests(): - print(request.form["level"]) - level = int(request.form["level"]) - t = re.from_string( - '{% from "macros.j2" import guest_control %}{{guest_control(level, value)}}' - ) - value = _rg.players.decrement_guests(level) - return render_template(t, level=level, value=value) - - @_app.route("/propose", methods=["POST"]) def get_random_games(): - levels = int(request.form["type"].split()[0]) - return render_template("games.j2", games=_rg.propose(levels), title="Proposal") + levels = int(request.form["levels"].split()[0]) + team_practice = request.form["team"] == "Teams" + return render_template( + "games.j2", games=_rg.propose(levels, team_practice), title="Proposal" + ) @_app.route("/confirm", methods=["POST"]) @@ -69,10 +51,9 @@ def toggle_player(player_request): @_app.route("/players", methods=["GET"]) def get_list_of_players(): - all_players = _rg.players.get_all_players() - guests = _rg.players.get_guests() + regulars, guests = _rg.players.get_all_players() return re.get_template("players.j2").render( - players=all_players, guests=guests, url_for=url_for + regulars=regulars, guests=guests, url_for=url_for ) diff --git a/src/match_up/templates/controls.j2 b/src/match_up/templates/controls.j2 index 24b42c6..0183c97 100644 --- a/src/match_up/templates/controls.j2 +++ b/src/match_up/templates/controls.j2 @@ -9,7 +9,7 @@ <div class="columns"> <div class="column"> <div class="select is-responsive"> - <select name="type"> + <select name="levels"> <option>10 Levels</option> <option>8 Levels</option> <option>6 Levels</option> @@ -20,10 +20,18 @@ </div> </div> <div class="column"> + <div class="select is-responsive"> + <select name="team"> + <option>Random</option> + <option>Teams</option> + </select> + </div> + </div> + <div class="column"> <button class="button is-dark is-responsive" hx-post="/propose" hx-target="#games" - hx-include="[name='type']">Create</button> + hx-include="[name='levels'], [name='team']">Generate</button> </div> <div class="column"></div> <div class="column"> diff --git a/src/match_up/templates/players.j2 b/src/match_up/templates/players.j2 index cd41925..0beeee9 100644 --- a/src/match_up/templates/players.j2 +++ b/src/match_up/templates/players.j2 @@ -5,19 +5,19 @@ <body> <section class="section"> {{ navbar(url_for) }} - <h4 class="title is-4">Players</h4> + <h4 class="title is-4">Members</h4> <div class="fixed-grid has-4-cols"> <div class="grid"> - {% for player in players %} - <div class="cell"> {{ player_button(player) }} </div> + {% for regular in regulars %} + <div class="cell"> {{ player_button(regular) }} </div> {% endfor %} </div> <h4 class="title is-4">Guests</h4> - <div class="fixed-grid has-3-cols"> + <div class="fixed-grid has-4-cols"> <div class="grid"> - {% for level in guests %} - <div class="cell"> {{ guest_control(level, guests[level]) }} </div> - {% endfor %} + {% for guest in guests %} + <div class="cell"> {{ player_button(guest) }} </div> + {% endfor %} </div> </section> <section class="section"> diff --git a/src/match_up/utilities.py b/src/match_up/utilities.py index db7c76a..7140a5f 100644 --- a/src/match_up/utilities.py +++ b/src/match_up/utilities.py @@ -1,17 +1,7 @@ from typing import Tuple -from pandas import read_csv -from random import shuffle, choice -from random import randint, getrandbits -from names import get_first_name, get_last_name -from match_up.data import ( - Player, - DisplayPlayer, - Team, - Game, - MIN_LEVEL, - MAX_LEVEL, - PLAYER_PER_COURT, -) +from random import shuffle, sample +from match_up.data import Player, DisplayPlayer, Team, Game +from match_up.data import MAX_LEVEL, PLAYER_PER_COURT def _shuffle_two_lists(a: list, b: list) -> Tuple[list, list]: @@ -71,7 +61,9 @@ def _create_groups_of_four_players( return court_players -def _create_games(courts: dict, players: list[Player], levels: int) -> list[Game]: +def _create_shuffle_games( + courts: dict, players: list[Player], levels: int +) -> list[Game]: court_players = _create_groups_of_four_players(players, levels) games = [] for court, players in zip(courts, court_players): @@ -85,66 +77,47 @@ def _create_games(courts: dict, players: list[Player], levels: int) -> list[Game return games -def generate_guest_type(count: int, level: int, description: str): - base_id = level * 10000 - return [ - Player( - id=base_id + i, - first=description, - last=f"{i + 1}", - level=level, - status=True, - count=choice([0, 1000]), +def _create_team_games(courts: dict, teams: dict) -> tuple[list[Game], list[Player]]: + games = [] + selected_players = [] + for court, (_, players) in zip(courts, teams.items()): + players = sample(players, PLAYER_PER_COURT) + selected_players += players + players.sort(key=lambda x: x.level, reverse=True) + team1 = Team( + player1=DisplayPlayer(players[0]), player2=DisplayPlayer(players[3]) ) - for i in range(count) - ] - - -def generate_guests(guests): - beginner_guests = generate_guest_type(guests[1], 1, "Beginner") - novice_guests = generate_guest_type(guests[3], 3, "Novice") - intermediate_guests = generate_guest_type(guests[5], 3, "Intermediate") - return beginner_guests + novice_guests + intermediate_guests - - -def _textualize_guest(level, value) -> str: - match level: - case 1: - text = "Beginners" - case 3: - text = "Novices" - case 5: - text = "Intermediates" - if value == 1: - text = text[:-1] - return f"{value} {text}" + team2 = Team( + player1=DisplayPlayer(players[1]), player2=DisplayPlayer(players[2]) + ) + games.append(Game(court, team1, team2)) + return games, selected_players -def _init_database_from_csv(session, csv_location: str) -> None: - data = read_csv(csv_location) - session.query(Player).delete() - for _, entry in data.iterrows(): - player = Player( - id=entry["id"], - first=entry["first"], - last=entry["last"], - level=entry["skill"], - status=False, - count=0, - ) - session.add(player) - session.commit() +def separate_teams_and_players(players: list[Player]) -> Tuple[dict, 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 _init_random_database(session, player_count: int = 80) -> None: - for i in range(player_count): - player = Player( - id=i, - first=get_first_name(), - last=get_last_name(), - level=randint(MIN_LEVEL, MAX_LEVEL), - status=bool(getrandbits(1)), - count=0, - ) - session.add(player) - session.commit() +def split_courts(courts: dict, n) -> Tuple[dict, dict]: + first_half = {k: v for (k, v) in [x for x in courts.items()][:n]} + second_half = {k: v for (k, v) in [x for x in courts.items()][n:]} + return first_half, second_half |
