601 lines
21 KiB
JavaScript
601 lines
21 KiB
JavaScript
"use strict";
|
|
/**
|
|
*
|
|
* @source http://tilde.club/~chmod777/ts/base.ts
|
|
*
|
|
* @license AGPL-3.0-only
|
|
* @licstart
|
|
* Copyright (c) 2021 chmod777
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
* @licend
|
|
*/
|
|
let pong;
|
|
window.onload = function () {
|
|
pong = new Pong(document);
|
|
};
|
|
function error(id, elementName) {
|
|
return `Failed to find ${elementName} element with id "${id}"`;
|
|
}
|
|
function getHTMLInput(id) {
|
|
const element = document.getElementById(id);
|
|
if (element instanceof HTMLInputElement)
|
|
return element;
|
|
throw error(id, 'html button');
|
|
}
|
|
function getHTMLSpan(id) {
|
|
const element = document.getElementById(id);
|
|
if (element instanceof HTMLSpanElement)
|
|
return element;
|
|
throw error(id, 'html span');
|
|
}
|
|
function getSVGRectElement(svgDocument, id) {
|
|
const element = svgDocument.getElementById(id);
|
|
if (element instanceof SVGRectElement)
|
|
return element;
|
|
throw error(id, 'svg rect');
|
|
}
|
|
function getSVGTextElement(svgDocument, id) {
|
|
const element = svgDocument.getElementById(id);
|
|
if (element instanceof SVGTextElement)
|
|
return element;
|
|
throw error(id, 'svg text');
|
|
}
|
|
function getSVGCircleElement(svgDocument, id) {
|
|
const element = svgDocument.getElementById(id);
|
|
if (element instanceof SVGCircleElement)
|
|
return element;
|
|
throw error(id, 'svg circle');
|
|
}
|
|
function getSVGAnimateMotionElement(svgDocument, id) {
|
|
const element = svgDocument.getElementById(id);
|
|
if (element instanceof SVGAnimateMotionElement)
|
|
return element;
|
|
throw error(id, 'svg animate motion');
|
|
}
|
|
function getSVGPathElement(svgDocument, id) {
|
|
const element = svgDocument.getElementById(id);
|
|
if (element instanceof SVGPathElement)
|
|
return element;
|
|
throw error(id, 'svg path');
|
|
}
|
|
/**
|
|
*
|
|
* @source http://tilde.club/~chmod777/ts/pong.ts
|
|
*
|
|
* @license AGPL-3.0-only
|
|
* @licstart
|
|
* Copyright (c) 2021 chmod777
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
* @licend
|
|
*/
|
|
class Vec2 {
|
|
constructor(x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
static zero() {
|
|
return new Vec2(0, 0);
|
|
}
|
|
static one() {
|
|
return new Vec2(1, 1);
|
|
}
|
|
clone() {
|
|
return new Vec2(this.x, this.y);
|
|
}
|
|
add(other) {
|
|
return new Vec2(this.x + other.x, this.y + other.y);
|
|
}
|
|
sub(other) {
|
|
return new Vec2(this.x - other.x, this.y - other.y);
|
|
}
|
|
mul(s) {
|
|
return new Vec2(this.x * s, this.y * s);
|
|
}
|
|
div(s) {
|
|
return new Vec2(this.x / s, this.y / s);
|
|
}
|
|
cross(other) {
|
|
return this.x * other.y - this.y * other.x;
|
|
}
|
|
dot(other) {
|
|
return this.x * other.y + this.y * other.y;
|
|
}
|
|
magnitude2() {
|
|
return this.x * this.x + this.y * this.y;
|
|
}
|
|
magnitude() {
|
|
return Math.sqrt(this.magnitude2());
|
|
}
|
|
asUnit() {
|
|
return this.div(this.magnitude());
|
|
}
|
|
static numberAsString(n) {
|
|
if (n != Math.floor(n)) {
|
|
n.toFixed(2);
|
|
}
|
|
return n.toString();
|
|
}
|
|
asString() {
|
|
return `${Vec2.numberAsString(this.x)},${Vec2.numberAsString(this.y)} `;
|
|
}
|
|
asAbsolute() {
|
|
return `M${this.asString()}`;
|
|
}
|
|
asLine() {
|
|
return `L${this.asString()}`;
|
|
}
|
|
}
|
|
class Line {
|
|
constructor(p1, p2) {
|
|
this.p1 = p1;
|
|
this.p2 = p2;
|
|
}
|
|
lineLineIntersect(l2) {
|
|
const p = this.p1;
|
|
const r = this.p2.sub(p);
|
|
const q = l2.p1;
|
|
const s = l2.p2.sub(q);
|
|
const r_cross_s = r.cross(s);
|
|
const q_minus_p = q.sub(p);
|
|
if (r_cross_s === 0) {
|
|
return null;
|
|
// let q_minus_p_cross_r = q_minus_p.cross(r);
|
|
// if (q_minus_p_cross_r === 0)
|
|
// return null; // collinear
|
|
// else
|
|
// return null; // parallel
|
|
}
|
|
else {
|
|
const t = q_minus_p.cross(s.div(r_cross_s));
|
|
const u = q_minus_p.cross(r.div(r_cross_s));
|
|
if (0 <= t && t <= 1 && 0 <= u && u <= 1) {
|
|
if (t === 0)
|
|
return q.add(s.mul(u));
|
|
else
|
|
return p.add(r.mul(t));
|
|
}
|
|
else {
|
|
return null; // divergent
|
|
}
|
|
}
|
|
}
|
|
}
|
|
class PongElements {
|
|
constructor(svgContent) {
|
|
this.playerPaddle = getSVGRectElement(svgContent, 'pong-player-paddle');
|
|
this.aiPaddle = getSVGRectElement(svgContent, 'pong-ai-paddle');
|
|
this.playerScoreElement = getSVGTextElement(svgContent, 'pong-player-score');
|
|
this.aiScoreElement = getSVGTextElement(svgContent, 'pong-ai-score');
|
|
this.ball = getSVGCircleElement(svgContent, 'pong-ball');
|
|
this.ballAnimation = getSVGAnimateMotionElement(svgContent, 'pong-ball-animation');
|
|
this.ballPath = getSVGPathElement(svgContent, 'pong-ball-path');
|
|
this.collisionPath = getSVGPathElement(svgContent, 'pong-collision-path');
|
|
this.resetButton = getHTMLInput('pong-reset');
|
|
this.serveButton = getHTMLInput('pong-serve');
|
|
this.upButton = getHTMLInput('pong-paddle-up');
|
|
this.stopButton = getHTMLInput('pong-paddle-stop');
|
|
this.downButton = getHTMLInput('pong-paddle-down');
|
|
this.fps = getHTMLSpan('fps');
|
|
const WIDTH_STRING = Pong.PADDLE_WIDTH.toString();
|
|
this.aiPaddle.setAttribute('width', WIDTH_STRING);
|
|
this.playerPaddle.setAttribute('width', WIDTH_STRING);
|
|
const BALL_RADIUS_STRING = Pong.BALL_RADIUS.toString();
|
|
this.ball.setAttribute('r', BALL_RADIUS_STRING);
|
|
this.resetPaddles();
|
|
this.resetBall();
|
|
}
|
|
resetBall() {
|
|
this.ballPath.setAttribute('d', Pong.ABSOLUTE_CENTER);
|
|
this.collisionPath.setAttribute('d', Pong.ABSOLUTE_CENTER);
|
|
this.ballAnimation.beginElement();
|
|
}
|
|
resetPaddles() {
|
|
translateToPosition(this.playerPaddle, Pong.PLAYER_STARTING_POSITION);
|
|
translateToPosition(this.aiPaddle, Pong.AI_STARTING_POSITION);
|
|
}
|
|
resetScores() {
|
|
this.playerScoreElement.innerHTML = '0';
|
|
this.aiScoreElement.innerHTML = '0';
|
|
}
|
|
}
|
|
class PongState {
|
|
constructor() {
|
|
this.lastAnimationFrame = 0;
|
|
this.playerPosition = Pong.PLAYER_STARTING_POSITION.clone();
|
|
this.aiPosition = Pong.AI_STARTING_POSITION.clone();
|
|
this.ballSpeed = Pong.BALL_BASE_SPEED;
|
|
this.ballVelocity = Vec2.zero();
|
|
this.running = false;
|
|
this.shouldServe = false;
|
|
this.playerServe = false;
|
|
this.time = 0;
|
|
this.lastTime = 0;
|
|
this.serveTime = 0;
|
|
this.dt_ms = 0;
|
|
this.dt = 0;
|
|
this.playerScore = 0;
|
|
this.aiScore = 0;
|
|
this.scoreDuration = 0;
|
|
this.collisionDuration = 0;
|
|
this.collisionPoint = Vec2.zero();
|
|
this.moveUp = false;
|
|
this.moveDown = false;
|
|
}
|
|
get timeSinceServe() { return (this.time - this.serveTime) / 1000; }
|
|
update_dt(time) {
|
|
this.lastTime = this.time;
|
|
this.time = time;
|
|
this.dt_ms = this.time - this.lastTime;
|
|
this.dt = this.dt_ms / 1000;
|
|
}
|
|
resetBall() {
|
|
this.ballSpeed = Pong.BALL_BASE_SPEED;
|
|
this.ballVelocity = Vec2.zero();
|
|
}
|
|
resetPaddles() {
|
|
this.playerPosition = Pong.PLAYER_STARTING_POSITION.clone();
|
|
this.aiPosition = Pong.AI_STARTING_POSITION.clone();
|
|
this.moveUp = false;
|
|
this.moveDown = false;
|
|
}
|
|
}
|
|
class Pong {
|
|
constructor(svgContent) {
|
|
this.state = new PongState();
|
|
this.elements = new PongElements(svgContent);
|
|
this.elements.resetButton.addEventListener('click', () => { this.reset(); });
|
|
this.elements.serveButton.addEventListener('click', () => {
|
|
if (!this.state.running) {
|
|
this.state.running = true;
|
|
this.state.shouldServe = true;
|
|
}
|
|
});
|
|
this.elements.upButton.addEventListener('click', () => {
|
|
this.state.moveUp = true;
|
|
this.state.moveDown = false;
|
|
});
|
|
this.elements.downButton.addEventListener('click', () => {
|
|
this.state.moveUp = false;
|
|
this.state.moveDown = true;
|
|
});
|
|
this.elements.stopButton.addEventListener('click', () => {
|
|
this.state.moveUp = false;
|
|
this.state.moveDown = false;
|
|
});
|
|
document.addEventListener('keydown', (event) => { this.handleKeydown(event); });
|
|
document.addEventListener('keyup', (event) => { this.handleKeyup(event); });
|
|
this.state.lastAnimationFrame = window.requestAnimationFrame((timestamp) => {
|
|
this.update(timestamp);
|
|
});
|
|
}
|
|
serve() {
|
|
this.state.shouldServe = false;
|
|
this.state.serveTime = this.state.time;
|
|
this.state.ballVelocity.x = genRandom();
|
|
this.state.ballVelocity.y = genRandom();
|
|
this.state.ballVelocity = this.state.ballVelocity.asUnit().mul(this.state.ballSpeed);
|
|
if (this.state.ballVelocity.x > 0) {
|
|
this.state.playerServe = true;
|
|
}
|
|
this.buildBallPaths(Pong.CENTER);
|
|
}
|
|
reset() {
|
|
this.state = new PongState();
|
|
window.cancelAnimationFrame(this.state.lastAnimationFrame);
|
|
this.updatePaddles();
|
|
this.elements.resetScores();
|
|
this.elements.resetBall();
|
|
}
|
|
resetBall() {
|
|
this.state.resetBall();
|
|
this.elements.resetBall();
|
|
}
|
|
resetPaddles() {
|
|
this.state.resetPaddles();
|
|
this.updatePaddles();
|
|
}
|
|
updatePaddles() {
|
|
translateToPosition(this.elements.playerPaddle, this.state.playerPosition);
|
|
translateToPosition(this.elements.aiPaddle, this.state.aiPosition);
|
|
}
|
|
update(timestamp) {
|
|
this.state.update_dt(timestamp);
|
|
const fps = 1 / this.state.dt;
|
|
this.elements.fps.innerHTML = fps.toString();
|
|
if (this.state.shouldServe) {
|
|
this.serve();
|
|
}
|
|
this.updatePlayer();
|
|
if (this.state.running) {
|
|
this.updateAI(this.elements.aiPaddle, this.state.aiPosition);
|
|
this.processScore();
|
|
if (this.state.running) {
|
|
this.processHit();
|
|
}
|
|
}
|
|
this.state.lastAnimationFrame = window.requestAnimationFrame((timestamp) => {
|
|
this.update(timestamp);
|
|
});
|
|
}
|
|
isColliding(paddle) {
|
|
return paddle.y + Pong.PADDLE_HEIGHT > this.state.collisionPoint.y &&
|
|
paddle.y < this.state.collisionPoint.y;
|
|
}
|
|
processScore() {
|
|
if (this.state.timeSinceServe > this.state.scoreDuration) {
|
|
if (this.state.playerServe) {
|
|
this.state.playerScore++;
|
|
this.elements.playerScoreElement.innerHTML = this.state.playerScore.toString();
|
|
}
|
|
else {
|
|
this.state.aiScore++;
|
|
this.elements.aiScoreElement.innerHTML = this.state.aiScore.toString();
|
|
}
|
|
this.state.running = false;
|
|
this.resetBall();
|
|
this.resetPaddles();
|
|
}
|
|
}
|
|
processHit() {
|
|
if (this.state.timeSinceServe > this.state.collisionDuration) {
|
|
let position = this.state.playerPosition;
|
|
let collisionOffset = 0.1;
|
|
if (this.state.playerServe) {
|
|
position = this.state.aiPosition;
|
|
collisionOffset = -collisionOffset;
|
|
}
|
|
if (this.isColliding(position)) {
|
|
this.state.ballVelocity.x = -this.state.ballVelocity.x;
|
|
this.state.ballSpeed += Pong.BALL_SPEED_INCREASE;
|
|
this.state.serveTime = this.state.time;
|
|
this.state.playerServe = !this.state.playerServe;
|
|
this.state.collisionPoint.x += collisionOffset;
|
|
this.buildBallPaths(this.state.collisionPoint);
|
|
}
|
|
}
|
|
}
|
|
clampPaddlePosition(paddlePosition) {
|
|
paddlePosition.y = Math.max(Math.min(paddlePosition.y, Pong.PADDLE_MAX_POSITION), 0);
|
|
}
|
|
updatePlayer() {
|
|
if (this.state.moveUp && this.state.moveDown
|
|
|| !(this.state.moveUp || this.state.moveDown)) {
|
|
return;
|
|
}
|
|
const dy = this.state.dt * Pong.PADDLE_VELOCITY;
|
|
if (this.state.moveDown) {
|
|
this.state.playerPosition.y += dy;
|
|
}
|
|
else {
|
|
this.state.playerPosition.y -= dy;
|
|
}
|
|
this.clampPaddlePosition(this.state.playerPosition);
|
|
translateToPosition(this.elements.playerPaddle, this.state.playerPosition);
|
|
}
|
|
updateAI(paddle, position) {
|
|
let targetPosition;
|
|
if (this.state.playerServe) {
|
|
targetPosition = this.state.collisionPoint.y;
|
|
}
|
|
else {
|
|
targetPosition = Pong.HALF_HEIGHT;
|
|
}
|
|
targetPosition -= Pong.PADDLE_HALF_HEIGHT;
|
|
const targetRange = Pong.PADDLE_HALF_HEIGHT / 2;
|
|
const dy = this.state.dt * Pong.PADDLE_VELOCITY;
|
|
let shouldMove = false;
|
|
if (position.y > targetPosition + targetRange) {
|
|
shouldMove = true;
|
|
position.y -= dy;
|
|
}
|
|
else if (position.y < targetPosition - targetRange) {
|
|
shouldMove = true;
|
|
position.y += dy;
|
|
}
|
|
if (shouldMove) {
|
|
this.clampPaddlePosition(position);
|
|
translateToPosition(paddle, position);
|
|
}
|
|
}
|
|
buildBallPath(startingPosition, dir, bounds) {
|
|
const path = new Line(startingPosition, startingPosition.add(dir));
|
|
const points = [];
|
|
while (true) {
|
|
const maybeIntersect = this.findNextCollision(path, bounds);
|
|
if (maybeIntersect !== null) {
|
|
const [point, isEnd] = maybeIntersect;
|
|
if (isEnd) {
|
|
if (point.x > Pong.HALF_WIDTH)
|
|
point.x += 0.01;
|
|
else
|
|
point.x -= 0.01;
|
|
}
|
|
else {
|
|
if (point.y > Pong.HALF_HEIGHT)
|
|
point.y -= 0.01;
|
|
else
|
|
point.y += 0.01;
|
|
}
|
|
points.push(point);
|
|
if (isEnd) {
|
|
break;
|
|
}
|
|
dir.y = -dir.y;
|
|
path.p1 = point;
|
|
path.p2 = point.add(dir);
|
|
}
|
|
else {
|
|
console.error("intersect is null");
|
|
break;
|
|
}
|
|
}
|
|
return points;
|
|
}
|
|
buildBallPaths(startingPosition) {
|
|
const dir = this.state.ballVelocity.mul(1000);
|
|
const animationPoints = this.buildBallPath(startingPosition, dir.clone(), Pong.BOARD_BOUNDS);
|
|
const collisionPoints = this.buildBallPath(startingPosition, dir, Pong.PADDLE_COLLISION_BOUNDS);
|
|
this.state.collisionPoint = collisionPoints[collisionPoints.length - 1].clone();
|
|
this.state.ballVelocity = dir.div(1000);
|
|
const animationPath = this.buildSVGPathFromPoints(startingPosition, animationPoints);
|
|
this.elements.ballPath.setAttribute('d', animationPath);
|
|
this.state.scoreDuration = this.elements.ballPath.getTotalLength() / this.state.ballSpeed;
|
|
this.elements.ballAnimation.setAttribute('dur', this.state.scoreDuration.toString());
|
|
const collisionPath = this.buildSVGPathFromPoints(startingPosition, collisionPoints);
|
|
this.elements.collisionPath.setAttribute('d', collisionPath);
|
|
this.state.collisionDuration = this.elements.collisionPath.getTotalLength() / this.state.ballSpeed;
|
|
this.elements.ballAnimation.beginElement();
|
|
}
|
|
buildSVGPathFromPoints(start, points) {
|
|
let directions = start.asAbsolute();
|
|
for (const point of points) {
|
|
directions = directions.concat(point.asLine());
|
|
}
|
|
return directions;
|
|
}
|
|
findNextCollision(path, bounds) {
|
|
for (const [line, isEnd] of bounds) {
|
|
const maybeIntersect = path.lineLineIntersect(line);
|
|
if (maybeIntersect !== null) {
|
|
return [maybeIntersect, isEnd];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
handleKeydown(event) {
|
|
const key = event.key;
|
|
if (key === ',') {
|
|
this.state.moveUp = true;
|
|
}
|
|
else if (key === '.') {
|
|
this.state.moveDown = true;
|
|
}
|
|
else if (key === 's') {
|
|
if (!this.state.running) {
|
|
this.state.running = true;
|
|
this.state.shouldServe = true;
|
|
}
|
|
}
|
|
else if (key === 'n') {
|
|
this.reset();
|
|
}
|
|
}
|
|
handleKeyup(event) {
|
|
const key = event.key;
|
|
if (key === ',') {
|
|
this.state.moveUp = false;
|
|
}
|
|
else if (key === '.') {
|
|
this.state.moveDown = false;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
x w
|
|
y +---------------------+
|
|
| | |
|
|
h | 0 |
|
|
| | |
|
|
+---------------------+
|
|
*/
|
|
Pong.WIDTH = 512;
|
|
Pong.HEIGHT = 256;
|
|
Pong.HALF_WIDTH = Pong.WIDTH / 2;
|
|
Pong.HALF_HEIGHT = Pong.HEIGHT / 2;
|
|
Pong.CENTER = new Vec2(Pong.HALF_WIDTH, Pong.HALF_HEIGHT);
|
|
Pong.ABSOLUTE_CENTER = Pong.CENTER.asAbsolute();
|
|
Pong.PADDLE_WIDTH = 4;
|
|
Pong.PADDLE_HEIGHT = 28;
|
|
Pong.PADDLE_HALF_WIDTH = Pong.PADDLE_WIDTH / 2;
|
|
Pong.PADDLE_HALF_HEIGHT = Pong.PADDLE_HEIGHT / 2;
|
|
Pong.PADDLE_MAX_POSITION = Pong.HEIGHT - Pong.PADDLE_HEIGHT;
|
|
Pong.PADDLE_VELOCITY = 150;
|
|
Pong.BALL_RADIUS = 5;
|
|
Pong.BALL_SPEED_INCREASE = 10;
|
|
Pong.BALL_BASE_SPEED = 200;
|
|
Pong.PADDLE_STARTING_POSITION_Y = Pong.CENTER.y - Pong.PADDLE_HALF_HEIGHT;
|
|
Pong.PLAYER_STARTING_POSITION = new Vec2(0, Pong.PADDLE_STARTING_POSITION_Y);
|
|
Pong.AI_STARTING_POSITION = new Vec2(Pong.WIDTH - Pong.PADDLE_WIDTH, Pong.PADDLE_STARTING_POSITION_Y);
|
|
Pong.RAW_CORNERS = [
|
|
// top left
|
|
new Vec2(0, 0 + Pong.BALL_RADIUS),
|
|
// top right
|
|
new Vec2(Pong.WIDTH, 0 + Pong.BALL_RADIUS),
|
|
// bottom right
|
|
new Vec2(Pong.WIDTH, Pong.HEIGHT - Pong.BALL_RADIUS),
|
|
// bottom left
|
|
new Vec2(0, Pong.HEIGHT - Pong.BALL_RADIUS)
|
|
];
|
|
Pong.BOARD_CORNERS = [
|
|
// top left
|
|
new Vec2(Pong.RAW_CORNERS[0].x - Pong.BALL_RADIUS, Pong.RAW_CORNERS[0].y),
|
|
// top right
|
|
new Vec2(Pong.RAW_CORNERS[1].x + Pong.BALL_RADIUS, Pong.RAW_CORNERS[1].y),
|
|
// bottom right
|
|
new Vec2(Pong.RAW_CORNERS[2].x + Pong.BALL_RADIUS, Pong.RAW_CORNERS[2].y),
|
|
// bottom left
|
|
new Vec2(Pong.RAW_CORNERS[3].x - Pong.BALL_RADIUS, Pong.RAW_CORNERS[3].y)
|
|
];
|
|
Pong.PADDLE_COLLISION_CORNERS = [
|
|
// top left
|
|
new Vec2(Pong.RAW_CORNERS[0].x + Pong.PADDLE_WIDTH + Pong.BALL_RADIUS, Pong.RAW_CORNERS[0].y),
|
|
// top right
|
|
new Vec2((Pong.RAW_CORNERS[1].x - Pong.PADDLE_WIDTH) - Pong.BALL_RADIUS, Pong.RAW_CORNERS[1].y),
|
|
// bottom right
|
|
new Vec2((Pong.RAW_CORNERS[2].x - Pong.PADDLE_WIDTH) - Pong.BALL_RADIUS, Pong.RAW_CORNERS[2].y),
|
|
// bottom left
|
|
new Vec2(Pong.RAW_CORNERS[3].x + Pong.PADDLE_WIDTH + Pong.BALL_RADIUS, Pong.RAW_CORNERS[3].y)
|
|
];
|
|
Pong.BOARD_BOUNDS = [
|
|
// top
|
|
[new Line(Pong.BOARD_CORNERS[0], Pong.BOARD_CORNERS[1]), false],
|
|
// left
|
|
[new Line(Pong.BOARD_CORNERS[0], Pong.BOARD_CORNERS[3]), true],
|
|
// bottom
|
|
[new Line(Pong.BOARD_CORNERS[3], Pong.BOARD_CORNERS[2]), false],
|
|
// right
|
|
[new Line(Pong.BOARD_CORNERS[1], Pong.BOARD_CORNERS[2]), true]
|
|
];
|
|
Pong.PADDLE_COLLISION_BOUNDS = [
|
|
// top
|
|
[new Line(Pong.PADDLE_COLLISION_CORNERS[0], Pong.PADDLE_COLLISION_CORNERS[1]), false],
|
|
// left
|
|
[new Line(Pong.PADDLE_COLLISION_CORNERS[0], Pong.PADDLE_COLLISION_CORNERS[3]), true],
|
|
// bottom
|
|
[new Line(Pong.PADDLE_COLLISION_CORNERS[3], Pong.PADDLE_COLLISION_CORNERS[2]), false],
|
|
// right
|
|
[new Line(Pong.PADDLE_COLLISION_CORNERS[1], Pong.PADDLE_COLLISION_CORNERS[2]), true]
|
|
];
|
|
// generate random number in range(-1, 1)
|
|
function genRandom() {
|
|
return (Math.random() - 0.5) * 2;
|
|
}
|
|
function translateToPosition(element, position) {
|
|
translateTo(element, position.x, position.y);
|
|
}
|
|
function translateTo(element, x, y) {
|
|
element.setAttribute('transform', `translate(${x}, ${y})`);
|
|
}
|
|
//# sourceMappingURL=library.js.map
|