newRound() and Game Over

+ Bit Font for score
+ improved API for move and collision
+ miscs
This commit is contained in:
LuckyLaszlo
2022-10-22 09:47:25 +02:00
parent 3f6d7c3afc
commit af3d885f12
5 changed files with 281 additions and 106 deletions

12
memo.txt Normal file
View File

@@ -0,0 +1,12 @@
- ball speed up
- on newRound()
- speed reset
- ball direction based on player hit location
---
- node.js server for http access (and javascipt modules files split)
---
- gamesocket with node.js

BIN
src/Bit5x3.woff Normal file

Binary file not shown.

BIN
src/Bit5x3.woff2 Normal file

Binary file not shown.

View File

@@ -3,6 +3,14 @@
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style> <style>
@font-face {
font-family: 'Bit5x3';
src: url('Bit5x3.woff2') format('woff2'),
url('Bit5x3.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
body { body {
margin: 0; margin: 0;
background-color: #222425; background-color: #222425;

View File

@@ -3,6 +3,7 @@
// @ts-check // @ts-check
let gridDisplay = false; let gridDisplay = false;
let ballInPlay = true;
let pong: gameArea; let pong: gameArea;
@@ -11,8 +12,8 @@ let wall_bottom: Rectangle;
let player1: Player; let player1: Player;
let player2: Player; let player2: Player;
let ball: Ball; let ball: Ball;
let score1: TextElem; let score1: TextNumericValue;
let score2: TextElem; let score2: TextNumericValue;
let midLine: Line; let midLine: Line;
let w_grid_mid: Rectangle; let w_grid_mid: Rectangle;
@@ -29,46 +30,64 @@ function startGame()
// Const // Const
const w = pong.canvas.width; const w = pong.canvas.width;
const h = pong.canvas.height; const h = pong.canvas.height;
const w_mid = w/2; const w_mid = Math.floor(w/2);
const h_mid = h/2; const h_mid = Math.floor(h/2);
const pw = w/50; const pw = Math.floor(w/50);
const ph = pw*5; const ph = pw*5;
const ball_size = pw; // const ball_size = w/50; const ballSize = pw;
const score_size = w/16; const scoreSize = Math.floor(w/16);
const midLineSize = w/150; const midLineSize = Math.floor(w/150);
const wall_size = w/100; const wallSize = Math.floor(w/100);
const grid_size = w/500; const gridSize = Math.floor(w/500);
const playerSpeed = w/75; const playerSpeed = Math.floor(w/75);
const ballSpeed = w/75; const ballSpeed = Math.floor(w/75);
let pos = new VectorInteger();
// Component // Component
wall_top = new Rectangle({x: 0, y: 0}, "grey", w, wall_size); pos.x = 0; pos.y = 0;
wall_bottom = new Rectangle({x: 0, y: h-wall_size}, "grey", w, wall_size); wall_top = new Rectangle(pos, "grey", w, wallSize);
pos.x = 0; pos.y = h-wallSize;
wall_bottom = new Rectangle(pos, "grey", w, wallSize);
player1 = new Player({x: 0+pw, y: h_mid-ph/2}, "white", pw, ph); pos.x = 0+pw; pos.y = h_mid-ph/2;
player2 = new Player({x: w-pw-pw, y: h_mid-ph/2}, "white", pw, ph); player1 = new Player(pos, "white", pw, ph);
pos.x = w-pw-pw; pos.y = h_mid-ph/2;
player2 = new Player(pos, "white", pw, ph);
player1.speed = playerSpeed; player1.speed = playerSpeed;
player2.speed = playerSpeed; player2.speed = playerSpeed;
ball = new Ball({x: w_mid-ball_size/2, y: h_mid-ball_size/2}, "white", ball_size); pos.x = w_mid-ballSize/2; pos.y = h_mid-ballSize/2;
ball = new Ball(pos, "white", ballSize);
ball.speed = ballSpeed; ball.speed = ballSpeed;
ball.dir.x = -0.8; ball.dir.x = -0.8;
ball.dir.y = +0.2; ball.dir.y = +0.2;
score1 = new TextElem({x: w_mid-w/8, y: w/12}, "white", score_size+"px"); pos.x = w_mid-w/8; pos.y = w/12;
score1.text = "0"; score1 = new TextNumericValue(pos, "white", scoreSize);
score2 = new TextElem({x: w_mid+w/8-score_size/2, y: w/12}, "white", score_size+"px"); pos.x = w_mid+w/8-scoreSize/2; pos.y = w/12;
score2.text = "0"; score2 = new TextNumericValue(pos, "white", scoreSize);
score1.value = 0;
score2.value = 0;
midLine = new Line({x: w_mid-midLineSize/2, y: 0+wall_size}, "white", midLineSize, h-wall_size*2, 15); pos.x = w_mid-midLineSize/2; pos.y = 0+wallSize;
midLine = new Line(pos, "white", midLineSize, h-wallSize*2, 15);
// Grid // Grid
w_grid_mid = new Rectangle({x: 0, y: h_mid}, "darkgreen", w, grid_size); pos.x = 0; pos.y = h_mid;
w_grid_u1 = new Rectangle({x: 0, y: h/4}, "darkgreen", w, grid_size); w_grid_mid = new Rectangle(pos, "darkgreen", w, gridSize);
w_grid_d1 = new Rectangle({x: 0, y: h-h/4}, "darkgreen", w, grid_size); pos.x = 0; pos.y = h/4;
h_grid_mid = new Rectangle({x: w_mid, y: 0}, "darkgreen", grid_size, h); w_grid_u1 = new Rectangle(pos, "darkgreen", w, gridSize);
h_grid_u1 = new Rectangle({x: w/4, y: 0}, "darkgreen", grid_size, h); pos.x = 0; pos.y = h-h/4;
h_grid_d1 = new Rectangle({x: w-w/4, y: 0}, "darkgreen", grid_size, h); w_grid_d1 = new Rectangle(pos, "darkgreen", w, gridSize);
pos.x = w_mid; pos.y = 0;
h_grid_mid = new Rectangle(pos, "darkgreen", gridSize, h);
pos.x = w/4; pos.y = 0;
h_grid_u1 = new Rectangle(pos, "darkgreen", gridSize, h);
pos.x = w-w/4; pos.y = 0;
h_grid_d1 = new Rectangle(pos, "darkgreen", gridSize, h);
score1.update(); // first Text draw init the custom font (graphic leftover ortherwise) (a better solution ?)
drawInit();
pong.start(); pong.start();
} }
@@ -110,7 +129,6 @@ class gameArea {
pong.keys.splice(i, 1); pong.keys.splice(i, 1);
} }
clear() { clear() {
// @ts-ignore
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
} }
stop() { stop() {
@@ -118,48 +136,84 @@ class gameArea {
} }
} }
// Bad drawLine, TODO make a drawLine with fillRect()
// function drawLine(start, end, color, pattern)
// {
// let ctx = pong.ctx;
// ctx.beginPath();
// ctx.setLineDash(pattern);
// ctx.moveTo(start[0], start[1]);
// ctx.lineTo(end[0], end[1]);
// ctx.strokeStyle = color;
// ctx.stroke();
// }
function gameLoop() function gameLoop()
{ {
/*
// I try to clear only what need to be update.
// Will revert to clear() all if not satisfactory.
pong.clear();
*/
handleInput(); handleInput();
ball.move(); if (ballInPlay)
if (ball.collision(wall_top) || ball.collision(wall_bottom)) {
ball.bounce(); ball.moveAndBounce([wall_top, wall_bottom, player1, player2]);
else if (ball.collision(player1)) if (ball.pos.x > pong.canvas.width) {
ball.bounce(player1); ballInPlay = false;
else if (ball.collision(player2)) score1.clear();
ball.bounce(player2); ++score1.value;
setTimeout(newRound, 1000);
}
else if (ball.pos.x < 0 - ball.width) {
ballInPlay = false;
score2.clear();
++score2.value;
setTimeout(newRound, 1000);
}
}
draw(); draw();
} }
function newRound()
{
// https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches
if (score1.value >= 11
|| score2.value >= 11)
{
if (Math.abs(score1.value - score2.value) >= 2)
{
if (score1.value > score2.value) {
alert("Player 1 WIN");
}
else {
alert("Player 2 WIN");
}
return;
}
}
ballInPlay = true;
ball.pos.x = pong.canvas.width/2;
ball.pos.y = pong.canvas.height/4 + Math.floor(random() * pong.canvas.height/2);
}
function random(min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;
}
function draw() function draw()
{ {
pong.clear(); if (gridDisplay) {
if (gridDisplay)
drawGrid(); drawGrid();
}
midLine.update();
score1.update();
score2.update();
}
function drawStatic()
{
wall_top.update(); wall_top.update();
wall_bottom.update(); wall_bottom.update();
midLine.update(); midLine.update();
}
function drawInit()
{
pong.clear();
drawStatic();
player1.update(); player1.update();
player2.update(); player2.update();
ball.update();
score1.update();
score2.update();
} }
function drawGrid() function drawGrid()
@@ -181,6 +235,11 @@ function handleInput()
if (keys.indexOf("g") != -1) if (keys.indexOf("g") != -1)
{ {
if (gridDisplay)
{
pong.clear();
drawStatic();
}
gridDisplay = !gridDisplay; gridDisplay = !gridDisplay;
pong.deleteKey("g"); pong.deleteKey("g");
} }
@@ -190,28 +249,22 @@ function handleInput()
function playerMove(keys: string[]) function playerMove(keys: string[])
{ {
player1.dir.y = 0; player1.dir.y = 0;
if (keys.indexOf("w") != -1) if (keys.indexOf("w") != -1) {
{ player1.dir.y += -1; } player1.dir.y += -1;
if (keys.indexOf("s") != -1)
{ player1.dir.y += 1; }
player1.move();
if (player1.collision(wall_top) || player1.collision(wall_bottom))
{
player1.dir.y = player1.dir.y * -1;
player1.move();
} }
if (keys.indexOf("s") != -1) {
player1.dir.y += 1;
}
player1.moveAndCollide([wall_top, wall_bottom]);
player2.dir.y = 0; player2.dir.y = 0;
if (keys.indexOf("ArrowUp".toLowerCase()) != -1) if (keys.indexOf("ArrowUp".toLowerCase()) != -1) {
{ player2.dir.y += -1; } player2.dir.y += -1;
if (keys.indexOf("ArrowDown".toLowerCase()) != -1)
{ player2.dir.y += 1; }
player2.move();
if (player2.collision(wall_top) || player2.collision(wall_bottom))
{
player2.dir.y = player2.dir.y * -1;
player2.move();
} }
if (keys.indexOf("ArrowDown".toLowerCase()) != -1) {
player2.dir.y += 1;
}
player2.moveAndCollide([wall_top, wall_bottom]);
} }
@@ -219,27 +272,66 @@ function playerMove(keys: string[])
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// class.js // class.js
type Vector = { // type Vector = {
// x: number;
// y: number;
// }
class Vector {
x: number; x: number;
y: number; y: number;
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
} }
class VectorInteger extends Vector {
// PLACEHOLDER
// VectorInteger with set/get dont work (No draw on the screen). Why ?
}
/*
class VectorInteger {
// private _x: number = 0;
// private _y: number = 0;
// constructor(x: number = 0, y: number = 0) {
// this._x = x;
// this._y = y;
// }
// get x(): number {
// return this._x;
// }
// set x(v: number) {
// // this._x = Math.floor(v);
// this._x = v;
// }
// get y(): number {
// return this._y;
// }
// set y(v: number) {
// // this._y = Math.floor(v);
// this._y = v;
// }
}
*/
interface Component { interface Component {
pos: Vector; pos: VectorInteger;
color: string; color: string;
// ctx: CanvasRenderingContext2D; // ctx: CanvasRenderingContext2D; // TODO: reference in place of global 'pong.ctx' call
update(): void; update(): void;
clear(): void;
} }
class Rectangle implements Component { class Rectangle implements Component {
pos: Vector; pos: VectorInteger;
color: string; color: string;
width: number; width: number;
height: number; height: number;
constructor(pos: Vector, color: string, width: number, height: number) { constructor(pos: VectorInteger, color: string, width: number, height: number) {
this.pos = pos; this.pos = Object.assign({}, pos);
this.color = color; this.color = color;
this.width = width; this.width = width;
this.height = height; this.height = height;
@@ -249,6 +341,13 @@ class Rectangle implements Component {
ctx.fillStyle = this.color; ctx.fillStyle = this.color;
ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height); ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height);
} }
clear(pos?: VectorInteger) {
let ctx = pong.ctx;
if (pos)
ctx.clearRect(pos.x, pos.y, this.width, this.height);
else
ctx.clearRect(this.pos.x, this.pos.y, this.width, this.height);
}
collision(collider: Rectangle): boolean { // Collision WIP. To redo collision(collider: Rectangle): boolean { // Collision WIP. To redo
var myleft = this.pos.x; var myleft = this.pos.x;
var myright = this.pos.x + (this.width); var myright = this.pos.x + (this.width);
@@ -277,35 +376,65 @@ interface Moving {
} }
class Player extends Rectangle implements Moving { class MovingRectangle extends Rectangle implements Moving {
dir: Vector = {x: 0.0, y: 0.0}; dir: Vector = {x: 0.0, y: 0.0};
speed: number = 1; speed: number = 1;
constructor(pos: Vector, color: string, width: number, height: number) { constructor(pos: VectorInteger, color: string, width: number, height: number) {
super(pos, color, width, height); super(pos, color, width, height);
} }
move() { move() {
this.pos.x += this.dir.x * this.speed; this.pos.x += this.dir.x * this.speed;
this.pos.y += this.dir.y * this.speed; this.pos.y += this.dir.y * this.speed;
} }
moveAndCollide(colliderArr: Rectangle[]) {
let oldPos = Object.assign({}, this.pos);
this.move();
if (colliderArr.some(this.collision, this))
{
this.pos.x = oldPos.x;
this.pos.y = oldPos.y;
}
else
{
this.clear(oldPos);
this.update();
}
}
} }
class Ball extends Rectangle implements Moving { class Player extends MovingRectangle {
dir: Vector = {x: 0.0, y: 0.0}; constructor(pos: VectorInteger, color: string, width: number, height: number) {
speed: number = 1; super(pos, color, width, height);
constructor(pos: Vector, color: string, size: number) { }
}
class Ball extends MovingRectangle {
constructor(pos: VectorInteger, color: string, size: number) {
super(pos, color, size, size); super(pos, color, size, size);
} }
move() {
this.pos.x += this.dir.x * this.speed;
this.pos.y += this.dir.y * this.speed;
}
bounce(collider?: Rectangle) { bounce(collider?: Rectangle) {
if (collider instanceof Player) /* Could be more generic, but testing only player is enough,
because in Pong collider can only be Player or Wall. */
if (collider instanceof Player) {
this._bouncePlayer(collider); this._bouncePlayer(collider);
else // Could be more generic, but it's OK, because in Pong collider can only be Player or Wall. }
else {
this._bounceWall(); this._bounceWall();
}
}
moveAndBounce(colliderArr: Rectangle[]) {
let oldPos = Object.assign({}, this.pos);
this.move();
let i = colliderArr.findIndex(this.collision, this);
if (i != -1)
{
this.bounce(colliderArr[i]);
this.move();
}
this.clear(oldPos);
this.update();
} }
private _bounceWall() { // Should be enough for Wall private _bounceWall() { // Should be enough for Wall
ball.dir.y = ball.dir.y * -1; ball.dir.y = ball.dir.y * -1;
@@ -318,24 +447,50 @@ class Ball extends Rectangle implements Moving {
// conflict with Text // conflict with Text
class TextElem implements Component { class TextElem implements Component {
pos: Vector; pos: VectorInteger;
color: string; color: string;
size: string; size: number;
font: string = "Consolas"; font: string;
text: string = ""; text: string = "";
constructor(pos: Vector, color: string, size: string, font?: string) { constructor(pos: VectorInteger, color: string, size: number, font: string = "Bit5x3") {
this.pos = pos; this.pos = Object.assign({}, pos);
this.color = color; this.color = color;
this.size = size; this.size = size;
if (font)
this.font = font; this.font = font;
} }
update() { update() {
let ctx = pong.ctx; let ctx = pong.ctx;
ctx.font = this.size + " " + this.font; ctx.font = this.size + "px" + " " + this.font;
ctx.fillStyle = this.color; ctx.fillStyle = this.color;
ctx.fillText(this.text, this.pos.x, this.pos.y); ctx.fillText(this.text, this.pos.x, this.pos.y);
} }
clear() {
// clear no very accurate for Text
// https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics
let ctx = pong.ctx;
let textMetric = ctx.measureText(this.text);
// console.log("textMetric.width = "+textMetric.width);
// console.log("size = "+this.size);
// console.log("x = "+this.pos.x);
// console.log("y = "+this.pos.y);
ctx.clearRect(this.pos.x - 1, this.pos.y-this.size + 1, textMetric.width, this.size);
// +1 and -1 because float imprecision (and Math.floor() with VectorInteger dont work for the moment)
// (or maybe its textMetric imprecision ?)
}
}
class TextNumericValue extends TextElem {
private _value: number = 0;
constructor(pos: VectorInteger, color: string, size: number, font?: string) {
super(pos, color, size, font);
}
get value() {
return this._value;
}
set value(v: number) {
this._value = v;
this.text = v.toString();
}
} }
@@ -344,7 +499,7 @@ class Line extends Rectangle {
segmentCount: number; segmentCount: number;
segmentWidth: number; segmentWidth: number;
segmentHeight: number; segmentHeight: number;
constructor(pos: Vector, color: string, width: number, height: number, gapeCount?: number) { constructor(pos: VectorInteger, color: string, width: number, height: number, gapeCount?: number) {
super(pos, color, width, height); super(pos, color, width, height);
if (gapeCount) if (gapeCount)
this.gapeCount = gapeCount; this.gapeCount = gapeCount;