23 Commits

Author SHA1 Message Date
072944c503 game: add: config 2023-12-28 15:21:02 +01:00
5db03a3c69 tournament: fix 2023-12-28 15:20:25 +01:00
231622a2c2 game: init: consumer 2023-12-28 12:02:39 +01:00
f11af8336a game: init: class 2023-12-28 12:01:52 +01:00
2e8eebc788 game: allow any to get game data 2023-12-28 11:34:18 +01:00
bfc58e74a9 game: add: retrive view 2023-12-28 11:33:06 +01:00
4739afbaf0 block: fix: .exists() 2023-12-28 11:32:49 +01:00
9f7b813292 chat: fix and opti 2023-12-27 16:14:39 +01:00
cfb14be945 Merge branch 'Chatte' into server 2023-12-27 09:59:19 +01:00
35a2f273f9 fix 2023-12-26 21:35:08 +01:00
98ec43e7b9 game: core: rename member to player 2023-12-26 21:32:20 +01:00
9bb6a32c70 tournament: add: TOURNAMENT CAN START 2023-12-26 18:24:23 +01:00
44fa122585 clean: tournament: remove debug print 2023-12-26 14:35:45 +01:00
0d926e78c1 fix: chat url 2023-12-26 14:33:08 +01:00
efbcd10fb0 fix: websocket url 2023-12-26 14:28:04 +01:00
52c7d9eafb add: websocket protocol now depends on http's 2023-12-26 01:03:09 +01:00
8ba55d5be2 tournament: add: player can join tournament now 2023-12-24 16:58:36 +01:00
2932c2af1f abstract room: append: fix: use right var 2023-12-24 15:37:31 +01:00
397344ee45 matchmaking: add: clear message if normal closure 2023-12-23 18:47:16 +01:00
88aca3a54b fix: de golmon 2023-12-23 18:36:16 +01:00
58f6214ad0 fix: de golmon 2023-12-23 18:34:18 +01:00
4fd6616786 add block option 2023-12-20 23:48:52 +01:00
57ed6165ea bug issue; rewrite all messages 2023-12-19 12:42:46 +01:00
43 changed files with 775 additions and 232 deletions

View File

@ -1,6 +1,6 @@
from django.contrib import admin
from .models import ChannelModel, MemberModel, MessageModel
from .models import ChatChannelModel, ChatMemberModel, ChatMessageModel
admin.site.register(ChannelModel)
admin.site.register(MemberModel)
admin.site.register(MessageModel)
admin.site.register(ChatChannelModel)
admin.site.register(ChatMemberModel)
admin.site.register(ChatMessageModel)

View File

@ -1,20 +1,25 @@
import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
from .models import MemberModel, MessageModel
from .models import ChatMemberModel, ChatMessageModel
from profiles.models import BlockModel
import time
import json
class ChatConsumer(WebsocketConsumer):
def connect(self):
channel_id : str = self.scope['path'].split('/')[3]
self.room_group_name = 'chat' + channel_id
def connect(self):
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
if MemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1:
channel_id : int = int(self.scope['url_route']['kwargs']['chat_id'])
self.room_group_name = f'chat{channel_id}'
if ChatMemberModel.objects.filter(member_id=user.pk, channel_id=int(channel_id)).count() != 1:
return
if (self.channel_layer == None):
@ -29,26 +34,40 @@ class ChatConsumer(WebsocketConsumer):
def receive(self, text_data=None, bytes_data=None):
if text_data == None:
return
text_data_json = json.loads(text_data)
message = text_data_json['message']
channel_id : int = int(self.scope['path'].split('/')[3])
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
if MemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1:
text_data_json: dict = json.loads(text_data)
message = text_data_json.get('message')
if (message is None):
return
receivers_id = text_data_json.get('receivers_id')
if (receivers_id is None):
return
channel_id : int = int(self.scope['url_route']['kwargs']['chat_id'])
if ChatMemberModel.objects.filter(member_id = user.pk, channel_id = channel_id).count() != 1:
return
if (self.channel_layer == None):
return
print(message)
message_time: int = int(time.time() * 1000)
if (len(receivers_id) == 1 and
BlockModel.objects.filter(blocker=user.pk, blocked=receivers_id[0]) or
BlockModel.objects.filter(blocker=receivers_id[0], blocked=user.pk)
):
return
message_time : int = int(time.time() * 1000)
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
@ -59,22 +78,17 @@ class ChatConsumer(WebsocketConsumer):
}
)
new_message = MessageModel()
new_message.channel_id = channel_id
new_message.author_id = user.pk
new_message.content = message
new_message.time = message_time
new_message.save()
new_message = ChatMessageModel(channel_id = channel_id, author_id = user.pk, content = message, time = message_time).save()
def chat_message(self, event):
channel_id : int = int(self.scope['path'].split('/')[3])
user = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
return
if MemberModel.objects.filter(member_id=user.pk, channel_id=channel_id).count() != 1:
channel_id : int = int(self.scope['url_route']['kwargs']['chat_id'])
if ChatMemberModel.objects.filter(member_id = user.pk, channel_id = channel_id).count() != 1:
return
self.send(text_data=json.dumps({

View File

@ -4,17 +4,24 @@ from django.contrib.auth.models import User
from django.contrib import admin
# Create your models here.
class ChannelModel(models.Model):
pass
class ChatChannelModel(models.Model):
class MemberModel(models.Model):
def create(self, users_id: [int]):
self.save()
for user_id in users_id:
ChatMemberModel(channel_id = self.pk, member_id = user_id).save()
def get_members_id(self):
return [member_channel.member_id for member_channel in ChatMemberModel.objects.filter(channel_id = self.pk)]
class ChatMemberModel(models.Model):
member_id = IntegerField(primary_key=False)
channel_id = IntegerField(primary_key=False)
def __str__(self):
return "member_id: " + str(self.member_id) + ", channel_id: " + str(self.channel_id)
class MessageModel(models.Model):
class ChatMessageModel(models.Model):
channel_id = IntegerField(primary_key=False)
author_id = IntegerField(primary_key=False)
content = models.CharField(max_length=255)

View File

@ -2,5 +2,5 @@ from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<chat_id>\d+)/$', consumers.ChatConsumer.as_asgi())
re_path(r'ws/chat/(?P<chat_id>\d+)$', consumers.ChatConsumer.as_asgi())
]

View File

@ -1,8 +1,30 @@
from rest_framework import serializers
from .models import ChannelModel
class ChannelSerializer(serializers.ModelSerializer):
from profiles.models import ProfileModel
from .models import ChatChannelModel, ChatMessageModel
class ChatChannelSerializer(serializers.ModelSerializer):
members_id = serializers.ListField(child = serializers.IntegerField())
class Meta:
model = ChannelModel
fields = []
model = ChatChannelModel
fields = ["members_id", "pk"]
def validate_members_id(self, value):
members_id: [int] = value
if len(members_id) < 2:
raise serializers.ValidationError('Not enought members to create the channel')
if len(set(members_id)) != len(members_id):
raise serializers.ValidationError('Same member')
for member_id in members_id:
if not ProfileModel.objects.filter(pk = member_id).exists():
raise serializers.ValidationError(f"The profile {member_id} doesn't exists.")
return members_id
class ChatMessageSerializer(serializers.Serializer):
class Meta:
model = ChatMessageModel
fields = ["channel_id", "author_id", "content", "time"]

View File

@ -1,3 +1,30 @@
from django.test import TestCase
from django.test.client import Client
from django.http import HttpResponse, HttpRequest
from django.contrib.auth.models import User
# Create your tests here.
class ChatTest(TestCase):
def setUp(self):
self.client = Client()
self.username='bozo1'
self.password='password'
self.user: User = User.objects.create_user(username=self.username, password=self.password)
self.dest: User = User.objects.create_user(username="bozo2", password=self.password)
self.url = "/api/chat/"
def test_create_chat(self):
self.client.login(username=self.username, password=self.password)
response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]})
response_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {})
def test_create_chat_unlogged(self):
response: HttpResponse = self.client.post(self.url + str(self.user.pk), {"members_id": [self.user.pk, self.dest.pk]})
response_dict: dict = eval(response.content)
self.assertDictEqual(response_dict, {'detail': 'Authentication credentials were not provided.'})

View File

@ -5,6 +5,5 @@ from django.conf.urls.static import static
from . import views
urlpatterns = [
path("<int:pk>", views.ChatView.as_view(), name="chat_page"),
path("", views.ChatsView.as_view(), name="chats_page"),
path("", views.ChannelView.as_view(), name="chats_page"),
]

View File

@ -1,79 +1,45 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions, status
from rest_framework import permissions, status
from rest_framework.authentication import SessionAuthentication
from .models import ChannelModel, MemberModel, MessageModel
from django.http import HttpRequest
from django.contrib.auth import login
from django.db.models import QuerySet
from django.core import serializers
class ChatView(APIView):
from .models import ChatChannelModel, ChatMemberModel, ChatMessageModel
from .serializers import ChatChannelSerializer, ChatMessageSerializer
class ChannelView(APIView):
queryset = ChatChannelModel.objects
serializer_class = ChatChannelSerializer
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request, pk):
if (ChannelModel.objects.filter(pk=pk)):
return Response({'channel_id': pk}, status=status.HTTP_200_OK)
else:
return Response("Channel doesn't exist", status=status.HTTP_404_NOT_FOUND)
def delete(self, request, pk):
ChannelModel.objects.filter(pk=pk).delete()
MessageModel.objects.filter(pk=pk).delete()
MemberModel.objects.filter(pk=pk).delete()
return Response({'channel_id': pk}, status=status.HTTP_200_OK)
class ChatsView(APIView):
def post(self, request):
data: dict = request.data
users_id = request.data.get("users_id", [])
if len(users_id) < 2:
return Response('Not enought members to create the channel', status=status.HTTP_400_BAD_REQUEST)
if users_id[0] == users_id[1]:
return Response('Same member', status=status.HTTP_400_BAD_REQUEST)
serializer = self.serializer_class(data = request.data)
for user_id1 in users_id:
for member1 in MemberModel.objects.filter(member_id=user_id1):
for user_id2 in users_id:
if user_id1 == user_id2:
continue
for member2 in MemberModel.objects.filter(member_id=user_id2):
if (member1.channel_id == member2.channel_id):
messages = MessageModel.objects.filter(channel_id=member1.channel_id).order_by("time")
messages = serializers.serialize("json", messages)
return Response({'channel_id': member1.channel_id, 'messages':messages}, status=status.HTTP_200_OK)
serializer.is_valid(raise_exception=True)
new_channel = ChannelModel()
new_channel.save()
data: dict = serializer.validated_data
for user_id in users_id:
members_id = data.get("members_id")
if self.request.user.pk not in members_id:
return Response({"detail": "You are trying to create a chat group without you."}, status = status.HTTP_400_BAD_REQUEST)
new_member = MemberModel()
new_member.channel_id = new_channel.pk
new_member.member_id = user_id
new_member.save()
for member_channel in ChatMemberModel.objects.filter(member_id = members_id[0]):
channel_id: int = member_channel.channel_id
if not ChatChannelModel.objects.filter(pk = channel_id).exists():
continue
channel: ChatChannelModel = ChatChannelModel.objects.get(pk = channel_id)
if set(channel.get_members_id()) == set(members_id):
messages = ChatMessageModel.objects.filter(channel_id = channel_id).order_by("time")
messages = serializers.serialize("json", messages)
return Response({'channel_id': channel_id, 'messages': messages}, status=status.HTTP_200_OK)
return Response({'channel_id': new_channel.pk}, status=status.HTTP_201_CREATED)
def delete(self, request):
data: dict = request.data
users_id = request.data.get("users_id", [])
#print(list(MemberModel.objects.all()))
for user_id1 in users_id:
for member1 in MemberModel.objects.filter(member_id=user_id1):
for user_id2 in users_id:
if user_id1 == user_id2:
break
for member2 in MemberModel.objects.filter(member_id=user_id2):
if (member1.channel_id == member2.channel_id):
MessageModel.objects.filter(channel_id=member1.channel_id).delete()
member1.delete()
member2.delete()
ChannelModel.objects.get(pk=member1.channel_id).delete()
return Response("Channel removed", status=status.HTTP_200_OK)
return Response("Channel doesn't exist", status=status.HTTP_404_NOT_FOUND)
new_channel_id = ChatChannelModel().create(members_id)
return Response({'channel_id': new_channel_id}, status=status.HTTP_201_CREATED)

View File

@ -14,7 +14,7 @@ class Channel {
// reload = function to use when we receive a message
async connect(reload) {
let url = `wss://${window.location.host}/ws/chat/${this.channel_id}/`;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/chat/${this.channel_id}`;
this.chatSocket = new WebSocket(url);
this.chatSocket.onmessage = (event) =>{
@ -35,7 +35,9 @@ class Channel {
this.chatSocket.close();
}
updateMessages(messages) {
updateMessages(messages)
{
console.log(messages);
messages = JSON.parse(messages);
let new_messages = [];
@ -54,13 +56,14 @@ class Channel {
return new_messages;
}
async sendMessageChannel(message) {
async sendMessageChannel(message, receivers_id) {
if (this.chatSocket == undefined)
return;
this.chatSocket.send(JSON.stringify({
'message':message
'message':message,
'receivers_id':receivers_id,
}));
}

View File

@ -6,18 +6,18 @@ class Channels {
this.client = client;
}
async createChannel(users_id, reload) {
async createChannel(members_id, reload) {
let null_id = false;
users_id.forEach(user_id => {
if (user_id == null)
members_id.forEach(member_id => {
if (member_id == null)
null_id = true;
});
if (null_id)
return console.log(users_id, "createChannel error, null id;");
return console.log(members_id, "createChannel error, null id;");
let response = await this.client._post("/api/chat/", {
users_id:users_id
members_id:members_id
});
let data = await response.json();
@ -28,12 +28,13 @@ class Channels {
let messages = undefined;
if (exit_code == 200)
messages = data.messages;
return new Channel(this.client, data.channel_id, users_id, messages, reload);
return new Channel(this.client, data.channel_id, members_id, messages, reload);
}
async deleteChannel(users_id) {
async deleteChannel(members_id) {
let response = await this.client._delete("/api/chat/", {
users_id:users_id
members_id:members_id
});
let data = await response.json();

View File

@ -38,10 +38,11 @@ class Client
return this.logged;
}
async _get(uri)
async _get(uri, data)
{
let response = await fetch(this._url + uri, {
method: "GET",
body: JSON.stringify(data),
});
return response;
}

View File

@ -19,7 +19,7 @@ class MatchMaking
if (!await this.client.isAuthentificate())
return null;
let url = `wss://${window.location.host}/ws/matchmaking/${mode}`;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${mode}`;
this._socket = new WebSocket(url);
@ -39,7 +39,7 @@ class MatchMaking
onclose(event)
{
this.stop();
this.disconnect_func()
this.disconnect_func(event);
}
async stop()

View File

@ -12,8 +12,9 @@ class Profile
*/
this.client = client;
this.username = username;
this.avatar_url = avatar_url
this.user_id = user_id
this.avatar_url = avatar_url;
this.user_id = user_id;
this.isBlocked = false;
}
async init(user_id)
@ -28,6 +29,20 @@ class Profile
this.user_id = response_data.user_id;
this.username = response_data.username;
this.avatar_url = response_data.avatar_url;
let block_response = await this.client._get("/api/profiles/block");
if (block_response.status == 404)
return
let block_data = await block_response.json();
let block_list = JSON.parse(block_data);
block_list.forEach(block => {
let blocker = block.fields.blocker;
let blocked = block.fields.blocked;
if (blocker == this.client.me.user_id && blocked == user_id)
return this.isBlocked = true;
});
}
}

View File

@ -36,11 +36,28 @@ class Profiles
async block(user_id) {
// blocker & blocked
let response = await this.client._post("/api/block/",
[this.client.me.user_id, user_id],
);
let response = await this.client._post("/api/profiles/block", {
users_id:[this.client.me.user_id, user_id],
});
let data = await response.json();
console.log(response.status);
console.log(data);
return data;
}
async deblock(user_id) {
// blocker & blocked
let response = await this.client._delete("/api/profiles/block", {
users_id:[this.client.me.user_id, user_id],
});
let data = await response.json();
console.log(response.status);
console.log(data);
return data;
}
}

View File

@ -20,6 +20,8 @@ class Tourmanent
this.levels = levels;
this.state = this.get_state();
this.id = id
this.connected = false;
}
get_state()
@ -49,6 +51,46 @@ class Tourmanent
this.finished = response_data.finished;
this.levels = response_data.levels;
this.id = response_data.id
this.state = this.get_state();
}
leave(event)
{
if (this.connected == false)
return
this.connected = false;
this._socket.close()
this.disconnect_func(event);
}
toggle_participation()
{
if (!this.connected)
return
this._socket.send(JSON.stringify({participate: ""}));
}
async join(receive_func, disconnect_func)
{
if (!await this.client.isAuthentificate())
return null;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/tournaments/${this.id}`;
this._socket = new WebSocket(url);
this.connected = true;
this.isParticipating = false;
this.receive_func = receive_func;
this.disconnect_func = disconnect_func;
this._socket.onmessage = function (event) {
const data = JSON.parse(event.data);
receive_func(data);
};
this._socket.onclose = this.leave.bind(this);
}
}

View File

@ -25,8 +25,10 @@ export default class extends AbstractAuthentifiedView {
}
}
ondisconnect()
ondisconnect(event)
{
if (event.code === 1000)
clear("innerText", ["detail"])
document.getElementById("button").value = "Find a game"
}

View File

@ -14,33 +14,48 @@ export default class extends AbstractView {
if (profile === null)
return 1;
let info = document.getElementById("info");
this.profile = await client.profiles.getProfile(this.user_id);
this.info = document.getElementById("info");
// Username
let username = document.createElement("a");
username.id = "username";
username.appendChild(document.createTextNode(profile.username));
info.appendChild(username);
username.appendChild(document.createTextNode(this.profile.username));
this.info.appendChild(username);
info.appendChild(document.createElement("br"));
this.info.appendChild(document.createElement("br"));
// Avatar
let avatar = document.createElement("img");
avatar.id = "avatar";
avatar.src = profile.avatar_url;
info.appendChild(avatar);
avatar.src = this.profile.avatar_url;
this.info.appendChild(avatar);
// Block option
let block = document.createElement("a");
block.id = "block";
block.addEventListener("click", async () => {
if (client.me.user_id != user.user_id) {
}
});
block.appendChild(document.createTextNode("Block"));
info.appendChild(block);
await this.blockButton();
}
async blockButton() {
// Block option
if (client.me.user_id != this.user_id) {
let block = document.getElementById("block") || document.createElement("a");
block.id = "block";
block.innerText = "";
block.onclick = async () => {
if (!this.profile.isBlocked)
await client.profiles.block(this.user_id);
else
await client.profiles.deblock(this.user_id);
this.profile = await client.profiles.getProfile(this.user_id);
this.blockButton();
};
if (this.profile.isBlocked)
block.appendChild(document.createTextNode("Deblock"));
else
block.appendChild(document.createTextNode("Block"));
this.info.appendChild(block);
}
}
async getHtml() {
return `
<link rel="stylesheet" href="/static/css/profile.css">

View File

@ -100,6 +100,7 @@ export default class extends AbstractView {
async chat() {
let users = await client.profiles.all();
let logged = await client.isAuthentificate();
/*let reload = document.getElementById("messages");
if (reload != null)
@ -135,7 +136,7 @@ export default class extends AbstractView {
// les messages, réecriture seulement du dernier
let i = 0;
client.channel.messages.forEach((message) => {
if (messages[i] == null || message != messages.children[i].innerText) {
if (messages.children[i] == null || message.content != messages.children[i].innerText) {
let text = document.createElement("p");
text.appendChild(document.createTextNode(message.content));
if (message.author_id == client.me.user_id)
@ -158,21 +159,25 @@ export default class extends AbstractView {
chat_input.maxLength=255;
chat.appendChild(chat_input);
chat_input.addEventListener("keydown", async () => {
chat_input.onkeydown = async () => {
if (event.keyCode == 13 && client.channel != undefined) {
//let chat_input = document.getElementById("input_chat");
let chat_text = chat_input.value;
await client.channel.sendMessageChannel(chat_text)
let receivers_id = [];
client.channel.members_id.forEach((member_id) => {
if (member_id != client.me.user_id)
receivers_id.push(users.filter(user => user.user_id == member_id)[0].user_id);
});
await client.channel.sendMessageChannel(chat_text, receivers_id)
// Reset
chat_input.value = "";
}
});
};
}
// nom des membres du chat
let users = await client.profiles.all();
let members = document.createElement("h2");
members.id = "members";
let usernames = "";

View File

@ -1,4 +1,4 @@
import {client} from "../index.js";
import {client, navigateTo} from "../index.js";
import AbstractAuthentifiedView from "./abstracts/AbstractAuthentifiedView.js";
export default class extends AbstractAuthentifiedView
@ -9,19 +9,54 @@ export default class extends AbstractAuthentifiedView
this.id = params.id;
}
pressButton()
{
this.tournament.toggle_participation();
}
async receive(data)
{
if (data.detail === "nb_participants" || data.detail === "update_participants")
document.getElementById("nb_participants").innerText = `${data.nb_participants} / ${this.tournament.nb_players}`
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";
}
async ondisconnect(event)
{
}
async postInit()
{
let tournament = await client.tournaments.getTournament(this.id);
this.tournament = await client.tournaments.getTournament(this.id);
if (tournament === null)
if (this.tournament === null)
return 1;
document.getElementById("name").innerText = tournament.name;
document.getElementById("nb_players").innerText = tournament.nb_players;
document.getElementById("nb_players_by_game").innerText = tournament.nb_players_by_game;
document.getElementById("level").innerText = tournament.level;
document.getElementById("state").innerText = tournament.state;
this.tournament.join(this.receive.bind(this), this.ondisconnect.bind(this));
let button = document.getElementById("button")
button.onclick = this.pressButton.bind(this);
document.getElementById("name").innerText = this.tournament.name;
document.getElementById("nb_players").innerText = this.tournament.nb_players;
document.getElementById("nb_players_by_game").innerText = this.tournament.nb_players_by_game;
document.getElementById("level").innerText = this.tournament.level;
document.getElementById("state").innerText = this.tournament.state;
if (this.tournament.state === "waiting")
button.disabled = false;
}
async getHtml()
@ -46,12 +81,18 @@ export default class extends AbstractAuthentifiedView
<td>Number of round</td>
<td id="level">Loading...</td>
</tr>
<tr>
<td>Number of player</td>
<td id="nb_participants">Loading...</td>
</tr>
<tr>
<td>status</td>
<td id="state">Loading...</td>
</tr>
</tbody>
</table>
`
<input type="button" id="button" value="Join tournament" disabled>
<span id="display"></span>
`
}
}

9
games/config.py Normal file
View File

@ -0,0 +1,9 @@
PADDLE_SPEED_MAX = 1
PADDLE_SIZE_HEIGHT = 10
PADDLE_SIZE_WIDTH = 100
PADDLE_RAIL = PADDLE_SIZE_WIDTH * 5
BALL_SPEED_INC = 1
BALL_SPEED_START = 1
BALL_SIZE = 4

42
games/consumers.py Normal file
View File

@ -0,0 +1,42 @@
from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth.models import User
from games.models import GameModel
import json
from .models import game_room_manager
class GameWebSocket(AsyncWebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.channel_name = "games"
self.group_name = "games"
def connect(self):
self.user: User = self.scope["user"]
if (self.user.is_anonymous or not self.user.is_authenticated):
return
self.channel_layer.group_add(self.group_name, self.channel_name)
self.game_id = int(self.scope['url_route']['kwargs']['game_id'])
self.room = game_room_manager.get(self.game_id)
if (self.room is None):
self.member.send("Tournament not found")
self.disconnect(1017)
self.room.append(self.member)
def receive(self, text_data: str = None, bytes_data: bytes = None):
self.member.receive(text_data, bytes_data)
def disconnect(self, close_code):
member = self.room.get_member_by_socket(self)
if (member is not None):
self.room.remove(self.member, close_code)

View File

@ -1,21 +1,27 @@
from django.db import models
from .objects.GameRoomManager import GameRoomManager
from channels.generic.websocket import AsyncWebsocketConsumer
# Create your models here.
class GameModel(models.Model):
finished = models.BooleanField(default=False)
started = models.BooleanField(default=False)
winner_id = models.IntegerField(default=-1)
finished = models.BooleanField(default = False)
started = models.BooleanField(default = False)
winner_id = models.IntegerField(default = -1)
def create(self, users_id: [int]):
def create(self, players_id: [int]):
self.save()
for user_id in users_id:
GameMembersModel(game_id=self.pk, member_id=user_id).save()
for player_id in players_id:
GameMembersModel(game_id = self.pk, player_id = player_id).save()
return self.pk
def get_users(self):
return list(GameMembersModel.objects.filter(self.pk))
def get_players_id(self):
return [game_member.member_id for game_member in GameMembersModel.objects.filter(game_id = self.pk)]
class GameMembersModel(models.Model):
game_id = models.IntegerField()
member_id = models.IntegerField()
player_id = models.IntegerField()
game_room_manager: GameRoomManager = GameRoomManager()

View File

@ -0,0 +1,17 @@
from channels.generic.websocket import AsyncWebsocketConsumer
from transcendence.abstract.AbstractRoomMember import AbstractRoomMember
class GameMember(AbstractRoomMember):
def __init__(self, user_id: int, socket: AsyncWebsocketConsumer):
super().__init__(user_id, socket)
self.is_a_player = False
def receive(self, data: dict):
if (not self.is_a_player):
self.send("You are not a player.")
return
def send_ball(self, ball):
pass

View File

@ -0,0 +1,9 @@
from transcendence.abstract.AbstractRoom import AbstractRoom
from .GameRoomManager import GameRoomManager
class GameRoom(AbstractRoom):
def __init__(self, game_room_manager: GameRoomManager, game_id: int):
super().__init__(game_room_manager)
self.game_id = game_id

View File

@ -0,0 +1,18 @@
from transcendence.abstract.AbstractRoomManager import AbstractRoomManager
from ..models import GameModel
class GameRoomManager(AbstractRoomManager):
def get(self, game_id: int):
for room in self._room_list:
if (room.game_id == game_id):
return room
if (GameModel.objects.filter(pk = tournament_id).exists()):
room = TournamentRoom(self, game_id)
self.append(room)
return room
return None

6
games/routing.py Normal file
View File

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

25
games/serializers.py Normal file
View File

@ -0,0 +1,25 @@
from rest_framework import serializers
from .models import GameModel, GameMembersModel
class GameSerializer(serializers.ModelSerializer):
players_id = serializers.SerializerMethodField()
winner_id = serializers.ReadOnlyField()
state = serializers.SerializerMethodField()
started = serializers.ReadOnlyField()
finished = serializers.ReadOnlyField()
class Meta:
model = GameModel
fields = ["id", "winner_id", "state", "started", "finished", "players_id"]
def get_state(self, instance: GameModel):
if (instance.finished):
return "finished"
if (instance.started):
return "started"
return "waiting"
def get_players_id(self, instance: GameModel):
players_id = [player_game.player_id for player_game in GameMembersModel.objects.filter(game_id = instance.pk)]
return players_id

11
games/urls.py Normal file
View File

@ -0,0 +1,11 @@
from django.urls import path, re_path
from django.conf import settings
from django.conf.urls.static import static
from .viewset import GameViewSet
from .views import GameConfigView
urlpatterns = [
path("<int:pk>", GameViewSet.as_view({"get": "retrieve"}), name="game_page"),
path("", GameConfigView.as_view(), name = "game_config")
]

View File

@ -1,3 +1,23 @@
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
# Create your views here.
from django.http import HttpRequest
from . import config
class GameConfigView(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request):
config_data = {
"BALL_SIZE": config.BALL_SIZE,
"PADDLE_SPEED_MAX": config.PADDLE_SPEED_MAX,
"PADDLE_SIZE_HEIGHT": config.PADDLE_SIZE_HEIGHT,
"PADDLE_SIZE_WIDTH": config.PADDLE_SIZE_WIDTH,
"PADDLE_RAIL": config.PADDLE_RAIL,
"BALL_SPEED_INC": config.BALL_SPEED_INC,
"BALL_SPEED_START": config.BALL_SPEED_START
}
return Response(config_data, status = status.HTTP_200_OK)

27
games/viewset.py Normal file
View File

@ -0,0 +1,27 @@
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.authentication import SessionAuthentication
from django.http import HttpRequest
from django.db.models import QuerySet
from .models import GameModel
from .serializers import GameSerializer
# Create your views here.
class GameViewSet(viewsets.ModelViewSet):
queryset = GameModel.objects
serializer_class = GameSerializer
permission_classes = (permissions.AllowAny,)
authentication_classes = (SessionAuthentication,)
def retrieve(self, request: HttpRequest, pk):
if (not self.queryset.filter(pk = pk).exists()):
return Response({"detail": "Game not found."}, status=status.HTTP_404_NOT_FOUND)
game = self.queryset.get(pk=pk)
return Response(self.serializer_class(game).data, status=status.HTTP_200_OK)

View File

@ -38,4 +38,4 @@ class MatchMaking(WebsocketConsumer):
waiting_room: WaitingRoom = normal.get(self.mode)
waiter: Waiter = waiting_room.get_member_by_socket(self)
if (waiter is not None):
waiting_room.remove(waiter)
waiting_room.remove(waiter, 1016)

View File

@ -1,6 +1,7 @@
from django.contrib import admin
from .models import ProfileModel
from .models import ProfileModel, BlockModel
# Register your models here.
admin.site.register(ProfileModel)
admin.site.register(BlockModel)

View File

@ -9,5 +9,7 @@ urlpatterns = [
path("me", viewsets.MyProfileViewSet.as_view({'patch': 'partial_update', 'get': 'retrieve'}), name="my_profile_page"),
path("<int:pk>", viewsets.ProfileViewSet.as_view({'get': 'retrieve'}), name="profile_page"),
path("", viewsets.ProfileViewSet.as_view({'get': 'list'}), name="profiles_list"),
path("block", views.BlockView.as_view(), name="block_page"),
path("block", views.BlocksView.as_view(), name="block_page"),
path("block/<int:pk>", views.BlockView.as_view(), name="block_page"),
] + static("/static/avatars/", document_root="./avatars")

View File

@ -2,17 +2,65 @@ from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions, status
from rest_framework.authentication import SessionAuthentication
from rest_framework.renderers import JSONRenderer
from django.core import serializers
from .models import BlockModel
class BlockView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def post(self, request, pk):
pass
def get(self, request, pk):
pass
block = BlockModel.objects.filter(pk=pk)
if (block.exists()):
return Response(serializers.serialize("json", block), status=status.HTTP_200_OK)
else:
return Response("Not Found", status=status.HTTP_404_NOT_FOUND)
def delete(self, request, pk):
pass
class BlocksView(APIView):
permission_classes = (permissions.IsAuthenticated,)
authentication_classes = (SessionAuthentication,)
def get(self, request):
blocks = BlockModel.objects.filter(blocker=request.user.pk)
if (blocks):
return Response(serializers.serialize("json", BlockModel.objects.filter(blocker=request.user.pk)), status=status.HTTP_200_OK)
else:
return Response({}, status=status.HTTP_404_NOT_FOUND)
def post(self, request):
data: dict = request.data
users_id = request.data.get("users_id", None)
if (users_id == None):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST)
if (BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])):
return Response({"Already Exist"}, status=status.HTTP_409_CONFLICT)
new_block = BlockModel()
new_block.blocker = users_id[0]
new_block.blocked = users_id[1]
new_block.save()
return Response({"block_id": new_block.pk}, status=status.HTTP_201_CREATED)
def delete(self, request):
data: dict = request.data
users_id = request.data.get("users_id", None)
if (users_id == None):
return Response({"Error"}, status=status.HTTP_400_BAD_REQUEST)
block = BlockModel.objects.filter(blocker=users_id[0], blocked=users_id[1])
print(list(block))
if (block.count() > 1):
return Response("Not normal >:[", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if (not block):
return Response("Don't exist", status=status.HTTP_404_NOT_FOUND)
block.delete()
return Response("Deleted", status=status.HTTP_200_OK)

View File

@ -6,9 +6,9 @@ from games.models import GameModel
import json
from .models import Waiter, WaitingRoom, WaitingRoomManager, normal
from .models import tournament_manager, TournamentMember, TournamentRoom, TournamentRoomManager
class TournamentWaitingRoom(WebsocketConsumer):
class TournamentWebConsumer(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -17,25 +17,27 @@ class TournamentWaitingRoom(WebsocketConsumer):
def connect(self):
user: User = self.scope["user"]
if (user.is_anonymous or not user.is_authenticated):
self.user: User = self.scope["user"]
if (self.user.is_anonymous or not self.user.is_authenticated):
return
self.channel_layer.group_add(self.group_name, self.channel_name)
self.tournament_id = int(self.scope['url_route']['kwargs']['tournament_id'])
waiting_room: WaitingRoom = normal.get(self.mode)
waiting_room.append(Waiter(user.pk, self))
self.room = tournament_manager.get(self.tournament_id)
self.member = TournamentMember(self.user.pk, self, self.room)
print(len(waiting_room), "/", self.mode, [len(waiting_room),self.mode])
if (len(waiting_room) == self.mode):
game_id: int = GameModel().create(waiting_room.get_users_id())
waiting_room.broadcast({"detail": "Game found !", "game_id": game_id})
waiting_room.clear()
if (self.room is None):
self.member.send("Tournament not found")
self.disconnect(1017)
self.room.append(self.member)
def receive(self, text_data: str = None, bytes_data: bytes = None):
self.member.receive(text_data, bytes_data)
def disconnect(self, close_code):
waiting_room: WaitingRoom = normal.get(self.mode)
waiter: Waiter = waiting_room.get_waiter_by_socket(self)
if (waiter is not None):
waiting_room.remove(self.scope["user"].pk)
member = self.room.get_member_by_socket(self)
if (member is not None):
self.room.remove(self.member, close_code)

View File

@ -1,36 +1,154 @@
from django.db import models
from channels.generic.websocket import WebsocketConsumer
from games.models import GameModel
import json
from transcendence.abstract.AbstractRoomMember import AbstractRoomMember
from transcendence.abstract.AbstractRoom import AbstractRoom
from transcendence.abstract.AbstractRoomManager import AbstractRoomManager
# Create your models here.tu
class TournamentModel(models.Model):
name = models.CharField(max_length=100)
name = models.CharField(max_length = 100)
nb_players = models.IntegerField()
nb_players_by_game = models.IntegerField()
level = models.IntegerField()
started = models.BooleanField(default=False)
finished = models.BooleanField(default=False)
started = models.BooleanField(default = False)
finished = models.BooleanField(default = False)
def create_game(self, users_id):
game_id = GameModel.create(users_id=users_id)
TournamentGamesModel(game_id=game_id, tournament_id=self.pk).save()
def create_game(self, level, players_id):
game_id = GameModel().create(players_id = players_id)
TournamentGamesModel(game_id = game_id, tournament_id = self.pk, tournament_level = level).save()
return game_id
def get_games_id_by_level(self, level):
return list(TournamentGamesModel.objects.filter(tournament_id=self.pk, tournament_level=level))
tmp = TournamentGamesModel.objects.filter(tournament_id = self.pk, tournament_level = level)
return [instance.game_id for instance in tmp]
def get_games_id(self):
return list(TournamentGamesModel.objects.filter(tournament_id=self.pk))
return [tournament_game.game_id for tournament_game in TournamentGamesModel.objects.filter(tournament_id = self.pk)]
def get_players_id(self):
lst: [int] = []
for game_id in self.get_games_id():
lst.append(GameMembersModel.objects.filter(game_id=game_id))
return lst
return [model.participant_id for model in TournamentParticipantsModel.objects.filter(tournament_id=self.pk)]
def is_a_participant(self, participant_id: int):
return TournamentParticipantsModel.objects.filter(participant_id = participant_id, tournament_id = self.pk).exists()
def add_participants(self, participants_id: [int]):
for participant_id in participants_id:
TournamentParticipantsModel(tournament_id = self.pk, participant_id = participant_id).save()
def start(self, participants_id: [int]):
self.started = True
self.add_participants(participants_id)
games_id = [int]
for i in range(0, len(participants_id), self.nb_players_by_game):
game_id = self.create_game(0, participants_id[i : i + self.nb_players_by_game])
games_id.append(game_id)
self.save()
return games_id
class TournamentParticipantsModel(models.Model):
tournament_id = models.IntegerField()
participant_id = models.IntegerField()
class TournamentGamesModel(models.Model):
tournament_id = models.IntegerField()
tournament_level = models.IntegerField()
game_id = models.IntegerField()
class TournamentMember(AbstractRoomMember):
def __init__(self, user_id: int, socket: WebsocketConsumer, room):
super().__init__(user_id, socket)
self.participate = False
self.room = room
def receive(self, text_data: str = None, byte_dates: bytes = None):
if (text_data is None):
return
data: dict = json.loads(text_data)
if (data.get("participate") is not None):
self.room.update_participants(self)
def send_error_message(self, message: str):
self.send("error", {"error_message": message})
def go_to(self, url: str):
self.send("go_to", {"url": url})
def send_participating(self):
self.send("is_participant", {"is_participant": self.participate})
class TournamentRoom(AbstractRoom):
def __init__(self, room_manager, tournament_id: int):
super().__init__(room_manager)
self.tournament_id = tournament_id
self.tournament = TournamentModel.objects.get(pk = tournament_id)
def start(self):
self.broadcast("tournament_start")
games_id = self.tournament.start(self.get_participants_id())
for i, participant in enumerate(self.get_participants()):
participant: TournamentMember
participant.go_to(f"games/{games_id[i // self.tournament.nb_players_by_game]}")
def update_participants(self, member: TournamentMember):
if (self.tournament.started):
member.send_error_message("Tournament already started")
return
member.participate = not member.participate
nb_participants = self.get_nb_participants()
self.broadcast("update_participants", {"nb_participants": nb_participants})
member.send_participating()
if (nb_participants == self.tournament.nb_players):
self.start()
def get_nb_participants(self):
if (self.tournament.started):
return self.tournament.nb_players
nb_participants = 0
for member in self._member_list:
member: TournamentMember
if (member.participate):
nb_participants += 1
return nb_participants
def get_participants(self):
return [member for member in self._member_list if member.participate]
def get_participants_id(self):
return [member.user_id for member in self._member_list if member.participate]
def append(self, member: TournamentMember):
super().append(member)
if self.tournament.started:
member.participate = self.tournament.is_a_participant(member.user_id)
member.send_participating()
member.send("nb_participants", {"nb_participants": self.get_nb_participants()})
class TournamentRoomManager(AbstractRoomManager):
def get(self, tournament_id: int):
for room in self._room_list:
if (room.tournament_id == tournament_id):
return room
if (TournamentModel.objects.filter(pk = tournament_id).exists()):
room = TournamentRoom(self, tournament_id)
self.append(room)
return room
return None
tournament_manager: TournamentRoomManager = TournamentRoomManager()

6
tournament/routing.py Normal file
View File

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

View File

@ -1,5 +1,6 @@
from rest_framework import serializers
from .models import TournamentModel
from games.serializers import GameSerializer
class TournamentSerializer(serializers.ModelSerializer):
@ -13,20 +14,19 @@ class TournamentSerializer(serializers.ModelSerializer):
model = TournamentModel
fields = ["name", "nb_players", "nb_players_by_game", "level", "started", "finished", "levels", "id"]
def get_levels(self, instance):
def get_levels(self, instance: TournamentModel):
levels: [[int]] = []
for i in range(instance.level):
level: [int] = instance.get_games_id_by_level(i)
if (level == []):
games_id: [int] = instance.get_games_id_by_level(i)
if (games_id == []):
break
levels.append(level)
levels.append(games_id)
return levels
def validate_nb_players(self, value: int):
if (value < 2):
raise serializers.ValidationError("The numbers of players must be greather than 2.")
return value
def validate_nb_players_by_game(self, value: int):
if (value < 2):
raise serializers.ValidationError("The numbers of players by game must be greather than 2.")

View File

@ -32,15 +32,15 @@ class AbstractRoom:
def append(self, member: AbstractRoomMember):
self._member_list.append(waiter)
self._member_list.append(member)
member.accept()
def remove(self, member: AbstractRoomMember):
def remove(self, member: AbstractRoomMember, code: int = 1000):
self._member_list.remove(member)
member.disconnect()
member.disconnect(code)
def empty(self):
for _ in self._waiter_list:
for _ in self._member_list:
return False
return True

View File

@ -5,14 +5,8 @@ class AbstractRoomManager:
def __init__(self):
self._room_list: [AbstractRoom] = []
def get(self, mode: int):
for room in self._room_list:
room: A
if (waiting_room._mode == mode):
return waiting_room
tmp: WaitingRoom = WaitingRoom(self, mode)
self._waiting_rooms.append(tmp)
return tmp
def remove(self, room: AbstractRoom):
self._room_list.remove(room)
def remove(self, waiting_room: WaitingRoom):
self._waiting_rooms.remove(waiting_room)
def append(self, room: AbstractRoom):
self._room_list.append(room)

View File

@ -16,5 +16,5 @@ class AbstractRoomMember:
def accept(self):
self.socket.accept()
def disconnect(self):
self.socket.disconnect(200)
def disconnect(self, code: int = 1000):
self.socket.disconnect(code)

View File

@ -13,6 +13,8 @@ from channels.auth import AuthMiddlewareStack
import chat.routing
import matchmaking.routing
import tournament.routing
import games.routing
from django.core.asgi import get_asgi_application
@ -23,7 +25,9 @@ application = ProtocolTypeRouter({
'websocket':AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns +
matchmaking.routing.websocket_urlpatterns
matchmaking.routing.websocket_urlpatterns +
tournament.routing.websocket_urlpatterns +
games.routing.websocket_urlpatterns
)
)
})

View File

@ -23,5 +23,6 @@ urlpatterns = [
path('api/accounts/', include('accounts.urls')),
path('api/chat/', include('chat.urls')),
path('api/tournaments/', include('tournament.urls')),
path('api/games/', include('games.urls')),
path('', include('frontend.urls')),
]