From 2b264abdf62822936e3510b0f9690ba1e359a9b8 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 5 Apr 2024 17:44:16 +0200 Subject: Added a basic version using flask and jinja --- app.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app.py (limited to 'app.py') diff --git a/app.py b/app.py new file mode 100644 index 0000000..6ff3b9a --- /dev/null +++ b/app.py @@ -0,0 +1,55 @@ +from dash import Dash +from pathlib import PurePath +from importlib.resources import files +from match_up.model import PlaySession +from match_up.view import MainLayout +from match_up.tests import ( + TEST_DATABASE_LOCATION, +) +from dash_bootstrap_components.themes import ZEPHYR +from flask import Flask, render_template + +DATABASE_LOCATION = "storage.json" + + +# class Application: +# def __init__(self, debug=False): +# self._debug = debug +# if debug: +# database_location = TEST_DATABASE_LOCATION +# else: +# database_location = DATABASE_LOCATION +# self._session = PlaySession(PurePath(database_location)) +# self._dashboard = Dash("Match Up!", external_stylesheets=[ZEPHYR]) +# self._layout = MainLayout( +# self._session.data.get_players(), self._session.get_matches() +# ) +# self._dashboard.layout = self._layout.get() + +# def run(self): +# self._dashboard.run(debug=self._debug) + + +# def main(): +# a = Application(debug=True) +# a.run() + + +app = Flask("Match Up") +session = PlaySession(PurePath("test_storage.json")) +base_template_path = "match_up.templates" + + +@app.route("/") +def home(): + print(files(base_template_path).joinpath("index.html").name) + return render_template( + "index.html", + player_list=session.data.get_players(), + game_list=session.get_matches(), + ) + + +if __name__ == "__main__": + print("Starting app") + app.run(host="localhost", debug=True, port=80) -- cgit v1.3.1 From c58caf5b8b8801ba826b0ff6b241d7f25a333bc8 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 12 Apr 2024 10:29:29 +0200 Subject: Added a more cleaner application --- app.py | 73 ++++++++---------- config.py | 16 ++++ dummy_data_generation.py | 28 +++++++ index.html | 0 model.py | 50 +++++++++++++ pyproject.toml | 35 --------- requirements-dev.txt | 1 + requirements.txt | 3 + src/match_up/__init__.py | 60 --------------- src/match_up/__main__.py | 4 - src/match_up/model.py | 153 -------------------------------------- src/match_up/templates/index.html | 15 ---- src/match_up/tests/__init__.py | 44 ----------- src/match_up/view.py | 96 ------------------------ templates/index.html | 51 ------------- templates/index.j2 | 45 +++++++++++ templates/macro.j2 | 3 + templates/player.j2 | 1 + templates/players.j2 | 7 ++ 19 files changed, 184 insertions(+), 501 deletions(-) create mode 100644 config.py create mode 100644 dummy_data_generation.py delete mode 100644 index.html create mode 100644 model.py delete mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 requirements.txt delete mode 100644 src/match_up/__init__.py delete mode 100644 src/match_up/__main__.py delete mode 100644 src/match_up/model.py delete mode 100644 src/match_up/templates/index.html delete mode 100644 src/match_up/tests/__init__.py delete mode 100644 src/match_up/view.py delete mode 100644 templates/index.html create mode 100644 templates/index.j2 create mode 100644 templates/macro.j2 create mode 100644 templates/player.j2 create mode 100644 templates/players.j2 (limited to 'app.py') diff --git a/app.py b/app.py index 6ff3b9a..7d9f808 100644 --- a/app.py +++ b/app.py @@ -1,55 +1,42 @@ -from dash import Dash -from pathlib import PurePath -from importlib.resources import files -from match_up.model import PlaySession -from match_up.view import MainLayout -from match_up.tests import ( - TEST_DATABASE_LOCATION, -) -from dash_bootstrap_components.themes import ZEPHYR -from flask import Flask, render_template +from os import getenv +from model import PlayerList +from flask import Flask, abort, render_template +from dotenv import load_dotenv -DATABASE_LOCATION = "storage.json" +load_dotenv() -# class Application: -# def __init__(self, debug=False): -# self._debug = debug -# if debug: -# database_location = TEST_DATABASE_LOCATION -# else: -# database_location = DATABASE_LOCATION -# self._session = PlaySession(PurePath(database_location)) -# self._dashboard = Dash("Match Up!", external_stylesheets=[ZEPHYR]) -# self._layout = MainLayout( -# self._session.data.get_players(), self._session.get_matches() -# ) -# self._dashboard.layout = self._layout.get() +print("Importing player list...\n") +PLAYER_LIST = PlayerList(location="test_data.csv") +print("List of player:\n") +print(PLAYER_LIST) -# def run(self): -# self._dashboard.run(debug=self._debug) +app = Flask("Match Up! Backend") +env_config = getenv("PROD_APP_SETTINGS", "config.DevelopmentConfig") +app.config.from_object(env_config) -# def main(): -# a = Application(debug=True) -# a.run() - - -app = Flask("Match Up") -session = PlaySession(PurePath("test_storage.json")) -base_template_path = "match_up.templates" +@app.route("/", methods=["GET"]) +def home(): + return render_template("index.j2") -@app.route("/") -def home(): - print(files(base_template_path).joinpath("index.html").name) +@app.route("/player-toggle/", methods=["POST"]) +def toggle_player(player_request): + selection = PLAYER_LIST.get_player(player_request) + if len(selection) != 1: + abort(404) + PLAYER_LIST.toggle_player(player_request) + selection = PLAYER_LIST.get_player(player_request) return render_template( - "index.html", - player_list=session.data.get_players(), - game_list=session.get_matches(), + "player.j2", + player=selection[0], ) -if __name__ == "__main__": - print("Starting app") - app.run(host="localhost", debug=True, port=80) +@app.route("/player-list", methods=["GET"]) +def get_list_of_players(): + return render_template( + "players.j2", + players=PLAYER_LIST.players, + ) diff --git a/config.py b/config.py new file mode 100644 index 0000000..7d01933 --- /dev/null +++ b/config.py @@ -0,0 +1,16 @@ +class Config: + DEBUG = False + DEVELOPMENT = False + CSRF_ENABLED = True + ASSETS_DEBUG = False + + +class ProductionConfig(Config): + pass + + +class DevelopmentConfig(Config): + DEBUG = True + DEVELOPMENT = True + TEMPLATES_AUTO_RELOAD = True + ASSETS_DEBUG = True diff --git a/dummy_data_generation.py b/dummy_data_generation.py new file mode 100644 index 0000000..3ee6e42 --- /dev/null +++ b/dummy_data_generation.py @@ -0,0 +1,28 @@ +from csv import writer +from model import Player +from names import get_full_name +from random import randint, getrandbits + + +def get_test_players(total_player: int = 20, min_skill: int = 1, max_skill: int = 10): + return [ + Player( + name=get_full_name(), + skill=randint(min_skill, max_skill), + status=bool(getrandbits(1)), + ) + for _ in range(total_player) + ] + + +def main(): + dummy_list = get_test_players() + with open("test_data.csv", "w", newline="") as file: + data_writer = writer(file) + data_writer.writerow(["name", "skill", "status"]) + for player in dummy_list: + data_writer.writerow([player.name, player.skill, player.status]) + + +if __name__ == "__main__": + main() diff --git a/index.html b/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/model.py b/model.py new file mode 100644 index 0000000..53119eb --- /dev/null +++ b/model.py @@ -0,0 +1,50 @@ +from csv import reader +from pathlib import Path +from dataclasses import dataclass + + +@dataclass +class Player: + name: str + skill: int + status: bool + + +class PlayerList: + def __init__(self, location: Path = "players.csv"): + with open(location, newline="") as file: + data_reader = reader(file) + next(data_reader, None) + self.players = [ + ( + Player( + name=row[0], + skill=row[1], + status=(row[2] == "True"), + ) + ) + for row in data_reader + ] + + def get_player(self, player_request: str): + return [player for player in self.players if player.name == player_request] + + 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 diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index a82318c..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" - -[project] -name = "match_up" -version = "0.1" -license = {file = "LICENSE"} -authors = [ - { name="Karan Jayachandra", email="mail@karanjayachandra.com" }, -] -description = "Plotly dashboard for skill based matchmaking." -readme = "README.md" -requires-python = ">=3.10" -classifiers = [ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", -] -dependencies = [ - 'tinydb', - 'plotly', - 'dash', - 'dash_bootstrap_components', -] - -[project.scripts] -match_up = "match_up:main" - -[project.urls] -Repository = "https://gitlab.com/KaranJayachandra/match_up" - -[project.optional-dependencies] -tests = ['names', 'pytest', 'pytest-cov'] -docs = ['sphinx', 'sphinx-autodoc-typehints'] -ci = ['coverage2clover'] \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..ee8bbca --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +names \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..886c3db --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +python-dotenv +gunicorn \ No newline at end of file diff --git a/src/match_up/__init__.py b/src/match_up/__init__.py deleted file mode 100644 index 95f767e..0000000 --- a/src/match_up/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -from dash import Dash -from pathlib import PurePath -from importlib.resources import files -from match_up.model import PlaySession -from match_up.view import MainLayout -from match_up.tests import ( - TEST_DATABASE_LOCATION, -) -from dash_bootstrap_components.themes import ZEPHYR -from flask import Flask, render_template - -DATABASE_LOCATION = "storage.json" - - -# class Application: -# def __init__(self, debug=False): -# self._debug = debug -# if debug: -# database_location = TEST_DATABASE_LOCATION -# else: -# database_location = DATABASE_LOCATION -# self._session = PlaySession(PurePath(database_location)) -# self._dashboard = Dash("Match Up!", external_stylesheets=[ZEPHYR]) -# self._layout = MainLayout( -# self._session.data.get_players(), self._session.get_matches() -# ) -# self._dashboard.layout = self._layout.get() - -# def run(self): -# self._dashboard.run(debug=self._debug) - - -# def main(): -# a = Application(debug=True) -# a.run() - - -app = Flask("Match Up", template_folder="templates") -session = PlaySession(PurePath("test_storage.json")) -base_template_path = "match_up.templates" - - -@app.route("/") -def home(): - print(files(base_template_path).joinpath("index.html").name) - # return render_template( - # files(base_template_path).joinpath("index.html").name, - # title="My Generated Page", - # people=session.data.get_players(), - # ) - return render_template( - "index.html", - title="My Generated Page", - people=session.data.get_players(), - ) - - -if __name__ == "__main__": - print("Starting app") - app.run(host="localhost", debug=True, port=80) diff --git a/src/match_up/__main__.py b/src/match_up/__main__.py deleted file mode 100644 index 3500cdb..0000000 --- a/src/match_up/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from match_up import main - -if __name__ == "__main__": - main() diff --git a/src/match_up/model.py b/src/match_up/model.py deleted file mode 100644 index d27893d..0000000 --- a/src/match_up/model.py +++ /dev/null @@ -1,153 +0,0 @@ -from pathlib import PurePath -from tinydb import TinyDB, where -from dataclasses import dataclass -from random import randint - -MIN_SKILL = 1 -MAX_SKILL = 10 - - -class PlaySession: - def __init__(self, database_location: PurePath): - self.data = PlayerDatabase(database_location) - - def get_matches(self, number_of_courts: int = 4): - player_list = self.data.get_players() - game_list = [] - for i in range(number_of_courts): - game_list.append( - Game( - court_number=i + 1, - team_1=Team( - player_1=player_list[randint(0, len(player_list) - 1)], - player_2=player_list[randint(0, len(player_list) - 1)], - ), - team_2=Team( - player_1=player_list[randint(0, len(player_list) - 1)], - player_2=player_list[randint(0, len(player_list) - 1)], - ), - ) - ) - return game_list - - def end(self): - self.data.close() - - def __str__(self): - display_string = "" - for player in self.data.get_players(): - display_string = display_string + player.__str__() + "\n" - return display_string.rstrip() - - def __del__(self): - self.end() - - -@dataclass -class Player: - first_name: str - last_name: str - skill: int = 1 - status: bool = False - wait_time: int = 0 - - def get_name(self): - return self.first_name + " " + self.last_name - - -@dataclass -class Team: - player_1: Player - player_2: Player - - def __str__(self): - return self.player_1.get_name() + " & " + self.player_2.get_name() - - -@dataclass -class Game: - court_number: int - team_1: Team - team_2: Team - - -class PlayerDatabase: - def __init__(self, database_location: PurePath = PurePath("storage.json")): - print(f"Using database location: {database_location.name}") - self._database = TinyDB(database_location.name) - - def get_players(self) -> list[Player]: - player_list = [] - for row in self._database: - player_list.append( - Player( - first_name=row["first_name"], - last_name=row["last_name"], - skill=row["skill"], - status=row["status"], - wait_time=row["wait_time"], - ) - ) - return player_list - - def activate_player(self, player: Player): - self._database.update( - {"status": True}, - where("first_name") == player.first_name - and where("last_name") == player.last_name, - ) - - def deactivate_player(self, player: Player): - self._database.update( - {"wait_time": False}, - where("first_name") == player.first_name - and where("last_name") == player.last_name, - ) - - def increment_player_wait(self, player: Player): - self._database.update( - {"wait_time": 1}, - where("first_name") == player.first_name - and where("last_name") == player.last_name, - ) - - def reset_player_wait(self, player: Player): - self._database.update( - {"wait_time": 0}, - where("first_name") == player.first_name - and where("last_name") == player.last_name, - ) - - def deactivate_all_players(self) -> bool: - for row in self._database: - row["status"] = False - - def add_player(self, player: Player) -> bool: - self._database.insert( - { - "first_name": player.first_name, - "last_name": player.last_name, - "skill": player.skill, - "status": player.status, - "wait_time": player.wait_time, - } - ) - - def remove_player(self, player: Player) -> bool: - self._database.remove( - where("first_name") == player.first_name - and where("last_name") == player.last_name - ) - - def update_player_skill(self, player: Player): - self._database.update( - {"skill": player.skill}, - where("first_name") == player.first_name - and where("last_name") == player.last_name, - ) - - def close(self) -> None: - self._database.close() - - def __del__(self): - self.close() diff --git a/src/match_up/templates/index.html b/src/match_up/templates/index.html deleted file mode 100644 index e292734..0000000 --- a/src/match_up/templates/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - {{ title }} - - -

Match Up!

-
    - {% for person in people %} -
  • {{ person.first_name }}
  • - {% endfor %} -
- - \ No newline at end of file diff --git a/src/match_up/tests/__init__.py b/src/match_up/tests/__init__.py deleted file mode 100644 index 3166746..0000000 --- a/src/match_up/tests/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -from os import remove -from os.path import isfile -from random import randint -from pathlib import PurePath -from names import get_first_name, get_last_name -from match_up.model import Player, PlayerDatabase, MIN_SKILL, MAX_SKILL - -TEST_DATABASE_LOCATION = PurePath("test_storage.json") - - -def generate_test_database(total_players: int = 20) -> None: - try: - if isfile(TEST_DATABASE_LOCATION): - remove(TEST_DATABASE_LOCATION) - db = PlayerDatabase(TEST_DATABASE_LOCATION) - for _ in range(total_players): - db.add_player( - Player( - first_name=get_first_name(), - last_name=get_last_name(), - skill=randint(MIN_SKILL, MAX_SKILL), - ) - ) - finally: - db.close() - - -def activate_random_players(activate_players: int = 17) -> None: - try: - db = PlayerDatabase(TEST_DATABASE_LOCATION) - player_list = db.get_players() - for index, player in enumerate(player_list): - if index < activate_players: - db.activate_player(player) - finally: - db.close() - - -def main(): - pass - - -if __name__ == "__main__": - main() diff --git a/src/match_up/view.py b/src/match_up/view.py deleted file mode 100644 index 8fa1c54..0000000 --- a/src/match_up/view.py +++ /dev/null @@ -1,96 +0,0 @@ -from dash.html import Div, H4, Thead, Tbody, Tr, Td, Th -from match_up.model import Player, Game -from dash_bootstrap_components import ( - Row, - Col, - Container, - Input, - Select, - Table, - NavbarSimple, -) - - -class GameLayout: - def __init__(self, game_list: list[Game]): - table_header = [Thead(Tr([Th("Court"), Th("Team 1"), Th("Team 2")]))] - table_body = [ - Tbody( - [ - Tr( - [ - Td(children=game.court_number), - Td(children=game.team_1.__str__()), - Td(children=game.team_2.__str__()), - ] - ) - for game in game_list - ] - ) - ] - self._layout = Div( - [ - H4(children="Game List", style={"textAlign": "center"}), - Table(table_header + table_body, bordered=True), - ] - ) - - def get(self): - return self._layout - - -class PlayerLayout: - def __init__(self, player_list: list[Player]): - table_header = [Thead(Tr([Th("Player Name")]))] - table_body = [ - Tbody( - [ - Tr( - [ - Td( - children=player.first_name + " " + player.last_name, - style={"color": ("green" if player.status else "red")}, - ) - ] - ) - for player in player_list - ] - ) - ] - self._layout = Div( - [ - H4(children="Player List", style={"textAlign": "center"}), - Table(table_header + table_body, bordered=True), - ] - ) - - def get(self): - return self._layout - - -class MainLayout: - def __init__(self, player_list: list[Player], game_list: list[Game]): - self._player_layout = PlayerLayout(player_list) - self._game_layout = GameLayout(game_list) - - def refresh(): ... - - def get(self): - full_layout = Div( - [ - NavbarSimple(brand="Match Up!"), - Container( - [ - Row( - [ - Col([self._game_layout.get()], width=9), - Col([self._player_layout.get()], width=3), - ], - style={"margin": "15px"}, - ) - ] - ), - ] - ) - # full_layout = Div([Div(children="Hello World")]) - return full_layout diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index db4a991..0000000 --- a/templates/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - Match Up! - - -

Match Up!

-
-
- - - - - - - - - - {% for game in game_list %} - - - - - - {% endfor %} - -
Court Team 1 Team 2
{{game.court_number}} {{game.team_1.player_1.get_name()}}
{{game.team_1.player_2.get_name()}}
{{game.team_2.player_1.get_name()}}
{{game.team_2.player_2.get_name()}}
-
-
- - - - - - - - {% for player in player_list %} - - - - {% endfor %} - -
Player
- {{ player.get_name() }} -
-
-
- - \ No newline at end of file diff --git a/templates/index.j2 b/templates/index.j2 new file mode 100644 index 0000000..82c3c80 --- /dev/null +++ b/templates/index.j2 @@ -0,0 +1,45 @@ + + + + + + 🏸Match Up! + + + + + +
+

🏸Match Up!

+

Come play with the Smashing Fellows!

+
+
+
+

Players

+
+
+ + + \ No newline at end of file diff --git a/templates/macro.j2 b/templates/macro.j2 new file mode 100644 index 0000000..e0089bc --- /dev/null +++ b/templates/macro.j2 @@ -0,0 +1,3 @@ +{% macro player_button(player) %} + +{%- endmacro %} \ No newline at end of file diff --git a/templates/player.j2 b/templates/player.j2 new file mode 100644 index 0000000..1176d65 --- /dev/null +++ b/templates/player.j2 @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/players.j2 b/templates/players.j2 new file mode 100644 index 0000000..3005fa4 --- /dev/null +++ b/templates/players.j2 @@ -0,0 +1,7 @@ +
+{% for player in players %} +
+ +
+{% endfor %} +
\ No newline at end of file -- cgit v1.3.1 From 88d7300021cd3963f2d6a6202609b0f6e956cdab Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 4 Oct 2024 18:23:33 +0200 Subject: Added the games view --- app.py | 33 +++++++++++++++++++++++++++++++-- model.py | 40 ++++++++++++++++++++++++++++++++++++++++ templates/courts.j2 | 3 +++ templates/games.j2 | 18 ++++++++++++++++++ templates/index.j2 | 12 ++++++++---- 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 templates/courts.j2 create mode 100644 templates/games.j2 (limited to 'app.py') diff --git a/app.py b/app.py index 7d9f808..7778b3e 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ from os import getenv -from model import PlayerList -from flask import Flask, abort, render_template +from model import PlayerList, GameList +from flask import Flask, abort, render_template, request from dotenv import load_dotenv load_dotenv() @@ -8,6 +8,7 @@ load_dotenv() print("Importing player list...\n") PLAYER_LIST = PlayerList(location="test_data.csv") +GAME_GENERATOR = GameList() print("List of player:\n") print(PLAYER_LIST) @@ -21,6 +22,34 @@ def home(): return render_template("index.j2") +# @app.route("/courts", methods=["POST"]) +# def set_courts(): +# print(request) +# print(request.data) +# print(request.json) +# print(request.args) +# return render_template( +# "courts.j2", +# courts=GAME_GENERATOR.courts, +# ) + + +@app.route("/new-games", methods=["POST"]) +def get_new_games(): + return render_template( + "games.j2", + games=GAME_GENERATOR.create_games(PLAYER_LIST.get_active_players()), + ) + + +@app.route("/game-list", methods=["GET"]) +def get_list_of_games(): + return render_template( + "games.j2", + games=GAME_GENERATOR.create_games(PLAYER_LIST.get_active_players()), + ) + + @app.route("/player-toggle/", methods=["POST"]) def toggle_player(player_request): selection = PLAYER_LIST.get_player(player_request) diff --git a/model.py b/model.py index 53119eb..ef6e762 100644 --- a/model.py +++ b/model.py @@ -1,3 +1,4 @@ +from math import floor from csv import reader from pathlib import Path from dataclasses import dataclass @@ -10,7 +11,20 @@ class Player: status: bool +@dataclass +class Team: + player1: Player + player2: Player + + +@dataclass +class Game: + team1: Team + team2: Team + + class PlayerList: + def __init__(self, location: Path = "players.csv"): with open(location, newline="") as file: data_reader = reader(file) @@ -29,6 +43,9 @@ class PlayerList: def get_player(self, player_request: str): return [player for player in self.players if player.name == player_request] + def get_active_players(self): + return [player for player in self.players if player.status] + def toggle_player(self, player_request: Player): self.players = [ ( @@ -48,3 +65,26 @@ class PlayerList: for player in self.players: display_string += player.__str__() + "\n" return display_string + + +@dataclass +class GameList: + courts: int = 8 + + def create_games(self, players: list[Player]): + possible_games = floor(len(players) / 4) + games = [] + for i in range(possible_games): + team1 = Team(player1=players[4 * i + 0], player2=players[4 * i + 1]) + team2 = Team(player1=players[4 * i + 2], player2=players[4 * i + 3]) + game = Game(team1, team2) + games.append(game) + empty_courts = ( + self.courts - possible_games if self.courts > possible_games else 0 + ) + for i in range(empty_courts): + dummy_player = Player(name="-", skill=0, status=True) + dummy_team = Team(player1=dummy_player, player2=dummy_player) + game = Game(dummy_team, dummy_team) + games.append(game) + return games diff --git a/templates/courts.j2 b/templates/courts.j2 new file mode 100644 index 0000000..a7893a7 --- /dev/null +++ b/templates/courts.j2 @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/templates/games.j2 b/templates/games.j2 new file mode 100644 index 0000000..22ea936 --- /dev/null +++ b/templates/games.j2 @@ -0,0 +1,18 @@ +
+ + + + + + +{% for game in games %} + + + +{% endfor %} +
Court Team 1 Team 2
{{ loop.index }}{{ game.team1.player1.name }} + {{ game.team1.player2.name }} + {{ game.team2.player1.name }} + {{ game.team2.player2.name }} +
+
\ No newline at end of file diff --git a/templates/index.j2 b/templates/index.j2 index 82c3c80..be92207 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -30,16 +30,20 @@
-

🏸Match Up!

-

Come play with the Smashing Fellows!

+

🏸The Smashing Fellows

+

Total Courts: 8

-
+
+

Games

+
+
+

Players

\ No newline at end of file -- cgit v1.3.1 From 39a01979a470e836da1128503e4587cb76eeae33 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 4 Oct 2024 18:39:27 +0200 Subject: Added the randomization and the persistent memory --- app.py | 5 +++-- model.py | 30 ++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) (limited to 'app.py') diff --git a/app.py b/app.py index 7778b3e..16659ec 100644 --- a/app.py +++ b/app.py @@ -36,9 +36,10 @@ def home(): @app.route("/new-games", methods=["POST"]) def get_new_games(): + GAME_GENERATOR.create_games(PLAYER_LIST.get_active_players()) return render_template( "games.j2", - games=GAME_GENERATOR.create_games(PLAYER_LIST.get_active_players()), + games=GAME_GENERATOR.games, ) @@ -46,7 +47,7 @@ def get_new_games(): def get_list_of_games(): return render_template( "games.j2", - games=GAME_GENERATOR.create_games(PLAYER_LIST.get_active_players()), + games=GAME_GENERATOR.games, ) diff --git a/model.py b/model.py index ef6e762..4a25400 100644 --- a/model.py +++ b/model.py @@ -1,6 +1,7 @@ from math import floor from csv import reader from pathlib import Path +from random import sample from dataclasses import dataclass @@ -67,24 +68,33 @@ class PlayerList: return display_string +TOTAL_PLAYERS_PER_COURT = 4 +DUMMY_PLAYER = Player(name="-", skill=0, status=True) +DUMMY_TEAM = Team(player1=DUMMY_PLAYER, player2=DUMMY_PLAYER) +DUMMY_GAME = Game(DUMMY_TEAM, DUMMY_TEAM) + + @dataclass class GameList: courts: int = 8 + def __post_init__(self): + self.games = [DUMMY_GAME for _ in range(self.courts)] + def create_games(self, players: list[Player]): - possible_games = floor(len(players) / 4) + possible_games = floor(len(players) / TOTAL_PLAYERS_PER_COURT) + chosen_players = sample(players, possible_games * TOTAL_PLAYERS_PER_COURT) + clumped_players = [ + chosen_players[i : i + TOTAL_PLAYERS_PER_COURT] + for i in range(0, len(chosen_players), TOTAL_PLAYERS_PER_COURT) + ] games = [] - for i in range(possible_games): - team1 = Team(player1=players[4 * i + 0], player2=players[4 * i + 1]) - team2 = Team(player1=players[4 * i + 2], player2=players[4 * i + 3]) + for clump in clumped_players: + team1 = Team(player1=clump[0], player2=clump[1]) + team2 = Team(player1=clump[2], player2=clump[3]) game = Game(team1, team2) games.append(game) empty_courts = ( self.courts - possible_games if self.courts > possible_games else 0 ) - for i in range(empty_courts): - dummy_player = Player(name="-", skill=0, status=True) - dummy_team = Team(player1=dummy_player, player2=dummy_player) - game = Game(dummy_team, dummy_team) - games.append(game) - return games + self.games = games + [DUMMY_GAME for _ in range(empty_courts)] -- cgit v1.3.1 From 0db0ff2f1c712273ac48cb859c24253b1efa8384 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 11 Oct 2024 16:59:45 +0200 Subject: First working version with court selection --- app.py | 45 ++++++++++++------ model.py | 131 +++++++++++++++++++++++++++++++++++++++------------- templates/court.j2 | 1 + templates/courts.j2 | 10 ++-- templates/games.j2 | 2 +- templates/index.j2 | 18 +++++--- 6 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 templates/court.j2 (limited to 'app.py') diff --git a/app.py b/app.py index 16659ec..9f4caed 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ from os import getenv -from model import PlayerList, GameList +from model import CourtList, PlayerList, GameList from flask import Flask, abort, render_template, request from dotenv import load_dotenv @@ -8,7 +8,8 @@ load_dotenv() print("Importing player list...\n") PLAYER_LIST = PlayerList(location="test_data.csv") -GAME_GENERATOR = GameList() +COURT_LIST = CourtList(12) +GAME_GENERATOR = GameList(COURT_LIST) print("List of player:\n") print(PLAYER_LIST) @@ -22,21 +23,9 @@ def home(): return render_template("index.j2") -# @app.route("/courts", methods=["POST"]) -# def set_courts(): -# print(request) -# print(request.data) -# print(request.json) -# print(request.args) -# return render_template( -# "courts.j2", -# courts=GAME_GENERATOR.courts, -# ) - - @app.route("/new-games", methods=["POST"]) def get_new_games(): - GAME_GENERATOR.create_games(PLAYER_LIST.get_active_players()) + GAME_GENERATOR.create_games(COURT_LIST, PLAYER_LIST) return render_template( "games.j2", games=GAME_GENERATOR.games, @@ -70,3 +59,29 @@ def get_list_of_players(): "players.j2", players=PLAYER_LIST.players, ) + + +@app.route("/court-toggle/", methods=["POST"]) +def toggle_court(court_number): + court_number = int(court_number) + selection = COURT_LIST.get_court(court_number) + print(len(selection)) + print(selection) + if len(selection) != 1: + abort(404, f"Multiple courts requested: {len(selection)}") + print(COURT_LIST.courts) + COURT_LIST.toggle_court(court_number) + print(COURT_LIST.courts) + selection = COURT_LIST.get_court(court_number) + return render_template( + "court.j2", + court=selection[0], + ) + + +@app.route("/court-list", methods=["GET"]) +def get_list_of_courts(): + return render_template( + "courts.j2", + courts=COURT_LIST.courts, + ) diff --git a/model.py b/model.py index 4a25400..1d6217b 100644 --- a/model.py +++ b/model.py @@ -2,26 +2,47 @@ from math import floor from csv import reader from pathlib import Path from random import sample +from operator import attrgetter from dataclasses import dataclass @dataclass -class Player: - name: str - skill: int +class Court: + number: int status: bool @dataclass -class Team: - player1: Player - player2: Player +class CourtList: + total: int + + def __post_init__(self): + self.courts = [Court(i + 1, True) for i in range(self.total)] + self.active = self.total + + def get_court(self, court_number: int): + return [court for court in self.courts if court.number == 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 + ] + self.active = sum([court.status for court in self.courts]) @dataclass -class Game: - team1: Team - team2: Team +class Player: + name: str + skill: int = 0 + status: bool = True class PlayerList: @@ -68,33 +89,81 @@ class PlayerList: return display_string -TOTAL_PLAYERS_PER_COURT = 4 -DUMMY_PLAYER = Player(name="-", skill=0, status=True) +@dataclass +class Team: + player1: Player + player2: Player + + +@dataclass +class Game: + court: Court + team1: Team + team2: Team + + +DUMMY_PLAYER = Player(name="-") DUMMY_TEAM = Team(player1=DUMMY_PLAYER, player2=DUMMY_PLAYER) -DUMMY_GAME = Game(DUMMY_TEAM, DUMMY_TEAM) + + +def create_dummy_games(courts: list[Court]): + return [Game(court, DUMMY_TEAM, DUMMY_TEAM) for court in courts] + + +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] + + +PLAYER_PER_COURT = 4 + + +def create_single_game(court: Court, players: list[Player]): + team1 = Team(player1=players[0], player2=players[1]) + team2 = Team(player1=players[2], player2=players[3]) + return Game(court, team1, team2) + + +def create_possible_games(court_list: list[Court], players: list[Player]): + current_players = sample(players, len(court_list) * PLAYER_PER_COURT) + clumped_players = [ + current_players[i : i + PLAYER_PER_COURT] + for i in range(0, len(current_players), PLAYER_PER_COURT) + ] + games = [ + create_single_game(court, clump) + for court, clump in zip(court_list, clumped_players) + ] + return games @dataclass class GameList: - courts: int = 8 + court_list: CourtList def __post_init__(self): - self.games = [DUMMY_GAME for _ in range(self.courts)] - - def create_games(self, players: list[Player]): - possible_games = floor(len(players) / TOTAL_PLAYERS_PER_COURT) - chosen_players = sample(players, possible_games * TOTAL_PLAYERS_PER_COURT) - clumped_players = [ - chosen_players[i : i + TOTAL_PLAYERS_PER_COURT] - for i in range(0, len(chosen_players), TOTAL_PLAYERS_PER_COURT) - ] - games = [] - for clump in clumped_players: - team1 = Team(player1=clump[0], player2=clump[1]) - team2 = Team(player1=clump[2], player2=clump[3]) - game = Game(team1, team2) - games.append(game) - empty_courts = ( - self.courts - possible_games if self.courts > possible_games else 0 + courts = self.court_list.courts + active_courts = [court for court in courts if court.status] + inactive_courts = [court for court in courts if not court.status] + 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")) + self.games = games + + def create_games(self, court_list: 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] + possible_games = floor(len(active_players) / PLAYER_PER_COURT) + active_games = create_possible_games( + active_courts[:possible_games], active_players ) - self.games = games + [DUMMY_GAME for _ in range(empty_courts)] + 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")) + self.games = games diff --git a/templates/court.j2 b/templates/court.j2 new file mode 100644 index 0000000..6ae4d91 --- /dev/null +++ b/templates/court.j2 @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/courts.j2 b/templates/courts.j2 index a7893a7..3134aca 100644 --- a/templates/courts.j2 +++ b/templates/courts.j2 @@ -1,3 +1,7 @@ -
- -
\ No newline at end of file +
+{% for court in courts %} +
+ +
+{% endfor %} +
\ No newline at end of file diff --git a/templates/games.j2 b/templates/games.j2 index 22ea936..83fff7f 100644 --- a/templates/games.j2 +++ b/templates/games.j2 @@ -7,7 +7,7 @@ {% for game in games %} - {{ loop.index }} + {{ game.court.number }} {{ game.team1.player1.name }} {{ game.team1.player2.name }} {{ game.team2.player1.name }} diff --git a/templates/index.j2 b/templates/index.j2 index be92207..a9c451f 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -4,8 +4,8 @@ 🏸Match Up! - - + +
-

🏸The Smashing Fellows

-

Total Courts: 8

+

🏸The Smashing Fellows

+

Games

+
+

Courts

+
+
+

Players

+
-- cgit v1.3.1 From 20d668454a1e1d8f17f7e5d4d25f667f62381cd1 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Fri, 11 Oct 2024 18:56:48 +0200 Subject: Working with Bulma CSS now --- app.py | 28 ++++++++++++-------- dummy_data_generation.py | 4 +-- model.py | 29 ++++++++++----------- templates/court.j2 | 2 +- templates/courts.j2 | 8 +++--- templates/games.j2 | 12 ++++----- templates/index.j2 | 68 +++++++++++++++++++++--------------------------- templates/player.j2 | 2 +- templates/players.j2 | 7 ++--- 9 files changed, 80 insertions(+), 80 deletions(-) (limited to 'app.py') diff --git a/app.py b/app.py index 9f4caed..fac8a29 100644 --- a/app.py +++ b/app.py @@ -1,17 +1,16 @@ from os import getenv -from model import CourtList, PlayerList, GameList -from flask import Flask, abort, render_template, request from dotenv import load_dotenv +from flask import Flask, abort, render_template +from model import CourtList, PlayerList, GameList load_dotenv() -print("Importing player list...\n") -PLAYER_LIST = PlayerList(location="test_data.csv") +PLAYER_LIST = PlayerList(location="test_data_large.csv") COURT_LIST = CourtList(12) -GAME_GENERATOR = GameList(COURT_LIST) -print("List of player:\n") -print(PLAYER_LIST) +GAME_GENERATOR = GameList() +GAME_GENERATOR.clear(COURT_LIST) + app = Flask("Match Up! Backend") env_config = getenv("PROD_APP_SETTINGS", "config.DevelopmentConfig") @@ -25,6 +24,7 @@ def home(): @app.route("/new-games", methods=["POST"]) def get_new_games(): + print("Creating new games...") GAME_GENERATOR.create_games(COURT_LIST, PLAYER_LIST) return render_template( "games.j2", @@ -32,6 +32,16 @@ def get_new_games(): ) +@app.route("/clear-games", methods=["POST"]) +def clear_games(): + print("Clearing games...") + GAME_GENERATOR.clear(COURT_LIST) + return render_template( + "games.j2", + games=GAME_GENERATOR.games, + ) + + @app.route("/game-list", methods=["GET"]) def get_list_of_games(): return render_template( @@ -65,13 +75,9 @@ def get_list_of_players(): def toggle_court(court_number): court_number = int(court_number) selection = COURT_LIST.get_court(court_number) - print(len(selection)) - print(selection) if len(selection) != 1: abort(404, f"Multiple courts requested: {len(selection)}") - print(COURT_LIST.courts) COURT_LIST.toggle_court(court_number) - print(COURT_LIST.courts) selection = COURT_LIST.get_court(court_number) return render_template( "court.j2", diff --git a/dummy_data_generation.py b/dummy_data_generation.py index 3ee6e42..3d6d116 100644 --- a/dummy_data_generation.py +++ b/dummy_data_generation.py @@ -4,7 +4,7 @@ from names import get_full_name from random import randint, getrandbits -def get_test_players(total_player: int = 20, min_skill: int = 1, max_skill: int = 10): +def get_test_players(total_player: int = 80, min_skill: int = 1, max_skill: int = 10): return [ Player( name=get_full_name(), @@ -17,7 +17,7 @@ def get_test_players(total_player: int = 20, min_skill: int = 1, max_skill: int def main(): dummy_list = get_test_players() - with open("test_data.csv", "w", newline="") as file: + with open("test_data_large.csv", "w", newline="") as file: data_writer = writer(file) data_writer.writerow(["name", "skill", "status"]) for player in dummy_list: diff --git a/model.py b/model.py index 1d6217b..9af7198 100644 --- a/model.py +++ b/model.py @@ -48,25 +48,25 @@ class Player: class PlayerList: def __init__(self, location: Path = "players.csv"): + players = [] with open(location, newline="") as file: data_reader = reader(file) next(data_reader, None) - self.players = [ - ( - Player( - name=row[0], - skill=row[1], - status=(row[2] == "True"), - ) - ) - for row in data_reader - ] + for row in data_reader: + player = Player(name=row[0], skill=row[1], status=(row[2] == "True")) + players.append(player) + players.sort(key=attrgetter("name")) + self.players = players def get_player(self, player_request: str): - return [player for player in self.players if player.name == player_request] + players = [player for player in self.players if player.name == player_request] + players.sort(key=attrgetter("name")) + return players def get_active_players(self): - return [player for player in self.players if player.status] + 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 = [ @@ -142,10 +142,9 @@ def create_possible_games(court_list: list[Court], players: list[Player]): @dataclass class GameList: - court_list: CourtList - def __post_init__(self): - courts = self.court_list.courts + 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] dummy_games = create_dummy_games(active_courts) diff --git a/templates/court.j2 b/templates/court.j2 index 6ae4d91..425e7d5 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 3134aca..64fd44a 100644 --- a/templates/courts.j2 +++ b/templates/courts.j2 @@ -1,7 +1,9 @@ -
+
+
{% for court in courts %} -
- +
+
{% endfor %} +
\ No newline at end of file diff --git a/templates/games.j2 b/templates/games.j2 index 83fff7f..b070b16 100644 --- a/templates/games.j2 +++ b/templates/games.j2 @@ -1,10 +1,10 @@ -
- - +
+ - + + {% for game in games %} @@ -14,5 +14,5 @@ {% endfor %} -
Court Team 1 Team 2
{{ game.court.number }}{{ game.team2.player2.name }}
-
\ No newline at end of file + + \ No newline at end of file diff --git a/templates/index.j2 b/templates/index.j2 index a9c451f..ac6ddf3 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -2,54 +2,46 @@ - + 🏸Match Up! - + + -
-

🏸The Smashing Fellows

-
-
-
-

Games

-
-
- -
-

Courts

+
+
+

🏸The Smashing Fellows

+
+
+
+

Games

+
+
+
+
+
+
+
+
+
+
+

Courts

-
-
-
-

Players

+ +
+

Players

-
-
-