登录
ZH
games/webrtc-demo / src / Game / Duel.php · commit 87ad512
<?phpnamespace App\Game;/** * The rules of the duel, run by the host only (the authority): two players catch stars for a while, the one with more wins. * Player 0 = the host, 1 = the other one. Positions are the top-left corners of their squares. */final class Duel{    public const W = 320;    public const H = 180;    public const TOP = 18;    public const SIZE = 10;    public const STAR_RADIUS = 4;    public const STARS = 3;    public const SECONDS = 40.0;    public const SPEED = 110.0;    /** @var list<array{0: float, 1: float}> */    public array $stars = [];    /** @var array{0: int, 1: int} */    public array $scores = [0, 0];    public float $left = self::SECONDS;    public bool $over = false;    public function __construct(int $seed = 0)    {        $this->reset($seed);    }    public function reset(int $seed = 0): void    {        mt_srand($seed !== 0 ? $seed : random_int(1, 1 << 30));        $this->scores = [0, 0];        $this->left = self::SECONDS;        $this->over = false;        $this->stars = [];        for ($i = 0; $i < self::STARS; $i++) {            $this->stars[] = $this->newStar();        }    }    /** @param array{0: float, 1: float} $host @param array{0: float, 1: float} $guest */    public function step(float $dt, array $host, array $guest): void    {        if ($this->over) {            return;        }        $this->left = max(0.0, $this->left - $dt);        foreach ($this->stars as $i => $star) {            foreach ([$host, $guest] as $who => $pos) {                if (self::touches($pos, $star)) {                    $this->scores[$who]++;                    $this->stars[$i] = $this->newStar();                    break;                }            }        }        $this->over = $this->left <= 0;    }    /** 0 = the host won, 1 = the other one, -1 = a draw (or not over). */    public function winner(): int    {        if (!$this->over || $this->scores[0] === $this->scores[1]) {            return -1;        }        return $this->scores[0] > $this->scores[1] ? 0 : 1;    }    /** What the host sends to the other player ~30 times a second. @param array{0: float, 1: float} $host @param array{0: float, 1: float} $guest */    public function snapshot(array $host, array $guest): array    {        $r = fn (array $p) => [round($p[0], 1), round($p[1], 1)];        return ['t' => 's', 'h' => $r($host), 'g' => $r($guest), 's' => array_map($r, $this->stars), 'sc' => $this->scores,            'l' => round($this->left, 1), 'o' => $this->over ? 1 : 0];    }    /** @param array{0: float, 1: float} $pos @param array{0: float, 1: float} $star */    public static function touches(array $pos, array $star): bool    {        $nx = max($pos[0], min($star[0], $pos[0] + self::SIZE));        $ny = max($pos[1], min($star[1], $pos[1] + self::SIZE));        return ($star[0] - $nx) ** 2 + ($star[1] - $ny) ** 2 <= (self::STAR_RADIUS + 1) ** 2;    }    /** A position moved by a direction (-1..1 each) for $dt seconds, kept in the arena. @param array{0: float, 1: float} $pos */    public static function move(array $pos, float $dx, float $dy, float $dt): array    {        $len = sqrt($dx * $dx + $dy * $dy);        if ($len > 1) {            $dx /= $len;            $dy /= $len;        }        return [max(0.0, min(self::W - self::SIZE, $pos[0] + $dx * self::SPEED * $dt)),            max((float)self::TOP, min(self::H - self::SIZE, $pos[1] + $dy * self::SPEED * $dt))];    }    /** The direction to the nearest star (the bot, the demo). @param array{0: float, 1: float} $pos @param list<array{0: float, 1: float}> $stars */    public static function towardNearest(array $pos, array $stars): array    {        $best = null;        $cx = $pos[0] + self::SIZE / 2;        $cy = $pos[1] + self::SIZE / 2;        foreach ($stars as $s) {            $d = ($s[0] - $cx) ** 2 + ($s[1] - $cy) ** 2;            if ($best === null || $d < $best[0]) {                $best = [$d, $s[0] - $cx, $s[1] - $cy];            }        }        if ($best === null || $best[0] < 1) {            return [0.0, 0.0];        }        $len = sqrt($best[0]);        return [$best[1] / $len, $best[2] / $len];    }    /** @return array{0: float, 1: float} */    private function newStar(): array    {        $m = 12;        return [(float)mt_rand($m, self::W - $m), (float)mt_rand(self::TOP + $m, self::H - $m)];    }}