clean: rm online tournament

This commit is contained in:
starnakin 2024-05-13 16:49:10 +02:00
parent ae76b82169
commit bb6353f578
25 changed files with 2 additions and 1196 deletions

View File

@ -3,7 +3,6 @@ import { MatchMaking } from "./Matchmaking.js";
import { Profiles } from "./Profiles.js";
import { Channels } from './chat/Channels.js';
import { MyProfile } from "./MyProfile.js";
import { Tourmanents } from "./tournament/Tournaments.js";
import { Channel } from "./chat/Channel.js";
import Notice from "./Notice.js";
import LanguageManager from './LanguageManager.js';
@ -46,11 +45,6 @@ class Client
*/
this.matchmaking = new MatchMaking(this);
/**
* @type {Tourmanents}
*/
this.tournaments = new Tourmanents(this);
/**
* @type {Boolean} A private var represent if the is is log NEVER USE IT use await isAuthenticated()
*/

View File

@ -1,201 +0,0 @@
import { AExchangeable } from "../AExchangable.js";
import { Client } from "../Client.js";
import { Profile } from "../Profile.js";
class Tourmanent extends AExchangeable
{
/**
*
* @param {Client} client
* @param {Number} id the id of the tournament
*/
constructor(client, id)
{
super();
/**
* @type {Number}
*/
this.id = id;
/**
* @type {Client}
*/
this.client = client;
/**
* @type {Number}
*/
this.nb_participants;
/**
* @type {[Profile]} proutman à encore frappé
*/
this.participantList = []
/**
* @type {Boolean}
*/
this.started;
/**
* @type {Number}
*/
this.finished;
/**
* @type {"finished" | "started" | "waiting"} must be "finished", or "started", or "waiting". Any other return all elements
*/
this.state;
/**
* @type {Boolean} the client is a participant of the tournament
*/
this.is_participating;
}
/**
* @param {Boolean} newParticipation
*/
async setParticipation(newParticipation)
{
if (this.isParticipating == newParticipation)
return;
this.isParticipating = newParticipation;
this._socket.send(JSON.stringify({"detail": "update_participating",
"is_participating": newParticipation})
);
}
/**
*
* @returns {Promise<?>}
*/
async init()
{
let response = await this.client._get(`/api/tournaments/${this.id}`);
if (response.status !== 200)
return response.status;
let response_data = await response.json();
this.import(response_data);
}
leave(event)
{
if (this.connected == false)
return;
this.connected = false;
this._socket.close();
if (this.disconnectHandler != null)
this.disconnectHandler(event);
}
/**
* @param {Object} data
*/
async _receiveAddParticipant(data)
{
const participant = new Profile(this.client, undefined, data.participant.user_id);
participant.import(data.participant)
this.participantList.push(participant);
await this._addParticipantHandler(this.participantList.length)
}
/**
* @param {Object} data
*/
async _receiveDelParticipant(data)
{
const index = this.participantList.indexOf((profile) => profile.id === data.profile.user_id)
this.participantList.splice(index, 1);
await this._delParticipantHandler(this.participantList.length);
}
async _receiveError(data)
{
await this.errorHandler(data);
}
async _receiveGoTo(data)
{
await this._goToHandler(data)
}
/**
*
* @param {MessageEvent} event
*/
async onReceive(event)
{
const data = JSON.parse(event.data);
switch (data.detail) {
case "error":
await this._receiveError(data)
break;
case "add_participant":
await this._receiveAddParticipant(data);
break;
case "del_participant":
await this._receiveDelParticipant(data);
break;
case "go_to":
await this._receiveGoTo(data);
break
default:
break;
}
}
/**
* Join the tournament Websocket
* @param {CallableFunction} errorHandler
* @param {CallableFunction} addParticipantHandler called when a participants join the tournament
* @param {CallableFunction} delParticipantHandler called when a participants leave the tournament
* @param {CallableFunction} disconnectHandler
* @param {CallableFunction} goToHandler called when the next game will start
* @param {CallableFunction} startHandler called when tournament start
* @param {CallableFunction} finishHandler called when tournament finish
* @returns {Promise}
*/
async join(addParticipantHandler, delParticipantHandler, startHandler, finishHandler, errorHandler, goToHandler, disconnectHandler)
{
if (!await this.client.isAuthenticated())
return null;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/tournaments/${this.id}`;
this._socket = new WebSocket(url);
this.connected = true;
this.isParticipating = false;
this._startHandler = startHandler;
this._finishHandler = finishHandler;
this._addParticipantHandler = addParticipantHandler;
this._delParticipantHandler = delParticipantHandler;
this._errorHandler = errorHandler;
this._disconnectHandler = disconnectHandler;
this._goToHandler = goToHandler;
this._socket.onmessage = this.onReceive.bind(this);
this._socket.onclose = this.leave.bind(this);
}
}
export { Tourmanent };

View File

@ -1,80 +0,0 @@
import { Client } from "../Client.js";
import { Tourmanent } from "./Tournament.js";
class Tourmanents
{
/**
* @param {Client} client
*/
constructor(client)
{
/**
* @type {Client}
*/
this.client = client;
}
/**
*
* @param {Number} id
* @returns {Promise<Tournament>}
*/
async getTournament(id)
{
let tournament = new Tourmanent(this.client, id);
if (await tournament.init())
return null;
return tournament;
}
/**
*
* @param {Number} nb_participants
* @param {String} name
* @returns {Response}
*/
async createTournament(nb_participants, name = "")
{
let response = await this.client._post("/api/tournaments/", {nb_participants: nb_participants, name: name});
return response;
}
/**
* @param {String} state must be "finished", or "started", or "waiting". Any other return all elements
* @returns {?Promise<[Tourmanent]>}
*/
async search(state)
{
let response = await this.client._get(`/api/tournaments/search/${state}`);
let response_data = await response.json();
if (response.status === 403)
{
this.client._update_logged(false);
return null;
}
let tournaments = [];``
response_data.forEach(tournament_data => {
let tournament = new Tourmanent(this.client, tournament_data.id);
tournament.import(tournament_data);
tournaments.push(tournament);
});
return tournaments;
}
/**
* Get all tournaments
* @returns {?Promise<[Tourmanent]>}
*/
async all()
{
return await this.search("");
}
}
export { Tourmanents };

View File

@ -16,9 +16,6 @@ import AbstractRedirectView from "./views/abstracts/AbstractRedirectView.js";
import SettingsView from "./views/SettingsView.js";
import ProfilePageView from "./views/ProfilePageView.js";
import MatchMakingView from "./views/MatchMakingView.js";
import TournamentPageView from "./views/tournament/TournamentPageView.js";
import TournamentsListView from "./views/tournament/TournamentsListView.js";
import TournamentCreateView from "./views/tournament/TournamentCreateView.js";
import AuthenticationView from "./views/accounts/AuthenticationView.js";
let client = new Client(location.origin);
@ -30,9 +27,9 @@ let lastPageUrlBeforeLogin;
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
const getParams = match => {
const values = match.result.slice(1);
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]];
}));
@ -79,9 +76,6 @@ const router = async(uri) => {
const routes = [
{ path: "/", view: HomeView},
{ path: "/profiles/:username", view: ProfilePageView },
{ path: "/tournaments/create", view: TournamentCreateView },
{ path: "/tournaments/:id", view: TournamentPageView },
{ path: "/tournaments/", view: TournamentsListView },
{ path: "/login", view: AuthenticationView },
{ path: "/register", view: AuthenticationView },
{ path: "/logout", view: LogoutView },

View File

@ -1,71 +0,0 @@
import {client, lang, navigateTo} from "../../index.js";
import { clearIds, fill_errors } from "../../utils/formUtils.js";
import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthenticatedView
{
constructor(params)
{
super(params, "Create tournament");
this.id = params.id;
}
async create()
{
let name = document.getElementById("name-input").value;
if (name.length == 0)
name = lang.get("TournamentCreateTournamentName");
let nb_participant = document.getElementById("nb-participant-input").value;
let response = await client.tournaments.createTournament(nb_participant, name);
let response_data = await response.json();
let id = response_data.id;
if (id !== undefined)
{
navigateTo(`/tournaments/${id}`);
return;
}
clearIds("innerHTML", ["name", "nb_participants"]);
fill_errors(response_data, "innerHTML");
}
async postInit()
{
document.getElementById("create-button").onclick = this.create;
}
async getHtml() {
return /* HTML */ `
<div class='container-fluid'>
<div class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'>
<h4 class='text-center fw-semibold mb-4' id="title">${lang.get("TournamentCreateTitle")}</h4>
<div class='form-floating mb-2'>
<input type='text' class='form-control' id='name-input' placeholder='${lang.get("TournamentCreateTournamentName")}'>
<label for='name-input' id='name-label'>${lang.get("TournamentCreateTournamentName")}</label>
<span class='text-danger' id='name'></span>
</div>
<div class='form-floating mb-2'>
<select class='form-control' id="nb-participant-input">
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
<option value="16">16</option>
<option value="32">32</option>
<option value="64">64</option>
</select>
<label for='nb-participant-input' id='nb-participant-label'>${lang.get("TournamentCreateNbPlayer")}</label>
<span class='text-danger' id='nb_participants'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2 mx-2' id='create-button'>${lang.get("TournamentCreateButton")}</button>
<span class='text-danger my-auto mx-2' id='detail'></span>
</div>
</div>
</div>
`;
}
}

View File

@ -1,173 +0,0 @@
import { Profile } from "../../api/Profile.js";
import { Tourmanent } from "../../api/tournament/Tournament.js";
import {client, navigateTo} from "../../index.js";
import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
const TEXT_CONVENTION = {
"error": "[ERROR]"
}
export default class extends AbstractAuthenticatedView
{
constructor(params)
{
super(params, "Tournament");
this.id = params.id;
}
pressButton()
{
this.tournament.setParticipation(!this.tournament.isParticipating);
this.updateParticipating()
}
updateParticipating()
{
document.getElementById("button").value = this.tournament.isParticipating ? `Leave ${this.tournament.name}` : `Join ${this.tournament.name}`;
document.getElementById("display").innerText = this.tournament.isParticipating ? "You are a particpant" : "You are not a participant";
}
async onDisconnect(event)
{
}
async onError(data)
{
this.addChatMessage(`${TEXT_CONVENTION} data.error_message`);
}
async onFinish()
{
document.getElementById("state").innerText = "finished"
}
async onStart()
{
document.getElementById("state").innerText = "started"
}
async onGoTo(data)
{
await navigateTo(`/games/pong/${data.game_id}`)
}
async onAddParticipant(nb_participants)
{
document.getElementById("nb_participants").innerText = nb_participants;
}
async onDelParticipant(nb_participants)
{
document.getElementById("nb_participants").innerText = nb_participants;
}
async postInit()
{
/**
* @type {Tourmanent}
*/
this.tournament = await client.tournaments.getTournament(this.id);
if (this.tournament === null)
return 404;
this.tournament.join(this.onAddParticipant, this.onDelParticipant, this.onStart, this.onFinish, this.onError, this.onGoTo, this.onDisconnect);
let button = document.getElementById("button");
button.onclick = this.pressButton.bind(this);
document.getElementById("name").innerText = this.tournament.name;
document.getElementById("nb_participants").innerText = this.tournament.participantList.length;
document.getElementById("expected_nb_participants").innerText = this.tournament.nb_participants;
document.getElementById("round").innerText = this.tournament.round;
document.getElementById("state").innerText = this.tournament.state;
if (this.tournament.started === false)
button.disabled = false;
this.chat = document.getElementById("chat");
console.log(this.tournament);
this.display_tree_tournament(this.tournament.nb_participants, this.tournament.participantList);
}
addChatMessage(message)
{
this.chat.innerText += message;
}
async display_tree_tournament(nb_participants, participants) {
const svg = document.getElementById('tree');
svg.innerHTML = '';
let width = 100;
let height = 25;
let gap_y = height + 25;
let gap_x = width + 25;
let start_y = height / 2;
let rounds = Math.log2(nb_participants) + 1;
for (let i = 0; i < rounds; i++) {
let number_square = nb_participants / Math.pow(2, i)
for(let j = 0; j < number_square; j++) {
const y = start_y + gap_y * j * Math.pow(2, i) + (gap_y / 2 * Math.pow(2, i));
svg.appendChild(await this.create_square(gap_x * i, y, width, height, "white", "black"));
}
}
}
async create_square(x, y, width, height, fill, stroke) {
const square = document.createElementNS("http://www.w3.org/2000/svg", "rect");
square.setAttribute("id", square)
square.setAttribute("x", x);
square.setAttribute("y", y);
square.setAttribute("width", width);
square.setAttribute("height", height);
square.setAttribute("fill", fill);
square.setAttribute("stroke", stroke);
return square
}
async getHtml()
{
return `
<link rel="stylesheet" href="/static/css/TournamentPage.css">
<table>
<thead>
<tr>
<th id="name">Loading...</th>
</tr>
</thead>
<tbody>
<tr>
<td>Number of round</td>
<td id="round">Loading...</td>
</tr>
<tr>
<td>Number of participants</td>
<td id="nb_participants">Loading...</td>
</tr>
<tr>
<td>Expected number of participants</td>
<td id="expected_nb_participants">Loading...</td>
</tr>
<tr>
<td>state</td>
<td id="state">Loading...</td>
</tr>
</tbody>
</table>
<input type="button" id="button" value="Join tournament" disabled>
<textarea id="chat" rows="4" cols="50" readonly>
</textarea>
<br>
<svg id="tree" height="3000" width="3000">
</svg>
`;
}
}

View File

@ -1,103 +0,0 @@
import {client, navigateTo} from "../../index.js";
import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthenticatedView
{
constructor(params)
{
super(params, "Tournament");
this.id = params.id;
}
async external_search()
{
let state = document.getElementById("state-select").value;
this.tournaments = await client.tournaments.search(state);
//console.log(this.tournaments);
}
internal_search()
{
this.display_tournaments = [];
this.tournaments.forEach(tournament => {
this.display_tournaments.push(tournament);
});
}
display_result()
{
const tournaments_list = document.getElementById("tournaments-list");
const new_children = [];
console.log(this.display_tournaments);
this.display_tournaments.forEach(tournament => {
let tr = document.createElement("tr");
// name
let td = document.createElement("td");
let button_tournament = document.createElement("a");
button_tournament.innerText = tournament.name;
button_tournament.onclick = async () => {
await navigateTo(String(tournament.id));
}
button_tournament.href = "";
td.appendChild(button_tournament);
tr.appendChild(td);
// state
td = document.createElement("td");
td.innerText = tournament.state;
tr.appendChild(td);
// nb_participants
td = document.createElement("td");
td.innerText = tournament.nb_participants;
tr.appendChild(td);
new_children.push(tr);
});
tournaments_list.replaceChildren(...new_children);
}
async update_query()
{
this.internal_search();
this.display_result();
}
async update_search()
{
await this.external_search();
this.update_query();
}
async postInit()
{
await this.update_search();
document.getElementById("state-select").onchange = this.update_search.bind(this);
}
async getHtml()
{
return `
<select id="state-select">
<option value="waiting">Waiting</option>
<option value="started">Started</option>
<option value="finished">Finished</option>
<option value="all">All</option>
</select>
<table>
<thead>
<td>Name</td>
<td>Status</td>
<td>Max numbers of participants</td>
</thead>
<tbody id="tournaments-list">
</tbody>
</table>
`;
}
}

View File

@ -7,11 +7,6 @@ from django.contrib.auth.models import User
import time
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from tournament.models import TournamentGameModel
class GameModel(models.Model):
finished = models.BooleanField(default = False)

View File

@ -6,8 +6,6 @@ from .ASpectator import ASpectator
from ..models import GameModel
from tournament.models import TournamentGameModel
from django.contrib.auth.models import User
class AGame(AbstractRoom):
@ -18,11 +16,7 @@ class AGame(AbstractRoom):
self.game_manager = game_manager
query = TournamentGameModel.objects.filter(pk = game_id, game_type = game_type)
if (query.exists()):
self.model: TournamentGameModel | GameModel = query[0]
else:
self.model: TournamentGameModel | GameModel = GameModel.objects.get(pk = game_id, game_type = game_type)
self.model: GameModel = GameModel.objects.get(pk = game_id, game_type = game_type)
players: list[User] = self.model.get_players()

View File

@ -3,8 +3,6 @@ from ..models import GameModel
from .pong.PongGame import PongGame
from .tictactoe.TicTacToeGame import TicTacToeGame
from tournament.models import TournamentGameModel
class GameManager():
def __init__(self) -> None:

1
run.sh
View File

@ -6,7 +6,6 @@ pip install -r requirements.txt
python manage.py makemigrations games
python manage.py makemigrations profiles
python manage.py makemigrations chat
python manage.py makemigrations tournament
python manage.py makemigrations notice
python manage.py migrate
python manage.py compilemessages

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class TournamentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'tournament'

View File

@ -1,235 +0,0 @@
from __future__ import annotations
from channels.generic.websocket import WebsocketConsumer
from django.contrib.auth.models import User
from django.db.models import QuerySet
from django.utils.translation import gettext as _
from profiles.models import ProfileModel
from profiles.serializers import ProfileSerializer
from .models import TournamentModel
from .models import TournamentGameModel
from .serializers import TournamentGameSerializer
import json
class TournamentMember:
def __init__(self, socket: TournamentWebConsumer, room: TournamentRoom, is_participating: bool = False) -> None:
self._socket: TournamentWebConsumer = socket
self._room: TournamentRoom = room
self.is_participating: bool = is_participating
def send(self, detail: str, data: dict = {}):
data_to_send: dict = {"detail": detail}
data_to_send.update(data)
self._socket.send(json.dumps(data_to_send))
def send_error(self, error_message: str, data: dict = {}):
data_to_send: dict = {"error_message": error_message}
data_to_send.update(data)
self.send("error", data_to_send)
def send_goto(self, game: TournamentGameModel):
self.send("go_to", {"game_id": game.pk})
def _receive_participating(self, data: dict) -> None:
is_participating: bool | None = data.get("is_participating")
if (is_participating is None):
self.send_error(_("Missing is_participating statement."))
return
self._room.set_participation(self, is_participating)
def receive(self, data: dict):
detail: str | None = data.get("detail")
if (detail is None):
return
match(detail):
case "update_participating":
self._receive_participating(data)
case _:
print("bozo_send")
class TournamentRoomManager:
def __init__(self):
self._room_list: list[TournamentRoom] = []
def get(self, tournament: TournamentModel) -> TournamentRoom:
for room in self._room_list:
if room._model is tournament:
return room
room: TournamentRoom = TournamentRoom(self, tournament)
self._room_list.append(room)
return room
def remove(self, room: TournamentRoom) -> None:
self._room_list.remove(room)
class TournamentRoom:
def __init__(self, room_manager: TournamentRoomManager, tournament: TournamentModel):
self._room_manager: TournamentRoomManager = room_manager
self._member_list: set[TournamentMember] = set()
self._model: TournamentModel = tournament
self._game_in_progress_list: set[TournamentGameModel] = set()
self._current_round = 0
def join(self, socket: TournamentWebConsumer) -> TournamentMember:
member: TournamentMember = TournamentMember(socket, self)
self._member_list.add(member)
return member
def set_game_as_finished(self, game: TournamentGameModel):
self._game_in_progress_list.remove(game)
self.broadcast("game_update", TournamentGameSerializer(game).data)
if len(self._game_in_progress_list) == 0:
self._round_finished()
def set_game_as_started(self, game: TournamentGameModel):
self._game_in_progress_list.add(game)
self.broadcast("game_update", TournamentGameSerializer(game).data)
def _finish(self, winner: User):
self._model.finish(winner)
def _round_finished(self):
if self._current_round == self._model.round:
last_game: TournamentGameSerializer = self._model.get_games_by_round(self._current_round)[0]
self._finish(last_game.winner)
return
self._start_round()
def _start_round(self):
self._current_round += 1
participant_list: set[User] = self._model.get_participants_by_round(self._current_round)
game_list = self._model.create_round(participant_list, self._current_round)
for game in game_list:
for player in game.get_players():
participant: TournamentMember = self.get_participant_by_profile(player)
participant.send_goto(game)
def get_participants_profiles(self) -> list[ProfileModel]:
return [participant._socket.user.profilemodel for participant in self.get_participants()]
def start(self) -> None:
self._model.start()
self.broadcast("start")
self._start_round()
def get_participant_by_profile(self, profile: ProfileModel):
for participant in self.get_participants():
if (participant._socket.user.profilemodel == profile):
return participant
def leave(self, member: TournamentMember) -> None:
# Delete room if nobody connected, no cringe memory leak
if (len(self._member_list) == 1):
self._room_manager.remove(self)
return
self._member_list.remove(member)
self.set_participation(member, False)
def everybody_is_here(self):
return len(self.get_participants()) == self._model.nb_participants
def broadcast(self, detail: str, data: dict, excludes: set[TournamentMember] = set()) -> None:
member_list: list[TournamentMember] = self._member_list - excludes
for member in member_list:
member.send(detail, data)
def get_participants(self) -> list[TournamentMember]:
return [member for member in self._member_list if member.is_participating]
def set_participation(self, member: TournamentMember, is_participating: bool) -> None:
if (self._model.started):
return
if (member.is_participating == is_participating):
return
if (is_participating == True):
self.broadcast("add_participant", {"participant": ProfileSerializer(member._socket.user.profilemodel).data})
else:
self.broadcast("del_participant", {"participant": ProfileSerializer(member._socket.user.profilemodel).data})
member.is_participating = is_participating
if self.everybody_is_here():
self.start()
tournament_manager: TournamentRoomManager = TournamentRoomManager()
class TournamentWebConsumer(WebsocketConsumer):
def connect(self):
self.user: User = self.scope["user"]
if (not self.user.is_authenticated):
return
tournament_id: int = int(self.scope['url_route']['kwargs']['tournament_id'])
query: QuerySet = TournamentModel.objects.filter(pk=tournament_id)
if (not query.exists()):
return
tournament: TournamentModel = query[0]
self.room = tournament_manager.get(tournament)
self.member: TournamentMember = self.room.join(self)
self.accept()
def receive(self, text_data: str = None, bytes_data: bytes = None):
user: User = self.scope["user"]
if (user != self.user):
return
data: dict = json.loads(text_data)
self.member.receive(data)
def disconnect(self, close_code):
self.room.leave(self.member)
super().disconnect(close_code) # proutman à encore frappé

View File

@ -1,116 +0,0 @@
from __future__ import annotations
from typing import Any
from games.models import GameModel
from django.contrib.auth.models import User
from django.db.models import CASCADE
from django.db import models
class TournamentModel(models.Model):
name = models.CharField(max_length = 100)
nb_participants = models.IntegerField()
round = models.IntegerField()
started = models.BooleanField(default = False)
finished = models.BooleanField(default = False)
winner = models.ForeignKey(User, on_delete=CASCADE, blank=True, null=True)
def _register_participant(self, participant: User) -> None:
TournamentParticipantModel(participant=participant, tournament=self).save()
def create_round(self, participants: set[User], round: int) -> set[GameModel]:
game_list: set[GameModel] = set()
for i, (participant1, participant2) in enumerate(zip(participants[0::2], participants[1::2])):
game: GameModel = self.create_game([participant1, participant2], round, i)
game_list.add(game)
return game_list
def start(self, participant_list: set[User]) -> None:
self.started = True
self.round = 1
for participant in participant_list:
self._register_participant(participant)
self.save()
def finish(self, winner: User):
self.finished = True
self.winner = winner
self.save()
def create_game(self, participants: set[User], round: int, pos: int) -> GameModel:
if (self.started == False):
return None
if (len(participants) != 2):
return None
from games.models import GameModel
game: GameModel = GameModel().create(participants)
TournamentGameModel(tournament=self, game=game, round=round, pos=pos).save()
return game
def get_games(self) -> set[GameModel]:
return [{games for games in self.get_games_by_round(i)} for i in range(1, self.round)]
def get_games_by_round(self, round: int) -> set[GameModel]:
return {tournament_game for tournament_game in TournamentGameModel.objects.filter(tournament=self, round=round)}
def get_participants_by_round(self, round: int) -> set[User]:
if round == 1:
return self.get_participants()
return {game.winner for game in self.get_games_by_round(round - 1)}
def get_winners_by_round(self, round: int) -> set[User]:
return {game.winner for game in self.get_games_by_round(round)}
def get_participants(self) -> set[User]:
return {tournament_participant.participant for tournament_participant in TournamentParticipantModel.objects.filter(tournament=self.pk)}
def get_state(self) -> str:
return ("waiting to start", "in progress", "finish")[self.started + self.finished]
def is_participating(self, profile: User) -> bool:
return TournamentParticipantModel.objects.filter(participant=profile, tournament=self).exists()
class TournamentParticipantModel(models.Model):
participant = models.ForeignKey(User, on_delete=CASCADE)
tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE)
class TournamentGameModel(GameModel):
tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE, null=True, blank=True)
round = models.IntegerField()
pos = models.IntegerField()
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
from .consumers import tournament_manager
self.room = tournament_manager.get(self.tournament)
def start(self):
super().start()
self.room.set_game_as_started(self)
def finish(self, winner_id):
super().finish(winner_id)
self.room.set_game_as_finished(self)

View File

@ -1,6 +0,0 @@
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/tournaments/(?P<tournament_id>\d+)$', consumers.TournamentWebConsumer.as_asgi())
]

View File

@ -1,63 +0,0 @@
from rest_framework import serializers
from .models import TournamentModel, TournamentGameModel
from profiles.serializers import ProfileSerializer
from games.serializers import GameSerializer
nb_participants = [2 ** i for i in range(1, 6)]
class TournamentSerializer(serializers.ModelSerializer):
state = serializers.SerializerMethodField(read_only=True, required=False)
participants = serializers.SerializerMethodField(read_only=True, required=False)
round = serializers.ReadOnlyField()
games = serializers.SerializerMethodField(read_only=True, required=False)
started = serializers.ReadOnlyField()
finished = serializers.ReadOnlyField()
name = serializers.CharField(default="")
class Meta:
model = TournamentModel
fields = ["name", "nb_participants", "round", "started", "finished", "id", "state", "participants", "games"]
def get_participants(self, instance: TournamentModel):
return ProfileSerializer(instance.get_participants(), many=True).data
def get_games(self, instance: TournamentModel):
return [GameSerializer(games, many=True).data for games in instance.get_games()]
def get_state(self, instance: TournamentModel):
return ["waiting", "started", "finished"][instance.started + instance.finished]
def validate_nb_participants(self, value: int):
if (value not in nb_participants):
raise serializers.ValidationError(f"The numbers of participants must be {str(nb_participants)}.")
return value
class TournamentGameSerializer(serializers.ModelSerializer):
players = serializers.SerializerMethodField()
winner_id = serializers.ReadOnlyField()
state = serializers.SerializerMethodField()
started = serializers.ReadOnlyField()
finished = serializers.ReadOnlyField()
start_timestamp = serializers.ReadOnlyField()
stop_timestamp = serializers.ReadOnlyField()
gamemode = serializers.ReadOnlyField()
round = serializers.ReadOnlyField()
pos = serializers.ReadOnlyField()
class Meta:
model = TournamentGameModel
fields = ["id", "winner_id", "state", "started", "finished", "players", "start_timestamp", "stop_timestamp", "game_type", "round", "pos"]
def get_state(self, instance: TournamentGameModel):
if (instance.finished):
return "finished"
if (instance.started):
return "started"
return "waiting"
def get_players(self, instance: TournamentGameModel):
return ProfileSerializer(instance.get_players_profiles(), many=True).data

View File

@ -1,44 +0,0 @@
from django.test import TestCase
# Create your tests here.
from django.test.client import Client
from django.http import HttpResponse
from django.contrib.auth.models import User
import json
import uuid
class CreateTest(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/tournaments/"
self.username = str(uuid.uuid4())
self.password = str(uuid.uuid4())
self.nb_players_by_game = 2
self.nb_players = 8
user: User = User.objects.create_user(username=self.username, password=self.password)
self.client.login(username=self.username, password=self.password)
def test_normal(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": self.nb_players})
response_data: dict = json.loads(response.content)
self.assertDictContainsSubset({"name": ""}, response_data)
def test_too_small_nb_players_by_game(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 1, "nb_players": self.nb_players})
response_data = json.loads(response.content)
self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be greather than 2.']})
def test_too_small_nb_players(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": self.nb_players_by_game, "nb_players": 1})
response_data = json.loads(response.content)
self.assertDictEqual(response_data, {'nb_players': ['The numbers of players must be greather than 2.'], 'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']})
def test_nb_players_smaller_nb_players_by_game(self):
response: HttpResponse = self.client.post(self.url, {"nb_players_by_game": 5, "nb_players": 3})
response_data = json.loads(response.content)
self.assertDictEqual(response_data, {'nb_players_by_game': ['The numbers of players by game must be smaller than the numbers of players.']})

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,11 +0,0 @@
from django.urls import path, re_path
from django.conf import settings
from django.conf.urls.static import static
from .viewset import TournamentViewSet
urlpatterns = [
path("<int:pk>", TournamentViewSet.as_view({"get": "retrieve"}), name="tournament_page"),
path("", TournamentViewSet.as_view({"post": "create"}), name="tournament_page"),
re_path(r"search/(?P<state>\w*)", TournamentViewSet.as_view({"get": "list", }), name="tournaments"),
]

View File

@ -1,49 +0,0 @@
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.authentication import SessionAuthentication
from django.http import HttpRequest
from django.contrib.auth import login
from django.db.models import QuerySet
from .models import TournamentModel
from .serializers import TournamentSerializer
import math
# Create your views here.
class TournamentViewSet(viewsets.ModelViewSet):
queryset = TournamentModel.objects.all
serializer_class = TournamentSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def perform_create(self, serializer: TournamentSerializer):
tournament = serializer.save(round=math.log2(serializer.validated_data['nb_participants']) + 1)
return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED)
def list(self, request: HttpRequest, state: str = ""):
query: QuerySet
match state:
case "started":
query = TournamentModel.objects.filter(started=True, finished=False)
case "finished":
query = TournamentModel.objects.filter(finished=True)
case "waiting":
query = TournamentModel.objects.filter(started=False, finished=False)
case _:
query = TournamentModel.objects.all()
serializer = self.serializer_class(query, many=True)
return Response(serializer.data)
def retrieve(self, request: HttpRequest, pk):
if (not TournamentModel.objects.filter(pk=pk).exists()):
return Response({"detail": "Tournament not found."}, status=status.HTTP_404_NOT_FOUND)
tournament = TournamentModel.objects.get(pk=pk)
return Response(self.serializer_class(tournament).data, status=status.HTTP_200_OK)

View File

@ -13,7 +13,6 @@ from channels.auth import AuthMiddlewareStack
import chat.routing
import matchmaking.routing
import tournament.routing
import games.routing
import notice.routing
@ -27,7 +26,6 @@ application = ProtocolTypeRouter({
URLRouter(
chat.routing.websocket_urlpatterns +
matchmaking.routing.websocket_urlpatterns +
tournament.routing.websocket_urlpatterns +
games.routing.websocket_urlpatterns +
notice.routing.websocket_urlpatterns
)

View File

@ -44,7 +44,6 @@ INSTALLED_APPS = [
'channels',
'daphne',
'tournament.apps.TournamentConfig',
'matchmaking.apps.MatchmakingConfig',
'games.apps.GamesConfig',
'accounts.apps.AccountsConfig',

View File

@ -23,7 +23,6 @@ urlpatterns = [
path('api/profiles/', include('profiles.urls')),
path('api/accounts/', include('accounts.urls')),
path('api/chat/', include('chat.urls')),
path('api/tournaments/', include('tournament.urls')),
path('api/games/', include('games.urls')),
re_path(r'^api/', handler_404_view),
path('', include('frontend.urls')),