diff --git a/README.md b/README.md index 69e2f03..34478b7 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,15 @@ pip install -r requirements.txt ``` - Setup database ``` +<<<<<<< HEAD python manage.py runserver makemigrations profiles python manage.py migrate profiles +======= +python manage.py makemigrations games +python manage.py makemigrations profiles +python manage.py runserver makemigrations chat +python manage.py migrate +>>>>>>> d03f90367f1fd075e79f4a69d31c5a49b9e53d01 ``` - Start the developpement server ``` diff --git a/frontend/static/js/api/client.js b/frontend/static/js/api/client.js index 90fc88e..c90dbd1 100644 --- a/frontend/static/js/api/client.js +++ b/frontend/static/js/api/client.js @@ -1,4 +1,5 @@ import { Account } from "./account.js"; +import { MatchMaking } from "./matchmaking.js"; import { Profile } from "./profile.js"; import { Profiles } from "./profiles.js"; @@ -19,6 +20,7 @@ class Client this._url = url; this.account = new Account(this); this.profiles = new Profiles(this); + this.matchmaking = new MatchMaking(this); this._logged = undefined; } diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js new file mode 100644 index 0000000..806349e --- /dev/null +++ b/frontend/static/js/api/matchmaking.js @@ -0,0 +1,37 @@ +import { client, navigateTo } from "../index.js" + +class MatchMaking +{ + /** + * @param {client} client + */ + constructor(client) + { + /** + * @type {client} + */ + this.client = client + } + + async start(func) + { + if (!await this.client.isAuthentificate()) + return null; + + let url = `wss://${window.location.host}/ws/matchmaking/`; + + this._chatSocket = new WebSocket(url); + + this._chatSocket.onmessage = function (event) { + const data = JSON.parse(event.data); + func(data.game_id) + }; + } + + async stop() + { + this._chatSocket.close() + } +} + +export {MatchMaking} \ No newline at end of file diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 3db6fa5..12dc11b 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -1,16 +1,20 @@ +import { Client } from "./api/client.js"; + import LoginView from "./views/accounts/LoginView.js"; import Dashboard from "./views/Dashboard.js"; -import Settings from "./views/Settings.js"; import Search from "./views/Search.js"; import Chat from "./views/Chat.js"; import HomeView from "./views/HomeView.js"; import RegisterView from "./views/accounts/RegisterView.js"; import LogoutView from "./views/accounts/LogoutView.js"; +import GameView from "./views/Game.js" + +import PageNotFoundView from './views/PageNotFoundView.js' -import { Client } from "./api/client.js"; import AbstractRedirectView from "./views/AbstractRedirectView.js"; import MeView from "./views/MeView.js"; import ProfilePageView from "./views/profiles/ProfilePageView.js"; +import MatchMakingView from "./views/MatchMakingView.js"; let client = new Client(location.protocol + "//" + location.host) @@ -36,7 +40,6 @@ const router = async (uri) => { const routes = [ { path: "/", view: Dashboard }, { path: "/profiles/:id", view: ProfilePageView }, - { path: "/settings", view: Settings }, { path: "/login", view: LoginView }, { path: "/logout", view: LogoutView }, { path: "/register", view: RegisterView }, @@ -44,6 +47,8 @@ const router = async (uri) => { { path: "/chat", view: Chat }, { path: "/home", view: HomeView }, { path: "/me", view: MeView }, + { path: "/matchmaking", view: MatchMakingView }, + { path: "/game/offline", view: GameView }, ]; // Test each route for potential match @@ -58,7 +63,10 @@ const router = async (uri) => { if (!match) { match = { - route: routes[0], + route: { + path: uri, + view: PageNotFoundView + }, result: [uri] }; } diff --git a/frontend/static/js/views/Game.js b/frontend/static/js/views/Game.js new file mode 100644 index 0000000..87dd0cb --- /dev/null +++ b/frontend/static/js/views/Game.js @@ -0,0 +1,250 @@ +import AbstractView from './AbstractView.js' + +export default class extends AbstractView { + constructor(params) { + super(params, 'Game'); + this.game = null; + } + + async getHtml() { + return ` +
Git gud
+ `; + } +} diff --git a/frontend/static/js/views/Settings.js b/frontend/static/js/views/Settings.js deleted file mode 100644 index 1c9f85f..0000000 --- a/frontend/static/js/views/Settings.js +++ /dev/null @@ -1,14 +0,0 @@ -import AbstractView from "./AbstractView.js"; - -export default class extends AbstractView { - constructor(params) { - super(params, "Settings"); - } - - async getHtml() { - return ` -Manage your privacy and configuration.
- `; - } -} \ No newline at end of file diff --git a/games/__init__.py b/games/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/games/admin.py b/games/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/games/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/games/apps.py b/games/apps.py new file mode 100644 index 0000000..1a3efec --- /dev/null +++ b/games/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GamesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'games' diff --git a/games/models.py b/games/models.py new file mode 100644 index 0000000..a2d8315 --- /dev/null +++ b/games/models.py @@ -0,0 +1,14 @@ +from django.db import models + +# Create your models here. +class GameModel(models.Model): + + def create(self, users_id: [int]): + self.save() + for user_id in users_id: + GameMembersModel(game_id=self.pk, member_id=user_id) + return self.pk + +class GameMembersModel(models.Model): + game_id = models.IntegerField() + member_id = models.IntegerField() \ No newline at end of file diff --git a/games/tests.py b/games/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/games/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/games/views.py b/games/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/games/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/matchmaking/__init__.py b/matchmaking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/matchmaking/admin.py b/matchmaking/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/matchmaking/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/matchmaking/apps.py b/matchmaking/apps.py new file mode 100644 index 0000000..7ca5434 --- /dev/null +++ b/matchmaking/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MatchmakingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'matchmaking' diff --git a/matchmaking/consumers.py b/matchmaking/consumers.py new file mode 100644 index 0000000..dfa65e8 --- /dev/null +++ b/matchmaking/consumers.py @@ -0,0 +1,48 @@ +from channels.generic.websocket import WebsocketConsumer + +from django.contrib.auth.models import User + +from games.models import GameModel + +import json + +queue_id: [int] = [] +queue_ws: [WebsocketConsumer] = [] + +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"] + if (user.is_anonymous or not user.is_authenticated): + return + + self.channel_layer.group_add(self.group_name, self.channel_name) + + self.accept() + + global queue_id, queue_ws + queue_id.append(user.pk) + queue_ws.append(self) + + if len(set(queue_id)) == 2: + game_id: int = GameModel().create(set(queue_id)) + event = {"game_id": game_id} + for ws in queue_ws: + ws.send(text_data=json.dumps({'game_id': game_id})) + queue_id.clear() + queue_ws.clear() + + + def disconnect(self, close_code): + user: User = self.scope["user"] + global queue_id, queue_ws + if (user.pk in queue_id): + queue_ws.pop(queue_id.index(user.pk)) + queue_id.remove(user.pk) + self.channel_layer.group_discard(self.group_name, self.channel_name) \ No newline at end of file diff --git a/matchmaking/models.py b/matchmaking/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/matchmaking/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/matchmaking/routing.py b/matchmaking/routing.py new file mode 100644 index 0000000..eed5072 --- /dev/null +++ b/matchmaking/routing.py @@ -0,0 +1,6 @@ +from django.urls import re_path +from . import consumers + +websocket_urlpatterns = [ + re_path(r'ws/matchmaking/', consumers.MatchMaking.as_asgi()) +] diff --git a/matchmaking/tests.py b/matchmaking/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/matchmaking/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/matchmaking/views.py b/matchmaking/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/matchmaking/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements.txt b/requirements.txt index 53e09ab..be1d817 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,30 @@ asgiref==3.7.2 +attrs==23.1.0 +autobahn==23.6.2 +Automat==22.10.0 +cffi==1.16.0 +channels==4.0.0 +constantly==23.10.4 +cryptography==41.0.7 +daphne==4.0.0 Django==4.2.6 django-cors-headers==4.3.0 django-restapi==0.1.3 djangorestframework==3.14.0 +hyperlink==21.0.0 +idna==3.6 +incremental==22.10.0 install==1.3.5 +Pillow==10.1.0 +pyasn1==0.5.1 +pyasn1-modules==0.3.0 +pycparser==2.21 +pyOpenSSL==23.3.0 pytz==2023.3.post1 +service-identity==23.1.0 +six==1.16.0 sqlparse==0.4.4 -channels==4.0.0 -daphne +Twisted==23.10.0 +txaio==23.1.1 +typing_extensions==4.8.0 +zope.interface==6.1 diff --git a/trancendence/asgi.py b/trancendence/asgi.py index d0068aa..fa7ffa8 100644 --- a/trancendence/asgi.py +++ b/trancendence/asgi.py @@ -10,7 +10,9 @@ https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ import os from channels.routing import ProtocolTypeRouter, URLRouter from channels.auth import AuthMiddlewareStack + import chat.routing +import matchmaking.routing from django.core.asgi import get_asgi_application @@ -20,7 +22,8 @@ application = ProtocolTypeRouter({ 'http':get_asgi_application(), 'websocket':AuthMiddlewareStack( URLRouter( - chat.routing.websocket_urlpatterns + chat.routing.websocket_urlpatterns + + matchmaking.routing.websocket_urlpatterns ) ) }) diff --git a/trancendence/settings.py b/trancendence/settings.py index 0ddacfc..411aca1 100644 --- a/trancendence/settings.py +++ b/trancendence/settings.py @@ -43,6 +43,8 @@ INSTALLED_APPS = [ 'channels', 'daphne', + 'matchmaking.apps.MatchmakingConfig', + 'games.apps.GamesConfig', 'accounts.apps.AccountsConfig', 'profiles.apps.ProfilesConfig', 'frontend.apps.FrontendConfig',