This commit is contained in:
Xamora 2024-04-27 08:56:51 +02:00
commit 8758e221cb
57 changed files with 974 additions and 822 deletions

View File

@ -4,7 +4,7 @@ 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 { Tourmanents } from "./tournament/Tournaments.js";
import { Notice } from "./chat/Notice.js"; import Notice from "./notice/Notice.js";
import { Channel } from "./chat/Channel.js"; import { Channel } from "./chat/Channel.js";
import LanguageManager from './LanguageManager.js'; import LanguageManager from './LanguageManager.js';
@ -217,7 +217,7 @@ class Client
{ {
this.me = new MyProfile(this); this.me = new MyProfile(this);
await this.me.init(); await this.me.init();
this.notice.connect(); this.notice.start();
document.getElementById('navbarLoggedOut').classList.add('d-none'); document.getElementById('navbarLoggedOut').classList.add('d-none');
document.getElementById('navbarLoggedIn').classList.remove('d-none'); document.getElementById('navbarLoggedIn').classList.remove('d-none');
document.getElementById('navbarDropdownButton').innerHTML = this.me.username; document.getElementById('navbarDropdownButton').innerHTML = this.me.username;
@ -226,7 +226,7 @@ class Client
else else
{ {
this.me = undefined; this.me = undefined;
this.notice.disconnect(); this.notice.stop();
document.getElementById('navbarLoggedOut').classList.remove('d-none'); document.getElementById('navbarLoggedOut').classList.remove('d-none');
document.getElementById('navbarLoggedIn').classList.add('d-none'); document.getElementById('navbarLoggedIn').classList.add('d-none');
document.getElementById('navbarDropdownButton').innerHTML = 'Me'; document.getElementById('navbarDropdownButton').innerHTML = 'Me';

View File

@ -21,12 +21,12 @@ class MatchMaking
* @param {Number} mode The number of players in a game * @param {Number} mode The number of players in a game
* @returns {Promise<?>} * @returns {Promise<?>}
*/ */
async start(receive_func, disconnect_func, gamemode, mode) async start(receive_func, disconnect_func, game_type, mode)
{ {
if (!await this.client.isAuthenticated()) if (!await this.client.isAuthenticated())
return null; return null;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${gamemode}/${mode}`; let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${game_type}/${mode}`;
this._socket = new WebSocket(url); this._socket = new WebSocket(url);

View File

@ -18,7 +18,7 @@ class MyProfile extends Profile
/** /**
* @type {[Profile]} * @type {[Profile]}
*/ */
this.friends = []; this.friendList = [];
/** /**
* @type {[Profile]} * @type {[Profile]}
*/ */
@ -40,28 +40,74 @@ class MyProfile extends Profile
async getBlockedUsers() { async getBlockedUsers() {
const response = await this.client._get('/api/profiles/block'); const response = await this.client._get('/api/profiles/block');
const data = await response.json(); const data = await response.json();
data.forEach(profileData => this.blockedUsers.push(new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar))); data.forEach(profileData => this.blockedUsers.push(new Profile(this.client, profileData.username, profileData.id, profileData.avatar)));
} }
async getFriends() { async getFriends() {
const response = await this.client._get('/api/profiles/friends'); const response = await this.client._get('/api/profiles/friends');
const data = await response.json(); const data = await response.json();
data.forEach(profileData => this.friends.push(new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar))); data.forEach(profileData => this.friendList.push(new Profile(this.client, profileData.username, profileData.id, profileData.avatar)));
} }
async getIncomingFriendRequests() { async getIncomingFriendRequests() {
const response = await this.client._get('/api/profiles/incoming_friend_requests'); const response = await this.client._get('/api/profiles/incoming_friend_requests');
const data = await response.json(); const data = await response.json();
data.forEach(profileData => this.incomingFriendRequests.push( data.forEach(profileData => this.incomingFriendRequests.push(
new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar) new Profile(this.client, profileData.username, profileData.id, profileData.avatar)
)); ));
} }
async getOutgoingFriendRequests() { async getOutgoingFriendRequests() {
const response = await this.client._get('/api/profiles/outgoing_friend_requests'); const response = await this.client._get('/api/profiles/outgoing_friend_requests');
const data = await response.json(); const data = await response.json();
data.forEach(profileData => this.outgoingFriendRequests.push( data.forEach(profileData => this.outgoingFriendRequests.push(
new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar) new Profile(this.client, profileData.username, profileData.id, profileData.avatar)
)); ));
} }
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_isFriend(profile) {
for (const user of this.friendList) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_isBlocked(profile) {
for (const user of this.blockedUsers) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_hasIncomingRequestFrom(profile) {
for (const user of this.incomingFriendRequests) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_hasOutgoingRequestTo(profile) {
for (const user of this.outgoingFriendRequests) {
if (user.id === profile.id)
return true;
}
return false;
}
/** /**
* *
* @param {File} selectedFile * @param {File} selectedFile

View File

@ -33,8 +33,10 @@ export class Profile extends AExchangeable
/** /**
* @type {Boolean} * @type {Boolean}
*/ */
this.isBlocked = false; this.isFriend;
this.isFriend = false; this.isBlocked;
this.hasIncomingRequest;
this.hasOutgoingRequest;
} }
/** /**
@ -53,10 +55,17 @@ export class Profile extends AExchangeable
return response.status; return response.status;
let response_data = await response.json(); let response_data = await response.json();
this.id = response_data.user_id; this.id = response_data.id;
this.username = response_data.username; this.username = response_data.username;
this.avatar = response_data.avatar; this.avatar = response_data.avatar;
if (!this.client.me || this.client.me.id === this.id)
return;
this.isBlocked = this.client.me._isBlocked(this);
this.hasIncomingRequest = this.client.me._hasIncomingRequestFrom(this);
this.hasOutgoingRequest = this.client.me._hasOutgoingRequestTo(this);
this.isFriend = this.client.me._isFriend(this);
} }
/** /**

View File

@ -24,7 +24,7 @@ class Profiles
let profiles = []; let profiles = [];
response_data.forEach((profile) => { response_data.forEach((profile) => {
profiles.push(new Profile(this.client, profile.username, profile.user_id, profile.avatar)); profiles.push(new Profile(this.client, profile.username, profile.id, profile.avatar));
}); });
return profiles; return profiles;
} }
@ -54,40 +54,6 @@ class Profiles
return null; return null;
return profile; return profile;
} }
/**
* Block a user
* @param {Number} user_id
* @returns {Promise<Object>}
*/
async block(user_id) {
// blocker & blocked
let response = await this.client._post("/api/profiles/block", {
users_id:[this.client.me.id, user_id],
});
let data = await response.json();
return data;
}
/**
* Unblock a user
* @param {Number} user_id
* @returns {Promise<Object>}
*/
async deblock(user_id) {
// blocker & blocked
let response = await this.client._delete("/api/profiles/block", {
users_id:[this.client.me.id, user_id],
});
let data = await response.json();
return data;
}
} }
export {Profiles}; export {Profiles};

View File

@ -1,308 +0,0 @@
import { navigateTo } from "../../index.js";
import {createNotification} from "../../utils/noticeUtils.js";
class Notice {
constructor(client) {
this.client = client;
this.data = {};
// users online, invited by ..., asked by ..., asker to ...
let data_variable = ["online", "invited", "asked", "asker"];
for (let i in data_variable)
this.data[data_variable[i]] = [];
//this.connect();
}
async connect() {
return
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/chat/notice`;
this.chatSocket = new WebSocket(url);
this.chatSocket.onmessage = (event) =>{
let send = JSON.parse(event.data);
//console.log("notice: ", send);
try {
this["receive_" + send.type](send);
}
catch (error) {
console.log("receive_" + send.type + ": Function not found");
}
};
this.chatSocket.onopen = (event) => {
this.getOnlineUser();
this.ask_friend();
};
}
async disconnect() {
if (this.chatSocket == undefined)
return ;
this.chatSocket.close();
}
async reconnect() {
this.disconnect();
this.connect();
}
async accept_invite(invitedBy) {
this.sendRequest({
type: "accept_invite",
targets: [invitedBy],
});
}
async receive_accept_invite(send) {
this.data.invited = send.invites;
let id_game = send.id_game;
navigateTo("/game/" + id_game);
}
async refuse_invite(invitedBy) {
this.sendRequest({
type: "refuse_invite",
targets: [invitedBy],
});
}
async receive_refuse_invite(send) {
this.data.invited = send.invites;
if (send.author_id == this.client.me.id) {
if (this.rewrite_invite !== undefined)
this.rewrite_invite();
}
else {
let sender = await this.client.profiles.getProfileId(send.author_id);
createNotification(sender.username + " refuse your invitation");
}
}
async send_invite(id_inviteds) {
this.sendRequest({
type: "invite",
targets: id_inviteds,
time: new Date().getTime(),
});
}
async receive_invite(send) {
if (this.client.me == undefined)
return ;
let content = send.invites;
if (send.author_id == this.client.me.id) {
if (send.status == 200) {
for (let target in send.targets)
return createNotification("Invitation send");
}
else if (send.status == 444)
return createNotification("User not connected");
else if (send.status == 409)
return createNotification("Already invited");
}
else {
// Regarder qu'il est bien invité par l'auteur
// Et qu'il n'est pas déjà invité
if (!content.includes(send.author_id) ||
this.data.invited.includes(send.author_id))
return;
this.data.invited = content;
let sender = await this.client.profiles.getProfileId(send.author_id);
createNotification("Invitation received by " + sender.username);
if (this.rewrite_invite !== undefined)
this.rewrite_invite();
}
}
async getOnlineUser() {
this.online_users = {};
this.sendRequest({
type: "online_users",
targets: [],
time: new Date().getTime(),
});
}
async receive_online_users(send) {
let content = send.online;
if (content !== undefined) {
if (this.data.online.length > 0) {
// get all disconnect user
//let disconnects = this.data["online"].filter(id => !Object.keys(content).includes(id));
let disconnects = [];
for (const [key, value] of Object.entries(this.data.online)) {
if (content[key] == "red" && value == "green")
disconnects.push(key);
}
// delete invite
this.data.invited = this.data.invited.filter(id => !disconnects.includes(id));
//console.log(this.data["invited"]);
}
this.data.online = content;
if (this.rewrite_usernames !== undefined)
this.rewrite_usernames();
}
}
async ask_friend(user_id=undefined) {
this.sendRequest({
type: "ask_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_ask_friend(send) {
let my_id = (this.client.me && this.client.me.id) || send.author_id;
if (send.author_id == my_id) {
if (send.status == 400)
createNotification("Friend ask error");
else if (send.status == 409)
createNotification("Already asked friend");
}
//if (!send.asked.includes(send.author_id) ||
//this.data["asked"].includes(send.author_id))
//return;
//if (!send.asker.includes(send.author_id) ||
//this.data["asker"].includes(send.author_id))
//return;
this.data.asked = send.asked;
this.data.asker = send.asker;
if (send.author_id != my_id) {
let sender = await this.client.profiles.getProfileId(send.author_id);
if (this.data.asker.includes(send.author_id))
createNotification(sender.username + " ask you as friend");
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
}
}
async remove_friend(user_id) {
this.sendRequest({
type: "remove_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_remove_friend(send) {
if (send.author_id == this.client.me.id) {
if (send.status == 400)
createNotification("Error remove Friend");
else if (send.status == 409)
createNotification("Not friend, wtf");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
this.receive_online_users(send);
}
async accept_friend(user_id) {
this.sendRequest({
type: "accept_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_accept_friend(send) {
this.data.asked = send.asked;
this.data.asker = send.asker;
let sender = await this.client.profiles.getProfileId(send.author_id);
if (send.author_id == this.client.me.id) {
if (send.status == 400)
createNotification("Error accept Friend");
else if (send.status == 404)
createNotification("Not found request Friend");
else if (send.status == 409)
createNotification("Already Friend, wtf");
}
else {
createNotification(sender.username + " accept your friend request");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
this.receive_online_users(send);
}
async refuse_friend(user_id) {
this.sendRequest({
type: "refuse_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_refuse_friend(send) {
this.data.asked = send.asked;
this.data.asker = send.asker;
let sender = await this.client.profiles.getProfileId(send.author_id);
if (send.author_id == this.client.me.id) {
if (send.status == 400)
createNotification("Error refuse Friend");
else if (send.status == 404)
createNotification("Not found request Friend");
else if (send.status == 409)
createNotification("Already Friend, WTF");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
}
async sendRequest(content) {
if (this.chatSocket == undefined)
return;
this.chatSocket.send(JSON.stringify(content));
}
}
export {Notice};

View File

@ -18,12 +18,12 @@ class TicTacToe
this.canvas = canvas this.canvas = canvas
this.context = this.canvas.getContext("2d"); this.context = this.canvas.getContext("2d");
this.sign; this.sign;
this.currentMorpion = 4;
this.turn; this.turn;
} }
async init() async init()
{ {
console.log(this.game_id);
await this.game.join(); await this.game.join();
this.canvas.addEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion)); this.canvas.addEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion));
} }
@ -37,22 +37,46 @@ class TicTacToe
async onReceive(messageData) async onReceive(messageData)
{ {
console.log(messageData) console.log(messageData)
if (messageData.detail == "x" || messageData.detail == "o") switch (messageData.detail)
{ {
case 'x':
case 'o':
this.sign = messageData.detail; this.sign = messageData.detail;
this.turn = messageData.detail == "x"; this.turn = messageData.detail == "x";
} if (this.turn)
else if (messageData.detail == "game_start") this.setOutline(4, false);
break;
case 'game_start':
this.game.started = true; this.game.started = true;
else if (messageData.targetMorpion && messageData.targetCase) this.game.finished = false;
{ break;
case 'game_move':
this.map[messageData.targetMorpion][messageData.targetCase] = (this.sign == "x") ? 1 : 0; this.map[messageData.targetMorpion][messageData.targetCase] = (this.sign == "x") ? 1 : 0;
this.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x"); this.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x");
if (this.checkWin() != -1) this.setOutline(this.currentMorpion, false);
printWin(); break;
case 'game_end':
this.game.finished = true;
this.canvas.removeEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion));
this.printWin(messageData.winning_sign);
break;
} }
} }
printWin(winning_sign)
{
this.context.beginPath();
this.context.fillStyle = "white";
this.context.fillRect(this.width / 2 - 200, this.height - this.gap + 10, 400, 80);
this.context.closePath();
this.context.beginPath();
this.context.fillStyle = (winning_sign == "o") ? "red" : "green";
this.context.fillText((winning_sign == "o") ? "Winner is : O" : "Winner is : X", this.width / 2 - 30, this.height - this.gap / 2, 140);
this.context.closePath();
}
checkWin() checkWin()
{ {
for (let i = 0; i < 9; i++) for (let i = 0; i < 9; i++)
@ -85,6 +109,10 @@ class TicTacToe
let y = event.offsetY; let y = event.offsetY;
let targetMorpion, targetCase; let targetMorpion, targetCase;
if (this.game.finished)
{
return;
}
targetMorpion = morpion.findPlace(x, this) + morpion.findPlace(y, this) * 3; targetMorpion = morpion.findPlace(x, this) + morpion.findPlace(y, this) * 3;
if (morpion.findPlace(x, this) < 0 || morpion.findPlace(y, this) < 0) if (morpion.findPlace(x, this) < 0 || morpion.findPlace(y, this) < 0)
return -1; return -1;
@ -92,8 +120,8 @@ class TicTacToe
if (morpion.checkCase(targetMorpion, targetCase)) if (morpion.checkCase(targetMorpion, targetCase))
{ {
morpion.setOutline(this.currentMorpion, true);
morpion.sendCase(targetMorpion, targetCase); morpion.sendCase(targetMorpion, targetCase);
morpion.setOutline();
} }
else else
morpion.incorrectCase(); morpion.incorrectCase();
@ -101,19 +129,20 @@ class TicTacToe
checkCase(targetMorpion, targetCase) checkCase(targetMorpion, targetCase)
{ {
return (this.map[targetMorpion][targetCase] == -1 && this.turn == true); return (this.map[targetMorpion][targetCase] == -1 && this.turn == true && targetMorpion == this.currentMorpion);
} }
incorrectCase() incorrectCase()
{ {
console.log("bozo");
} }
sendCase(targetMorpion, targetCase) sendCase(targetMorpion, targetCase)
{ {
this.map[targetMorpion][targetCase] = (this.sign == "x") ? 0 : 1; this.map[targetMorpion][targetCase] = (this.sign == "x") ? 0 : 1;
this.currentMorpion = targetCase;
this.printSign(targetMorpion, targetCase, this.sign); this.printSign(targetMorpion, targetCase, this.sign);
console.log(this.game.send, targetMorpion, targetCase) console.log(targetMorpion, targetCase)
this.game.send(JSON.stringify({"targetMorpion" : targetMorpion, "targetCase" : targetCase, "sign" : this.sign})); this.game.send(JSON.stringify({"targetMorpion" : targetMorpion, "targetCase" : targetCase, "sign" : this.sign}));
console.log(this.turn); console.log(this.turn);
this.turn = !this.turn; this.turn = !this.turn;
@ -172,21 +201,25 @@ class TicTacToe
return -1; return -1;
} }
setOutline() setOutline(targetMorpion, clear)
{ {
if (this.turn) let targetX = (this.gap + targetMorpion % 3 * this.rectsize * 3);
let targetY = (this.gap + Math.floor(targetMorpion / 3) * this.rectsize * 3);
if (this.game.finished)
return;
if (!clear)
{ {
this.context.beginPath(); this.context.beginPath();
this.context.strokeStyle = (this.sign == "x") ? "green" : "red"; this.context.strokeStyle = (this.sign == "x") ? "green" : "red";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25); this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3)
this.context.stroke(); this.context.stroke();
this.context.closePath(); this.context.closePath();
} }
else else
{ {
this.context.beginPath(); this.context.beginPath();
this.context.strokeStyle = "#1a1a1d"; this.context.strokeStyle = `rgb(230 230 230)`;
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25); this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3)
this.context.stroke(); this.context.stroke();
this.context.closePath(); this.context.closePath();
} }
@ -244,34 +277,6 @@ class TicTacToe
} }
this.context.closePath(); this.context.closePath();
} }
selectCase(x, y)
{
case_morpion = Math.floor(x / this.rectsize) + Math.floor((y / this.rectsize)) * 3
case_square = Math.floor(x / Math.floor(this.rectsize / 3)) % this.rectsize + Math.floor(y / this.rectsize) % this.rectsize
// ask server if case_morpion == playing_case && case_square == empty
}
setOutline()
{
if (this.turn)
{
this.context.beginPath();
this.context.strokeStyle = (this.sign == "x") ? "green" : "red";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.stroke();
this.context.closePath();
}
else
{
this.context.beginPath();
this.context.strokeStyle = "#1a1a1d";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.stroke();
this.context.closePath();
}
}
} }
export { TicTacToe }; export { TicTacToe };

View File

@ -0,0 +1,81 @@
import {Client} from '../Client.js';
import {createNotification} from '../../utils/noticeUtils.js'
import { client, lastView } from '../../index.js';
import { Profile } from '../Profile.js';
import ProfilePageView from '../../views/ProfilePageView.js';
export default class Notice {
/**
* @param {Client} client
*/
constructor(client) {
/**
* @type {Client}
*/
this.client = client;
this.url = location.origin.replace('http', 'ws') + '/ws/notice';
}
start() {
this._socket = new WebSocket(this.url);
this._socket.onclose = _ => this._socket = undefined;
this._socket.onmessage = message => {
const data = JSON.parse(message.data);
if (data.type === 'friend_request') {
this.friend_request(data.author);
} else if (data.type === 'new_friend') {
this.new_friend(data.friend);
} else if (data.type === 'friend_removed') {
this.friend_removed(data.friend);
} else if (data.type === 'friend_request_canceled') {
this.friend_request_canceled(data.author);
}
};
}
stop() {
if (this._socket) {
this._socket.close();
this._socket = undefined;
}
}
friend_request(author) {
client.me.incomingFriendRequests.push(new Profile(author.username, author.id, author.avatar));
createNotification('Friend Request', `<strong>${author.username}</strong> sent you a friend request.`);
if (lastView instanceof ProfilePageView && lastView.profile.id === author.id) {
lastView.profile.hasIncomingRequest = true;
lastView.loadFriendshipStatus();
}
}
new_friend(friend) {
client.me.friendList.push(new Profile(friend.username, friend.id, friend.avatar));
createNotification('New Friend', `<strong>${friend.username}</strong> accepted your friend request.`);
if (lastView instanceof ProfilePageView && lastView.profile.id === friend.id) {
lastView.profile.isFriend = true;
lastView.profile.hasIncomingRequest = false;
lastView.profile.hasOutgoingRequest = false;
lastView.loadFriendshipStatus();
}
}
friend_removed(exFriend) {
client.me.friendList = client.me.friendList.filter(friend => friend.id !== exFriend.id);
if (lastView instanceof ProfilePageView && lastView.profile.id === exFriend.id) {
lastView.profile.isFriend = false;
lastView.loadFriendshipStatus();
}
}
friend_request_canceled(author) {
client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(user => user.id !== author.id);
if (lastView instanceof ProfilePageView && lastView.profile.id === author.id) {
lastView.profile.hasIncomingRequest = false;
lastView.loadFriendshipStatus();
}
}
}

View File

@ -43,13 +43,32 @@ class Tourmanent extends AExchangeable
*/ */
this.finished; this.finished;
/** /**
* @type {"finished" | "started" | "waiting"} must be "finished", or "started", or "waiting". Any other return all elements * @type {"finished" | "started" | "waiting"} must be "finished", or "started", or "waiting". Any other return all elements
*/ */
this.state; 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<?>} * @returns {Promise<?>}
@ -79,28 +98,66 @@ class Tourmanent extends AExchangeable
/** /**
* @param {Object} data * @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); await this.errorHandler(data);
} }
async _receiveGoTo(data)
{
await this._goToHandler(data)
}
/** /**
* *
* @param {MessageEvent} event * @param {MessageEvent} event
*/ */
onReceive(event) async onReceive(event)
{ {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.detail === "error") switch (data.detail) {
this.onError(data); case "error":
else if (["del_participant", "add_participant"].includes(data.detail)) await this._receiveError(data)
this._receiveParticipantUpdate(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;
}
} }
/** /**
@ -110,9 +167,11 @@ class Tourmanent extends AExchangeable
* @param {CallableFunction} delParticipantHandler called when a participants leave the tournament * @param {CallableFunction} delParticipantHandler called when a participants leave the tournament
* @param {CallableFunction} disconnectHandler * @param {CallableFunction} disconnectHandler
* @param {CallableFunction} goToHandler called when the next game will start * @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()) if (!await this.client.isAuthenticated())
return null; return null;
@ -124,10 +183,13 @@ class Tourmanent extends AExchangeable
this.connected = true; this.connected = true;
this.isParticipating = false; this.isParticipating = false;
this.participantsUpdateHandler = participantsUpdateHandler; this._startHandler = startHandler;
this.errorHandler = errorHandler; this._finishHandler = finishHandler;
this.disconnectHandler = disconnectHandler; this._addParticipantHandler = addParticipantHandler;
this.goToHandler = goToHandler; this._delParticipantHandler = delParticipantHandler;
this._errorHandler = errorHandler;
this._disconnectHandler = disconnectHandler;
this._goToHandler = goToHandler;
this._socket.onmessage = this.onReceive.bind(this); this._socket.onmessage = this.onReceive.bind(this);

View File

@ -167,4 +167,4 @@ document.addEventListener("DOMContentLoaded", async () => {
document.querySelector('a[href=\'' + location.pathname + '\']')?.classList.add('active'); document.querySelector('a[href=\'' + location.pathname + '\']')?.classList.add('active');
}); });
export { client, lang, navigateTo, reloadView }; export { client, lang, lastView, navigateTo, reloadView };

Binary file not shown.

View File

@ -1,22 +1,15 @@
export function createNotification(text, timer = 3000) { export function createNotification(title = 'New notification', content, delay = 3000) {
if (!createNotification.templateToast) { const toastElement = document.createElement('div');
createNotification.templateToast = new DOMParser().parseFromString(` toastElement.classList.add('toast');
<div class='toast' role='alert' data-bs-delay='${timer}'> toastElement.role = 'alert';
<div class='toast-header'> toastElement.setAttribute('data-bs-delay', delay);
<strong class='me-auto'>Notification</strong> toastElement.innerHTML =
`<div class='toast-header'>
<strong class='me-auto'>${title}</strong>
<button type='button' class='btn-close' data-bs-dismiss='toast'></button> <button type='button' class='btn-close' data-bs-dismiss='toast'></button>
</div> </div>
<div class='toast-body'> <div class='toast-body'>${content}</div>`
</div>
</div>
`, 'text/html')
.querySelector('body')
.firstChild;
}
const toastElement = createNotification.templateToast.cloneNode(true);
toastElement.getElementsByClassName('toast-body')[0].innerHTML = text;
toastElement.addEventListener('hidden.bs.toast', e => e.target.remove()); toastElement.addEventListener('hidden.bs.toast', e => e.target.remove());
new bootstrap.Toast(toastElement).show(); new bootstrap.Toast(toastElement).show();

View File

@ -19,7 +19,7 @@ export default class extends AbstractAuthenticatedView {
} }
else else
{ {
await client.matchmaking.start(this.onreceive.bind(this), this.ondisconnect.bind(this), this.gamemode_input.value, this.nb_players_input.value); await client.matchmaking.start(this.onreceive.bind(this), this.ondisconnect.bind(this), this.game_type_input.value, this.nb_players_input.value);
this.button.innerHTML = lang.get("matchmakingStopSearch"); this.button.innerHTML = lang.get("matchmakingStopSearch");
} }
@ -34,10 +34,10 @@ export default class extends AbstractAuthenticatedView {
{ {
if (data.detail === "game_found") if (data.detail === "game_found")
{ {
if (this.gamemode_input.value == "pong") if (this.game_type_input.value == "pong")
navigateTo(`/games/${data.gamemode}/${data.game_id}`); navigateTo(`/games/${data.game_type}/${data.game_id}`);
else else
navigateTo(`/games/${data.gamemode}/${data.game_id}`); navigateTo(`/games/${data.game_type}/${data.game_id}`);
return; return;
} }
this.display_data(data); this.display_data(data);
@ -51,7 +51,7 @@ export default class extends AbstractAuthenticatedView {
addEnterEvent() addEnterEvent()
{ {
[this.nb_players_input, this.gamemode_input].forEach((input) => { [this.nb_players_input, this.game_type_input].forEach((input) => {
input.addEventListener('keydown', async ev => { input.addEventListener('keydown', async ev => {
@ -74,13 +74,13 @@ export default class extends AbstractAuthenticatedView {
}); });
} }
addChangeGameModeEvent() addChangegame_typeEvent()
{ {
let nb_players_div = document.getElementById("nb-players-div"); let nb_players_div = document.getElementById("nb-players-div");
this.gamemode_input.addEventListener("change", () => { this.game_type_input.addEventListener("change", () => {
if (this.gamemode_input.value === "tictactoe") if (this.game_type_input.value === "tictactoe")
{ {
nb_players_div.style.display = 'none'; nb_players_div.style.display = 'none';
this.nb_players_input.value = 2; this.nb_players_input.value = 2;
@ -96,7 +96,7 @@ export default class extends AbstractAuthenticatedView {
addEvents() addEvents()
{ {
this.addEnterEvent(); this.addEnterEvent();
this.addChangeGameModeEvent(); this.addChangegame_typeEvent();
this.addChangeNbPlayersEvent(); this.addChangeNbPlayersEvent();
} }
@ -104,7 +104,7 @@ export default class extends AbstractAuthenticatedView {
{ {
this.button = document.getElementById("toggle-search"); this.button = document.getElementById("toggle-search");
this.nb_players_input = document.getElementById("nb-players-input"); this.nb_players_input = document.getElementById("nb-players-input");
this.gamemode_input = document.getElementById("gamemode-input"); this.game_type_input = document.getElementById("game-type-input");
this.button.onclick = this.toggle_search.bind(this); this.button.onclick = this.toggle_search.bind(this);
@ -117,12 +117,12 @@ export default class extends AbstractAuthenticatedView {
<div class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'> <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("matchmakingTitle")}</h4> <h4 class='text-center fw-semibold mb-4' id="title">${lang.get("matchmakingTitle")}</h4>
<div> <div>
<div class='form-floating mb-2' id='gamemode-div'> <div class='form-floating mb-2' id='game_type-div'>
<select class='form-control' id='gamemode-input'> <select class='form-control' id='game-type-input'>
<option value='pong'>Pong</option> <option value='pong'>Pong</option>
<option value='tictactoe'>ticTacToe</option> <option value='tictactoe'>ticTacToe</option>
</select> </select>
<label for='gamemode-input'>${lang.get("gamemodeChoice")}</label> <label for='game-type-input'>${lang.get("game_typeChoice")}</label>
</div> </div>
<div class='form-floating mb-2' id='nb-players-div'> <div class='form-floating mb-2' id='nb-players-div'>
<input type='number' min='2' value='2' max='4' class='form-control' id='nb-players-input' placeholder='${lang.get("matchmakingNbPlayers")}'> <input type='number' min='2' value='2' max='4' class='form-control' id='nb-players-input' placeholder='${lang.get("matchmakingNbPlayers")}'>

View File

@ -8,7 +8,7 @@ export default class extends AbstractView {
} }
setTitle() { setTitle() {
document.title = `${this.username} - Profile`; document.title = this.titleKey;
} }
async postInit() async postInit()
@ -16,99 +16,48 @@ export default class extends AbstractView {
if (!this.profile) if (!this.profile)
return 404; return 404;
this.userId = this.profile.id; if (this.profile.id === client.me.id)
await this.blockButton();
await this.friendButton();
client.notice.rewrite_profile = async () => {
await this.profile.getFriend();
await this.profile.getBlock();
await this.friendButton();
};
}
async blockButton() {
// Block option
if (await client.isAuthenticated() === false)
return; return;
if (client.me.id != this.userId) { const addFriendButton = document.getElementById('addFriendButton'),
let block = document.getElementById("block"); removeFriendButton = document.getElementById('removeFriendButton'),
if (block == undefined) { blockButton = document.getElementById('blockButton'),
block = document.createElement("p"); unblockButton = document.getElementById('unblockButton');
// this.info.appendChild(block);
}
block.id = "block"; this.loadFriendshipStatus();
block.onclick = async () => {
if (!this.profile.isBlocked)
await client.profiles.block(this.userId);
else
await client.profiles.deblock(this.userId);
this.profile = await client.profiles.getProfile(this.username);
this.blockButton();
};
if (this.profile.isBlocked) if (this.profile.isBlocked)
block.textContent = lang.get('profileUnblock', 'Unblock'); unblockButton.classList.remove('d-none');
else else
block.textContent = lang.get('profileBlock', 'Block'); blockButton.classList.remove('d-none');
}
addFriendButton.onclick = _ => this.addFriend();
removeFriendButton.onclick = _ => this.removeFriend();
unblockButton.onclick = _ => this.unblockUser();
blockButton.onclick = _ => this.blockUser();
} }
async friendButton() { loadFriendshipStatus() {
if (await client.isAuthenticated() === false) const addFriendButton = document.getElementById('addFriendButton'),
return; removeFriendButton = document.getElementById('removeFriendButton');
if (client.me.id != this.userId) { if (this.profile.hasIncomingRequest) {
let yes = document.getElementById("yes") || document.createElement("p"); removeFriendButton.classList.add('d-none');
let no = document.getElementById("no") || document.createElement("p"); addFriendButton.classList.remove('d-none');
let friend = document.getElementById("friend") || document.createElement("p"); addFriendButton.innerHTML = 'Accept Request';
} else if (this.profile.hasOutgoingRequest) {
if (client.notice.data.asker.includes(this.userId)) { addFriendButton.classList.add('d-none');
removeFriendButton.classList.remove('d-none');
if (friend) removeFriendButton.classList.replace('btn-danger', 'btn-secondary');
friend.remove(); removeFriendButton.innerHTML = 'Cancel Request';
} else if (this.profile.isFriend) {
yes.id = "yes"; addFriendButton.classList.add('d-none');
yes.textContent = lang.get('profileAcceptRequest', 'Accept Friend'); removeFriendButton.classList.remove('d-none');
yes.onclick = async () => { removeFriendButton.classList.replace('btn-secondary', 'btn-danger');
client.notice.accept_friend(this.userId); removeFriendButton.innerHTML = 'Remove Friend';
}; } else {
addFriendButton.innerHTML = 'Add Friend';
no.id = "no"; removeFriendButton.classList.add('d-none');
no.textContent = lang.get('profileDenyRequest', 'Decline Friend'); addFriendButton.classList.remove('d-none');
no.onclick = async () => {
client.notice.refuse_friend(this.userId);
};
// this.info.appendChild(yes);
// this.info.appendChild(document.createTextNode(" "));
// this.info.appendChild(no);
}
else {
if (yes && no)
yes.remove(); no.remove();
friend.id = "friend";
friend.onclick = async () => {
if (this.profile.isFriend)
await client.notice.remove_friend(this.userId);
else
await client.notice.ask_friend(this.userId);
await client.profiles.getProfile(this.username);
this.friendButton();
};
if (this.profile.isFriend)
friend.textContent = lang.get('profileRemoveFriend', 'Remove Friend');
else {
friend.textContent = lang.get('profileAddFriend', 'Ask Friend');
}
// this.info.appendChild(friend);
}
} }
} }
@ -118,9 +67,8 @@ export default class extends AbstractView {
if (!this.profile) if (!this.profile)
return ''; return '';
const logged = await client.isAuthenticated();
return ` return `
<div>
<div class='mb-3' id='profileInfo'> <div class='mb-3' id='profileInfo'>
<h1>${this.username}</h1> <h1>${this.username}</h1>
<a href=${this.profile.avatar} target='_blank'> <a href=${this.profile.avatar} target='_blank'>
@ -128,9 +76,85 @@ export default class extends AbstractView {
</a> </a>
</div> </div>
<div> <div>
<button class='btn btn-sm btn-success ${logged ? '' : 'd-none'}' id='addFriendButton'>Add Friend</button> <button class='btn btn-sm btn-success d-none' id='addFriendButton'>Add Friend</button>
<button class='btn btn-sm btn-danger ${logged ? '' : 'd-none'}' id='removeFriendButton'>Remove Friend</button> <button class='btn btn-sm btn-danger d-none' id='removeFriendButton'>Remove Friend</button>
<button class='btn btn-sm btn-danger d-none' id='blockButton'>Block</button>
<button class='btn btn-sm btn-secondary d-none' id='unblockButton'>Unblock</button>
</div>
</div> </div>
`; `;
} }
async addFriend() {
const removeFriendButton = document.getElementById('removeFriendButton');
const response = await client._post(`/api/profiles/friends/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
removeFriendButton.classList.remove('d-none');
document.getElementById('addFriendButton').classList.add('d-none');
}
if (response.status === 200) {
removeFriendButton.innerHTML = 'Cancel Request';
removeFriendButton.classList.replace('btn-danger', 'btn-secondary');
client.me.outgoingFriendRequests.push(this.profile);
this.profile.hasOutgoingRequest = true;
} else if (response.status === 201) {
removeFriendButton.innerHTML = 'Remove Friend';
removeFriendButton.classList.replace('btn-secondary', 'btn-danger');
this.profile.friend = true;
this.profile.hasIncomingRequest = false;
client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(profile => profile.id !== this.profile.id);
client.me.friendList.push(this.profile);
}
}
async removeFriend() {
const addFriendButton = document.getElementById('addFriendButton');
const response = await client._delete(`/api/profiles/friends/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
addFriendButton.innerHTML = 'Add Friend';
addFriendButton.classList.remove('d-none');
document.getElementById('removeFriendButton').classList.add('d-none');
}
if (response.status === 200) {
this.profile.hasOutgoingRequest = false;
client.me.outgoingFriendRequests = client.me.outgoingFriendRequests.filter(profile => profile.id !== this.profile.id);
} else if (response.status === 201) {
this.profile.isFriend = false;
client.me.friendList = client.me.friendList.filter(friend => friend.id !== this.profile.id);
}
}
async blockUser() {
const response = await client._post(`/api/profiles/block/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
document.getElementById('blockButton').classList.add('d-none');
document.getElementById('unblockButton').classList.remove('d-none');
client.me.blockedUsers.push(this.profile);
this.profile.isBlocked = true;
}
}
async unblockUser() {
const response = await client._delete(`/api/profiles/block/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
document.getElementById('unblockButton').classList.add('d-none');
document.getElementById('blockButton').classList.remove('d-none');
client.me.blockedUsers = client.me.blockedUsers.filter(profile => profile.id !== this.profile.id);
this.profile.isBlocked = false;
}
}
} }

View File

@ -19,7 +19,6 @@ export class TicTacToeOnlineView extends AbstractView
this.Morpion = new TicTacToe(this.height, this.width, 60, 60, document.getElementById("Morpion"), this.game_id); this.Morpion = new TicTacToe(this.height, this.width, 60, 60, document.getElementById("Morpion"), this.game_id);
this.Morpion.DrawSuperMorpion(); this.Morpion.DrawSuperMorpion();
await this.Morpion.init(); await this.Morpion.init();
this.Morpion.setOutline();
} }
async leavePage() async leavePage()

View File

@ -10,7 +10,6 @@ export default class extends AbstractAuthenticatedView
async postInit() { async postInit() {
await client.logout(); await client.logout();
await client.notice.disconnect();
navigateTo(this.lastPageUrl); navigateTo(this.lastPageUrl);
} }
} }

View File

@ -16,10 +16,9 @@ export default class extends AbstractAuthenticatedView
if (name.length == 0) if (name.length == 0)
name = lang.get("TournamentCreateTournamentName"); name = lang.get("TournamentCreateTournamentName");
console.log(name); let nb_participant = document.getElementById("nb-participant-input").value;
let nb_players = document.getElementById("nb-players-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 response_data = await response.json();
let id = response_data.id; let id = response_data.id;
@ -29,7 +28,7 @@ export default class extends AbstractAuthenticatedView
return; return;
} }
clearIds("innerHTML", ["name", "nb_players"]); clearIds("innerHTML", ["name", "nb_participants"]);
fill_errors(response_data, "innerHTML"); fill_errors(response_data, "innerHTML");
} }
@ -50,9 +49,16 @@ export default class extends AbstractAuthenticatedView
<span class='text-danger' id='name'></span> <span class='text-danger' id='name'></span>
</div> </div>
<div class='form-floating mb-2'> <div class='form-floating mb-2'>
<input type='number' class='form-control' min='2' value='4' id='nb-players-input' placeholder='${lang.get("TournamentCreateNbPlayer")}'> <select class='form-control' id="nb-participant-input">
<label for='nb-players-input' id='nb-players-label'>${lang.get("TournamentCreateNbPlayer")}</label> <option value="2">2</option>
<span class='text-danger' id='nb_players'></span> <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>
<div class='d-flex'> <div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2 mx-2' id='create-button'>${lang.get("TournamentCreateButton")}</button> <button type='button' class='btn btn-primary mt-3 mb-2 mx-2' id='create-button'>${lang.get("TournamentCreateButton")}</button>

View File

@ -3,6 +3,10 @@ import { Tourmanent } from "../../api/tournament/Tournament.js";
import {client, navigateTo} from "../../index.js"; import {client, navigateTo} from "../../index.js";
import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js"; import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
const TEXT_CONVENTION = {
"error": "[ERROR]"
}
export default class extends AbstractAuthenticatedView export default class extends AbstractAuthenticatedView
{ {
constructor(params) constructor(params)
@ -13,44 +17,49 @@ export default class extends AbstractAuthenticatedView
pressButton() pressButton()
{ {
this.tournament.toggle_participation(); this.tournament.setParticipation(!this.tournament.isParticipating);
this.updateParticipating()
} }
async receive(data) updateParticipating()
{ {
if (data.detail === "update_participants") document.getElementById("button").value = this.tournament.isParticipating ? `Leave ${this.tournament.name}` : `Join ${this.tournament.name}`;
document.getElementById("nb_participants").innerText = `${data.participants} / ${this.tournament.nb_participants}`; document.getElementById("display").innerText = this.tournament.isParticipating ? "You are a particpant" : "You are not a participant";
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)
{
} }
async onDisconnect(event) async onDisconnect(event)
{ {
} }
async onError(data) 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() async postInit()
@ -63,25 +72,32 @@ export default class extends AbstractAuthenticatedView
if (this.tournament === null) if (this.tournament === null)
return 404; 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"); let button = document.getElementById("button");
button.onclick = this.pressButton.bind(this); button.onclick = this.pressButton.bind(this);
document.getElementById("name").innerText = this.tournament.name; 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("round").innerText = this.tournament.round;
document.getElementById("state").innerText = this.tournament.state; document.getElementById("state").innerText = this.tournament.state;
if (this.tournament.started === false) if (this.tournament.started === false)
button.disabled = false; button.disabled = false;
console.log(this.tournament); this.chat = document.getElementById("chat");
console.log(this.tournament);
this.display_tree_tournament(this.tournament.nb_participants, this.tournament.participantList); this.display_tree_tournament(this.tournament.nb_participants, this.tournament.participantList);
} }
addChatMessage(message)
{
this.chat.innerText += message;
}
async display_tree_tournament(nb_participants, participants) { async display_tree_tournament(nb_participants, participants) {
const svg = document.getElementById('tree'); const svg = document.getElementById('tree');
@ -137,15 +153,25 @@ export default class extends AbstractAuthenticatedView
<td id="nb_participants">Loading...</td> <td id="nb_participants">Loading...</td>
</tr> </tr>
<tr> <tr>
<td>status</td> <td>Expected number of participants</td>
<td id="expected_nb_participants">Loading...</td>
</tr>
<tr>
<td>state</td>
<td id="state">Loading...</td> <td id="state">Loading...</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<input type="button" id="button" value="Join tournament" disabled> <input type="button" id="button" value="Join tournament" disabled>
<<<<<<< HEAD
<br> <br>
<svg id="tree" height="3000" width="3000"> <svg id="tree" height="3000" width="3000">
</svg> </svg>
=======
<span id="display"></span>
<textarea id="chat" rows="4" cols="50" readonly>
</textarea>
>>>>>>> 55d29d5763f22f136d2b4bae6464463acb514a5c
`; `;
} }
} }

View File

@ -45,9 +45,8 @@
</div> </div>
</div> </div>
</nav> </nav>
<div class='toast-container position-fixed end-0 p-3 pt-0' id='toastContainer'></div>
<div id="app" class="m-3"></div> <div id="app" class="m-3"></div>
<div class='toast-container position-fixed bottom-0 end-0 p-3' id='toastContainer'>
</div>
<script type="module" src="{% static 'js/index.js' %}"></script> <script type="module" src="{% static 'js/index.js' %}"></script>
<script src="{% static 'js/bootstrap/bootstrap.bundle.min.js' %}"></script> <script src="{% static 'js/bootstrap/bootstrap.bundle.min.js' %}"></script>
</body> </body>

View File

@ -21,7 +21,6 @@ if TYPE_CHECKING:
from .objects.pong.PongGame import PongGame from .objects.pong.PongGame import PongGame
from .objects.tictactoe.TicTacToeGame import TicTacToeGame from .objects.tictactoe.TicTacToeGame import TicTacToeGame
from .objects.tictactoe.TicTacToeSpectator import TicTacToeSpectator
game_manager: GameManager = GameManager() game_manager: GameManager = GameManager()
@ -45,6 +44,8 @@ class TicTacToeWebSocket(WebsocketConsumer):
self.member.send(self.member.sign) self.member.send(self.member.sign)
if (self.game._everbody_is_here() and self.game.model.started == False): if (self.game._everbody_is_here() and self.game.model.started == False):
if (self.game.time != -1):
self.game.broadcast("opponent_joined")
self.game.broadcast("game_start") self.game.broadcast("game_start")
self.game.model.start() self.game.model.start()
@ -54,7 +55,12 @@ class TicTacToeWebSocket(WebsocketConsumer):
if (data.get("targetMorpion") is not None and data.get("targetCase") is not None): if (data.get("targetMorpion") is not None and data.get("targetCase") is not None):
if (self.game.add(data, self.member) == False): if (self.game.add(data, self.member) == False):
return return
self.game.broadcast("", data, [self.member]) if (data.get("catchup") is not None and self.game.model.finished == False and self.game.model.finished == True):
self.member.send("catchup", {"Morpion": self.game._map, "turn": self.game.turn})
if (self.game.checkWin() != False):
print(self.game.checkWin())
self.game.broadcast("game_end", {"winning_sign": self.member.sign})
self.game.broadcast("game_move", data, [self.member])
pass pass
def disconnect(self, event): def disconnect(self, event):

View File

@ -3,40 +3,46 @@ from __future__ import annotations
from django.db import models from django.db import models
from django.db.models import QuerySet, CASCADE from django.db.models import QuerySet, CASCADE
from profiles.models import ProfileModel from django.contrib.auth.models import User
import time import time
# Create your models here. 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)
started = models.BooleanField(default = False) started = models.BooleanField(default = False)
winner = models.ForeignKey(ProfileModel, on_delete=CASCADE, null=True, blank=True) winner = models.ForeignKey(User, on_delete=CASCADE, null=True, blank=True)
start_timestamp = models.BigIntegerField(null = True, blank = True) start_timestamp = models.BigIntegerField(null = True, blank = True)
stop_timestamp = models.BigIntegerField(null = True, blank = True) stop_timestamp = models.BigIntegerField(null = True, blank = True)
game_type = models.CharField(max_length = 60, default = "pong") game_type = models.CharField(max_length = 60, default = "pong")
def create(self, players: list[ProfileModel]): def create(self, players: set[User]) -> GameModel:
self.save() self.save()
for player in players: for player in players:
GameMembersModel(game = self.pk, player=player).save() GameMembersModel(game = self, player=player).save()
return self.pk return self
def start(self): def start(self):
self.start_timestamp = round(time.time() * 1000, 1) self.start_timestamp = round(time.time() * 1000, 1)
self.started = True self.started = True
self.save() self.save()
def finish(self, winner_id): def finish(self, winner: User):
self.winner_id = winner_id self.winner = winner
self.finished = True self.finished = True
self.stop_timestamp = round(time.time() * 1000, 1) self.stop_timestamp = round(time.time() * 1000, 1)
self.save() self.save()
def get_players(self) -> list[ProfileModel]: def get_players(self) -> set[User]:
return [game_player.player for game_player in GameMembersModel.objects.filter(game = self)] return {game_player.player for game_player in GameMembersModel.objects.filter(game = self)}
def get_players_profiles(self) -> set[User]:
return {game_player.player.profilemodel for game_player in GameMembersModel.objects.filter(game = self)}
def get_score_by_player_id(self, player_id: int) -> list[int]: def get_score_by_player_id(self, player_id: int) -> list[int]:
query: QuerySet = GameGoalModel.objects.filter(game_id = self.pk, player_id = player_id) query: QuerySet = GameGoalModel.objects.filter(game_id = self.pk, player_id = player_id)
@ -56,10 +62,10 @@ class GameModel(models.Model):
class GameMembersModel(models.Model): class GameMembersModel(models.Model):
game = models.ForeignKey(GameModel, on_delete=CASCADE) game = models.ForeignKey(GameModel, on_delete=CASCADE)
player = models.ForeignKey(ProfileModel, on_delete=CASCADE) player = models.ForeignKey(User, on_delete=CASCADE)
class GameGoalModel(models.Model): class GameGoalModel(models.Model):
game = models.ForeignKey(GameModel, on_delete=CASCADE) game = models.ForeignKey(GameModel, on_delete=CASCADE)
player = models.ForeignKey(ProfileModel, on_delete=CASCADE) player = models.ForeignKey(User, on_delete=CASCADE)
timestamp = models.IntegerField() timestamp = models.IntegerField()

View File

@ -6,6 +6,10 @@ from .ASpectator import ASpectator
from ..models import GameModel from ..models import GameModel
from tournament.models import TournamentGameModel
from django.contrib.auth.models import User
class AGame(AbstractRoom): class AGame(AbstractRoom):
def __init__(self, game_type: str, game_id: int, game_manager): def __init__(self, game_type: str, game_id: int, game_manager):
@ -14,25 +18,29 @@ class AGame(AbstractRoom):
self.game_manager = game_manager self.game_manager = game_manager
self.model: GameModel = GameModel.objects.get(pk = game_id, game_type = game_type) 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)
players_id: list[int] = self.model.get_players_id() players: list[User] = self.model.get_players()
self.players: list[APlayer] = [APlayer(player_id, None, self) for player_id in players_id] self.players: list[APlayer] = [APlayer(player.pk, None, self) for player in players]
self.spectators: list[ASpectator] = [] self.spectators: list[ASpectator] = []
self.game_id: int = game_id self.game_id: int = game_id
def get_players_id(self) -> list[int]: def get_players_id(self) -> list[int]:
return [player.user_id for player in self.players] return [player.pk for player in self.players]
def get_players_connected(self) -> list[APlayer]: def get_players_connected(self) -> list[APlayer]:
return [player for player in self.players if player.is_connected()] return [player for player in self.players if player.is_connected()]
def get_player_by_user_id(self, user_id: int) -> APlayer: def get_player_by_user_id(self, user_id: int) -> APlayer:
for player in self.players: for player in self.players:
if (player.user_id == user_id): if (player.user.pk == user_id):
return player return player
return None return None

View File

@ -10,9 +10,6 @@ if TYPE_CHECKING:
class APlayer(ASpectator): class APlayer(ASpectator):
def __init__(self, user_id: int, socket: WebsocketConsumer, game: AGame):
super().__init__(user_id, socket, game)
def is_connected(self) -> bool: def is_connected(self) -> bool:
return self.socket != None return self.socket != None

View File

@ -3,6 +3,8 @@ from channels.generic.websocket import WebsocketConsumer
from transcendence.abstract.AbstractRoomMember import AbstractRoomMember from transcendence.abstract.AbstractRoomMember import AbstractRoomMember
from django.contrib.auth.models import User
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
@ -10,8 +12,8 @@ if TYPE_CHECKING:
class ASpectator(AbstractRoomMember): class ASpectator(AbstractRoomMember):
def __init__(self, user_id: int, socket: WebsocketConsumer, game): def __init__(self, user: User, socket: WebsocketConsumer, game):
super().__init__(user_id, socket) super().__init__(user, socket)
self.game: AGame = game self.game: AGame = game

View File

@ -3,6 +3,8 @@ 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:

View File

@ -22,7 +22,7 @@ import threading
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
pass from profiles.models import ProfileModel
class PongGame(AGame): class PongGame(AGame):
@ -36,7 +36,7 @@ class PongGame(AGame):
radius: float = min(config.MAP_SIZE_X, config.MAP_SIZE_Y) / 2 - 10 radius: float = min(config.MAP_SIZE_X, config.MAP_SIZE_Y) / 2 - 10
players_id: list[int] = self.model.get_players_id() players: list[ProfileModel] = self.model.get_players()
nb_sides = 4 nb_sides = 4
@ -58,16 +58,16 @@ class PongGame(AGame):
self.walls: list[Wall] self.walls: list[Wall]
self.players: list[PongPlayer] self.players: list[PongPlayer]
nb_players: int = len(players_id) nb_players: int = len(players)
if (nb_players == 2): if (nb_players == 2):
self.players = [PongPlayer(self, players_id[0], None, segments[0]), PongPlayer(self, players_id[1], None, segments[2])] self.players = [PongPlayer(self, players[0], None, segments[0]), PongPlayer(self, players[1], None, segments[2])]
self.walls = [Wall(segments[1].start, segments[1].stop), Wall(segments[3].start, segments[3].stop)] self.walls = [Wall(segments[1].start, segments[1].stop), Wall(segments[3].start, segments[3].stop)]
else: else:
self.players = [] self.players = []
self.walls = [] self.walls = []
for i in range(4): for i in range(4):
if (i < nb_players): if (i < nb_players):
self.players.append(PongPlayer(self, players_id[i], None, segments[i])) self.players.append(PongPlayer(self, players[i], None, segments[i]))
else: else:
self.walls.append(Wall(segments[i])) self.walls.append(Wall(segments[i]))

View File

@ -17,9 +17,9 @@ if TYPE_CHECKING:
class PongPlayer(APlayer): class PongPlayer(APlayer):
def __init__(self, game: PongGame, user_id: int, socket: WebsocketConsumer, rail: Segment) -> None: def __init__(self, game: PongGame, user: User, socket: WebsocketConsumer, rail: Segment) -> None:
super().__init__(user_id, socket, game) super().__init__(user, socket, game)
self.position: Position = Position(0.5, 0) self.position: Position = Position(0.5, 0)
@ -29,8 +29,6 @@ class PongPlayer(APlayer):
self.game: PongGame self.game: PongGame
self.username: str = User.objects.get(pk = self.user_id).username
def eliminate(self): def eliminate(self):
self.disconnect(1000) self.disconnect(1000)
@ -68,7 +66,7 @@ class PongPlayer(APlayer):
return return
if (self.position.time > new_position.time): if (self.position.time > new_position.time):
self.game_member.send_error("time error") self.send_error("time error")
return return
distance: float = abs(self.position.location - new_position.location) distance: float = abs(self.position.location - new_position.location)
@ -119,8 +117,8 @@ class PongPlayer(APlayer):
def to_dict(self) -> dict: def to_dict(self) -> dict:
data = { data = {
"username": self.username, "username": self.user.username,
"id": self.user_id, "id": self.user.pk,
"position": self.position.to_dict(), "position": self.position.to_dict(),
"score": self.score, "score": self.score,

View File

@ -14,9 +14,13 @@ class TicTacToeGame(AGame):
def __init__(self, game_id: int, game_manager): def __init__(self, game_id: int, game_manager):
super().__init__("tictactoe", game_id, game_manager) super().__init__("tictactoe", game_id, game_manager)
players_id: list[int] = self.model.get_players_id() players: list[int] = self.model.get_players()
self.players: list[TicTacToePlayer] = [TicTacToePlayer(player_id, None, self, ["x", "o"][i]) for i, player_id in enumerate(players_id)] self.players: list[TicTacToePlayer] = [TicTacToePlayer(player, None, self, ["x", "o"][i]) for i, player in enumerate(players)]
self.time = -1
self.turn = 'x'
self._map = [[-1 for _ in range(9)] for _ in range(9)] self._map = [[-1 for _ in range(9)] for _ in range(9)]
@ -44,14 +48,15 @@ class TicTacToeGame(AGame):
if (self.checkMove(newmove, player)): if (self.checkMove(newmove, player)):
self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] = newmove.get("sign") self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] = newmove.get("sign")
player.currentMorpion = int(newmove.get("targetMorpion")) player.currentMorpion = int(newmove.get("targetCase"))
self.turn = newmove.get("sign")
return True return True
return False return False
def checkMove(self, newmove, player): def checkMove(self, newmove, player):
print(int(newmove.get("targetMorpion")), player.currentMorpion) print(int(newmove.get("targetMorpion")), player.currentMorpion)
if (int(newmove.get("targetMorpion")) != player.currentMorpion): if (int(newmove.get("targetMorpion")) != player.currentMorpion or newmove.get("sign") != self.turn):
return False return False
if (self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] != -1): if (self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] != -1):
@ -62,16 +67,16 @@ class TicTacToeGame(AGame):
def checkWin(self): def checkWin(self):
for tab in self._map: for tab in self._map:
for i in range(3): for i in range(3):
if tab[i] == tab[i + 3] == tab[i + 6]: if tab[i] != -1 and tab[i] == tab[i + 3] and tab[i + 3] == tab[i + 6]:
return tab[i] return tab[i]
for i in range(0, 9, 3): for i in range(0, 9, 3):
if tab[i] == tab[i + 1] == tab[i + 2]: if tab[i] != -1 and tab[i] == tab[i + 1] and tab[i + 1] == tab[i + 2]:
return tab[i] return tab[i]
if tab[0] == tab[4] == tab[8]: if tab[0] != -1 and tab[0] == tab[4] and tab[4] == tab[8]:
return tab[0] return tab[0]
if tab[6] == tab[4] == tab[2]: if tab[6] != -1 and tab[6] == tab[4] and tab[4] == tab[2]:
return tab[6] return tab[6]
return None return False
def _spectator_join(self, user_id: int, socket: WebsocketConsumer): def _spectator_join(self, user_id: int, socket: WebsocketConsumer):

View File

@ -2,11 +2,13 @@ from games.objects.AGame import AGame
from ..APlayer import APlayer from ..APlayer import APlayer
from django.contrib.auth.models import User
from channels.generic.websocket import WebsocketConsumer from channels.generic.websocket import WebsocketConsumer
class TicTacToePlayer(APlayer): class TicTacToePlayer(APlayer):
def __init__(self, user_id: int, socket: WebsocketConsumer, game: AGame, sign): def __init__(self, user: User, socket: WebsocketConsumer, game: AGame, sign):
super().__init__(user_id, socket, game) super().__init__(user, socket, game)
self.sign = sign self.sign = sign
self.currentMorpion = 4 self.currentMorpion = 4
self.timestamp = None self.timestamp = None

View File

@ -270,7 +270,6 @@ async def render_ball(game: PongGame):
async def render_players(game: PongGame): async def render_players(game: PongGame):
while True: while True:
for player in game._updated_players: for player in game._updated_players:
await SyncToAsync(game.broadcast)("update_player", player.to_dict(), [player]) await SyncToAsync(game.broadcast)("update_player", player.to_dict(), [player])

View File

@ -3,7 +3,8 @@ from rest_framework import serializers
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import QuerySet from django.db.models import QuerySet
from .models import GameModel, GameMembersModel from .models import GameModel
from profiles.serializers import ProfileSerializer
class GameSerializer(serializers.ModelSerializer): class GameSerializer(serializers.ModelSerializer):
@ -14,11 +15,11 @@ class GameSerializer(serializers.ModelSerializer):
finished = serializers.ReadOnlyField() finished = serializers.ReadOnlyField()
start_timestamp = serializers.ReadOnlyField() start_timestamp = serializers.ReadOnlyField()
stop_timestamp = serializers.ReadOnlyField() stop_timestamp = serializers.ReadOnlyField()
gamemode = serializers.ReadOnlyField() game_type = serializers.ReadOnlyField()
class Meta: class Meta:
model = GameModel model = GameModel
fields = ["id", "winner_id", "state", "started", "finished", "players", "start_timestamp", "stop_timestamp", "gamemode"] fields = ["id", "winner_id", "state", "started", "finished", "players", "start_timestamp", "stop_timestamp", "game_type"]
def get_state(self, instance: GameModel): def get_state(self, instance: GameModel):
if (instance.finished): if (instance.finished):
@ -28,19 +29,4 @@ class GameSerializer(serializers.ModelSerializer):
return "waiting" return "waiting"
def get_players(self, instance: GameModel): def get_players(self, instance: GameModel):
players_data: list = [] return ProfileSerializer(instance.get_players_profiles(), many=True).data
for player_id in instance.get_players_id():
query: QuerySet = User.objects.filter(pk = player_id)
username: str = "Deleted User"
if (query.exists()):
username = query[0].username
data: dict = {
"id": player_id,
"username": username,
"score": instance.get_score_by_player_id(player_id)
}
players_data.append(data)
return players_data

View File

@ -6,15 +6,10 @@ from games.models import GameModel
import json import json
from .models import Waiter, WaitingRoom, WaitingRoomManager, normal from .models import Waiter, WaitingRoom, waiting_room_manager
class MatchMaking(WebsocketConsumer): class MatchMaking(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "matchmaking"
self.group_name = "matchmaking"
def connect(self): def connect(self):
user: User = self.scope["user"] user: User = self.scope["user"]
@ -22,14 +17,12 @@ class MatchMaking(WebsocketConsumer):
if (user.is_anonymous or not user.is_authenticated): if (user.is_anonymous or not user.is_authenticated):
return return
self.channel_layer.group_add(self.group_name, self.channel_name)
self.mode: int = int(self.scope['url_route']['kwargs']['mode']) self.mode: int = int(self.scope['url_route']['kwargs']['mode'])
self.gamemode: str = self.scope['url_route']['kwargs']['gamemode'] self.game_type: str = self.scope['url_route']['kwargs']['game_type']
self.group_name = self.mode self.group_name = self.mode
waiting_room: WaitingRoom = normal.get(self.gamemode, self.mode) self.waiting_room: WaitingRoom = waiting_room_manager.get(self.game_type, self.mode)
waiting_room.append(Waiter(user.pk, self)) self.waiting_room.append(Waiter(user, self))
if (self.mode < 2 or self.mode > 4): if (self.mode < 2 or self.mode > 4):
data: dict = { data: dict = {
@ -39,23 +32,22 @@ class MatchMaking(WebsocketConsumer):
self.disconnect(1000) self.disconnect(1000)
return return
if (self.gamemode not in ["tictactoe", "pong"]): if (self.game_type not in ["tictactoe", "pong"]):
data: dict = { data: dict = {
"detail": "The gamemode must 'pong' or 'tictactoe'.", "detail": "The game_type must 'pong' or 'tictactoe'.",
} }
self.send(json.dumps(data)) self.send(json.dumps(data))
self.disconnect(1000) self.disconnect(1000)
return return
waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}") self.waiting_room.broadcast(f"{len(self.waiting_room)} / {self.waiting_room.mode}")
if (len(waiting_room) == waiting_room.mode): if (len(self.waiting_room) == self.waiting_room.mode):
game_id: int = GameModel().create(self.gamemode, waiting_room.get_users_id()) game: GameModel = GameModel(game_type=self.game_type).create(self.waiting_room.get_members())
waiting_room.broadcast("game_found", {"game_id": game_id, "gamemode": self.gamemode}) self.waiting_room.broadcast("game_found", {"game_id": game.pk, "game_type": self.game_type})
waiting_room.clear()
def disconnect(self, close_code): def disconnect(self, close_code):
super().close(close_code) super().disconnect(close_code)
waiting_room: WaitingRoom = normal.get(self.gamemode, self.mode) waiting_room: WaitingRoom = waiting_room_manager.get(self.game_type, self.mode)
waiter: Waiter = waiting_room.get_member_by_socket(self) waiter: Waiter = waiting_room.get_member_by_socket(self)
if (waiter is not None): if (waiter is not None):
waiting_room.remove(waiter, close_code) waiting_room.remove(waiter)

View File

@ -13,29 +13,44 @@ class Waiter(AbstractRoomMember):
class WaitingRoom(AbstractRoom): class WaitingRoom(AbstractRoom):
def __init__(self, room_manager, gamemode: str, mode: int): def __init__(self, room_manager, game_type: str, mode: int):
super().__init__(room_manager) super().__init__(room_manager)
self.mode = mode self._member_list: set[Waiter]
self.gamemode = gamemode
self.mode: int = mode
self.game_type: str = game_type
def append(self, waiter: Waiter): def append(self, waiter: Waiter):
tmp: Waiter = self.get_member_by_user_id(waiter.user_id)
tmp: Waiter = self.get_member_by_user(waiter.user)
if (tmp is not None): if (tmp is not None):
tmp.send("Connection close: Another connection open with the same user id.") tmp.send("Connection close: Another connection open with the same user id.")
self.remove(tmp) self.remove(tmp)
waiter.accept()
self._member_list.append(waiter) waiter.socket.accept()
super().append(waiter)
class WaitingRoomManager(AbstractRoomManager): class WaitingRoomManager(AbstractRoomManager):
def get(self, gamemode: str, mode: int): def __init__(self):
super().__init__()
self._room_list: set[WaitingRoom]
def get(self, game_type: str, mode: int) -> WaitingRoom:
for waiting_room in self._room_list: for waiting_room in self._room_list:
waiting_room: WaitingRoom waiting_room: WaitingRoom
if (waiting_room.mode == mode and waiting_room.gamemode == gamemode): if (waiting_room.mode == mode and waiting_room.game_type == game_type):
return waiting_room return waiting_room
tmp: WaitingRoom = WaitingRoom(self, gamemode, mode)
tmp: WaitingRoom = WaitingRoom(self, game_type, mode)
super().append(tmp) super().append(tmp)
return tmp return tmp
normal: WaitingRoomManager = WaitingRoomManager() waiting_room_manager: WaitingRoomManager = WaitingRoomManager()

View File

@ -2,5 +2,5 @@ from django.urls import re_path
from . import consumers from . import consumers
websocket_urlpatterns = [ websocket_urlpatterns = [
re_path(r'ws/matchmaking/(?P<gamemode>\w+)/(?P<mode>\d+)$', consumers.MatchMaking.as_asgi()) re_path(r'ws/matchmaking/(?P<game_type>\w+)/(?P<mode>\d+)$', consumers.MatchMaking.as_asgi())
] ]

0
notice/__init__.py Normal file
View File

3
notice/admin.py Normal file
View File

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

6
notice/apps.py Normal file
View File

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

68
notice/consumers.py Normal file
View File

@ -0,0 +1,68 @@
from __future__ import annotations
import json
from channels.generic.websocket import WebsocketConsumer
from django.contrib.auth.models import User
from profiles.serializers import ProfileSerializer
from profiles.models import ProfileModel
from .models import NoticeModel
class NoticeManager:
def __init__(self):
self._list: list[NoticeConsumer] = []
def add(self, consumer: NoticeConsumer):
self._list.append(consumer)
unsend_notices = NoticeModel.objects.filter(user=consumer.user)
for notice in unsend_notices:
self.notify_user(consumer.user, json_data=notice.data)
notice.delete()
def remove(self, consumer: NoticeConsumer):
self._list.remove(consumer)
def get_consumer_by_user(self, user: User):
for consumer in self._list:
if consumer.user == user:
return consumer
def notify_user(self, user: User, data: dict = None, json_data: str = None):
consumer = self.get_consumer_by_user(user)
data_str: str = json.dumps(data) if json_data is None else json_data
if consumer:
consumer.send(data_str)
else:
NoticeModel(user=user, data=data_str).save()
def notify_friend_request(self, user: User, friend: ProfileModel):
self.notify_user(user, {'type': 'friend_request', 'author': ProfileSerializer(friend).data})
def notify_friend_request_canceled(self, user: User, friend: ProfileModel):
self.notify_user(user, {'type': 'friend_request_canceled', 'author': ProfileSerializer(friend).data})
def notify_new_friend(self, user: User, friend: ProfileModel):
self.notify_user(user, {'type': 'new_friend', 'friend': ProfileSerializer(friend).data})
def notify_friend_removed(self, user: User, friend: ProfileModel):
self.notify_user(user, {'type': 'friend_removed', 'friend': ProfileSerializer(friend).data})
notice_manager = NoticeManager()
class NoticeConsumer(WebsocketConsumer):
def connect(self):
self.user: User = self.scope['user']
if not self.user.is_authenticated:
self.close()
return
self.accept()
notice_manager.add(self)
def disconnect(self, code):
notice_manager.remove(self)
super().disconnect(code)

7
notice/models.py Normal file
View File

@ -0,0 +1,7 @@
from django.db.models import Model, ForeignKey, CharField, CASCADE
from django.contrib.auth.models import User
class NoticeModel(Model):
user = ForeignKey(User, on_delete=CASCADE)
data = CharField(max_length=100)

7
notice/routing.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import re_path
from .consumers import NoticeConsumer
websocket_urlpatterns = [
re_path(r'ws/notice$', NoticeConsumer.as_asgi()),
]

3
notice/tests.py Normal file
View File

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

3
notice/views.py Normal file
View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
from rest_framework import serializers from rest_framework import serializers
from ..models import ProfileModel from .models import ProfileModel
class ProfileSerializer(serializers.ModelSerializer): class ProfileSerializer(serializers.ModelSerializer):
@ -13,7 +13,7 @@ class ProfileSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ProfileModel model = ProfileModel
fields = ["username", "avatar", "user_id"] fields = ["username", "avatar", "id"]
def validate_avatar(self, value): def validate_avatar(self, value):
''' '''

View File

@ -8,7 +8,7 @@ from django.utils.translation import gettext as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from ..models import BlockModel, ProfileModel from ..models import BlockModel, ProfileModel
from ..serializers.ProfileSerializer import ProfileSerializer from ..serializers import ProfileSerializer
class GetBlocksView(APIView): class GetBlocksView(APIView):

View File

@ -7,7 +7,8 @@ from django.utils.translation import gettext as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from ..models import ProfileModel, FriendRequestModel from ..models import ProfileModel, FriendRequestModel
from ..serializers.ProfileSerializer import ProfileSerializer from ..serializers import ProfileSerializer
from notice.consumers import notice_manager
class GetFriendsView(APIView): class GetFriendsView(APIView):
@ -41,25 +42,29 @@ class EditFriendView(APIView):
incoming_request = user_profile.get_received_friend_request_from(friend_profile) incoming_request = user_profile.get_received_friend_request_from(friend_profile)
if incoming_request: if incoming_request:
incoming_request.accept() incoming_request.accept()
return Response(_('Friendship succssfully created.'), status.HTTP_201_CREATED) notice_manager.notify_new_friend(friend_profile.user, user_profile)
return Response(_('Friendship successfully created.'), status.HTTP_201_CREATED)
FriendRequestModel(author=user_profile, target=friend_profile).save() FriendRequestModel(author=user_profile, target=friend_profile).save()
notice_manager.notify_friend_request(friend_profile.user, user_profile)
return Response(_('Friend request sent.'), status.HTTP_200_OK) return Response(_('Friend request sent.'), status.HTTP_200_OK)
def delete(self, request, pk=None): def delete(self, request, pk=None):
user_profile = self.get_object() user_profile = self.get_object()
friend_profile = get_object_or_404(ProfileModel, pk=pk) friend_profile = get_object_or_404(ProfileModel, pk=pk)
if not user_profile.is_friend(friend_profile):
return Response(_('You are not friend with this user.'), status.HTTP_400_BAD_REQUEST)
outgoing_request = user_profile.get_outgoing_friend_request_to(friend_profile) outgoing_request = user_profile.get_outgoing_friend_request_to(friend_profile)
if outgoing_request: if outgoing_request:
outgoing_request.delete() outgoing_request.delete()
notice_manager.notify_friend_request_canceled(friend_profile.user, user_profile)
return Response(_('Friend request cancelled.')) return Response(_('Friend request cancelled.'))
if not user_profile.is_friend(friend_profile):
return Response(_('You are not friend with this user.'), status.HTTP_400_BAD_REQUEST)
user_profile.delete_friend(friend_profile) user_profile.delete_friend(friend_profile)
return Response(_('Friendship succssfully deleted.')) notice_manager.notify_friend_removed(friend_profile.user, user_profile)
return Response(_('Friendship successfully deleted.'), status.HTTP_201_CREATED)
class GetIncomingFriendRequestView(APIView): class GetIncomingFriendRequestView(APIView):

View File

@ -3,7 +3,7 @@ from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
from ..serializers.ProfileSerializer import ProfileSerializer from ..serializers import ProfileSerializer
from ..models import ProfileModel from ..models import ProfileModel

View File

@ -5,7 +5,7 @@ from rest_framework import permissions
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from ..serializers.ProfileSerializer import ProfileSerializer from ..serializers import ProfileSerializer
from ..models import ProfileModel from ..models import ProfileModel

1
run.sh
View File

@ -7,6 +7,7 @@ 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 tournament
python manage.py makemigrations notice
python manage.py migrate python manage.py migrate
python manage.py compilemessages python manage.py compilemessages
python manage.py runserver 0.0.0.0:8000 python manage.py runserver 0.0.0.0:8000

View File

@ -6,11 +6,13 @@ from django.contrib.auth.models import User
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from profiles.models import ProfileModel from profiles.models import ProfileModel
from profiles.serializers.ProfileSerializer import ProfileSerializer from profiles.serializers import ProfileSerializer
from .models import TournamentModel from .models import TournamentModel
from .models import TournamentGameModel
from .serializers import TournamentGameSerializer
import json import json
class TournamentMember: class TournamentMember:
@ -32,26 +34,27 @@ class TournamentMember:
self.send("error", data_to_send) 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: def _receive_participating(self, data: dict) -> None:
is_participating: bool | None = data.get("is_participating") is_participating: bool | None = data.get("is_participating")
if (is_participating is None): if (is_participating is None):
self.send_error(_("Missing is_participating statement.")) self.send_error(_("Missing is_participating statement."))
return return
self._room.set_participation()
self._room.set_participation(self, is_participating)
def receive(self, data: dict): def receive(self, data: dict):
if self.is_participating == False:
return
detail: str | None = data.get("detail") detail: str | None = data.get("detail")
if (detail is None): if (detail is None):
return return
match(detail): match(detail):
case "update_particapating": case "update_participating":
self._receive_participating() self._receive_participating(data)
case _: case _:
print("bozo_send") print("bozo_send")
@ -80,16 +83,74 @@ class TournamentRoom:
def __init__(self, room_manager: TournamentRoomManager, tournament: TournamentModel): def __init__(self, room_manager: TournamentRoomManager, tournament: TournamentModel):
self._room_manager: TournamentRoomManager = room_manager self._room_manager: TournamentRoomManager = room_manager
self._member_list: list[TournamentMember] = [] self._member_list: set[TournamentMember] = set()
self._model: TournamentModel = tournament self._model: TournamentModel = tournament
self._game_in_progress_list: set[TournamentGameModel] = set()
self._current_round = 0
def join(self, socket: TournamentWebConsumer) -> TournamentMember: def join(self, socket: TournamentWebConsumer) -> TournamentMember:
member: TournamentMember = TournamentMember(socket, self) member: TournamentMember = TournamentMember(socket, self)
self._member_list.append(member) self._member_list.add(member)
return 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: def leave(self, member: TournamentMember) -> None:
# Delete room if nobody connected, no cringe memory leak # Delete room if nobody connected, no cringe memory leak
@ -101,9 +162,12 @@ class TournamentRoom:
self.set_participation(member, False) self.set_participation(member, False)
def broadcast(self, detail: str, data: dict, excludes: list[TournamentMember] = []) -> None: def everybody_is_here(self):
return len(self.get_participants()) == self._model.nb_participants
member_list: list[TournamentMember] = [member for member in self._member_list if member not in excludes] 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: for member in member_list:
member.send(detail, data) member.send(detail, data)
@ -120,12 +184,15 @@ class TournamentRoom:
return return
if (is_participating == True): 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: 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 member.is_participating = is_participating
if self.everybody_is_here():
self.start()
tournament_manager: TournamentRoomManager = TournamentRoomManager() tournament_manager: TournamentRoomManager = TournamentRoomManager()
class TournamentWebConsumer(WebsocketConsumer): class TournamentWebConsumer(WebsocketConsumer):

View File

@ -1,21 +1,13 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
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 games.models import GameModel
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import CASCADE from django.db.models import CASCADE
from channels.generic.websocket import WebsocketConsumer
from django.db import models from django.db import models
import json
# Create your models here.tu
class TournamentModel(models.Model): class TournamentModel(models.Model):
name = models.CharField(max_length = 100) name = models.CharField(max_length = 100)
@ -23,24 +15,40 @@ class TournamentModel(models.Model):
round = models.IntegerField() round = models.IntegerField()
started = models.BooleanField(default = False) started = models.BooleanField(default = False)
finished = 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() TournamentParticipantModel(participant=participant, tournament=self).save()
def start(self, participants: list[ProfileModel]) -> None: 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.started = True
self.round = 1
for player in participants: for participant in participant_list:
self._register_participant(player) self._register_participant(participant)
for (participant1, participant2) in zip(participants[0::2], participants[1::2]):
self.create_game([participant1, participant2], round=1)
self.save() self.save()
def create_game(self, participants: list[ProfileModel], round: int) -> GameModel: 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): if (self.started == False):
return None return None
@ -52,38 +60,57 @@ class TournamentModel(models.Model):
game: GameModel = GameModel().create(participants) game: GameModel = GameModel().create(participants)
TournamentGameModel(tournament=self, game=game, round=round).save() TournamentGameModel(tournament=self, game=game, round=round, pos=pos).save()
return game return game
def get_games(self) -> list[GameModel]: def get_games(self) -> set[GameModel]:
return [tournament_game.game for tournament_game in TournamentGameModel.objects.filter(tournament=self)] 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) -> list[GameModel]: def get_games_by_round(self, round: int) -> set[GameModel]:
return [tournament_game.game for tournament_game in TournamentGameModel.objects.filter(tournament=self, round=round)] return {tournament_game for tournament_game in TournamentGameModel.objects.filter(tournament=self, round=round)}
def get_players_by_round(self, round: int) -> list[ProfileModel]: def get_participants_by_round(self, round: int) -> set[User]:
return [game.get_players() for game in self.get_games_by_round(round)] 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) -> list[ProfileModel]: def get_winners_by_round(self, round: int) -> set[User]:
return [game.winner for game in self.get_games_by_round(round)] return {game.winner for game in self.get_games_by_round(round)}
def get_participants(self) -> list[TournamentParticipantModel]: def get_participants(self) -> set[User]:
return TournamentParticipantModel.objects.filter(tournament=self.pk) return {tournament_participant.participant for tournament_participant in TournamentParticipantModel.objects.filter(tournament=self.pk)}
def get_state(self) -> str: def get_state(self) -> str:
return ("waiting to start", "in progress", "finish")[self.started + self.finished] return ("waiting to start", "in progress", "finish")[self.started + self.finished]
def is_participanting(self, profile: ProfileModel) -> bool: def is_participating(self, profile: User) -> bool:
return TournamentParticipantModel.objects.filter(participant=profile, tournament=self).exists() return TournamentParticipantModel.objects.filter(participant=profile, tournament=self).exists()
class TournamentParticipantModel(models.Model): 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) tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE)
#prout à encore frappé
class TournamentGameModel(models.Model): class TournamentGameModel(GameModel):
tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE, null=True, blank=True) tournament = models.ForeignKey(TournamentModel, on_delete=CASCADE, null=True, blank=True)
round = models.IntegerField() round = models.IntegerField()
game = models.ForeignKey(GameModel, on_delete=CASCADE) 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,45 +1,63 @@
from rest_framework import serializers from rest_framework import serializers
from django.db.models.query import QuerySet from .models import TournamentModel, TournamentGameModel
from django.contrib.auth.models import User from profiles.serializers import ProfileSerializer
from .models import TournamentModel
from profiles.models import ProfileModel
from profiles.serializers.ProfileSerializer import ProfileSerializer
from games.serializers import GameSerializer from games.serializers import GameSerializer
nb_participants = [2 ** i for i in range(1, 6)]
class TournamentSerializer(serializers.ModelSerializer): class TournamentSerializer(serializers.ModelSerializer):
state = serializers.SerializerMethodField(read_only=True, required=False) state = serializers.SerializerMethodField(read_only=True, required=False)
participants = serializers.SerializerMethodField(read_only=True, required=False) participants = serializers.SerializerMethodField(read_only=True, required=False)
round = serializers.ReadOnlyField() round = serializers.ReadOnlyField()
games = serializers.SerializerMethodField(read_only=True, required=False)
started = serializers.ReadOnlyField() started = serializers.ReadOnlyField()
finished = serializers.ReadOnlyField() finished = serializers.ReadOnlyField()
name = serializers.CharField(default="") name = serializers.CharField(default="")
class Meta: class Meta:
model = TournamentModel model = TournamentModel
fields = ["name", "nb_participants", "round", "started", "finished", "id", "state", "participants"] fields = ["name", "nb_participants", "round", "started", "finished", "id", "state", "participants", "games"]
def get_participants(self, instance: TournamentModel): def get_participants(self, instance: TournamentModel):
return ProfileSerializer(instance.get_participants(), many=True).data 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): def get_state(self, instance: TournamentModel):
return ["waiting", "started", "finished"][instance.started + instance.finished] return ["waiting", "started", "finished"][instance.started + instance.finished]
def validate_nb_participants(self, value: int): def validate_nb_participants(self, value: int):
if (value < 2): if (value not in nb_participants):
raise serializers.ValidationError("The numbers of participants must be greather than 2.") raise serializers.ValidationError(f"The numbers of participants must be {str(nb_participants)}.")
return value return value
def validate_nb_participants_by_game(self, value: int): class TournamentGameSerializer(serializers.ModelSerializer):
if (value < 2):
raise serializers.ValidationError("The numbers of participants by game must be greather than 2.") players = serializers.SerializerMethodField()
nb_participants: str = self.initial_data.get("nb_participants") winner_id = serializers.ReadOnlyField()
if (nb_participants is not None and nb_participants.isnumeric()): state = serializers.SerializerMethodField()
nb_participants: int = int(nb_participants) started = serializers.ReadOnlyField()
if (value > nb_participants): finished = serializers.ReadOnlyField()
raise serializers.ValidationError("The numbers of participants by game must be smaller than the numbers of participants.") start_timestamp = serializers.ReadOnlyField()
return value 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

@ -10,6 +10,8 @@ from django.db.models import QuerySet
from .models import TournamentModel from .models import TournamentModel
from .serializers import TournamentSerializer from .serializers import TournamentSerializer
import math
# Create your views here. # Create your views here.
class TournamentViewSet(viewsets.ModelViewSet): class TournamentViewSet(viewsets.ModelViewSet):
@ -19,7 +21,7 @@ class TournamentViewSet(viewsets.ModelViewSet):
authentication_classes = (SessionAuthentication,) authentication_classes = (SessionAuthentication,)
def perform_create(self, serializer: TournamentSerializer): def perform_create(self, serializer: TournamentSerializer):
tournament = serializer.save(round=1) tournament = serializer.save(round=math.log2(serializer.validated_data['nb_participants']) + 1)
return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED) return Response(self.serializer_class(tournament).data, status=status.HTTP_201_CREATED)

View File

@ -1,51 +1,57 @@
from __future__ import annotations
from channels.generic.websocket import WebsocketConsumer from channels.generic.websocket import WebsocketConsumer
from .AbstractRoomMember import AbstractRoomMember from .AbstractRoomMember import AbstractRoomMember
from django.contrib.auth.models import User
from profiles.models import ProfileModel
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .AbstractRoomManager import AbstractRoomManager
class AbstractRoom: class AbstractRoom:
def __init__(self, room_manager): def __init__(self, room_manager: AbstractRoomManager):
self._member_list: list[AbstractRoomMember] = [] self._member_list: set[AbstractRoomMember] = set()
self.room_manager = room_manager self._room_manager: AbstractRoomManager = room_manager
def broadcast(self, detail: str, data: dict = {}): def broadcast(self, detail: str, data: dict = {}, excludes: set[AbstractRoomMember] = set()) -> None:
for member in self._member_list:
member: AbstractRoomMember members: set[AbstractRoomMember] = self._member_list - excludes
for member in members:
member.send(detail, data) member.send(detail, data)
def clear(self): def get_member_by_socket(self, socket: WebsocketConsumer) -> AbstractRoomMember | None:
self._member_list.clear()
def get_member_by_socket(self, socket: WebsocketConsumer):
for member in self._member_list: for member in self._member_list:
member: AbstractRoomMember if member.socket is socket:
if (member.socket is socket):
return member return member
return None
def get_member_by_user_id(self, user_id: int): def get_member_by_user(self, user: User) -> AbstractRoomMember:
for member in self._member_list: for member in self._member_list:
member: AbstractRoomMember if member.user == user:
if (member.user_id == user_id):
return member return member
return None
def get_members_profiles(self) -> set[ProfileModel]:
return set(member.user.profilemodel for member in self._member_list)
def append(self, member: AbstractRoomMember): def get_members(self) -> set[ProfileModel]:
self._member_list.append(member) return set(member.user for member in self._member_list)
member.accept()
def remove(self, member: AbstractRoomMember, code: int = 1000): def append(self, member: AbstractRoomMember) -> None:
self._member_list.add(member)
def remove(self, member: AbstractRoomMember) -> None:
self._member_list.remove(member) self._member_list.remove(member)
member.disconnect(code)
def empty(self): def get_users(self) -> set[User]:
for _ in self._member_list: return set(member.user for member in self._member_list)
return False
return True
def get_users_id(self): def __len__(self) -> int:
return [member.user_id for member in self._member_list]
def __len__(self):
return len(self._member_list) return len(self._member_list)

View File

@ -5,8 +5,8 @@ class AbstractRoomManager:
def __init__(self): def __init__(self):
self._room_list: list[AbstractRoom] = [] self._room_list: list[AbstractRoom] = []
def remove(self, room: AbstractRoom): def remove(self, room: AbstractRoom) -> None:
self._room_list.remove(room) self._room_list.remove(room)
def append(self, room: AbstractRoom): def append(self, room: AbstractRoom) -> None:
self._room_list.append(room) self._room_list.append(room)

View File

@ -1,14 +1,16 @@
from channels.generic.websocket import WebsocketConsumer from channels.generic.websocket import WebsocketConsumer
from django.contrib.auth.models import User
import json import json
class AbstractRoomMember: class AbstractRoomMember:
def __init__(self, user_id: int, socket: WebsocketConsumer): def __init__(self, user: User, socket: WebsocketConsumer):
self.user_id: int = user_id self.user: User = user
self.socket: WebsocketConsumer = socket self.socket: WebsocketConsumer = socket
def send(self, detail: str, data: dict = {}): def send(self, detail: str, data: dict = {}) -> None:
raw_data: dict = {"detail": detail} raw_data: dict = {"detail": detail}
raw_data.update(data) raw_data.update(data)
self.socket.send(text_data=json.dumps(raw_data)) self.socket.send(text_data=json.dumps(raw_data))

View File

@ -15,6 +15,7 @@ import chat.routing
import matchmaking.routing import matchmaking.routing
import tournament.routing import tournament.routing
import games.routing import games.routing
import notice.routing
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
@ -27,8 +28,8 @@ application = ProtocolTypeRouter({
chat.routing.websocket_urlpatterns + chat.routing.websocket_urlpatterns +
matchmaking.routing.websocket_urlpatterns + matchmaking.routing.websocket_urlpatterns +
tournament.routing.websocket_urlpatterns + tournament.routing.websocket_urlpatterns +
games.routing.websocket_urlpatterns games.routing.websocket_urlpatterns +
notice.routing.websocket_urlpatterns
) )
) )
}) })

View File

@ -51,6 +51,7 @@ INSTALLED_APPS = [
'profiles.apps.ProfilesConfig', 'profiles.apps.ProfilesConfig',
'frontend.apps.FrontendConfig', 'frontend.apps.FrontendConfig',
'chat.apps.ChatConfig', 'chat.apps.ChatConfig',
'notice.apps.NoticeConfig',
'corsheaders', 'corsheaders',
'rest_framework', 'rest_framework',