From 4b092b31bbf49cb6a4190410ba2cab687ae6cc5c Mon Sep 17 00:00:00 2001 From: starnakin Date: Thu, 1 Feb 2024 13:18:11 +0100 Subject: [PATCH] game: add: vector colision (Not work) --- frontend/static/js/api/game/Ball.js | 75 +++++++------- frontend/static/js/api/game/Game.js | 105 ++++++++++--------- frontend/static/js/api/game/GameConfig.js | 5 + frontend/static/js/api/game/Time.js | 4 +- frontend/static/js/views/GameView.js | 13 +-- games/config.py | 2 +- games/objects/Ball.py | 23 +++-- games/objects/Game.py | 4 +- games/objects/Point.py | 19 +++- games/objects/Segment.py | 10 +- games/objects/Vector.py | 35 +++++++ games/routine.py | 118 +++++++++++++--------- 12 files changed, 260 insertions(+), 153 deletions(-) create mode 100644 games/objects/Vector.py diff --git a/frontend/static/js/api/game/Ball.js b/frontend/static/js/api/game/Ball.js index bcd0ca1..24de54e 100644 --- a/frontend/static/js/api/game/Ball.js +++ b/frontend/static/js/api/game/Ball.js @@ -7,33 +7,37 @@ class Ball /** * * @param {Game} game - * @param {Number} position_x - * @param {Number} position_y - * @param {Number} velocity_x - * @param {Number} velocity_y + * @param {Point} position + * @param {Number} angle + * @param {Number} speed + * @param {Number} size */ - constructor(game, position_x, position_y, velocity_x, velocity_y) + constructor(game, size, position, angle, speed) { /** * @type {Game} */ this.game = game; + + /** + * @type {Point} + */ + this.position = position === undefined ? new Point() : position; + /** * @type {Number} */ - this.position_x = position_x; + this.size = size; + /** * @type {Number} */ - this.position_y = position_y; + this.angle = angle; + /** * @type {Number} */ - this.velocity_x = velocity_x; - /** - * @type {Number} - */ - this.velocity_y = velocity_y; + this.speed = speed; } /** @@ -42,40 +46,35 @@ class Ball */ draw(ctx) { - ctx.rect(this.position_x, this.position_y, this.game.config.ball_size, this.game.config.ball_size); + ctx.rect(this.position.x - this.size / 2, this.position.y - this.size / 2, this.game.config.ball_size, this.game.config.ball_size); } - render() + /** + * + * @param {CanvasRenderingContext2D} ctx + */ + render(ctx) { - /** - * @type {Number} - */ - let new_pos_x = this.position_x + this.velocity_x * this.game.time.deltaTime(); - /** - * @type {Number} - */ - let new_pos_y = position_y + this.velocity_y * this.game.time.deltaTime(); + let distance = this.speed * (this.game.time.deltaTime() / 1000) - if (this._collision(this.position_x, this.position_y, new_pos_x, new_pos_y)) - { - this.velocity_x = -this.velocity_x; - this.velocity_y = -this.velocity_y; - new_pos_x = this.position_x + this.velocity_x * this.game.time.deltaTime(); - new_pos_y = this.position_y + this.velocity_y * this.game.time.deltaTime(); - } - this.position_x = new_pos_x - this.position_y = new_pos_y + let angle_radian = this.angle * Math.PI / 180 - this.velocity_x = this.velocity_x + this.game.config.ball_speed_inc; - this.velocity_y = this.velocity_y + this.game.config.ball_speed_inc; + console.log(angle_radian) + + this.position.x = this.position.x + distance * Math.cos(angle_radian); + this.position.y = this.position.y + distance * Math.sin(angle_radian); + + this.draw(ctx); } - update (position_x, position_y, velocity_x, velocity_y) + from_json (data) { - this.position_x = position_x; - this.position_y = position_y; - this.velocity_x = velocity_x; - this.velocity_y = velocity_y; + this.position = this.position.from_json(data.position); + this.size = data.size; + this.angle = data.angle; + this.speed = data.speed; + + return this } } diff --git a/frontend/static/js/api/game/Game.js b/frontend/static/js/api/game/Game.js index 08253b0..028e547 100644 --- a/frontend/static/js/api/game/Game.js +++ b/frontend/static/js/api/game/Game.js @@ -4,6 +4,7 @@ import { GameConfig } from "./GameConfig.js" import { Player } from "./Player.js"; import { Time } from "./Time.js"; import { Wall } from "./Wall.js"; +import { Client } from "../client.js"; class Game { @@ -21,7 +22,7 @@ class Game /** * - * @returns {Number} + * @returns {Promise} */ async init() { @@ -32,23 +33,52 @@ class Game let response_data = await response.json(); + /** + * @type {[Number]} + */ this.players_id = response_data.players_id; + + /** + * @type {String} + */ this.state = response_data.state; + + /** + * @type {Boolean} + */ this.started = response_data.started; + + /** + * @type {Boolean} + */ this.finished = response_data.finished; + + /** + * @type {Number} + */ this.winner_id = this.finished ? response_data.winner_id : undefined; if (this.finished === true) return 0; + /** + * @type {GameConfig} + */ this.config = new GameConfig(this.client); + let ret = await this.config.init(); if (ret !== 0) return ret; + /** + * @type {Time} + */ this.time = new Time(); - this.last_pos = null + + /** + * @type {Boolean} + */ this._inited = false; return 0; @@ -72,11 +102,11 @@ class Game * * @param {CanvasRenderingContext2D} ctx */ - draw(ctx) + render(ctx) { ctx.clearRect(0, 0, this.config.size_x, this.config.size_y); this.draw_sides(ctx); - this.ball.draw(ctx); + this.ball.render(ctx); } _send(data) @@ -90,50 +120,25 @@ class Game } } + /** + * @param {Number} position + * @param {Number} time + */ _send_paddle_position(position, time) { - if (this.last_pos !== null && this.last_pos.time >= time) - return; - - this.last_pos = {"time": time, "position": position}; - - this._send({"detail": "update_my_paddle_pos", ...this.last_pos}); - } - - _receive_player_join(player_data) - { - console.log(player_data) - let index = this.players.indexOf((player) => player.id === player_data.user_id); - - this.players[index].is_connected = true; - } - - _receive_player_leave(player_data) - { - let index = this.players.indexOf((player) => player.id === player_data.user_id); - - this.players[index].is_connected = false; - } - - _receive_update_ball(data) - { - this.ball.position_x = data.position_x - this.ball.position_y = data.position_y - this.ball.position_x = data.position_x - this.ball.position_x = data.position_x + this._send({"detail": "update_my_paddle_pos", ...{"time": time, "position": position}}); } _receive_update_paddle(data) { let player = this.players.find((player) => player.id === data.user_id); - if (player === null) - { - this._receive_player_join(data); - return; - } - player.is_connected = data.is_connected; - player.update_pos(data.position.position, data.position.time); + player.from_json(data); + } + + _receive_ball(data) + { + this.ball.from_json(data); } _receive(data) @@ -141,26 +146,30 @@ class Game if (data.detail === "update_paddle") this._receive_update_paddle(data); else if (data.detail === "update_ball") - this._receive_update_ball(data); + this._receive_ball(data) else if (data.detail === "init_game") - this._init_game(data) - else if (data.detail === "player_join") - this._receive_player_join(data) - else if (data.detail === "player_leave") - this._receive_player_leave(data) + this._init_game(data); } _init_game(data) { - const ball_data = data.ball; - this.ball = new Ball(this, ball_data.position_x, ball_data.position_y, ball_data.velocity_x, ball_data.velocity_y); + /** + * @type {Ball} + */ + this.ball = (new Ball(this)).from_json(data.ball) + /** + * @type {[Wall]} + */ this.walls = []; const walls_data = data.walls; walls_data.forEach((wall_data) => { this.walls.push(new Wall().from_json(wall_data)); }); + /** + * @type {[Player]} + */ this.players = [] const players_data = data.players; players_data.forEach((player_data) => { diff --git a/frontend/static/js/api/game/GameConfig.js b/frontend/static/js/api/game/GameConfig.js index dab00ad..4b2ae61 100644 --- a/frontend/static/js/api/game/GameConfig.js +++ b/frontend/static/js/api/game/GameConfig.js @@ -24,14 +24,17 @@ class GameConfig * @type {Number} */ this.size_x = response_data.MAP_SIZE_X; + /** * @type {Number} */ this.size_y = response_data.MAP_SIZE_Y; + /** * @type {Number} */ this.center_x = this.size_x / 2; + /** * @type {Number} */ @@ -63,10 +66,12 @@ class GameConfig * @type {Number} */ this.ball_size = response_data.BALL_SIZE; + /** * @type {Number} */ this.ball_spawn_x = this.center_x; + /** * @type {Number} */ diff --git a/frontend/static/js/api/game/Time.js b/frontend/static/js/api/game/Time.js index e8885b9..7edb1d7 100644 --- a/frontend/static/js/api/game/Time.js +++ b/frontend/static/js/api/game/Time.js @@ -17,7 +17,9 @@ class Time deltaTime() { - return (this._current_frame - this._last_frame) !== NaN ? this._current_frame - this._last_frame : 0; + if (this._last_frame === undefined) + return 0; + return (this._current_frame - this._last_frame); } deltaTimeSecond() diff --git a/frontend/static/js/views/GameView.js b/frontend/static/js/views/GameView.js index 2b8e3c7..7242add 100644 --- a/frontend/static/js/views/GameView.js +++ b/frontend/static/js/views/GameView.js @@ -26,7 +26,7 @@ export default class extends AbstractView this.keys_pressed.push(event.key); } - draw() + render_game() { const canva = document.getElementById('canva'); @@ -40,22 +40,23 @@ export default class extends AbstractView ctx.beginPath(); - this.game.draw(ctx); + this.game.render(ctx); ctx.strokeStyle = "#000000"; - ctx.lineWidth = 10; + ctx.lineWidth = 1; ctx.stroke(); } - render_game() + render() { let loop_id = setInterval(() => { if (this.game === undefined) clearInterval(loop_id); if (this.my_player) this.my_player.update_paddle(this.keys_pressed); - this.draw(); + this.render_game(); this.game?.time.new_frame(); + //clearInterval(loop_id); // 1 sec fps }, 1000 / 60); } @@ -99,7 +100,7 @@ export default class extends AbstractView this.register_key() - this.render_game(); + this.render(); } async update_game_state() diff --git a/games/config.py b/games/config.py index 40b72ef..11ccfba 100644 --- a/games/config.py +++ b/games/config.py @@ -12,7 +12,7 @@ MAP_CENTER_Y = MAP_SIZE_Y / 2 WALL_RATIO = 1 BALL_SPEED_INC = 1 -BALL_SPEED_START = 1 +BALL_SPEED_START = 170 BALL_SIZE = 4 BALL_SPAWN_POS_X = MAP_SIZE_X / 2 BALL_SPAWN_POS_Y = MAP_SIZE_Y / 2 diff --git a/games/objects/Ball.py b/games/objects/Ball.py index 4d2dbcb..77f4896 100644 --- a/games/objects/Ball.py +++ b/games/objects/Ball.py @@ -1,23 +1,28 @@ +from __future__ import annotations + from .. import config +from .Point import Point + +import math class Ball: def __init__(self) -> None: - self.postion_x: float = config.BALL_SPAWN_POS_X - self.postion_y: float = config.BALL_SPAWN_POS_Y - self.velocity_x: float = config.BALL_SPEED_START - self.velocity_y: float = config.BALL_SPEED_START self.size: float = config.BALL_SIZE + self.position: Point = Point(config.BALL_SPAWN_POS_X + self.size / 2, config.BALL_SPAWN_POS_Y + self.size / 2) + self.angle: float = math.pi * 0 + self.speed: float = config.BALL_SPEED_START def to_dict(self): data: dict = { "size": self.size, - "position_x": self.postion_x, - "position_y": self.postion_y, - "velocity_x": self.velocity_x, - "velocity_y": self.velocity_y, + "speed": self.speed, + "position": self.position.to_dict(), + "angle": self.angle, } return data - \ No newline at end of file + + def __str__(self) -> str: + return f"Ball(size: {self.size}, speed: {self.speed}, director_coefficient: {self.director_coefficient}, ordinate_at_origin: {self.ordinate_at_origin}, position: {self.position})" \ No newline at end of file diff --git a/games/objects/Game.py b/games/objects/Game.py index 664dcf5..3550f48 100644 --- a/games/objects/Game.py +++ b/games/objects/Game.py @@ -44,8 +44,8 @@ class Game(AbstractRoom): angle: float = (i * 2 * math.pi / nb_sides) + (math.pi * 3 / nb_sides) - x: float = config.MAP_CENTER_X + radius * math.cos(angle) - y: float = config.MAP_CENTER_Y + radius * math.sin(angle) + x: float = round(config.MAP_CENTER_X + radius * math.cos(angle)) + y: float = round(config.MAP_CENTER_Y + radius * math.sin(angle)) polygon.append(Point(x, y)) diff --git a/games/objects/Point.py b/games/objects/Point.py index 3d01a04..08a10da 100644 --- a/games/objects/Point.py +++ b/games/objects/Point.py @@ -1,11 +1,28 @@ +from __future__ import annotations - +from math import dist class Point: def __init__(self, x: float, y: float) -> None: self.x = x self.y = y + + def __str__(self) -> str: + return f"Point(x: {self.x}, y: {self.y})" + + def __repr__(self) -> str: + return f"Point(x: {self.x}, y: {self.x})" + + def __eq__(self, __value: object) -> bool: + return (self.x == __value.x and self.y == __value.y) + + def distance(self, point: Point): + return dist((point.x, point.y), (self.x, self.y)) + + def copy(self): + return Point(self.x, self.y) + def to_dict(self): data: dict[str: float] = { diff --git a/games/objects/Segment.py b/games/objects/Segment.py index 8de44c5..49e3d88 100644 --- a/games/objects/Segment.py +++ b/games/objects/Segment.py @@ -1,12 +1,20 @@ from .Point import Point +import math class Segment: def __init__(self, start: Point, stop: Point) -> None: self.start: Point = start self.stop: Point = stop - + self.length: float = math.dist((self.start.x, self.start.y), (self.stop.x, self.stop.y)) + + def __repr__(self) -> str: + return f"Segment(start: {self.start}, stop: {self.stop})" + + def __str__(self) -> str: + return f"Segment(start: {self.start}, stop: {self.stop})" + def to_dict(self): data: dict[str: dict] = { diff --git a/games/objects/Vector.py b/games/objects/Vector.py new file mode 100644 index 0000000..f62cbbd --- /dev/null +++ b/games/objects/Vector.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import math +from .Point import Point + +class Vector: + + def __init__(self, x: float, y: float) -> None: + self.norm: float = math.dist((0, 0), (x, y)) + self.x: float = x + self.y: float = y + + def __truediv__(self, denominator: float): + return Vector(self.x / denominator, self.y / denominator) + + def angle(self, vector: Vector): + scalar_product: float = self.scalar(vector) + if (scalar_product is None): + return None + cos: float = scalar_product / (vector.norm * self.norm) + + angle: float = math.acos(cos) + + return angle + + def scalar(self, vector: Vector): + return self.x * vector.x + vector.y * self.y + + def __str__(self) -> str: + return f"Vector(x: {self.x}, y: {self.y}, norme: {self.norm})" + + def __eq__(self, __value: Vector) -> bool: + return (self.x == __value.x and + self.x == __value.x and + self.norm == __value.norm) \ No newline at end of file diff --git a/games/routine.py b/games/routine.py index 6d52fe0..6dd909a 100644 --- a/games/routine.py +++ b/games/routine.py @@ -9,73 +9,99 @@ if TYPE_CHECKING: from .objects.Ball import Ball from .objects.Point import Point +from .objects.Vector import Point from .objects.Segment import Segment +from .objects.Vector import Vector from . import config import math +import asyncio + +from asgiref.sync import SyncToAsync + from time import sleep -#see the video to understand the algorithme -#https://www.youtube.com/watch?v=KOYoMYWUTEo -def determine_director_coefficient(segment: Segment): - return ((segment.start.y - segment.stop.y) / (segment.start.x - segment.stop.x)) +def get_sign(num: float): + return 1 if num >= 0 else -1 -def determine_ordinate_at_origin(point: Point, director_cofficient: float): - return point.y - point.x * director_cofficient +def get_impact_point(segments: list[Segment], ball: Ball): -def determine_intersection(director_coefficient1: float, ordinate_at_origin1: float, director_coefficient2: float, ordinate_at_origin2: float): - if (director_coefficient1 == director_coefficient2): - return None - return (ordinate_at_origin1 + ordinate_at_origin2) / (director_coefficient1 + director_coefficient2) + angle_radian: float = ball.angle * math.pi / 180 -def determine_intersections(ball: Ball, segments: list[Segment]): - - intersections: list[Point] = [] + direction_vector: Vector = Vector(math.cos(angle_radian), math.sin(angle_radian)) + + x: float = ball.position.x + if (direction_vector.x > 0): + x = x + ball.size / 2 + elif (direction_vector.x < 0): + x = x - ball.size / 2 + y: float = ball.position.y + if (direction_vector.y > 0): + y = y + ball.size / 2 + elif (direction_vector.y < 0): + y = y - ball.size / 2 + + position: Point = Point(x, y) + for segment in segments: - # form m * x + p - m: float = determine_director_coefficient(segment) - p: float = determine_ordinate_at_origin(segment.start, m) + segment_vector: Vector = Vector(segment.start.x - segment.stop.x, segment.start.y - segment.stop.y) + segment_vector_unit = segment_vector / segment_vector.norm - x: float = determine_intersection(m, p, ball.velocity_y, 0) + scalar: float = segment_vector_unit.scalar(direction_vector) - if (x is None): - continue - - y: float = m * x + p - - intersections.append(Point(x, y)) + if (scalar < 0.01): + continue - return intersections + print(segment_vector, segment_vector_unit, direction_vector) + + distance: float = scalar * segment_vector.norm / 2 + + impact_x: float = position.x + distance * direction_vector.x + + impact_y: float = position.y + distance * direction_vector.y + + impact: Point = Point(impact_x, impact_y) + + print("impact", impact) + + return impact + +async def update_ball(game: Game, impact: Point): + + distance: float = impact.distance(game.ball.position) - game.ball.size / 2 + + time_before_impact: float = distance / game.ball.speed + + await asyncio.sleep(time_before_impact) + + game.ball.angle = game.ball.angle + 180 + + game.ball.position = impact + + await SyncToAsync(game.broadcast)("update_ball", game.ball.to_dict()) + +async def render(game: Game): + + while True: + + segments: list[Segment] = [player.rail for player in game.players] + [wall.rail for wall in game.walls] + + impact = get_impact_point(segments, game.ball) + + await update_ball(game, impact) -def determine_distance_between_ball_and_wall(ball: Ball, segments: list[Segment]): - - intersections: list[Point] = determine_intersections(ball, segments) - - distances = list(map(math.dist, intersections)) - - return min(distances) - -def render(ball: Ball, game: Game): - - segments: list[Segment] = [player.rail for player in game.players] - - print(determine_distance_between_ball_and_wall(ball)) def routine(game: Game): - + + asyncio.run(render(game)) + while True: for player in game._updated_players: game.broadcast("update_paddle", player.to_dict(), [player]) - + game._updated_players.clear() - - if (game.started): - game.ball.postion_x = game.ball.postion_x + game.ball.velocity_x - game.ball.postion_y = game.ball.postion_y + game.ball.velocity_y - - game.broadcast("update_ball", game.ball.to_dict()) - + sleep(1 / config.SERVER_TPS) \ No newline at end of file