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

View File

@ -21,12 +21,12 @@ class MatchMaking
* @param {Number} mode The number of players in a game
* @returns {Promise<?>}
*/
async start(receive_func, disconnect_func, gamemode, mode)
async start(receive_func, disconnect_func, game_type, mode)
{
if (!await this.client.isAuthenticated())
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);

View File

@ -18,7 +18,7 @@ class MyProfile extends Profile
/**
* @type {[Profile]}
*/
this.friends = [];
this.friendList = [];
/**
* @type {[Profile]}
*/
@ -40,28 +40,74 @@ class MyProfile extends Profile
async getBlockedUsers() {
const response = await this.client._get('/api/profiles/block');
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() {
const response = await this.client._get('/api/profiles/friends');
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() {
const response = await this.client._get('/api/profiles/incoming_friend_requests');
const data = await response.json();
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() {
const response = await this.client._get('/api/profiles/outgoing_friend_requests');
const data = await response.json();
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

View File

@ -33,8 +33,10 @@ export class Profile extends AExchangeable
/**
* @type {Boolean}
*/
this.isBlocked = false;
this.isFriend = false;
this.isFriend;
this.isBlocked;
this.hasIncomingRequest;
this.hasOutgoingRequest;
}
/**
@ -53,10 +55,17 @@ export class Profile extends AExchangeable
return response.status;
let response_data = await response.json();
this.id = response_data.user_id;
this.id = response_data.id;
this.username = response_data.username;
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 = [];
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;
}
@ -54,40 +54,6 @@ class Profiles
return null;
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};

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.context = this.canvas.getContext("2d");
this.sign;
this.currentMorpion = 4;
this.turn;
}
async init()
{
console.log(this.game_id);
await this.game.join();
this.canvas.addEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion));
}
@ -37,22 +37,46 @@ class TicTacToe
async onReceive(messageData)
{
console.log(messageData)
if (messageData.detail == "x" || messageData.detail == "o")
switch (messageData.detail)
{
case 'x':
case 'o':
this.sign = messageData.detail;
this.turn = messageData.detail == "x";
}
else if (messageData.detail == "game_start")
if (this.turn)
this.setOutline(4, false);
break;
case 'game_start':
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.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x");
if (this.checkWin() != -1)
printWin();
this.setOutline(this.currentMorpion, false);
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()
{
for (let i = 0; i < 9; i++)
@ -85,6 +109,10 @@ class TicTacToe
let y = event.offsetY;
let targetMorpion, targetCase;
if (this.game.finished)
{
return;
}
targetMorpion = morpion.findPlace(x, this) + morpion.findPlace(y, this) * 3;
if (morpion.findPlace(x, this) < 0 || morpion.findPlace(y, this) < 0)
return -1;
@ -92,8 +120,8 @@ class TicTacToe
if (morpion.checkCase(targetMorpion, targetCase))
{
morpion.setOutline(this.currentMorpion, true);
morpion.sendCase(targetMorpion, targetCase);
morpion.setOutline();
}
else
morpion.incorrectCase();
@ -101,19 +129,20 @@ class TicTacToe
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()
{
console.log("bozo");
}
sendCase(targetMorpion, targetCase)
{
this.map[targetMorpion][targetCase] = (this.sign == "x") ? 0 : 1;
this.currentMorpion = targetCase;
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}));
console.log(this.turn);
this.turn = !this.turn;
@ -172,21 +201,25 @@ class TicTacToe
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.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.closePath();
}
else
{
this.context.beginPath();
this.context.strokeStyle = "#1a1a1d";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.strokeStyle = `rgb(230 230 230)`;
this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3)
this.context.stroke();
this.context.closePath();
}
@ -244,34 +277,6 @@ class TicTacToe
}
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 };

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

View File

@ -167,4 +167,4 @@ document.addEventListener("DOMContentLoaded", async () => {
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) {
createNotification.templateToast = new DOMParser().parseFromString(`
<div class='toast' role='alert' data-bs-delay='${timer}'>
<div class='toast-header'>
<strong class='me-auto'>Notification</strong>
const toastElement = document.createElement('div');
toastElement.classList.add('toast');
toastElement.role = 'alert';
toastElement.setAttribute('data-bs-delay', delay);
toastElement.innerHTML =
`<div class='toast-header'>
<strong class='me-auto'>${title}</strong>
<button type='button' class='btn-close' data-bs-dismiss='toast'></button>
</div>
<div class='toast-body'>
</div>
</div>
`, 'text/html')
.querySelector('body')
.firstChild;
}
const toastElement = createNotification.templateToast.cloneNode(true);
toastElement.getElementsByClassName('toast-body')[0].innerHTML = text;
<div class='toast-body'>${content}</div>`
toastElement.addEventListener('hidden.bs.toast', e => e.target.remove());
new bootstrap.Toast(toastElement).show();

View File

@ -19,7 +19,7 @@ export default class extends AbstractAuthenticatedView {
}
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");
}
@ -34,10 +34,10 @@ export default class extends AbstractAuthenticatedView {
{
if (data.detail === "game_found")
{
if (this.gamemode_input.value == "pong")
navigateTo(`/games/${data.gamemode}/${data.game_id}`);
if (this.game_type_input.value == "pong")
navigateTo(`/games/${data.game_type}/${data.game_id}`);
else
navigateTo(`/games/${data.gamemode}/${data.game_id}`);
navigateTo(`/games/${data.game_type}/${data.game_id}`);
return;
}
this.display_data(data);
@ -51,7 +51,7 @@ export default class extends AbstractAuthenticatedView {
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 => {
@ -74,13 +74,13 @@ export default class extends AbstractAuthenticatedView {
});
}
addChangeGameModeEvent()
addChangegame_typeEvent()
{
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';
this.nb_players_input.value = 2;
@ -96,7 +96,7 @@ export default class extends AbstractAuthenticatedView {
addEvents()
{
this.addEnterEvent();
this.addChangeGameModeEvent();
this.addChangegame_typeEvent();
this.addChangeNbPlayersEvent();
}
@ -104,7 +104,7 @@ export default class extends AbstractAuthenticatedView {
{
this.button = document.getElementById("toggle-search");
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);
@ -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'>
<h4 class='text-center fw-semibold mb-4' id="title">${lang.get("matchmakingTitle")}</h4>
<div>
<div class='form-floating mb-2' id='gamemode-div'>
<select class='form-control' id='gamemode-input'>
<div class='form-floating mb-2' id='game_type-div'>
<select class='form-control' id='game-type-input'>
<option value='pong'>Pong</option>
<option value='tictactoe'>ticTacToe</option>
</select>
<label for='gamemode-input'>${lang.get("gamemodeChoice")}</label>
<label for='game-type-input'>${lang.get("game_typeChoice")}</label>
</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")}'>

View File

@ -8,7 +8,7 @@ export default class extends AbstractView {
}
setTitle() {
document.title = `${this.username} - Profile`;
document.title = this.titleKey;
}
async postInit()
@ -16,99 +16,48 @@ export default class extends AbstractView {
if (!this.profile)
return 404;
this.userId = this.profile.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)
if (this.profile.id === client.me.id)
return;
if (client.me.id != this.userId) {
let block = document.getElementById("block");
if (block == undefined) {
block = document.createElement("p");
// this.info.appendChild(block);
}
const addFriendButton = document.getElementById('addFriendButton'),
removeFriendButton = document.getElementById('removeFriendButton'),
blockButton = document.getElementById('blockButton'),
unblockButton = document.getElementById('unblockButton');
block.id = "block";
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();
};
this.loadFriendshipStatus();
if (this.profile.isBlocked)
block.textContent = lang.get('profileUnblock', 'Unblock');
unblockButton.classList.remove('d-none');
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() {
if (await client.isAuthenticated() === false)
return;
loadFriendshipStatus() {
const addFriendButton = document.getElementById('addFriendButton'),
removeFriendButton = document.getElementById('removeFriendButton');
if (client.me.id != this.userId) {
let yes = document.getElementById("yes") || document.createElement("p");
let no = document.getElementById("no") || document.createElement("p");
let friend = document.getElementById("friend") || document.createElement("p");
if (client.notice.data.asker.includes(this.userId)) {
if (friend)
friend.remove();
yes.id = "yes";
yes.textContent = lang.get('profileAcceptRequest', 'Accept Friend');
yes.onclick = async () => {
client.notice.accept_friend(this.userId);
};
no.id = "no";
no.textContent = lang.get('profileDenyRequest', 'Decline Friend');
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);
}
if (this.profile.hasIncomingRequest) {
removeFriendButton.classList.add('d-none');
addFriendButton.classList.remove('d-none');
addFriendButton.innerHTML = 'Accept Request';
} else if (this.profile.hasOutgoingRequest) {
addFriendButton.classList.add('d-none');
removeFriendButton.classList.remove('d-none');
removeFriendButton.classList.replace('btn-danger', 'btn-secondary');
removeFriendButton.innerHTML = 'Cancel Request';
} else if (this.profile.isFriend) {
addFriendButton.classList.add('d-none');
removeFriendButton.classList.remove('d-none');
removeFriendButton.classList.replace('btn-secondary', 'btn-danger');
removeFriendButton.innerHTML = 'Remove Friend';
} else {
addFriendButton.innerHTML = 'Add Friend';
removeFriendButton.classList.add('d-none');
addFriendButton.classList.remove('d-none');
}
}
@ -118,9 +67,8 @@ export default class extends AbstractView {
if (!this.profile)
return '';
const logged = await client.isAuthenticated();
return `
<div>
<div class='mb-3' id='profileInfo'>
<h1>${this.username}</h1>
<a href=${this.profile.avatar} target='_blank'>
@ -128,9 +76,85 @@ export default class extends AbstractView {
</a>
</div>
<div>
<button class='btn btn-sm btn-success ${logged ? '' : '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-success d-none' id='addFriendButton'>Add 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>
`;
}
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.DrawSuperMorpion();
await this.Morpion.init();
this.Morpion.setOutline();
}
async leavePage()

View File

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

View File

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

View File

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

View File

@ -45,9 +45,8 @@
</div>
</div>
</nav>
<div class='toast-container position-fixed end-0 p-3 pt-0' id='toastContainer'></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 src="{% static 'js/bootstrap/bootstrap.bundle.min.js' %}"></script>
</body>

View File

@ -21,7 +21,6 @@ if TYPE_CHECKING:
from .objects.pong.PongGame import PongGame
from .objects.tictactoe.TicTacToeGame import TicTacToeGame
from .objects.tictactoe.TicTacToeSpectator import TicTacToeSpectator
game_manager: GameManager = GameManager()
@ -45,6 +44,8 @@ class TicTacToeWebSocket(WebsocketConsumer):
self.member.send(self.member.sign)
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.model.start()
@ -54,7 +55,12 @@ class TicTacToeWebSocket(WebsocketConsumer):
if (data.get("targetMorpion") is not None and data.get("targetCase") is not None):
if (self.game.add(data, self.member) == False):
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
def disconnect(self, event):

View File

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

View File

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

View File

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

View File

@ -3,6 +3,8 @@ from channels.generic.websocket import WebsocketConsumer
from transcendence.abstract.AbstractRoomMember import AbstractRoomMember
from django.contrib.auth.models import User
from typing import TYPE_CHECKING
if TYPE_CHECKING:
@ -10,8 +12,8 @@ if TYPE_CHECKING:
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

View File

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

View File

@ -22,7 +22,7 @@ import threading
from typing import TYPE_CHECKING
if TYPE_CHECKING:
pass
from profiles.models import ProfileModel
class PongGame(AGame):
@ -36,7 +36,7 @@ class PongGame(AGame):
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
@ -58,16 +58,16 @@ class PongGame(AGame):
self.walls: list[Wall]
self.players: list[PongPlayer]
nb_players: int = len(players_id)
nb_players: int = len(players)
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)]
else:
self.players = []
self.walls = []
for i in range(4):
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:
self.walls.append(Wall(segments[i]))

View File

@ -17,9 +17,9 @@ if TYPE_CHECKING:
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)
@ -29,8 +29,6 @@ class PongPlayer(APlayer):
self.game: PongGame
self.username: str = User.objects.get(pk = self.user_id).username
def eliminate(self):
self.disconnect(1000)
@ -68,7 +66,7 @@ class PongPlayer(APlayer):
return
if (self.position.time > new_position.time):
self.game_member.send_error("time error")
self.send_error("time error")
return
distance: float = abs(self.position.location - new_position.location)
@ -119,8 +117,8 @@ class PongPlayer(APlayer):
def to_dict(self) -> dict:
data = {
"username": self.username,
"id": self.user_id,
"username": self.user.username,
"id": self.user.pk,
"position": self.position.to_dict(),
"score": self.score,

View File

@ -14,9 +14,13 @@ class TicTacToeGame(AGame):
def __init__(self, game_id: int, 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)]
@ -44,14 +48,15 @@ class TicTacToeGame(AGame):
if (self.checkMove(newmove, player)):
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 False
def checkMove(self, newmove, player):
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
if (self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] != -1):
@ -62,16 +67,16 @@ class TicTacToeGame(AGame):
def checkWin(self):
for tab in self._map:
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]
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]
if tab[0] == tab[4] == tab[8]:
if tab[0] != -1 and tab[0] == tab[4] and tab[4] == tab[8]:
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 None
return False
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 django.contrib.auth.models import User
from channels.generic.websocket import WebsocketConsumer
class TicTacToePlayer(APlayer):
def __init__(self, user_id: int, socket: WebsocketConsumer, game: AGame, sign):
super().__init__(user_id, socket, game)
def __init__(self, user: User, socket: WebsocketConsumer, game: AGame, sign):
super().__init__(user, socket, game)
self.sign = sign
self.currentMorpion = 4
self.timestamp = None

View File

@ -270,7 +270,6 @@ async def render_ball(game: PongGame):
async def render_players(game: PongGame):
while True:
for player in game._updated_players:
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.db.models import QuerySet
from .models import GameModel, GameMembersModel
from .models import GameModel
from profiles.serializers import ProfileSerializer
class GameSerializer(serializers.ModelSerializer):
@ -14,11 +15,11 @@ class GameSerializer(serializers.ModelSerializer):
finished = serializers.ReadOnlyField()
start_timestamp = serializers.ReadOnlyField()
stop_timestamp = serializers.ReadOnlyField()
gamemode = serializers.ReadOnlyField()
game_type = serializers.ReadOnlyField()
class Meta:
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):
if (instance.finished):
@ -28,19 +29,4 @@ class GameSerializer(serializers.ModelSerializer):
return "waiting"
def get_players(self, instance: GameModel):
players_data: list = []
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
return ProfileSerializer(instance.get_players_profiles(), many=True).data

View File

@ -6,15 +6,10 @@ from games.models import GameModel
import json
from .models import Waiter, WaitingRoom, WaitingRoomManager, normal
from .models import Waiter, WaitingRoom, waiting_room_manager
class MatchMaking(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "matchmaking"
self.group_name = "matchmaking"
def connect(self):
user: User = self.scope["user"]
@ -22,14 +17,12 @@ class MatchMaking(WebsocketConsumer):
if (user.is_anonymous or not user.is_authenticated):
return
self.channel_layer.group_add(self.group_name, self.channel_name)
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
waiting_room: WaitingRoom = normal.get(self.gamemode, self.mode)
waiting_room.append(Waiter(user.pk, self))
self.waiting_room: WaitingRoom = waiting_room_manager.get(self.game_type, self.mode)
self.waiting_room.append(Waiter(user, self))
if (self.mode < 2 or self.mode > 4):
data: dict = {
@ -39,23 +32,22 @@ class MatchMaking(WebsocketConsumer):
self.disconnect(1000)
return
if (self.gamemode not in ["tictactoe", "pong"]):
if (self.game_type not in ["tictactoe", "pong"]):
data: dict = {
"detail": "The gamemode must 'pong' or 'tictactoe'.",
"detail": "The game_type must 'pong' or 'tictactoe'.",
}
self.send(json.dumps(data))
self.disconnect(1000)
return
waiting_room.broadcast(f"{len(waiting_room)} / {waiting_room.mode}")
if (len(waiting_room) == waiting_room.mode):
game_id: int = GameModel().create(self.gamemode, waiting_room.get_users_id())
waiting_room.broadcast("game_found", {"game_id": game_id, "gamemode": self.gamemode})
waiting_room.clear()
self.waiting_room.broadcast(f"{len(self.waiting_room)} / {self.waiting_room.mode}")
if (len(self.waiting_room) == self.waiting_room.mode):
game: GameModel = GameModel(game_type=self.game_type).create(self.waiting_room.get_members())
self.waiting_room.broadcast("game_found", {"game_id": game.pk, "game_type": self.game_type})
def disconnect(self, close_code):
super().close(close_code)
waiting_room: WaitingRoom = normal.get(self.gamemode, self.mode)
super().disconnect(close_code)
waiting_room: WaitingRoom = waiting_room_manager.get(self.game_type, self.mode)
waiter: Waiter = waiting_room.get_member_by_socket(self)
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):
def __init__(self, room_manager, gamemode: str, mode: int):
def __init__(self, room_manager, game_type: str, mode: int):
super().__init__(room_manager)
self.mode = mode
self.gamemode = gamemode
self._member_list: set[Waiter]
self.mode: int = mode
self.game_type: str = game_type
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):
tmp.send("Connection close: Another connection open with the same user id.")
self.remove(tmp)
waiter.accept()
self._member_list.append(waiter)
waiter.socket.accept()
super().append(waiter)
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:
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
tmp: WaitingRoom = WaitingRoom(self, gamemode, mode)
tmp: WaitingRoom = WaitingRoom(self, game_type, mode)
super().append(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
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 ..models import ProfileModel
from .models import ProfileModel
class ProfileSerializer(serializers.ModelSerializer):
@ -13,7 +13,7 @@ class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = ProfileModel
fields = ["username", "avatar", "user_id"]
fields = ["username", "avatar", "id"]
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 ..models import BlockModel, ProfileModel
from ..serializers.ProfileSerializer import ProfileSerializer
from ..serializers import ProfileSerializer
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 ..models import ProfileModel, FriendRequestModel
from ..serializers.ProfileSerializer import ProfileSerializer
from ..serializers import ProfileSerializer
from notice.consumers import notice_manager
class GetFriendsView(APIView):
@ -41,25 +42,29 @@ class EditFriendView(APIView):
incoming_request = user_profile.get_received_friend_request_from(friend_profile)
if incoming_request:
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()
notice_manager.notify_friend_request(friend_profile.user, user_profile)
return Response(_('Friend request sent.'), status.HTTP_200_OK)
def delete(self, request, pk=None):
user_profile = self.get_object()
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)
if outgoing_request:
outgoing_request.delete()
notice_manager.notify_friend_request_canceled(friend_profile.user, user_profile)
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)
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):

View File

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

View File

@ -5,7 +5,7 @@ from rest_framework import permissions
from rest_framework import viewsets
from rest_framework.response import Response
from ..serializers.ProfileSerializer import ProfileSerializer
from ..serializers import ProfileSerializer
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 chat
python manage.py makemigrations tournament
python manage.py makemigrations notice
python manage.py migrate
python manage.py compilemessages
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.utils.translation import gettext as _
from profiles.models import ProfileModel
from profiles.serializers.ProfileSerializer import ProfileSerializer
from profiles.serializers import ProfileSerializer
from .models import TournamentModel
from .models import TournamentGameModel
from .serializers import TournamentGameSerializer
import json
class TournamentMember:
@ -32,26 +34,27 @@ class TournamentMember:
self.send("error", data_to_send)
def send_goto(self, game: TournamentGameModel):
self.send("go_to", {"game_id": game.pk})
def _receive_participating(self, data: dict) -> None:
is_participating: bool | None = data.get("is_participating")
if (is_participating is None):
self.send_error(_("Missing is_participating statement."))
return
self._room.set_participation()
self._room.set_participation(self, is_participating)
def receive(self, data: dict):
if self.is_participating == False:
return
detail: str | None = data.get("detail")
if (detail is None):
return
match(detail):
case "update_particapating":
self._receive_participating()
case "update_participating":
self._receive_participating(data)
case _:
print("bozo_send")
@ -80,16 +83,74 @@ class TournamentRoom:
def __init__(self, room_manager: TournamentRoomManager, tournament: TournamentModel):
self._room_manager: TournamentRoomManager = room_manager
self._member_list: list[TournamentMember] = []
self._member_list: set[TournamentMember] = set()
self._model: TournamentModel = tournament
self._game_in_progress_list: set[TournamentGameModel] = set()
self._current_round = 0
def join(self, socket: TournamentWebConsumer) -> TournamentMember:
member: TournamentMember = TournamentMember(socket, self)
self._member_list.append(member)
self._member_list.add(member)
return member
def set_game_as_finished(self, game: TournamentGameModel):
self._game_in_progress_list.remove(game)
self.broadcast("game_update", TournamentGameSerializer(game).data)
if len(self._game_in_progress_list) == 0:
self._round_finished()
def set_game_as_started(self, game: TournamentGameModel):
self._game_in_progress_list.add(game)
self.broadcast("game_update", TournamentGameSerializer(game).data)
def _finish(self, winner: User):
self._model.finish(winner)
def _round_finished(self):
if self._current_round == self._model.round:
last_game: TournamentGameSerializer = self._model.get_games_by_round(self._current_round)[0]
self._finish(last_game.winner)
return
self._start_round()
def _start_round(self):
self._current_round += 1
participant_list: set[User] = self._model.get_participants_by_round(self._current_round)
game_list = self._model.create_round(participant_list, self._current_round)
for game in game_list:
for player in game.get_players():
participant: TournamentMember = self.get_participant_by_profile(player)
participant.send_goto(game)
def get_participants_profiles(self) -> list[ProfileModel]:
return [participant._socket.user.profilemodel for participant in self.get_participants()]
def start(self) -> None:
self._model.start()
self.broadcast("start")
self._start_round()
def get_participant_by_profile(self, profile: ProfileModel):
for participant in self.get_participants():
if (participant._socket.user.profilemodel == profile):
return participant
def leave(self, member: TournamentMember) -> None:
# Delete room if nobody connected, no cringe memory leak
@ -101,9 +162,12 @@ class TournamentRoom:
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:
member.send(detail, data)
@ -120,12 +184,15 @@ class TournamentRoom:
return
if (is_participating == True):
self.broadcast("add_participant", {"profile", ProfileSerializer(member._socket.user.profilemodel).data})
self.broadcast("add_participant", {"participant": ProfileSerializer(member._socket.user.profilemodel).data})
else:
self.broadcast("del_participant", {"profile", ProfileSerializer(member._socket.user.profilemodel).data})
self.broadcast("del_participant", {"participant": ProfileSerializer(member._socket.user.profilemodel).data})
member.is_participating = is_participating
if self.everybody_is_here():
self.start()
tournament_manager: TournamentRoomManager = TournamentRoomManager()
class TournamentWebConsumer(WebsocketConsumer):

View File

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

View File

@ -10,6 +10,8 @@ from django.db.models import QuerySet
from .models import TournamentModel
from .serializers import TournamentSerializer
import math
# Create your views here.
class TournamentViewSet(viewsets.ModelViewSet):
@ -19,7 +21,7 @@ class TournamentViewSet(viewsets.ModelViewSet):
authentication_classes = (SessionAuthentication,)
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)

View File

@ -1,51 +1,57 @@
from __future__ import annotations
from channels.generic.websocket import WebsocketConsumer
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:
def __init__(self, room_manager):
self._member_list: list[AbstractRoomMember] = []
self.room_manager = room_manager
def __init__(self, room_manager: AbstractRoomManager):
self._member_list: set[AbstractRoomMember] = set()
self._room_manager: AbstractRoomManager = room_manager
def broadcast(self, detail: str, data: dict = {}):
for member in self._member_list:
member: AbstractRoomMember
def broadcast(self, detail: str, data: dict = {}, excludes: set[AbstractRoomMember] = set()) -> None:
members: set[AbstractRoomMember] = self._member_list - excludes
for member in members:
member.send(detail, data)
def clear(self):
self._member_list.clear()
def get_member_by_socket(self, socket: WebsocketConsumer) -> AbstractRoomMember | None:
def get_member_by_socket(self, socket: WebsocketConsumer):
for member in self._member_list:
member: AbstractRoomMember
if (member.socket is socket):
if member.socket is socket:
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:
member: AbstractRoomMember
if (member.user_id == user_id):
if member.user == user:
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):
self._member_list.append(member)
member.accept()
def get_members(self) -> set[ProfileModel]:
return set(member.user for member in self._member_list)
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)
member.disconnect(code)
def empty(self):
for _ in self._member_list:
return False
return True
def get_users(self) -> set[User]:
return set(member.user for member in self._member_list)
def get_users_id(self):
return [member.user_id for member in self._member_list]
def __len__(self):
def __len__(self) -> int:
return len(self._member_list)

View File

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

View File

@ -1,14 +1,16 @@
from channels.generic.websocket import WebsocketConsumer
from django.contrib.auth.models import User
import json
class AbstractRoomMember:
def __init__(self, user_id: int, socket: WebsocketConsumer):
self.user_id: int = user_id
def __init__(self, user: User, socket: WebsocketConsumer):
self.user: User = user
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.update(data)
self.socket.send(text_data=json.dumps(raw_data))

View File

@ -15,6 +15,7 @@ import chat.routing
import matchmaking.routing
import tournament.routing
import games.routing
import notice.routing
from django.core.asgi import get_asgi_application
@ -27,8 +28,8 @@ application = ProtocolTypeRouter({
chat.routing.websocket_urlpatterns +
matchmaking.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',
'frontend.apps.FrontendConfig',
'chat.apps.ChatConfig',
'notice.apps.NoticeConfig',
'corsheaders',
'rest_framework',