diff --git a/frontend/static/js/api/tournament/Tournament.js b/frontend/static/js/api/tournament/Tournament.js
index 634ee11..caee57c 100644
--- a/frontend/static/js/api/tournament/Tournament.js
+++ b/frontend/static/js/api/tournament/Tournament.js
@@ -42,14 +42,33 @@ class Tourmanent extends AExchangeable
* @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>}
@@ -72,18 +91,35 @@ class Tourmanent extends AExchangeable
return;
this.connected = false;
this._socket.close();
- this.disconnectHandler(event);
+ this._disconnectHandler(event);
}
/**
* @param {Object} data
*/
- async _receiveParticipantUpdate(data)
+ async _receiveAddParticipant(data)
{
- this.par
+ 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)
}
- async onError(data)
+ /**
+ * @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);
}
@@ -92,14 +128,26 @@ class Tourmanent extends AExchangeable
*
* @param {MessageEvent} event
*/
- onReceive(event)
+ async onReceive(event)
{
const data = JSON.parse(event.data);
- if (data.detail === "error")
- this.onError(data);
- else if (["del_participant", "add_participant"].includes(data.detail))
- this._receiveParticipantUpdate(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;
+
+ default:
+ break;
+ }
}
/**
@@ -109,9 +157,11 @@ class Tourmanent extends AExchangeable
* @param {CallableFunction} delParticipantHandler called when a participants leave the tournament
* @param {CallableFunction} disconnectHandler
* @param {CallableFunction} goToHandler called when the next game will start
- * @returns {?}
+ * @param {CallableFunction} startHandler called when tournament start
+ * @param {CallableFunction} finishHandler called when tournament finish
+ * @returns {Promise}
*/
- async join(participantsUpdateHandler, errorHandler, goToHandler, disconnectHandler)
+ async join(addParticipantHandler, delParticipantHandler, startHandler, finishHandler, errorHandler, goToHandler, disconnectHandler)
{
if (!await this.client.isAuthenticated())
return null;
@@ -123,10 +173,13 @@ class Tourmanent extends AExchangeable
this.connected = true;
this.isParticipating = false;
- this.participantsUpdateHandler = participantsUpdateHandler;
- this.errorHandler = errorHandler;
- this.disconnectHandler = disconnectHandler;
- this.goToHandler = goToHandler;
+ 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);
diff --git a/frontend/static/js/views/tournament/TournamentCreateView.js b/frontend/static/js/views/tournament/TournamentCreateView.js
index 6565520..24752f1 100644
--- a/frontend/static/js/views/tournament/TournamentCreateView.js
+++ b/frontend/static/js/views/tournament/TournamentCreateView.js
@@ -16,10 +16,9 @@ export default class extends AbstractAuthenticatedView
if (name.length == 0)
name = lang.get("TournamentCreateTournamentName");
- console.log(name);
- let nb_players = document.getElementById("nb-players-input").value;
+ let nb_participant = document.getElementById("nb-participant-input").value;
- let response = await client.tournaments.createTournament(nb_players, name);
+ let response = await client.tournaments.createTournament(nb_participant, name);
let response_data = await response.json();
let id = response_data.id;
@@ -29,7 +28,7 @@ export default class extends AbstractAuthenticatedView
return;
}
- clearIds("innerHTML", ["name", "nb_players"]);
+ clearIds("innerHTML", ["name", "nb_participants"]);
fill_errors(response_data, "innerHTML");
}
@@ -50,9 +49,16 @@ export default class extends AbstractAuthenticatedView
-
-
-
+
+
+
diff --git a/frontend/static/js/views/tournament/TournamentPageView.js b/frontend/static/js/views/tournament/TournamentPageView.js
index a43c84e..e7a5833 100644
--- a/frontend/static/js/views/tournament/TournamentPageView.js
+++ b/frontend/static/js/views/tournament/TournamentPageView.js
@@ -3,6 +3,10 @@ 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)
@@ -13,95 +17,49 @@ export default class extends AbstractAuthenticatedView
pressButton()
{
- this.tournament.toggle_participation();
+ this.tournament.setParticipation(!this.tournament.isParticipating);
+ this.updateParticipating()
}
- createGraph()
+ updateParticipating()
{
- console.log(this.tournament);
- let tournament_tree = document.createElement("div");
-
- tournament_tree.id = "tournament-tree";
-
- document.getElementById("app").appendChild(tournament_tree);
-
- for (let round_id = 0; round_id < this.tournament.round.length; round_id++)
- {
- let current_round = document.createElement("ul");
-
- tournament_tree.appendChild(current_round);
-
- current_round.className = `round round-${round_id}`;
-
- for (let participant_i = 0; participant_i < this.tournament.round[round_id].length; participant_i += 2)
- {
- let spacer = document.createElement("li");
-
- spacer.className = "spacer";
- spacer.innerText = " ";
-
- current_round.appendChild(spacer);
-
- let game_top = document.createElement("li");
-
- game_top.className = "game game-top";
- game_top.innerText = `${this.tournament.round[round_id][participant_i]}`;
-
- current_round.appendChild(game_top);
-
- let game_spacer = document.createElement("li");
-
- spacer.className = "game game-spacer";
- spacer.innerText = " ";
-
- current_round.appendChild(game_spacer);
-
- let game_bottom = document.createElement("li");
-
- game_bottom.className = "game game-bottom";
- game_bottom.innerText = `${this.tournament.round[round_id][participant_i + 1]}`;
-
- current_round.appendChild(game_bottom);
- }
- }
-
- }
-
- async receive(data)
- {
- if (data.detail === "update_participants")
- document.getElementById("nb_participants").innerText = `${data.participants} / ${this.tournament.nb_participants}`;
- if (data.detail === "go_to")
- navigateTo(data.url);
- if (data.detail === "is_participant")
- this.updateParticipating(data.is_participant);
- if (data.detail === "error")
- document.getElementById("display").innerText = data.error_message;
- }
-
- async updateParticipating(state)
- {
- document.getElementById("button").value = state ? `Leave ${this.tournament.name}` : `Join ${this.tournament.name}`;
- document.getElementById("display").innerText = state ? "You are a particpant" : "You are not a participant";
- }
-
- /**
- *
- * @param {[Profile]} oldParticipantsList
- * @param {[Profile]} currentParticipantsList
- */
- async onParticipantsUpdate(oldParticipantsList, currentParticipantsList)
- {
-
+ 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()
@@ -114,20 +72,27 @@ export default class extends AbstractAuthenticatedView
if (this.tournament === null)
return 404;
- this.tournament.join(this.onParticipantsUpdate.bind(this), this.onError.bind(this), this.onDisconnect.bind(this));
+ 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.nb_participants;
+ 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.createGraph();
+
+ this.chat = document.getElementById("chat");
+ }
+
+ addChatMessage(message)
+ {
+ this.chat.innerText += message;
}
async getHtml()
@@ -150,13 +115,19 @@ export default class extends AbstractAuthenticatedView
Loading... |
- status |
+ Expected number of participants |
+ Loading... |
+
+
+ state |
Loading... |
+
`;
}
}
diff --git a/tournament/consumers.py b/tournament/consumers.py
index cb99d9c..78aa59f 100644
--- a/tournament/consumers.py
+++ b/tournament/consumers.py
@@ -6,7 +6,7 @@ from django.contrib.auth.models import User
from django.db.models import QuerySet
from django.utils.translation import gettext as _
-
+from games.models import GameModel
from profiles.models import ProfileModel
from profiles.serializers.ProfileSerializer import ProfileSerializer
from .models import TournamentModel
@@ -31,6 +31,9 @@ class TournamentMember:
data_to_send.update(data)
self.send("error", data_to_send)
+
+ def send_goto(self, game: GameModel):
+ self.send("go_to", {"game_id": game.pk})
def _receive_participating(self, data: dict) -> None:
@@ -38,20 +41,18 @@ class TournamentMember:
if (is_participating is None):
self.send_error(_("Missing is_participating statement."))
return
- self._room.set_participation()
+
+ self._room.set_participation(self, is_participating)
def receive(self, data: dict):
- if self.is_participating == False:
- return
-
detail: str | None = data.get("detail")
if (detail is None):
return
match(detail):
- case "update_particapating":
- self._receive_participating()
+ case "update_participating":
+ self._receive_participating(data)
case _:
print("bozo_send")
@@ -80,16 +81,35 @@ class TournamentRoom:
def __init__(self, room_manager: TournamentRoomManager, tournament: TournamentModel):
self._room_manager: TournamentRoomManager = room_manager
- self._member_list: list[TournamentMember] = []
+ self._member_list: set[TournamentMember] = set()
self._model: TournamentModel = tournament
def join(self, socket: TournamentWebConsumer) -> TournamentMember:
member: TournamentMember = TournamentMember(socket, self)
- self._member_list.append(member)
+ self._member_list.add(member)
return member
+
+ def get_participants_profiles(self) -> list[ProfileModel]:
+ return [participant._socket.user.profilemodel for participant in self.get_participants()]
+ def start(self) -> None:
+
+ games: list[GameModel] = self._model.start()
+
+ self.broadcast("start")
+
+ for game in games:
+ for player in game.get_players():
+ participant: TournamentMember = self.get_participant_by_profile(player)
+ participant.send_goto(game)
+
+ 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
@@ -100,10 +120,13 @@ class TournamentRoom:
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: list[TournamentMember] = []) -> None:
+ def broadcast(self, detail: str, data: dict, excludes: set[TournamentMember] = set()) -> None:
- member_list: list[TournamentMember] = [member for member in self._member_list if member not in excludes]
+ member_list: list[TournamentMember] = self._member_list - excludes
for member in member_list:
member.send(detail, data)
@@ -120,11 +143,14 @@ class TournamentRoom:
return
if (is_participating == True):
- self.broadcast("add_participant", {"profile", ProfileSerializer(member._socket.user.profilemodel).data})
+ self.broadcast("add_participant", {"participant": ProfileSerializer(member._socket.user.profilemodel).data})
else:
- self.broadcast("del_participant", {"profile", ProfileSerializer(member._socket.user.profilemodel).data})
+ 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()
diff --git a/tournament/models.py b/tournament/models.py
index 176fdfc..4f0fe28 100644
--- a/tournament/models.py
+++ b/tournament/models.py
@@ -1,21 +1,12 @@
from __future__ import annotations
-from transcendence.abstract.AbstractRoomMember import AbstractRoomMember
-from transcendence.abstract.AbstractRoom import AbstractRoom
-from transcendence.abstract.AbstractRoomManager import AbstractRoomManager
-
-from profiles.models import ProfileModel
from games.models import GameModel
from django.contrib.auth.models import User
from django.db.models import CASCADE
-from channels.generic.websocket import WebsocketConsumer
from django.db import models
-import json
-
-# Create your models here.tu
class TournamentModel(models.Model):
name = models.CharField(max_length = 100)
@@ -23,12 +14,14 @@ class TournamentModel(models.Model):
round = models.IntegerField()
started = models.BooleanField(default = False)
finished = models.BooleanField(default = False)
- winner = models.ForeignKey(ProfileModel, on_delete=CASCADE, blank=True, null=True)
+ winner = models.ForeignKey(User, on_delete=CASCADE, blank=True, null=True)
- def _register_participant(self, participant: ProfileModel) -> None:
+ def _register_participant(self, participant: User) -> None:
TournamentParticipantModel(participant=participant, tournament=self).save()
- def start(self, participants: list[ProfileModel]) -> None:
+ def start(self, participants: list[User]) -> None:
+
+ games: list[GameModel] = []
self.started = True
@@ -36,11 +29,14 @@ class TournamentModel(models.Model):
self._register_participant(player)
for (participant1, participant2) in zip(participants[0::2], participants[1::2]):
- self.create_game([participant1, participant2], round=1)
+ game: GameModel = self.create_game([participant1, participant2], round=1)
+ games.append(game)
self.save()
- def create_game(self, participants: list[ProfileModel], round: int) -> GameModel:
+ return games
+
+ def create_game(self, participants: list[User], round: int) -> GameModel:
if (self.started == False):
return None
@@ -62,10 +58,10 @@ class TournamentModel(models.Model):
def get_games_by_round(self, round: int) -> list[GameModel]:
return [tournament_game.game for tournament_game in TournamentGameModel.objects.filter(tournament=self, round=round)]
- def get_players_by_round(self, round: int) -> list[ProfileModel]:
+ def get_players_by_round(self, round: int) -> list[User]:
return [game.get_players() for game in self.get_games_by_round(round)]
- def get_winners_by_round(self, round: int) -> list[ProfileModel]:
+ def get_winners_by_round(self, round: int) -> list[User]:
return [game.winner for game in self.get_games_by_round(round)]
def get_participants(self) -> list[TournamentParticipantModel]:
@@ -74,13 +70,12 @@ class TournamentModel(models.Model):
def get_state(self) -> str:
return ("waiting to start", "in progress", "finish")[self.started + self.finished]
- def is_participanting(self, profile: ProfileModel) -> bool:
+ def is_participanting(self, profile: User) -> bool:
return TournamentParticipantModel.objects.filter(participant=profile, tournament=self).exists()
class TournamentParticipantModel(models.Model):
- participant = models.ForeignKey(ProfileModel, on_delete=CASCADE)
+ participant = models.ForeignKey(User, on_delete=CASCADE)
tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE)
- #prout à encore frappé
class TournamentGameModel(models.Model):
diff --git a/tournament/serializers.py b/tournament/serializers.py
index 18b4f13..abf2cfb 100644
--- a/tournament/serializers.py
+++ b/tournament/serializers.py
@@ -1,15 +1,13 @@
from rest_framework import serializers
-from django.db.models.query import QuerySet
-
-from django.contrib.auth.models import User
-
from .models import TournamentModel
from profiles.models import ProfileModel
from profiles.serializers.ProfileSerializer import ProfileSerializer
from games.serializers import GameSerializer
+nb_participants = [2 ** i for i in range(2, 6)]
+
class TournamentSerializer(serializers.ModelSerializer):
state = serializers.SerializerMethodField(read_only=True, required=False)
@@ -30,16 +28,6 @@ class TournamentSerializer(serializers.ModelSerializer):
return ["waiting", "started", "finished"][instance.started + instance.finished]
def validate_nb_participants(self, value: int):
- if (value < 2):
- raise serializers.ValidationError("The numbers of participants must be greather than 2.")
- return value
-
- def validate_nb_participants_by_game(self, value: int):
- if (value < 2):
- raise serializers.ValidationError("The numbers of participants by game must be greather than 2.")
- nb_participants: str = self.initial_data.get("nb_participants")
- if (nb_participants is not None and nb_participants.isnumeric()):
- nb_participants: int = int(nb_participants)
- if (value > nb_participants):
- raise serializers.ValidationError("The numbers of participants by game must be smaller than the numbers of participants.")
+ if (value not in nb_participants):
+ raise serializers.ValidationError(f"The numbers of participants must be {str(nb_participants)}.")
return value
\ No newline at end of file