pong pretty / local rdy in back, need hooking in front
This commit is contained in:
parent
c656839450
commit
0727067b27
4 changed files with 150 additions and 77 deletions
|
|
@ -1,35 +1,35 @@
|
||||||
<div class="displaybox">
|
<div class="displaybox">
|
||||||
<div id="mainbox" class="mainboxDisplay">
|
<div id="mainbox" class="mainboxDisplay">
|
||||||
<button id="b-whoami" class="btn-style absolute top-4 left-6">Who am i</button>
|
<button id="QueueBtn" class="btn-style absolute top-4 left-6">Queue Up</button>
|
||||||
<br>
|
|
||||||
<button id="b-joinQueu" class="btn-style absolute top-16 left-6">Queue Up</button>
|
|
||||||
<br>
|
<br>
|
||||||
<h1 class="text-3xl font-bold text-gray-800">
|
<h1 class="text-3xl font-bold text-gray-800">
|
||||||
Pong Box<span id="t-username"></span>
|
Pong Box<span id="t-username"></span>
|
||||||
</h1><br>
|
</h1><br>
|
||||||
|
|
||||||
<label class="inline-flex items-center gap-3 cursor-pointer select-none">
|
<!-- on/off switch for local / online play : idea was re-worked, we probably wont use that -->
|
||||||
<!-- Checkbox -->
|
<!-- <label class="inline-flex items-center gap-3 cursor-pointer select-none"> -->
|
||||||
<input id="modeToggle" type="checkbox" class="sr-only"/>
|
<!-- <input id="modeToggle" type="checkbox" class="sr-only"/> -->
|
||||||
<!-- Switch -->
|
<!-- <div id="toggleTrack" class="relative w-14 h-7 rounded-full bg-gray-300 transition-colors duration-300"> -->
|
||||||
<div id="toggleTrack" class="relative w-14 h-7 rounded-full bg-gray-300 transition-colors duration-300">
|
<!-- <span id="toggleKnob" class="absolute top-0.5 left-0.5 w-6 h-6 bg-white rounded-full transition-transform duration-300"></span> -->
|
||||||
<span id="toggleKnob" class="absolute top-0.5 left-0.5 w-6 h-6 bg-white rounded-full transition-transform duration-300"></span>
|
<!-- </div> -->
|
||||||
</div>
|
<!-- <span id="toggleLabel" class="text-sm font-medium text-gray-700"> Local </span> -->
|
||||||
<!-- Label -->
|
<!-- </label> -->
|
||||||
<span id="toggleLabel" class="text-sm font-medium text-gray-700"> Local </span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<!-- Horizontal Message Box -->
|
<!-- Horizontal Message Box -->
|
||||||
<div id="system-box" class="system-info">System: connecting ... </div>
|
<div id="system-box" class="system-info">System: connecting ... </div>
|
||||||
<h1 id="score-board" class="text-p10 font-bold text-gray-800">0:0</h1>
|
<div id="score-box" class="grid grid-cols-[1fr_auto_1fr] items-center">
|
||||||
|
<h1 id="player-left"></h1>
|
||||||
|
<h1 id="score-board" class="justify-self-center text-p10 font-bold text-gray-800">0:0</h1>
|
||||||
|
<h1 id="player-right"></h1>
|
||||||
|
</div>
|
||||||
<!-- Pong Box -->
|
<!-- Pong Box -->
|
||||||
<div class="flex justify-center mt-2">
|
<div class="flex justify-center mt-2">
|
||||||
<div id="pongspace" class="flex flex-col">
|
<div id="pongspace" class="flex flex-col">
|
||||||
<div id="pongbox" class="pongbox-style">
|
<div id="pongbox" class="pongbox-style">
|
||||||
<div class="pong-field">
|
<div class="pong-field">
|
||||||
<div id="batleft" class="pong-batleft bg-amber-400 top-0"></div>
|
<div id="batleft" class="pong-batleft top-0"></div>
|
||||||
<div class="pong-center-line"></div>
|
<div class="pong-center-line"></div>
|
||||||
<div id="batright" class="pong-batright bg-amber-400 top-0"></div>
|
<div id="batright" class="pong-batright top-0"></div>
|
||||||
<div id="ball" class="rounded-full border-4 bg-white border-gray-400"></div>
|
<div id="ball" class="rounded-full border-4 bg-white border-gray-400"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { addRoute, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
|
import { addRoute, navigateTo, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
|
||||||
import authHtml from './pong.html?raw';
|
import authHtml from './pong.html?raw';
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import type { CSocket, GameMove, GameUpdate } from "./socket";
|
import type { CSocket, GameMove, GameUpdate } from "./socket";
|
||||||
import { showError, showInfo } from "@app/toast";
|
import { showError, showInfo } from "@app/toast";
|
||||||
|
import { getUser } from "@app/auth";
|
||||||
// TODO: local game (2player -> server -> 2player : current setup)
|
import { isNullish } from "@app/utils";
|
||||||
// TODO: tournament via remote (dedicated queu? idk)
|
|
||||||
//
|
|
||||||
|
|
||||||
// get the name of the machine used to connect
|
// get the name of the machine used to connect
|
||||||
declare module 'ft_state' {
|
declare module 'ft_state' {
|
||||||
|
|
@ -15,8 +13,15 @@ declare module 'ft_state' {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GameRdyDown = "Ready Up?"
|
||||||
|
// GameRdyUp = "Ready down?"
|
||||||
|
enum QueueState {
|
||||||
|
InQueu = "In Queue",
|
||||||
|
InGame = "In Game",
|
||||||
|
Iddle = "Queue Up",
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener("ft:pageChange", (newUrl) => {
|
document.addEventListener("ft:pageChange", (newUrl) => {
|
||||||
// we are still on a pong page => keep the socket around !
|
|
||||||
if (newUrl.detail.startsWith('/app/pong') || newUrl.detail.startsWith('/pong')) return;
|
if (newUrl.detail.startsWith('/app/pong') || newUrl.detail.startsWith('/pong')) return;
|
||||||
if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
|
if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
|
||||||
window.__state.pongSock = undefined;
|
window.__state.pongSock = undefined;
|
||||||
|
|
@ -30,73 +35,83 @@ export function getSocket(): CSocket {
|
||||||
|
|
||||||
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
|
function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
|
||||||
setTitle('Pong Game Page');
|
setTitle('Pong Game Page');
|
||||||
|
// MAYBE: "queue up" btn : adds timer to page for duration of queue
|
||||||
|
// TODO: "local play" btn : emit "local new game" evt to server; play game on single computer (maybe need to change keys-handling logic)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
html: authHtml, postInsert: async (app) => {
|
html: authHtml, postInsert: async (app) => {
|
||||||
const checkbox = document.getElementById("modeToggle") as HTMLInputElement;
|
const DEFAULT_COLOR = "white";
|
||||||
const label = document.getElementById("toggleLabel") as HTMLSpanElement;
|
const SELF_COLOR = "red";
|
||||||
const track = document.getElementById("toggleTrack") as HTMLDivElement;
|
|
||||||
const knob = document.getElementById("toggleKnob") as HTMLSpanElement;
|
|
||||||
|
|
||||||
checkbox.addEventListener("change", () => { // web vs local
|
|
||||||
if (checkbox.checked) {
|
|
||||||
label.textContent = "Web";
|
|
||||||
track.classList.replace("bg-gray-300", "bg-blue-600");
|
|
||||||
knob.classList.add("translate-x-7");
|
|
||||||
} else {
|
|
||||||
label.textContent = "Local";
|
|
||||||
track.classList.replace("bg-blue-600", "bg-gray-300");
|
|
||||||
knob.classList.remove("translate-x-7");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const user = getUser();
|
||||||
|
let currentGame: GameUpdate | null = null;
|
||||||
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
|
const batLeft = document.querySelector<HTMLDivElement>("#batleft");
|
||||||
const batRight = document.querySelector<HTMLDivElement>("#batright");
|
const batRight = document.querySelector<HTMLDivElement>("#batright");
|
||||||
const ball = document.querySelector<HTMLDivElement>("#ball");
|
const ball = document.querySelector<HTMLDivElement>("#ball");
|
||||||
const score = document.querySelector<HTMLDivElement>("#score-board");
|
const score = document.querySelector<HTMLDivElement>("#score-board");
|
||||||
if (!batLeft || !batRight || !ball || !score)
|
const playerL = document.querySelector<HTMLDivElement>('#player-left');
|
||||||
return showError('fatal error');
|
const playerR = document.querySelector<HTMLDivElement>('#player-right');
|
||||||
|
const queueBtn = document.querySelector<HTMLButtonElement>("#QueueBtn");
|
||||||
|
|
||||||
let socket = getSocket();
|
let socket = getSocket();
|
||||||
|
|
||||||
let currentGame: GameUpdate | null = null;
|
if (isNullish(user)) { // if no user (no loggin / other) : GTFO
|
||||||
|
navigateTo("/app");
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if (!batLeft || !batRight || !ball || !score || !queueBtn || !playerL || !playerR) // sanity check
|
||||||
|
return showError('fatal error');
|
||||||
|
|
||||||
|
// ---
|
||||||
// keys handler
|
// keys handler
|
||||||
|
// ---
|
||||||
const keys: Record<string, boolean> = {};
|
const keys: Record<string, boolean> = {};
|
||||||
|
|
||||||
document.addEventListener("keydown", (e) => {
|
document.addEventListener("keydown", (e) => {keys[e.key.toLowerCase()] = true;});
|
||||||
keys[e.key.toLowerCase()] = true;
|
document.addEventListener("keyup", (e) => {keys[e.key.toLowerCase()] = false;});
|
||||||
});
|
|
||||||
document.addEventListener("keyup", (e) => {
|
|
||||||
keys[e.key.toLowerCase()] = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => { // key sender
|
setInterval(() => { // key sender
|
||||||
|
if (queueBtn.innerText !== QueueState.InGame)//we're in game ? continue | gtfo
|
||||||
|
return ;
|
||||||
if (currentGame === null) return;
|
if (currentGame === null) return;
|
||||||
|
|
||||||
let packet: GameMove = {
|
let packet: GameMove = {
|
||||||
move: null,
|
move: null,
|
||||||
moveRight: null,
|
moveRight: null,
|
||||||
}
|
}
|
||||||
if ((keys['w'] !== keys['s'])) {
|
|
||||||
packet.move = keys['w'] ? 'up' : 'down';
|
|
||||||
}
|
|
||||||
if (currentGame.local && (keys['o'] !== keys['l'])) {
|
|
||||||
packet.moveRight = keys['o'] ? 'up' : 'down';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (queueBtn.innerText !== QueueState.InGame)//we're in game ? continue | gtfo
|
||||||
|
return ;
|
||||||
|
if (currentGame === null) return;
|
||||||
|
|
||||||
|
if ((keys['w'] !== keys['s']))
|
||||||
|
packet.move = keys['w'] ? 'up' : 'down';
|
||||||
|
if (currentGame.local && (keys['o'] !== keys['l']))
|
||||||
|
packet.moveRight = keys['o'] ? 'up' : 'down';
|
||||||
socket.emit('gameMove', packet);
|
socket.emit('gameMove', packet);
|
||||||
}, 1000 / 60);
|
}, 1000 / 60);
|
||||||
|
// ---
|
||||||
|
// keys end
|
||||||
|
// ---
|
||||||
|
|
||||||
|
// ---
|
||||||
|
// position logic (client)
|
||||||
|
// ---
|
||||||
|
const DEFAULT_POSITIONS : GameUpdate = {
|
||||||
|
gameId:"",
|
||||||
|
ball:{size:16, x:800/2, y:450/2},
|
||||||
|
left:{id:"", paddle:{x:40, y:185, width:12, height:80}, score:0},
|
||||||
|
right:{id:"", paddle:{x:748, y:185, width:12, height:80}, score:0},
|
||||||
|
local:false
|
||||||
|
};
|
||||||
|
|
||||||
const render = (state: GameUpdate) => {
|
const render = (state: GameUpdate) => {
|
||||||
currentGame = state;
|
currentGame = state;
|
||||||
//batLeft.style.transform = `translateY(${state.left.paddle.y}px) translateX(${state.left.paddle.x}px)`;
|
|
||||||
batLeft.style.top = `${state.left.paddle.y}px`;
|
batLeft.style.top = `${state.left.paddle.y}px`;
|
||||||
batLeft.style.left = `${state.left.paddle.x}px`;
|
batLeft.style.left = `${state.left.paddle.x}px`;
|
||||||
batLeft.style.width = `${state.left.paddle.width}px`;
|
batLeft.style.width = `${state.left.paddle.width}px`;
|
||||||
batLeft.style.height = `${state.left.paddle.height}px`;
|
batLeft.style.height = `${state.left.paddle.height}px`;
|
||||||
|
|
||||||
//batRight.style.transform = `translateY(${state.right.paddle.y}px) translateX(-${state.left.paddle.x}px)`;
|
|
||||||
batRight.style.top = `${state.right.paddle.y}px`;
|
batRight.style.top = `${state.right.paddle.y}px`;
|
||||||
batRight.style.left = `${state.right.paddle.x}px`;
|
batRight.style.left = `${state.right.paddle.x}px`;
|
||||||
batRight.style.width = `${state.right.paddle.width}px`;
|
batRight.style.width = `${state.right.paddle.width}px`;
|
||||||
|
|
@ -106,19 +121,71 @@ function pongClient(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
ball.style.height = `${state.ball.size * 2}px`;
|
ball.style.height = `${state.ball.size * 2}px`;
|
||||||
ball.style.width = `${state.ball.size * 2}px`;
|
ball.style.width = `${state.ball.size * 2}px`;
|
||||||
|
|
||||||
|
|
||||||
score.innerText = `${state.left.score} | ${state.right.score}`
|
score.innerText = `${state.left.score} | ${state.right.score}`
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.on('gameEnd', () => currentGame = null);
|
|
||||||
|
|
||||||
socket.on('gameUpdate', (state: GameUpdate) => render(state));
|
socket.on('gameUpdate', (state: GameUpdate) => render(state));
|
||||||
socket.on('newGame', (state: GameUpdate) => render(state));
|
// ---
|
||||||
|
// position logic (client) end
|
||||||
|
// ---
|
||||||
|
|
||||||
socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`));
|
// ---
|
||||||
socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`));
|
// queue evt
|
||||||
//socket.emit('enqueue');
|
// ---
|
||||||
socket.emit('localGame');
|
function set_pretty(batU : HTMLDivElement, txtU : HTMLDivElement, txtO : HTMLDivElement, colorYou : string) {
|
||||||
|
batU.style.backgroundColor = colorYou;
|
||||||
|
txtU.style.color = colorYou;
|
||||||
|
txtU.innerText = "you";
|
||||||
|
txtO.innerHTML = "The Mechant";
|
||||||
|
}
|
||||||
|
queueBtn.addEventListener("click", ()=>{
|
||||||
|
if (queueBtn.innerText !== QueueState.Iddle) {
|
||||||
|
if (queueBtn.innerText === QueueState.InQueu) {
|
||||||
|
socket.emit("dequeue");
|
||||||
|
queueBtn.innerText = QueueState.Iddle;
|
||||||
|
}
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
queueBtn.innerText = QueueState.InQueu;
|
||||||
|
socket.emit('enqueue');
|
||||||
|
});
|
||||||
|
socket.on('newGame', (state) => {
|
||||||
|
render(state);
|
||||||
|
queueBtn.innerText = QueueState.InGame;
|
||||||
|
queueBtn.style.color = 'red';
|
||||||
|
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
batRight.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
if (state.left.id === user.id) {
|
||||||
|
set_pretty(batLeft, playerL, playerR, SELF_COLOR);
|
||||||
|
} else if (state.right.id === user.id) {
|
||||||
|
set_pretty(batRight, playerR, playerL, SELF_COLOR);
|
||||||
|
} else
|
||||||
|
showError("couldn't find your id in game");
|
||||||
|
}); // TODO: notif user of new game w "ready up" btn
|
||||||
|
|
||||||
|
socket.on("gameEnd", () => {
|
||||||
|
queueBtn.innerHTML = QueueState.Iddle;
|
||||||
|
queueBtn.style.color = 'white';
|
||||||
|
render(DEFAULT_POSITIONS);
|
||||||
|
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
batRight.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
playerR.style.color = "";
|
||||||
|
playerL.style.color = "";
|
||||||
|
playerR.innerText = "";
|
||||||
|
playerL.innerText = "";
|
||||||
|
currentGame = null;
|
||||||
|
})
|
||||||
|
// ---
|
||||||
|
// queue evt end
|
||||||
|
// ---
|
||||||
|
render(DEFAULT_POSITIONS);
|
||||||
|
batLeft.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
batRight.style.backgroundColor = DEFAULT_COLOR;
|
||||||
|
|
||||||
|
socket.on('updateInformation', (e) => showInfo(`UpdateInformation: t=${e.totalUser};q=${e.inQueue}`)); // queue info TODO: delete for final product
|
||||||
|
socket.on('queueEvent', (e) => showInfo(`QueueEvent: ${e}`)); // queue evt can be left in product
|
||||||
|
showInfo("butter");
|
||||||
|
showInfo("butter-toast");
|
||||||
|
// socket.emit('localGame');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export type GameMove = {
|
||||||
moveRight: 'up' | 'down' | null,
|
moveRight: 'up' | 'down' | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add new evt such as "local play", "ready-up" see: ./pong.ts
|
||||||
export interface ClientToServer {
|
export interface ClientToServer {
|
||||||
enqueue: () => void;
|
enqueue: () => void;
|
||||||
dequeue: () => void;
|
dequeue: () => void;
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ export class Paddle {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Ball {
|
class Ball {
|
||||||
public static readonly DEFAULT_SPEED = 1;
|
public static readonly DEFAULT_SPEED = 3;
|
||||||
public static readonly DEFAULT_SIZE = 16;
|
public static readonly DEFAULT_SIZE = 16;
|
||||||
public static readonly DEFAULT_MAX_SPEED = 30;
|
public static readonly DEFAULT_MAX_SPEED = 15;
|
||||||
public static readonly DEFAULT_MIN_SPEED = Ball.DEFAULT_SPEED;
|
public static readonly DEFAULT_MIN_SPEED = Ball.DEFAULT_SPEED;
|
||||||
public static readonly DEFAULT_ACCEL_FACTOR = 1.2;
|
public static readonly DEFAULT_ACCEL_FACTOR = 1.2;
|
||||||
|
|
||||||
|
|
@ -49,6 +49,7 @@ class Ball {
|
||||||
public collided(
|
public collided(
|
||||||
side: 'left' | 'right' | 'top' | 'bottom',
|
side: 'left' | 'right' | 'top' | 'bottom',
|
||||||
walls: { [k in typeof side]: number },
|
walls: { [k in typeof side]: number },
|
||||||
|
snap: boolean = true,
|
||||||
) {
|
) {
|
||||||
// this.speed *= this.accel_factor;
|
// this.speed *= this.accel_factor;
|
||||||
this.speed = Math.max(
|
this.speed = Math.max(
|
||||||
|
|
@ -65,9 +66,11 @@ class Ball {
|
||||||
this.angle = -this.angle + Math.PI;
|
this.angle = -this.angle + Math.PI;
|
||||||
c = 'x';
|
c = 'x';
|
||||||
}
|
}
|
||||||
this[c] =
|
if (snap){
|
||||||
walls[side] +
|
this[c] =
|
||||||
this.size * (side === 'right' || side === 'bottom' ? -1 : 1);
|
walls[side] +
|
||||||
|
this.size * (side === 'right' || side === 'bottom' ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
while (this.angle >= Math.PI) {
|
while (this.angle >= Math.PI) {
|
||||||
this.angle -= 2 * Math.PI;
|
this.angle -= 2 * Math.PI;
|
||||||
|
|
@ -139,8 +142,8 @@ export class Pong {
|
||||||
left: this.leftPaddle.x + this.leftPaddle.width,
|
left: this.leftPaddle.x + this.leftPaddle.width,
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0}, false
|
||||||
});
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.paddleCollision(this.rightPaddle, 'right')) {
|
if (this.paddleCollision(this.rightPaddle, 'right')) {
|
||||||
|
|
@ -148,8 +151,7 @@ export class Pong {
|
||||||
right: this.rightPaddle.x,
|
right: this.rightPaddle.x,
|
||||||
left: 0,
|
left: 0,
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0}, false);
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const wallCollision = this.boxCollision();
|
const wallCollision = this.boxCollision();
|
||||||
|
|
@ -184,6 +186,10 @@ export class Pong {
|
||||||
}
|
}
|
||||||
|
|
||||||
private paddleCollision(paddle: Paddle, side: 'left' | 'right'): boolean {
|
private paddleCollision(paddle: Paddle, side: 'left' | 'right'): boolean {
|
||||||
|
if (Math.abs(this.ball.angle) > Math.PI / 2 && side !== 'left' ||
|
||||||
|
Math.abs(this.ball.angle) < Math.PI / 2 && side !== 'right')
|
||||||
|
return (false)
|
||||||
|
|
||||||
// now we check only if the ball is near enought in the y axis to permform the collision
|
// now we check only if the ball is near enought in the y axis to permform the collision
|
||||||
if (!(
|
if (!(
|
||||||
// check if ball is bellow the top of the paddle
|
// check if ball is bellow the top of the paddle
|
||||||
|
|
@ -194,7 +200,6 @@ export class Pong {
|
||||||
// so we know that the y is close enougth to be a bit, so we check the X. are we closer than the ball size ? if yes -> hit
|
// so we know that the y is close enougth to be a bit, so we check the X. are we closer than the ball size ? if yes -> hit
|
||||||
if (
|
if (
|
||||||
// check if the paddle.x is at most ball.size away from the center of the ball => we have a hit houston
|
// check if the paddle.x is at most ball.size away from the center of the ball => we have a hit houston
|
||||||
// call he pentagon, 9 11
|
|
||||||
Math.abs(
|
Math.abs(
|
||||||
paddle.x + paddle.width * (side === 'left' ? 1 : 0)
|
paddle.x + paddle.width * (side === 'left' ? 1 : 0)
|
||||||
- this.ball.x)
|
- this.ball.x)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue