from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from .objects.Spectator import Spectator from .objects.Player import Player from .objects.Game import Game 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 from . import config import math import asyncio from asgiref.sync import SyncToAsync from time import sleep VERTICALLY = 1 NORMAL = 2 def get_sign(num: float) -> int: if (num == 0): return 0 if (num > 0): return 1 if (num < 0): return -1 def simplify_angle(angle: float): return (angle + 2 * math.pi) % (2 * math.pi) 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 tmp1 = Segment(Point(segment1.start.x, config.MAP_SIZE_Y - segment1.start.y), Point(segment1.stop.x, config.MAP_SIZE_Y - segment1.stop.y)) tmp2 = 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(tmp1) m2 = get_derive(tmp2) p1 = get_intercept(m1, tmp1.start) p2 = get_intercept(m2, tmp2.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(tmp1) == VERTICALLY): constant: float = get_constant(tmp1) m: float = get_derive(tmp2) p: float = get_intercept(m, tmp2.start) else: constant: float = get_constant(tmp2) m: float = get_derive(tmp1) p: float = get_intercept(m, tmp1.start) x: float = constant y: float = config.MAP_SIZE_Y - (m * x + p) impact: Point = Point(x, y) return impact def get_impact_point(segments: list[Segment], ball: Ball): cos: float = round(math.cos(ball.angle), 6) sin: float = round(math.sin(ball.angle), 6) point: Point = Point(ball.position.x + cos, ball.position.y - sin) ball_segment = Segment(ball.position, point) closest: Point = None for segment in segments: impact: Point = get_interception(segment, ball_segment) if (impact is None): continue diff_x: float = ball.position.x - impact.x if (get_sign(diff_x) == get_sign(cos) and cos != 0): continue diff_y: float = (ball.position.y - impact.y) if (get_sign(diff_y) != get_sign(sin) and sin != 0): continue impact.x += (ball.size / 2) * get_sign(cos) * (-1) impact.y += (ball.size / 2) * get_sign(sin) if (closest is None or impact.distance(ball.position) < closest.distance(ball.position)): closest = impact return closest async def update_ball(game: Game, impact: Point): distance: float = impact.distance(game.ball.position) time_before_impact: float = distance / game.ball.speed await asyncio.sleep(time_before_impact) game.ball.angle = game.ball.angle + math.pi 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 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() sleep(1 / config.SERVER_TPS)