diff --git a/frontend/static/js/api/game/Game.js b/frontend/static/js/api/game/Game.js index 9eb2a94..811a3d4 100644 --- a/frontend/static/js/api/game/Game.js +++ b/frontend/static/js/api/game/Game.js @@ -56,6 +56,7 @@ class Game this.walls.forEach(wall => { wall.draw(ctx); }); + console.log(this.players) this.players.forEach(player => { player.draw(ctx); }); @@ -82,7 +83,7 @@ class Game }, 500); } - _send_paddle(position, time) + _send_paddle_position(position, time) { if (this.last_pos !== null && this.last_pos.time >= time) return; @@ -92,9 +93,38 @@ class Game this._send({"detail": "update_my_paddle_pos", ...this.last_pos}); } - _update_paddles(data) + _receive_player_join(player_data) + { + let index = this.players.indexOf((player) => player.id === player_data.user_id); + + if (index !== -1) + this.players.slice(index, 1); + + this.players.push(new Player(player_data.user_id, + this, + player_data.rail_start_x, + player_data.rail_start_y, + player_data.rail_stop_x, + player_data.rail_stop_y, + player_data.nb_goal, + player_data.position.position + )); + } + + _receive_player_leave(player_data) + { + const user_id = player_data.user_id; + + const index = this.players.find(player => player.id === user_id); + + if (index === -1) + return + + this.players.slice(index, 1); + } + + _receive_paddle_position(data) { - console.log(data) data.players.forEach((player_data) => { let player = this.players.find((player) => player.id === player_data.id); if (player === null) @@ -103,14 +133,16 @@ class Game }) } - _update(data) + _receive(data) { - if (data.detail === "update_paddles") - this._update_paddles(data); + if (data.detail === "paddle_position") + this._receive_paddle_position(data); else if (data.detail === "update_ball") this._update_ball(data); else if (data.detail === "init_game") this._init_game(data) + else if (data.detail === "player_join") + this._receive_player_join(data) } _init_game(data) @@ -130,15 +162,7 @@ class Game this.players = [] const players_data = data.players; players_data.forEach((player_data) => { - this.players.push(new Player(player_data.user_id, - this, - player_data.rail_start_x, - player_data.rail_start_y, - player_data.rail_stop_x, - player_data.rail_stop_y, - player_data.nb_goal, - player_data.position - )); + this._receive_player_join(player_data) }); this._inited = true; @@ -164,7 +188,7 @@ class Game this._socket.onmessage = (event) => { const data = JSON.parse(event.data); - this._update(data); + this._receive(data); }; return this.wait_init(); diff --git a/frontend/static/js/api/game/MyPlayer.js b/frontend/static/js/api/game/MyPlayer.js index 0dbffe2..4c28fb0 100644 --- a/frontend/static/js/api/game/MyPlayer.js +++ b/frontend/static/js/api/game/MyPlayer.js @@ -25,7 +25,7 @@ class MyPlayer extends Player this.positon = new_pos; - this.game._send_paddle(this.positon, this.game.time._current_frame); + this.game._send_paddle_position(this.positon, this.game.time._current_frame); } update_pos(new_position, time) diff --git a/games/config.py b/games/config.py index 88e90bd..f1209dc 100644 --- a/games/config.py +++ b/games/config.py @@ -1,6 +1,8 @@ PADDLE_SPEED_PER_SECOND_MAX = 0.6 PADDLE_SPEED_PER_SECOND_TOLERANCE = 1.01 PADDLE_RATIO = 0.3 +PADDLE_POSITION_MIN: float = PADDLE_RATIO / 2 +PADDLE_POSITION_MAX: float = 1 - PADDLE_POSITION_MIN MAP_SIZE_X = 700 MAP_SIZE_Y = 700 diff --git a/games/consumers.py b/games/consumers.py index a2c6524..fdd5015 100644 --- a/games/consumers.py +++ b/games/consumers.py @@ -1,15 +1,20 @@ +from __future__ import annotations + from channels.generic.websocket import WebsocketConsumer from django.contrib.auth.models import User import json - -from .objects.GameRoomManager import GameRoomManager -from .objects.GameMember import GameMember -from .objects.Game import Game + from .objects.GameManager import GameManager -game_room_manager: GameRoomManager = GameRoomManager() +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .objects.Spectator import Spectator + from .objects.Player import Player + from .objects.Game import Game + game_manager: GameManager = GameManager() class GameWebSocket(WebsocketConsumer): @@ -19,9 +24,6 @@ class GameWebSocket(WebsocketConsumer): self.channel_name = "games" self.group_name = "games" - def send_game_data(self): - self.member.send("init_game", self.game.to_dict()) - def connect(self): self.user: User = self.scope["user"] @@ -32,19 +34,13 @@ class GameWebSocket(WebsocketConsumer): self.game_id = int(self.scope['url_route']['kwargs']['game_id']) - self.game = game_manager.get(self.game_id) + self.game: Game = game_manager.get(self.game_id) - self.room = game_room_manager.get(self.game_id) - - if (self.room is None): + if (self.game is None): self.send("Game not found.") self.disconnect(1017) - self.member = GameMember(self.user.pk, self, self.room) - - self.room.append(self.member) - - self.send_game_data() + self.member: Player | Spectator = self.game.join(self.user.pk, self) def receive(self, text_data: str = None, bytes_data: bytes = None): @@ -56,6 +52,4 @@ class GameWebSocket(WebsocketConsumer): self.member.receive(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) \ No newline at end of file + self.game.remove(self.member) \ No newline at end of file diff --git a/games/objects/Game.py b/games/objects/Game.py index ff03683..176224b 100644 --- a/games/objects/Game.py +++ b/games/objects/Game.py @@ -1,5 +1,11 @@ +from transcendence.abstract.AbstractRoom import AbstractRoom +from transcendence.abstract.AbstractRoomMember import AbstractRoomMember + +from channels.generic.websocket import WebsocketConsumer + from .Ball import Ball from .Player import Player +from .Spectator import Spectator from .Wall import Wall import math @@ -8,39 +14,125 @@ from .. import config from ..models import GameModel -class Game: - - def __init__(self, game_id: int) -> None: +class Game(AbstractRoom): + + def __init__(self, game_id): + super().__init__(None) + self.ball: Ball = Ball() - game: GameModel = GameModel.objects.get(pk = game_id) - players_id: [int] = game.get_players_id() + game_model: GameModel = GameModel.objects.get(pk = game_id) + self.players_id: list[int] = game_model.get_players_id() + + self.started = False radius: float = min(config.MAP_SIZE_X, config.MAP_SIZE_Y) / 2 - 10 - nb_sides = len(players_id) * 2 + self.nb_sides = len(self.players_id) * 2 - self.polygon = [] + self.polygon: list[tuple[float, float]] = [] - for i in range(nb_sides): + for i in range(self.nb_sides): - angle: float = (i * 2 * math.pi / nb_sides) + (math.pi * 3 / 4) + angle: float = (i * 2 * math.pi / self.nb_sides) + (math.pi * 3 / 4) x: float = config.MAP_CENTER_X + radius * math.cos(angle) y: float = config.MAP_CENTER_Y + radius * math.sin(angle) self.polygon.append((x, y)) - self.players: list(Player) = [] - self.walls: list(Wall) = [] + self.players: list[Player] = [] + self.spectators: list[Spectator] = [] - for i in range(nb_sides): - if (i % 2 == 0): - self.players.append(Player(players_id[ i // 2], *self.polygon[i], *self.polygon[(i + 1) % nb_sides])) - else: - self.walls.append(Wall(*self.polygon[i], *self.polygon[(i + 1) % nb_sides])) + self.walls: list[Wall] = [] + + for i in range(1, self.nb_sides, 2): + self.walls.append(Wall(*self.polygon[i], *self.polygon[(i + 1) % self.nb_sides])) self.game_id: int = game_id + def broadcast(self, detail: str, data: dict = {}, excludeds: list[Spectator | Player] = []): + + members: list[Player | Spectator] = self.players + self.spectators + + for excluded in excludeds: + members.remove(excluded) + + for member in members: + member.send(detail, data) + + def get_player_by_user_id(self, user_id: int) -> Player: + for player in self.players: + player: Player + if (player.user_id == user_id): + return player + return None + + def _send_game_data(self, member: AbstractRoomMember): + member.send("init_game", self.to_dict()) + + def append(self, member: AbstractRoomMember): + super().append(member) + member.send("init_game", self.to_dict()) + + def _player_join(self, user_id: int, socket: WebsocketConsumer): + + #check if member is a player + if (user_id not in self.players_id): + return None + + # check if player is already connected + player = self.get_player_by_user_id(user_id) + if (player is not None): + player.disconnect(1001) + + index: int = self.players_id.index(user_id) * 2 + + player = Player(user_id, socket, *self.polygon[index], *self.polygon[(index + 1) % self.nb_sides]) + + self.players.append(player) + + player.accept() + + self.broadcast("player_join", player.to_dict(), [player]) + + return player + + def _player_leave(self, player: Player): + # TODO send data to all players + self.players.remove(player) + + def _spectator_join(self, user_id: int, socket: WebsocketConsumer): + + spectator: Spectator = Spectator(user_id, socket) + + self.spectators.append(spectator) + + spectator.accept() + + return spectator + + def _spectator_leave(self, spectator: Spectator): + self.spectators.remove(spectator) + + def join(self, user_id: int, socket: WebsocketConsumer) -> Spectator | Player: + if (user_id in self.players_id): + member: Player = self._player_join(user_id, socket) + print("yo") + else: + member: Spectator = self._spectator_join(user_id, socket) + self._send_game_data(member) + return member + + + def start(self): + self.started = True + + def remove(self, member: AbstractRoomMember): + if (isinstance(member, Player)): + self._player_leave(member) + elif (isinstance(member, Spectator)): + self._spectator_leave(member) + def to_dict(self): data: dict = {"ball": self.ball.to_dict(), @@ -48,6 +140,6 @@ class Game: "walls": [wall.to_dict() for wall in self.walls], } - print(data) + print(self.players ) return data \ No newline at end of file diff --git a/games/objects/GameManager.py b/games/objects/GameManager.py index 81c18b8..e8367e8 100644 --- a/games/objects/GameManager.py +++ b/games/objects/GameManager.py @@ -5,9 +5,9 @@ from ..models import GameModel class GameManager(): def __init__(self) -> None: - self._game_list: [Game] = [] + self._game_list: list[Game] = [] - def get(self, game_id: int): + def get(self, game_id: int) -> Game: if (not GameModel.objects.filter(pk = game_id, started = True, finished = False).exists()): return None @@ -17,6 +17,10 @@ class GameManager(): if (game.game_id == game_id): return game - return Game(game_id) + game: Game = Game(game_id) + + self._game_list.append(game) + + return game \ No newline at end of file diff --git a/games/objects/GameMember.py b/games/objects/GameMember.py deleted file mode 100644 index c2187c6..0000000 --- a/games/objects/GameMember.py +++ /dev/null @@ -1,97 +0,0 @@ -from channels.generic.websocket import AsyncWebsocketConsumer - -from transcendence.abstract.AbstractRoomMember import AbstractRoomMember - -from .Position import Position - -import time - -from .. import config - -from ..models import GameModel - -MIN_POSITION: float = config.PADDLE_RATIO / 2 -MAX_POSITION: float = 1 - MIN_POSITION - -class GameMember(AbstractRoomMember): - - def __init__(self, user_id: int, socket: AsyncWebsocketConsumer, room): - super().__init__(user_id, socket) - self.position: Position = Position(0.5, int(time.time() * 1000.0)) - self.room = room - self.is_a_player = user_id in GameModel.objects.get(pk = self.room.game_id).get_players_id() - - def receive(self, data: dict): - - detail: str = data.get("detail") - - if (detail is None): - return - - if (detail == "update_my_paddle_pos"): - self.update_position(data) - - def send_error(self, error_message: str): - - self.send("error", {"error_message": error_message}) - - def update_position(self, data: dict): - - if (not self.is_a_player): - return - - new_position: Position = Position() - - new_position.position = data.get("position") - - if (new_position.position is None): - self.send_error("missing new_position") - return - - new_position.time = data.get("time") - - if (new_position.time is None): - self.send_error("missing time") - return - - if (self.position.time > new_position.time): - self.send_error("time error") - return - - distance: float = abs(self.position.position - new_position.position) - - sign = 1 if self.position.position >= new_position.position else -1 - - time_difference = (new_position.time - self.position.time) / 1000 - - max_distance = config.PADDLE_SPEED_PER_SECOND_MAX * (time_difference) * config.PADDLE_SPEED_PER_SECOND_TOLERANCE - - new_position_verified: Position = new_position.copy() - - if (distance > max_distance): - print(max_distance, distance, time_difference, self.position.position, new_position.position) - new_position_verified.position = self.position.position + max_distance * sign - - if (not MIN_POSITION <= new_position_verified.position <= MAX_POSITION): - - new_position_verified.position = max(new_position_verified.position, MIN_POSITION) - new_position_verified.position = min(new_position_verified.position, MAX_POSITION) - - if (new_position.position != new_position_verified.position): - self.send("update_paddles", { - "players": [ - { - "position": new_position_verified.position, - "time": new_position_verified.time, - "id": self.user_id, - }, - ], - }) - print("trop vite") - - self.position = new_position - - - def send_ball(self, ball): - self.send("update_ball_pos", {"x": ball.x, - "y": ball.y}) \ No newline at end of file diff --git a/games/objects/GameRoom.py b/games/objects/GameRoom.py deleted file mode 100644 index 2f76e4b..0000000 --- a/games/objects/GameRoom.py +++ /dev/null @@ -1,7 +0,0 @@ -from transcendence.abstract.AbstractRoom import AbstractRoom - -class GameRoom(AbstractRoom): - - def __init__(self, game_room_manager, game_id: int): - super().__init__(game_room_manager) - self.game_id = game_id \ No newline at end of file diff --git a/games/objects/GameRoomManager.py b/games/objects/GameRoomManager.py deleted file mode 100644 index 01780ae..0000000 --- a/games/objects/GameRoomManager.py +++ /dev/null @@ -1,19 +0,0 @@ -from transcendence.abstract.AbstractRoomManager import AbstractRoomManager - -from ..models import GameModel -from .GameRoom import GameRoom - -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 = game_id).exists()): - room = GameRoom(self, game_id) - self.append(room) - return room - - return None \ No newline at end of file diff --git a/games/objects/Player.py b/games/objects/Player.py index 7f4f148..fb74022 100644 --- a/games/objects/Player.py +++ b/games/objects/Player.py @@ -1,22 +1,106 @@ +from __future__ import annotations +from .. import config -class Player: +from channels.generic.websocket import WebsocketConsumer + +from .Position import Position +from .Spectator import Spectator + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...transcendence.abstract.AbstractRoomMember import AbstractRoomMember + +class Player(Spectator): - def __init__(self, user_id, rail_start_x, rail_start_y, rail_stop_x, rail_stop_y) -> None: - self.user_id = user_id - self.position = 0.5 - self.nb_goal = 0 + def __init__(self, user_id: int, socket: WebsocketConsumer, rail_start_x: float, rail_start_y: float, rail_stop_x: float, rail_stop_y: float) -> None: + + super().__init__(user_id, socket) + + self.position: Position = Position(0.5, 0) + + self.nb_goal: int = 0 + + self.rail_start_x: float = rail_start_x + self.rail_start_y: float = rail_start_y + self.rail_stop_x: float = rail_stop_x + self.rail_stop_y: float = rail_stop_y + + def receive(self, data: dict): + + detail: str = data.get("detail") + + if (detail is None): + return + + if (detail == "update_my_paddle_pos"): + self.update_position(data) + + def send_error(self, error_message: str, error_data = {}): + + data: dict = { + "error_message": error_message + } + + data.update(error_data) + + self.send("error", data) + + def update_position(self, data: dict): + + print(data) + + new_position: Position = Position() + + new_position.position: float = data.get("position") + + if (new_position.position is None): + self.send_error("missing new_position") + return + + new_position.time: float = data.get("time") + + if (new_position.time is None): + self.game_member.send_error("missing time") + return + + if (self.position.time > new_position.time): + self.game_member.send_error("time error") + return + + distance: float = abs(self.position.position - new_position.position) + + sign: int = 1 if self.position.position >= new_position.position else -1 + + time_difference: float = (new_position.time - self.position.time) / 1000 + + max_distance: float = config.PADDLE_SPEED_PER_SECOND_MAX * (time_difference) * config.PADDLE_SPEED_PER_SECOND_TOLERANCE + + new_position_verified: Position = new_position.copy() + + if (distance > max_distance): + print(max_distance, distance, time_difference, self.position.position, new_position.position) + new_position_verified.position = self.position.position + max_distance * sign + + if (not config.PADDLE_POSITION_MIN <= new_position_verified.position <= config.PADDLE_POSITION_MAX): + + new_position_verified.position = max(new_position_verified.position, config.PADDLE_POSITION_MIN) + new_position_verified.position = min(new_position_verified.position, config.PADDLE_POSITION_MAX) + + invalid_pos: bool = new_position.position != new_position_verified.position + + self.position = new_position + + if (invalid_pos): + self.game_member.send("update_paddle", self.to_dict()) - self.rail_start_x = rail_start_x - self.rail_start_y = rail_start_y - self.rail_stop_x = rail_stop_x - self.rail_stop_y = rail_stop_y def to_dict(self): data = { "user_id": self.user_id, - "position": self.position, + "position": self.position.to_dict(), "nb_goal": self.nb_goal, "rail_start_x": self.rail_start_x, diff --git a/games/objects/Position.py b/games/objects/Position.py index 7c119d1..aa13099 100644 --- a/games/objects/Position.py +++ b/games/objects/Position.py @@ -7,4 +7,13 @@ class Position: self.position = position def copy(self): - return Position(self.position, self.time) \ No newline at end of file + return Position(self.position, self.time) + + def to_dict(self): + + data: dict = { + "position": self.position, + "time": self.time, + } + + return data \ No newline at end of file diff --git a/games/objects/Spectator.py b/games/objects/Spectator.py new file mode 100644 index 0000000..444875c --- /dev/null +++ b/games/objects/Spectator.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from transcendence.abstract.AbstractRoomMember import AbstractRoomMember + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .Player import Player + +from .Ball import Ball + +class Spectator(AbstractRoomMember): + + def send_paddle(self, player: Player): + self.send("update_paddle", player.to_dict()) + + def send_ball(self, ball: Ball): + self.send("update_ball", ball.to_dict()) \ No newline at end of file diff --git a/games/routine.py b/games/routine.py new file mode 100644 index 0000000..e69de29