clean: rm online tournament
This commit is contained in:
parent
ae76b82169
commit
bb6353f578
@ -3,7 +3,6 @@ import { MatchMaking } from "./Matchmaking.js";
|
|||||||
import { Profiles } from "./Profiles.js";
|
import { Profiles } from "./Profiles.js";
|
||||||
import { Channels } from './chat/Channels.js';
|
import { Channels } from './chat/Channels.js';
|
||||||
import { MyProfile } from "./MyProfile.js";
|
import { MyProfile } from "./MyProfile.js";
|
||||||
import { Tourmanents } from "./tournament/Tournaments.js";
|
|
||||||
import { Channel } from "./chat/Channel.js";
|
import { Channel } from "./chat/Channel.js";
|
||||||
import Notice from "./Notice.js";
|
import Notice from "./Notice.js";
|
||||||
import LanguageManager from './LanguageManager.js';
|
import LanguageManager from './LanguageManager.js';
|
||||||
@ -46,11 +45,6 @@ class Client
|
|||||||
*/
|
*/
|
||||||
this.matchmaking = new MatchMaking(this);
|
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()
|
* @type {Boolean} A private var represent if the is is log NEVER USE IT use await isAuthenticated()
|
||||||
*/
|
*/
|
||||||
|
@ -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 };
|
|
@ -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 };
|
|
@ -16,9 +16,6 @@ import AbstractRedirectView from "./views/abstracts/AbstractRedirectView.js";
|
|||||||
import SettingsView from "./views/SettingsView.js";
|
import SettingsView from "./views/SettingsView.js";
|
||||||
import ProfilePageView from "./views/ProfilePageView.js";
|
import ProfilePageView from "./views/ProfilePageView.js";
|
||||||
import MatchMakingView from "./views/MatchMakingView.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";
|
import AuthenticationView from "./views/accounts/AuthenticationView.js";
|
||||||
|
|
||||||
let client = new Client(location.origin);
|
let client = new Client(location.origin);
|
||||||
@ -30,9 +27,9 @@ let lastPageUrlBeforeLogin;
|
|||||||
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
|
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
|
||||||
|
|
||||||
const getParams = match => {
|
const getParams = match => {
|
||||||
|
|
||||||
const values = match.result.slice(1);
|
const values = match.result.slice(1);
|
||||||
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
|
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
|
||||||
|
|
||||||
return Object.fromEntries(keys.map((key, i) => {
|
return Object.fromEntries(keys.map((key, i) => {
|
||||||
return [key, values[i]];
|
return [key, values[i]];
|
||||||
}));
|
}));
|
||||||
@ -79,9 +76,6 @@ const router = async(uri) => {
|
|||||||
const routes = [
|
const routes = [
|
||||||
{ path: "/", view: HomeView},
|
{ path: "/", view: HomeView},
|
||||||
{ path: "/profiles/:username", view: ProfilePageView },
|
{ path: "/profiles/:username", view: ProfilePageView },
|
||||||
{ path: "/tournaments/create", view: TournamentCreateView },
|
|
||||||
{ path: "/tournaments/:id", view: TournamentPageView },
|
|
||||||
{ path: "/tournaments/", view: TournamentsListView },
|
|
||||||
{ path: "/login", view: AuthenticationView },
|
{ path: "/login", view: AuthenticationView },
|
||||||
{ path: "/register", view: AuthenticationView },
|
{ path: "/register", view: AuthenticationView },
|
||||||
{ path: "/logout", view: LogoutView },
|
{ path: "/logout", view: LogoutView },
|
||||||
|
@ -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>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,11 +7,6 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from tournament.models import TournamentGameModel
|
|
||||||
|
|
||||||
class GameModel(models.Model):
|
class GameModel(models.Model):
|
||||||
|
|
||||||
finished = models.BooleanField(default = False)
|
finished = models.BooleanField(default = False)
|
||||||
|
@ -6,8 +6,6 @@ from .ASpectator import ASpectator
|
|||||||
|
|
||||||
from ..models import GameModel
|
from ..models import GameModel
|
||||||
|
|
||||||
from tournament.models import TournamentGameModel
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
class AGame(AbstractRoom):
|
class AGame(AbstractRoom):
|
||||||
@ -18,11 +16,7 @@ class AGame(AbstractRoom):
|
|||||||
|
|
||||||
self.game_manager = game_manager
|
self.game_manager = game_manager
|
||||||
|
|
||||||
query = TournamentGameModel.objects.filter(pk = game_id, game_type = game_type)
|
self.model: GameModel = GameModel.objects.get(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)
|
|
||||||
|
|
||||||
players: list[User] = self.model.get_players()
|
players: list[User] = self.model.get_players()
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@ from ..models import GameModel
|
|||||||
from .pong.PongGame import PongGame
|
from .pong.PongGame import PongGame
|
||||||
from .tictactoe.TicTacToeGame import TicTacToeGame
|
from .tictactoe.TicTacToeGame import TicTacToeGame
|
||||||
|
|
||||||
from tournament.models import TournamentGameModel
|
|
||||||
|
|
||||||
class GameManager():
|
class GameManager():
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
1
run.sh
1
run.sh
@ -6,7 +6,6 @@ pip install -r requirements.txt
|
|||||||
python manage.py makemigrations games
|
python manage.py makemigrations games
|
||||||
python manage.py makemigrations profiles
|
python manage.py makemigrations profiles
|
||||||
python manage.py makemigrations chat
|
python manage.py makemigrations chat
|
||||||
python manage.py makemigrations tournament
|
|
||||||
python manage.py makemigrations notice
|
python manage.py makemigrations notice
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
python manage.py compilemessages
|
python manage.py compilemessages
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class TournamentConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'tournament'
|
|
@ -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é
|
|
@ -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)
|
|
@ -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())
|
|
||||||
]
|
|
@ -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
|
|
@ -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.']})
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
@ -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"),
|
|
||||||
]
|
|
@ -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)
|
|
@ -13,7 +13,6 @@ from channels.auth import AuthMiddlewareStack
|
|||||||
|
|
||||||
import chat.routing
|
import chat.routing
|
||||||
import matchmaking.routing
|
import matchmaking.routing
|
||||||
import tournament.routing
|
|
||||||
import games.routing
|
import games.routing
|
||||||
import notice.routing
|
import notice.routing
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ application = ProtocolTypeRouter({
|
|||||||
URLRouter(
|
URLRouter(
|
||||||
chat.routing.websocket_urlpatterns +
|
chat.routing.websocket_urlpatterns +
|
||||||
matchmaking.routing.websocket_urlpatterns +
|
matchmaking.routing.websocket_urlpatterns +
|
||||||
tournament.routing.websocket_urlpatterns +
|
|
||||||
games.routing.websocket_urlpatterns +
|
games.routing.websocket_urlpatterns +
|
||||||
notice.routing.websocket_urlpatterns
|
notice.routing.websocket_urlpatterns
|
||||||
)
|
)
|
||||||
|
@ -44,7 +44,6 @@ INSTALLED_APPS = [
|
|||||||
'channels',
|
'channels',
|
||||||
'daphne',
|
'daphne',
|
||||||
|
|
||||||
'tournament.apps.TournamentConfig',
|
|
||||||
'matchmaking.apps.MatchmakingConfig',
|
'matchmaking.apps.MatchmakingConfig',
|
||||||
'games.apps.GamesConfig',
|
'games.apps.GamesConfig',
|
||||||
'accounts.apps.AccountsConfig',
|
'accounts.apps.AccountsConfig',
|
||||||
|
@ -23,7 +23,6 @@ urlpatterns = [
|
|||||||
path('api/profiles/', include('profiles.urls')),
|
path('api/profiles/', include('profiles.urls')),
|
||||||
path('api/accounts/', include('accounts.urls')),
|
path('api/accounts/', include('accounts.urls')),
|
||||||
path('api/chat/', include('chat.urls')),
|
path('api/chat/', include('chat.urls')),
|
||||||
path('api/tournaments/', include('tournament.urls')),
|
|
||||||
path('api/games/', include('games.urls')),
|
path('api/games/', include('games.urls')),
|
||||||
re_path(r'^api/', handler_404_view),
|
re_path(r'^api/', handler_404_view),
|
||||||
path('', include('frontend.urls')),
|
path('', include('frontend.urls')),
|
||||||
|
Loading…
Reference in New Issue
Block a user