game: online: add collision but weird lag

This commit is contained in:
starnakin 2024-05-14 11:21:51 +02:00
parent 6fe4c9fe3b
commit 7b841f6f9e
3 changed files with 196 additions and 190 deletions

View File

@ -21,7 +21,7 @@ class Ball:
def reset(self) -> None: def reset(self) -> None:
self.size = config.BALL_SIZE 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.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 self.speed = config.BALL_SPEED_START
def to_dict(self) -> dict: def to_dict(self) -> dict:

View File

@ -35,7 +35,7 @@ class PongGame(AGame):
] ]
self.players = [PongPlayer(self, players[0], None, Segment(corners[1].copy(), corners[2].copy())), 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]), self.walls = [Wall(corners[0], corners[1]),
Wall(corners[2], corners[3])] Wall(corners[2], corners[3])]
@ -74,7 +74,6 @@ class PongGame(AGame):
return return
self.ball.reset() self.ball.reset()
self.broadcast("update_ball", self.ball.to_dict())
def get_valid_players(self) -> list[PongPlayer]: def get_valid_players(self) -> list[PongPlayer]:
return [player for player in self.players if player.is_connected and not player.is_eliminated] return [player for player in self.players if player.is_connected and not player.is_eliminated]

View File

@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
from .objects.pong.PongGame import PongPlayer
from .objects.pong.Segment import Segment from .objects.pong.Segment import Segment
from .objects.pong.Point import Point from .objects.pong.Point import Point
from .objects.pong.Ball import Ball from .objects.pong.Ball import Ball
from .objects.pong.PongPlayer import PongPlayer
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -12,38 +12,179 @@ if TYPE_CHECKING:
from . import config from . import config
import asyncio
import math import math
import time
from asgiref.sync import SyncToAsync from asgiref.sync import SyncToAsync
import asyncio
VERTICALLY = 1 VERTICALLY = 1
NORMAL = 2 NORMAL = 2
def get_player_hitted(players: list[PongPlayer], segment: Segment) -> PongPlayer | None: def identify(segment: Segment) -> str:
for player in players: if (segment.start.x == segment.stop.x):
if (player.rail is segment): 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 player
return None return None
def wall_collision(ball_angle: float, wall: Segment) -> float: def paddle_collision(impact: Point, player: PongPlayer, inc_x: float, inc_y: float) -> Point | None:
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):
diff_x: float = player.rail.stop.x - player.rail.start.x diff_x: float = player.rail.stop.x - player.rail.start.x
diff_y: float = player.rail.stop.y - player.rail.start.y 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) hit_point: Point = Point(impact.x - inc_x, impact.y - inc_y)
if not paddle.is_on(hit_point): if not paddle.is_on(hit_point):
await asyncio.sleep(0.1) # delay to create frontend animation
await SyncToAsync(game.goal)(player)
return None return None
paddle_angle: float = paddle.angle() paddle_angle: float = paddle.angle()
@ -88,39 +227,38 @@ async def paddle_collision(game: PongGame, impact: Point, player: PongPlayer, in
return new_angle 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") segment: Segment = impact_data.get("segment")
player_hitted = None player_hitted: PongGame = get_player_hitted(game, segment)
for player in game.players:
if (not player.is_connected()):
continue
if (player.rail is segment):
player_hitted = player
break
angle: float angle: float
print(impact_data.get("impact")) if player_hitted is None:
if (player_hitted is None):
print("wall")
angle = wall_collision(game.ball.angle, segment) angle = wall_collision(game.ball.angle, segment)
else: else:
print("paddle") angle = paddle_collision(impact_data.get("impact"), player_hitted, impact_data.get("inc_x"), impact_data.get("inc_y"))
angle = await paddle_collision(game, impact_data.get("impact"), player_hitted, impact_data.get("inc_x"), impact_data.get("inc_y"))
if (angle is None): if angle is None:
return False return player_hitted
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
return angle
async def update_ball(game: PongGame, impact_data: dict): 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 time_before_impact: float = distance / game.ball.speed
print(time_before_impact)
await asyncio.sleep(time_before_impact) await asyncio.sleep(time_before_impact)
hit: bool = await collision(game, impact_data) ret: int | PongPlayer = collision(game, impact_data)
print("HIT" * 10 + str(hit)) if isinstance(ret, PongPlayer):
await asyncio.sleep(0.1) # create frontend animation
if hit: await SyncToAsync(game.goal)(ret)
else:
game.ball.speed += config.BALL_SPEED_INC
game.ball.position.location = impact_data.get("impact") game.ball.position.location = impact_data.get("impact")
SyncToAsync(game.broadcast)("update_ball", game.ball.to_dict()) game.ball.position.time = time.time() * 1000
game.ball.angle = ret
def get_sign(num: float) -> int: await SyncToAsync(game.broadcast)("update_ball", game.ball.to_dict())
if (num == 0):
return 0
if (num > 0):
return 1
if (num < 0):
return -1
def get_derive(segment: Segment) -> float:
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)
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)
impact: Point = Point(x, y)
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): async def render_ball(game: PongGame):
@ -279,14 +288,12 @@ async def render_ball(game: PongGame):
while True: 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) await update_ball(game, impact_data)
async def async_routine(game: PongGame): async def async_routine(game: PongGame):
#TODO DEBUG collision
ball_routine = asyncio.create_task(render_ball(game)) ball_routine = asyncio.create_task(render_ball(game))
while True: while True: