diff --git a/django/frontend/static/js/views/Search.js b/django/frontend/static/js/views/Search.js index 9c19ca0..a145465 100644 --- a/django/frontend/static/js/views/Search.js +++ b/django/frontend/static/js/views/Search.js @@ -14,9 +14,6 @@ export default class extends AbstractView { this.profiles = await client.profiles.all(); this.logged = await client.isAuthenticated(); - this.logged = await client.isAuthenticated(); - this.profiles = await client.profiles.all(); - document.getElementById("username-input").oninput = () => this.display_users(); this.last_add_chat = undefined; diff --git a/django/games/objects/pong/Ball.py b/django/games/objects/pong/Ball.py index ada7b90..d0cdcc5 100644 --- a/django/games/objects/pong/Ball.py +++ b/django/games/objects/pong/Ball.py @@ -21,7 +21,7 @@ class Ball: def reset(self) -> None: self.size = config.BALL_SIZE self.position = Position(Point(config.BALL_SPAWN_POS_X + self.size / 2, config.BALL_SPAWN_POS_Y + self.size / 2), time.time()) - self.angle = math.pi * 1 + self.angle = math.pi * 0 self.speed = config.BALL_SPEED_START def to_dict(self) -> dict: diff --git a/django/games/objects/pong/PongGame.py b/django/games/objects/pong/PongGame.py index 4af615e..1aca573 100644 --- a/django/games/objects/pong/PongGame.py +++ b/django/games/objects/pong/PongGame.py @@ -35,7 +35,7 @@ class PongGame(AGame): ] self.players = [PongPlayer(self, players[0], None, Segment(corners[1].copy(), corners[2].copy())), - PongPlayer(self, players[1], None, Segment(corners[0].copy(), corners[3].copy()))] + PongPlayer(self, players[1], None, Segment(corners[3].copy(), corners[0].copy()))] self.walls = [Wall(corners[0], corners[1]), Wall(corners[2], corners[3])] @@ -74,7 +74,6 @@ class PongGame(AGame): return self.ball.reset() - self.broadcast("update_ball", self.ball.to_dict()) def get_valid_players(self) -> list[PongPlayer]: return [player for player in self.players if player.is_connected and not player.is_eliminated] diff --git a/django/games/routine.py b/django/games/routine.py index 9c69079..59e0493 100644 --- a/django/games/routine.py +++ b/django/games/routine.py @@ -1,9 +1,9 @@ from __future__ import annotations -from .objects.pong.PongGame import PongPlayer from .objects.pong.Segment import Segment from .objects.pong.Point import Point from .objects.pong.Ball import Ball +from .objects.pong.PongPlayer import PongPlayer from typing import TYPE_CHECKING @@ -12,38 +12,179 @@ if TYPE_CHECKING: from . import config +import asyncio + import math +import time + from asgiref.sync import SyncToAsync -import asyncio VERTICALLY = 1 NORMAL = 2 -def get_player_hitted(players: list[PongPlayer], segment: Segment) -> PongPlayer | None: +def identify(segment: Segment) -> str: - for player in players: - if (player.rail is segment): + if (segment.start.x == segment.stop.x): + return VERTICALLY + return NORMAL + + +def get_sign(num: float) -> int: + if (num == 0): + return 0 + if (num > 0): + return 1 + if (num < 0): + return -1 + +def get_intercept(derive: float, point: Point) -> float: + + if (derive is None): + return None + + return point.y - (point.x * derive) + + +def get_derive(segment: Segment) -> float | None: + + if (segment.start.x == segment.stop.x): + return None + + return (segment.stop.y - segment.start.y) / (segment.stop.x - segment.start.x) + +def get_ball_segment(ball: Ball) -> tuple[Segment, float, float]: + + cos: float = round(math.cos(ball.angle), 6) + # invert because mathematical y coordinate and computor science y coordinate are opposed, y = 5 is above y = 10 + sin: float = round(math.sin(ball.angle)) * -1 + + inc_x: float = (-1) * get_sign(cos) * (config.STROKE_THICKNESS + config.BALL_SIZE / 2) + inc_y: float = get_sign(sin) * (config.STROKE_THICKNESS + config.BALL_SIZE / 2) + + ball_segment: tuple[Segment, float, float] = (Segment(ball.position.location, Point(ball.position.location.x + inc_x, ball.position.location.y + inc_y)), inc_x, inc_y) + + return ball_segment + +def get_constant(segment: Segment) -> float: + + return segment.start.x + +def get_impact_point(segment: Segment, ball_segment: Segment) -> Point | None: + + if identify(segment) == VERTICALLY and identify(ball_segment) == VERTICALLY: + + return None + + # because of in matematics world y = 10 is above y = 5 and on a display it is inverted I invert the coordonate + + inverted_segment = Segment(Point(segment.start.x, config.MAP_SIZE_Y - segment.start.y), Point(segment.stop.x, config.MAP_SIZE_Y - segment.stop.y)) + inverted_ball_segment = Segment(Point(ball_segment.start.x, config.MAP_SIZE_Y - ball_segment.start.y), Point(ball_segment.stop.x, config.MAP_SIZE_Y - ball_segment.stop.y)) + + y: float + x: float + + if (identify(segment) == NORMAL and identify(ball_segment) == NORMAL): + + # representation m * x + p + + m1 = get_derive(inverted_segment) + m2 = get_derive(inverted_ball_segment) + + p1 = get_intercept(m1, inverted_segment.start) + p2 = get_intercept(m2, inverted_ball_segment.start) + + # m1 * x + p1 = m2 * x + p2 + # m1 * x = m2 * x + p2 -p1 + # m1 * x - m2 * x = p1 - p2 + # x * (m1 - m2) = p1 - p2 + # x = (p1 - p2) / (m1 - m2) + if (m1 == m2): + return None + + # reinvert + x: float = (p1 - p2) / (m1 - m2) * (-1) + + y: float = config.MAP_SIZE_Y - (m1 * x + p1) + + else: + + if (identify(inverted_segment) == VERTICALLY): + constant: float = get_constant(inverted_segment) + m: float = get_derive(inverted_ball_segment) + p: float = get_intercept(m, inverted_ball_segment.start) + else: + constant: float = get_constant(inverted_ball_segment) + m: float = get_derive(inverted_segment) + p: float = get_intercept(m, inverted_segment.start) + + x: float = constant + y: float = config.MAP_SIZE_Y - (m * x + p) + + impact: Point = Point(x, y) + + return impact + + +def get_first_impact(segments: list[Segment], ball: Ball): + + cos: float = round(math.cos(ball.angle), 6) + sin: float = round(math.sin(ball.angle)) + + inc_x: float = (-1) * get_sign(cos) * (config.STROKE_THICKNESS + config.BALL_SIZE / 2) + inc_y: float = get_sign(sin) * (config.STROKE_THICKNESS + config.BALL_SIZE / 2) + + ball_segment: Segment = Segment(ball.position.location, Point(ball.position.location.x + inc_x, ball.position.location.y + inc_y)) + + data: dict | None = None + + for segment in segments: + + segment_with_padding = segment.copy() + + segment_with_padding.start.x += inc_x + segment_with_padding.stop.x += inc_x + + segment_with_padding.start.y += inc_y + segment_with_padding.stop.y += inc_y + + impact: Point = get_impact_point(segment_with_padding, ball_segment) + if (impact is None): + continue + + # check if the impact point is in the right direction + + diff_x: float = ball.position.location.x - impact.x + if (get_sign(diff_x) == get_sign(cos) and cos != 0): + continue + + diff_y: float = (ball.position.location.y - impact.y) + if (get_sign(diff_y) != get_sign(sin) and sin != 0): + continue + + distance: float = ball.position.location.distance(impact) + + if data is None or distance < data.get("distance"): + + data = { + "distance": distance, + "impact": impact, + "segment": segment, + "inc_x": inc_x, + "inc_y": inc_y, + } + + return data + +def get_player_hitted(game: PongGame, segment_hitted: Segment) -> PongPlayer | None: + + for player in game.get_valid_players(): + if player.rail is segment_hitted: return player return None -def wall_collision(ball_angle: float, wall: Segment) -> float: - - wall_angle: float = wall.angle() - - cos: float = math.cos(wall_angle) * -1 - sin: float = math.sin(wall_angle) - - wall_angle: float = math.atan2(sin, cos) - - incident_angle: float = ball_angle - wall_angle - - reflection_angle: float = wall_angle - incident_angle - - return reflection_angle - -async def paddle_collision(game: PongGame, impact: Point, player: PongPlayer, inc_x: float, inc_y: float): +def paddle_collision(impact: Point, player: PongPlayer, inc_x: float, inc_y: float) -> Point | None: diff_x: float = player.rail.stop.x - player.rail.start.x diff_y: float = player.rail.stop.y - player.rail.start.y @@ -69,8 +210,6 @@ async def paddle_collision(game: PongGame, impact: Point, player: PongPlayer, in hit_point: Point = Point(impact.x - inc_x, impact.y - inc_y) if not paddle.is_on(hit_point): - await asyncio.sleep(0.1) # delay to create frontend animation - await SyncToAsync(game.goal)(player) return None paddle_angle: float = paddle.angle() @@ -88,39 +227,38 @@ async def paddle_collision(game: PongGame, impact: Point, player: PongPlayer, in return new_angle -async def collision(game: PongGame, impact_data: dict) -> bool: +def wall_collision(ball_angle: float, wall: Segment) -> float: + + wall_angle: float = wall.angle() + + cos: float = math.cos(wall_angle) * -1 + sin: float = math.sin(wall_angle) + + wall_angle: float = math.atan2(sin, cos) + + incident_angle: float = ball_angle - wall_angle + + reflection_angle: float = wall_angle - incident_angle + + return reflection_angle + +def collision(game: PongGame, impact_data: dict) -> int | PongPlayer: segment: Segment = impact_data.get("segment") - player_hitted = None - for player in game.players: - if (not player.is_connected()): - continue - if (player.rail is segment): - player_hitted = player - break + player_hitted: PongGame = get_player_hitted(game, segment) angle: float - print(impact_data.get("impact")) - - if (player_hitted is None): - print("wall") + if player_hitted is None: angle = wall_collision(game.ball.angle, segment) else: - print("paddle") - angle = await paddle_collision(game, impact_data.get("impact"), player_hitted, impact_data.get("inc_x"), impact_data.get("inc_y")) + angle = paddle_collision(impact_data.get("impact"), player_hitted, impact_data.get("inc_x"), impact_data.get("inc_y")) - if (angle is None): - return False - - print((game.ball.angle % (math.pi * 2)) * 180 / math.pi, (angle % (math.pi * 2)) * 180 / math.pi) - - game.ball.speed += config.BALL_SPEED_INC - game.ball.angle = angle - - return True + if angle is None: + return player_hitted + return angle async def update_ball(game: PongGame, impact_data: dict): @@ -128,150 +266,21 @@ async def update_ball(game: PongGame, impact_data: dict): time_before_impact: float = distance / game.ball.speed - print(time_before_impact) - await asyncio.sleep(time_before_impact) - - hit: bool = await collision(game, impact_data) - - print("HIT" * 10 + str(hit)) - - if hit: - game.ball.position.location = impact_data.get("impact") - SyncToAsync(game.broadcast)("update_ball", game.ball.to_dict()) -def get_sign(num: float) -> int: - if (num == 0): - return 0 - if (num > 0): - return 1 - if (num < 0): - return -1 - -def get_derive(segment: Segment) -> float: + ret: int | PongPlayer = collision(game, impact_data) - if (segment.start.x == segment.stop.x): - return None - - return (segment.stop.y - segment.start.y) / (segment.stop.x - segment.start.x) - -def get_intercept(derive: float, point: Point) -> float: - - if (derive is None): - return None - - return point.y - (point.x * derive) - -def get_constant(segment: Segment) -> float: - - return segment.start.x - -def identify(segment: Segment) -> str: - - if (segment.start.x == segment.stop.x): - return VERTICALLY - return NORMAL - -def get_interception(segment1: Segment, segment2: Segment): - - if (identify(segment1) == VERTICALLY and identify(segment2) == VERTICALLY): - return None - - # because of in matematics world y = 10 is above y = 5 and on a display it is inverted I invert the coordonate - - inverted_segment1 = Segment(Point(segment1.start.x, config.MAP_SIZE_Y - segment1.start.y), Point(segment1.stop.x, config.MAP_SIZE_Y - segment1.stop.y)) - inverted_segment2 = Segment(Point(segment2.start.x, config.MAP_SIZE_Y - segment2.start.y), Point(segment2.stop.x, config.MAP_SIZE_Y - segment2.stop.y)) - - if (identify(segment1) == NORMAL and identify(segment2) == NORMAL): - - # representation m * x + p - - m1 = get_derive(inverted_segment1) - m2 = get_derive(inverted_segment2) - - p1 = get_intercept(m1, inverted_segment1.start) - p2 = get_intercept(m2, inverted_segment2.start) - - # m1 * x + p1 = m2 * x + p2 - # m1 * x = m2 * x + p2 -p1 - # m1 * x - m2 * x = p1 - p2 - # x * (m1 - m2) = p1 - p2 - # x = (p1 - p2) / (m1 - m2) - if (m1 == m2): - return None - - # reinvert - x: float = (p1 - p2) / (m1 - m2) * (-1) - - y: float = config.MAP_SIZE_Y - (m1 * x + p1) - + if isinstance(ret, PongPlayer): + await asyncio.sleep(0.1) # create frontend animation + await SyncToAsync(game.goal)(ret) else: - - if (identify(inverted_segment1) == VERTICALLY): - constant: float = get_constant(inverted_segment1) - m: float = get_derive(inverted_segment2) - p: float = get_intercept(m, inverted_segment2.start) - else: - constant: float = get_constant(inverted_segment2) - m: float = get_derive(inverted_segment1) - p: float = get_intercept(m, inverted_segment1.start) - - x: float = constant - y: float = config.MAP_SIZE_Y - (m * x + p) + game.ball.speed += config.BALL_SPEED_INC + game.ball.position.location = impact_data.get("impact") + game.ball.position.time = time.time() * 1000 + game.ball.angle = ret - impact: Point = Point(x, y) + await SyncToAsync(game.broadcast)("update_ball", game.ball.to_dict()) - return impact - -def get_impact_data(segments: list[Segment], ball: Ball) -> dict: - - cos: float = round(math.cos(ball.angle), 6) - sin: float = round(math.sin(ball.angle), 6) - - inc_x: float = (-1) * get_sign(cos) * (config.STROKE_THICKNESS + config.BALL_SIZE / 2) - inc_y: float = get_sign(sin) * (config.STROKE_THICKNESS + config.BALL_SIZE / 2) - - point: Point = Point(ball.position.location.x + cos, ball.position.location.y - sin) - - ball_segment = Segment(ball.position.location, point) - - closest: dict = None - - for segment in segments: - - segment_with_padding = segment.copy() - - segment_with_padding.start.x += inc_x - segment_with_padding.stop.x += inc_x - - segment_with_padding.start.y += inc_y - segment_with_padding.stop.y += inc_y - - impact: Point = get_interception(segment_with_padding, ball_segment) - - if (impact is None): - continue - - diff_x: float = ball.position.location.x - impact.x - if (get_sign(diff_x) == get_sign(cos) and cos != 0): - continue - - diff_y: float = (ball.position.location.y - impact.y) - if (get_sign(diff_y) != get_sign(sin) and sin != 0): - continue - - distance: float = impact.distance(ball.position.location) - - if (closest is None or distance < closest.get("distance")): - closest = { - "inc_x": inc_x, - "inc_y": inc_y, - "impact": impact, - "segment": segment, - "distance": distance, - } - - return closest async def render_ball(game: PongGame): @@ -279,14 +288,12 @@ async def render_ball(game: PongGame): while True: - impact_data: dict = get_impact_data(segments, game.ball) + impact_data: dict = get_first_impact(segments, game.ball) await update_ball(game, impact_data) async def async_routine(game: PongGame): - #TODO DEBUG collision - ball_routine = asyncio.create_task(render_ball(game)) while True: