WIP, tout est en chantier, très content :)

This commit is contained in:
LuckyLaszlo
2022-11-20 15:46:45 +01:00
parent 023b5ed485
commit 48665cfe30
22 changed files with 580 additions and 302 deletions

View File

@@ -0,0 +1,51 @@
import * as c from "../constants.js"
import {Vector, VectorInteger} from "../../shared_js/class/Vector.js";
import {Rectangle, Line} from "../../shared_js/class/Rectangle.js";
import {TextElem, TextNumericValue} from "./Text.js";
import { GameComponents } from "../../shared_js/class/GameComponents.js";
class GameComponentsClient extends GameComponents {
midLine: Line;
score1: TextNumericValue;
score2: TextNumericValue;
w_grid_mid: Rectangle;
w_grid_u1: Rectangle;
w_grid_d1: Rectangle;
h_grid_mid: Rectangle;
h_grid_u1: Rectangle;
h_grid_d1: Rectangle;
constructor(ctx: CanvasRenderingContext2D)
{
super(ctx);
let pos = new VectorInteger;
// Scores
pos.assign(c.w_mid-c.scoreSize*1.6, c.scoreSize*1.5);
this.score1 = new TextNumericValue(ctx, pos, "white", c.scoreSize);
pos.assign(c.w_mid+c.scoreSize*1.1, c.scoreSize*1.5);
this.score2 = new TextNumericValue(ctx, pos, "white", c.scoreSize);
this.score1.value = 0;
this.score2.value = 0;
// Dotted Midline
pos.assign(c.w_mid-c.midLineSize/2, 0+c.wallSize);
this.midLine = new Line(ctx, pos, "white", c.midLineSize, c.h-c.wallSize*2, 15);
// Grid
pos.assign(0, c.h_mid);
this.w_grid_mid = new Rectangle(ctx, pos, "darkgreen", c.w, c.gridSize);
pos.assign(0, c.h/4);
this.w_grid_u1 = new Rectangle(ctx, pos, "darkgreen", c.w, c.gridSize);
pos.assign(0, c.h-c.h/4);
this.w_grid_d1 = new Rectangle(ctx, pos, "darkgreen", c.w, c.gridSize);
pos.assign(c.w_mid, 0);
this.h_grid_mid = new Rectangle(ctx, pos, "darkgreen", c.gridSize, c.h);
pos.assign(c.w/4, 0);
this.h_grid_u1 = new Rectangle(ctx, pos, "darkgreen", c.gridSize, c.h);
pos.assign(c.w-c.w/4, 0);
this.h_grid_d1 = new Rectangle(ctx, pos, "darkgreen", c.gridSize, c.h);
}
}
export {GameComponentsClient}

View File

@@ -1,4 +1,4 @@
import * as g from "./global.js"
import {pong, gc, clientInfo} from "./global.js"
import {gridDisplay} from "./handleInput.js";
function draw()
@@ -6,35 +6,35 @@ function draw()
if (gridDisplay) {
drawGrid();
}
g.midLine.update();
g.score1.update();
g.score2.update();
gc.midLine.update();
gc.score1.update();
gc.score2.update();
}
function drawStatic()
{
g.wall_top.update();
g.wall_bottom.update();
g.midLine.update();
gc.wallTop.update();
gc.wallBottom.update();
gc.midLine.update();
}
function drawInit()
{
g.pong.clear();
pong.clear();
drawStatic();
g.player1.update();
g.player2.update();
gc.playerLeft.update();
gc.playerRight.update();
}
function drawGrid()
{
g.w_grid_mid.update();
g.w_grid_u1.update();
g.w_grid_d1.update();
gc.w_grid_mid.update();
gc.w_grid_u1.update();
gc.w_grid_d1.update();
g.h_grid_mid.update();
g.h_grid_u1.update();
g.h_grid_d1.update();
gc.h_grid_mid.update();
gc.h_grid_u1.update();
gc.h_grid_d1.update();
}
export {draw, drawStatic, drawInit, drawGrid}

View File

@@ -1,6 +1,6 @@
import * as g from "./global.js"
import {pong, gc, clientInfo} from "./global.js"
import * as d from "./draw.js";
import {random} from "../shared_js/utils.js";
import {random} from "./utils.js";
import {handleInput} from "./handleInput.js";
let ballInPlay = false;
@@ -23,17 +23,17 @@ function gameLoop()
if (ballInPlay)
{
g.ball.moveAndBounce(delta_time, [g.wall_top, g.wall_bottom, g.player1, g.player2]);
if (g.ball.pos.x > g.pong.canvas.width) {
gc.ball.moveAndBounce(delta_time, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
if (gc.ball.pos.x > pong.canvas.width) {
ballInPlay = false;
g.score1.clear();
++g.score1.value;
gc.score1.clear();
++gc.score1.value;
setTimeout(newRound, 1500);
}
else if (g.ball.pos.x < 0 - g.ball.width) {
else if (gc.ball.pos.x < 0 - gc.ball.width) {
ballInPlay = false;
g.score2.clear();
++g.score2.value;
gc.score2.clear();
++gc.score2.value;
setTimeout(newRound, 1500);
}
}
@@ -44,12 +44,12 @@ function gameLoop()
function newRound()
{
// https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches
if (g.score1.value >= 11
|| g.score2.value >= 11)
if (gc.score1.value >= 11
|| gc.score2.value >= 11)
{
if (Math.abs(g.score1.value - g.score2.value) >= 2)
if (Math.abs(gc.score1.value - gc.score2.value) >= 2)
{
if (g.score1.value > g.score2.value) {
if (gc.score1.value > gc.score2.value) {
alert("Player 1 WIN");
}
else {
@@ -58,9 +58,9 @@ function newRound()
return;
}
}
g.ball.pos.x = Math.floor(g.pong.canvas.width/2);
g.ball.pos.y = Math.floor((g.pong.canvas.height * 0.1) + random() * (g.pong.canvas.height * 0.8));
g.ball.speed = g.ball.baseSpeed;
gc.ball.pos.x = Math.floor(pong.canvas.width/2);
gc.ball.pos.y = Math.floor((pong.canvas.height * 0.1) + random() * (pong.canvas.height * 0.8));
gc.ball.speed = gc.ball.baseSpeed;
ballInPlay = true;
}

View File

@@ -1,3 +1,3 @@
export * from "../shared_js/initGame.js"
export * from "./initGameClientOnly.js"
export {pong} from "./pong.js"
export {pong, gc} from "./pong.js"
export {clientInfo} from "./ws.js"

View File

@@ -1,13 +1,14 @@
import * as g from "./global.js"
import {pong, gc, clientInfo} from "./global.js"
import * as d from "./draw.js";
import { socket } from "./ws.js";
import {InputEnum, EventInput} from "../shared_js/class/Event.js"
import {InputEnum} from "../shared_js/enums.js"
import {EventInput} from "../shared_js/class/Event.js"
let gridDisplay = false;
function handleInput(delta: number)
{
var keys = g.pong.keys;
var keys = pong.keys;
if (keys.length == 0)
return;
@@ -15,34 +16,36 @@ function handleInput(delta: number)
{
if (gridDisplay)
{
g.pong.clear();
pong.clear();
d.drawStatic();
}
gridDisplay = !gridDisplay;
g.pong.deleteKey("g");
pong.deleteKey("g");
}
playerMove(delta, keys);
}
function playerMove(delta: number, keys: string[])
{
g.player1.dir.y = 0;
// gc.playerLeft.dir.y = 0;
if (keys.indexOf("w") != -1) {
socket.send(JSON.stringify(new EventInput(InputEnum.up)));
// gc.playerLeft.dir.y += -1;
}
if (keys.indexOf("s") != -1) {
socket.send(JSON.stringify(new EventInput(InputEnum.down)));
// gc.playerLeft.dir.y += 1;
}
g.player1.moveAndCollide(delta, [g.wall_top, g.wall_bottom]);
// gc.playerLeft.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
g.player2.dir.y = 0;
gc.playerRight.dir.y = 0;
if (keys.indexOf("ArrowUp".toLowerCase()) != -1) {
g.player2.dir.y += -1;
gc.playerRight.dir.y += -1;
}
if (keys.indexOf("ArrowDown".toLowerCase()) != -1) {
g.player2.dir.y += 1;
gc.playerRight.dir.y += 1;
}
g.player2.moveAndCollide(delta, [g.wall_top, g.wall_bottom]);
gc.playerRight.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
}
export {handleInput, gridDisplay}

View File

@@ -1,48 +0,0 @@
import * as c from "./constants.js"
import {Vector, VectorInteger} from "../shared_js/class/Vector.js";
import {Rectangle, MovingRectangle, Player, Ball, Line} from "../shared_js/class/Rectangle.js";
import {TextElem, TextNumericValue} from "./class/Text.js";
export let midLine: Line;
export let score1: TextNumericValue;
export let score2: TextNumericValue;
export let w_grid_mid: Rectangle;
export let w_grid_u1: Rectangle;
export let w_grid_d1: Rectangle;
export let h_grid_mid: Rectangle;
export let h_grid_u1: Rectangle;
export let h_grid_d1: Rectangle;
function initGameClientOnly(ctx: CanvasRenderingContext2D)
{
let pos = new VectorInteger;
// Scores
pos.assign(c.w_mid-c.scoreSize*1.6, c.scoreSize*1.5);
score1 = new TextNumericValue(ctx, pos, "white", c.scoreSize);
pos.assign(c.w_mid+c.scoreSize*1.1, c.scoreSize*1.5);
score2 = new TextNumericValue(ctx, pos, "white", c.scoreSize);
score1.value = 0;
score2.value = 0;
// Dotted Midline
pos.assign(c.w_mid-c.midLineSize/2, 0+c.wallSize);
midLine = new Line(ctx, pos, "white", c.midLineSize, c.h-c.wallSize*2, 15);
// Grid
pos.assign(0, c.h_mid);
w_grid_mid = new Rectangle(ctx, pos, "darkgreen", c.w, c.gridSize);
pos.assign(0, c.h/4);
w_grid_u1 = new Rectangle(ctx, pos, "darkgreen", c.w, c.gridSize);
pos.assign(0, c.h-c.h/4);
w_grid_d1 = new Rectangle(ctx, pos, "darkgreen", c.w, c.gridSize);
pos.assign(c.w_mid, 0);
h_grid_mid = new Rectangle(ctx, pos, "darkgreen", c.gridSize, c.h);
pos.assign(c.w/4, 0);
h_grid_u1 = new Rectangle(ctx, pos, "darkgreen", c.gridSize, c.h);
pos.assign(c.w-c.w/4, 0);
h_grid_d1 = new Rectangle(ctx, pos, "darkgreen", c.gridSize, c.h);
}
export {initGameClientOnly}

View File

@@ -2,24 +2,28 @@
import {GameArea} from "./class/GameArea.js";
import * as d from "./draw.js";
import {gameLoop, newRound} from "./gameLoop.js"
// import {random} from "../shared_js/utils.js";
// import {socket} from "./ws.js";
// import * as c from "./constants.js"
import {initGame} from "../shared_js/initGame.js";
import {initGameClientOnly} from "./initGameClientOnly.js";
import { GameComponentsClient } from "./class/GameComponentsClient.js";
import {countdown} from "./utils.js";
/* Keys
Player 1: W/S
Player 2: Up/Down
Racket 1: W/S
Racket 2: Up/Down
Grid On-Off: G
*/
export const pong = new GameArea();
export const gc = new GameComponentsClient(pong.ctx);
function init()
function matchmaking()
{
initGame(pong.ctx);
initGameClientOnly(pong.ctx);
console.log("Searching an opponent..."); // PLACEHOLDER, todo draw
}
function matchmakingComplete()
{
console.log("Match Found !"); // PLACEHOLDER, TODO draw on canvas
countdown(3, startGame);
}
function startGame()
@@ -39,6 +43,4 @@ function startGame()
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
init();
export {startGame}
export {matchmaking, matchmakingComplete}

15
src/client/utils.ts Normal file
View File

@@ -0,0 +1,15 @@
export * from "../shared_js/utils.js"
function countdown(count: number, callback?: () => void)
{
console.log("countdown ", count); // PLACEHOLDER, TODO draw on canvas
if (count > 0) {
setTimeout(countdown, 1000, --count, callback);
}
else if (callback) {
callback();
}
}
export {countdown}

View File

@@ -1,64 +1,81 @@
import * as g from "./global.js" // temp
import {startGame} from "./pong.js";
import {EventTypes, EventData, EventGameUpdate} from "../shared_js/class/Event.js"
import {pong, gc} from "./global.js"
import * as ev from "../shared_js/class/Event.js"
import {matchmaking, matchmakingComplete} from "./pong.js";
import * as en from "../shared_js/enums.js"
import { Racket } from "../shared_js/class/Rectangle.js";
const wsPort = 8042;
const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong";
const socket = new WebSocket(wsUrl, "json");
socket.addEventListener("message", logListener);
socket.addEventListener("message", matchmakingListener);
function logListener(event: MessageEvent) {
console.log("data: " + event.data + " | [" + Date.now() + "]");
class ClientInfo {
id = "";
side: en.PlayerSide;
racket: Racket;
}
function matchmakingListener(event: MessageEvent)
{
console.log("matchmakingListener");
const data: EventData = JSON.parse(event.data);
if (data.type == EventTypes.start)
{
console.log("Event type = start");
socket.removeEventListener("message", matchmakingListener);
socket.addEventListener("message", inGameListener);
startGame();
export const clientInfo = new ClientInfo();
socket.addEventListener("open", (event) => {
socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, clientInfo.id) ));
});
socket.addEventListener("message", logListener);
socket.addEventListener("message", preMatchListener);
function logListener(this: WebSocket, event: MessageEvent) {
console.log("%i: " + event.data, Date.now());
}
function preMatchListener(this: WebSocket, event: MessageEvent) {
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
case en.EventTypes.assignId:
clientInfo.id = (<ev.EventAssignId>data).id;
break;
case en.EventTypes.matchmakingInProgress:
matchmaking();
break;
case en.EventTypes.matchmakingComplete:
clientInfo.side = (<ev.EventMatchmakingComplete>data).side;
if (clientInfo.side === en.PlayerSide.left) {
clientInfo.racket = gc.playerLeft;
}
else if (clientInfo.side === en.PlayerSide.right) {
clientInfo.racket = gc.playerRight;
}
socket.removeEventListener("message", preMatchListener);
socket.addEventListener("message", inGameListener);
socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) ));
matchmakingComplete();
break;
}
}
function inGameListener(event: MessageEvent)
{
console.log("inGameListener");
const data: EventData = JSON.parse(event.data);
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
case EventTypes.gameUpdate:
console.log("Event type = gameUpdate");
serverGameUpdate(data as EventGameUpdate);
case en.EventTypes.gameUpdate:
console.log("gameUpdate");
serverGameUpdate(data as ev.EventGameUpdate);
break;
case EventTypes.pause:
console.log("Event type = pause");
break;
case EventTypes.resume:
console.log("Event type = resume");
case en.EventTypes.matchNewRound:
console.log("matchNewRound//WIP");
break;
}
}
function serverGameUpdate(data: EventGameUpdate)
function serverGameUpdate(data: ev.EventGameUpdate)
{
g.player1.clear();
g.player1.pos.y = Math.floor(data.player1.y);
g.player1.update();
gc.playerLeft.clear();
gc.playerLeft.pos.y = Math.floor(data.playerLeft.y);
gc.playerLeft.update();
g.player2.clear();
g.player2.pos.y = Math.floor(data.player2.y);
g.player2.update();
gc.playerRight.clear();
gc.playerRight.pos.y = Math.floor(data.playerRight.y);
gc.playerRight.update();
}
// socket.addEventListener("open", (event) => {
// console.log("socket state %i", socket.readyState);
// });
export {socket}

View File

@@ -0,0 +1,32 @@
import {WebSocket} from "ws";
import {Racket} from "../../shared_js/class/Rectangle.js";
import { GameSession } from "./GameSession.js";
class Client {
socket: WebSocket;
id: string; // Pas indispensable si "socket" a une copie de "id"
isAlive: boolean;
gameSession: GameSession;
constructor(socket: WebSocket, id: string) {
this.socket = socket;
this.id = id;
this.isAlive = true;
}
}
class ClientPlayer extends Client {
racket: Racket;
constructor(socket: WebSocket, id: string, racket: Racket) {
super(socket, id);
this.racket = racket;
}
}
class ClientSpectator extends Client { // Wip, unused
constructor(socket: WebSocket, id: string) {
super(socket, id);
}
}
export {Client, ClientPlayer, ClientSpectator}

View File

@@ -0,0 +1,17 @@
import * as c from "../constants.js"
import { GameComponents } from "../../shared_js/class/GameComponents.js";
// Empty object replacement to the web-API (web-API useless on server-side)
class CanvasRenderingContext2D {}
const mockCTX = new CanvasRenderingContext2D();
class GameComponentsServer extends GameComponents {
constructor()
{
// @ts-ignore
super(mockCTX);
}
}
export {GameComponentsServer}

View File

@@ -0,0 +1,30 @@
import { ClientPlayer } from "./Client";
import {gameUpdate} from "../gameUpdate.js"
import { GameComponentsServer } from "./GameComponentsServer";
// Empty object replacement to the web-API (web-API useless on server-side)
class GameSession {
id: string; // url ?
playersMap: Map<string, ClientPlayer>;
unreadyPlayersMap: Map<string, ClientPlayer>;
updateInterval: NodeJS.Timer;
// gc: GameComponentsServer;
// updateInterval: NodeJS.Timer;
constructor(id: string) {
this.id = id;
this.playersMap = new Map();
this.unreadyPlayersMap = new Map();
// this.gc = new GameComponentsServer();
}
start() {
this.updateInterval = setInterval( () => {
const update = gameUpdate();
this.playersMap.forEach( (client) => {
client.socket.send(JSON.stringify(update));
});
}, 500);
}
}
export {GameSession}

2
src/server/constants.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "../shared_js/constants.js"

26
src/server/gameUpdate.ts Normal file
View File

@@ -0,0 +1,26 @@
import {EventTypes} from "../shared_js/enums.js"
import {EventGameUpdate} from "../shared_js/class/Event.js"
import { random } from "../shared_js/utils.js"; // temp
/*
import {Rectangle, MovingRectangle, Racket, Ball, Line} from "../shared_js/class/Rectangle.js";
import { Vector } from "../shared_js/class/Vector.js";
class CanvasRenderingContext2D {} // Empty object replacement to the web-API (web-API useless on server-side)
const mockCtx = new CanvasRenderingContext2D;
// @ts-ignore
const playerLeft = new Racket(mockCtx, new Vector(), "white", 1, 1, 1);
*/
function gameUpdate() : EventGameUpdate
{
const update: EventGameUpdate = {
type: EventTypes.gameUpdate,
playerLeft: {y: random(50, 650)},
playerRight: {y: random(50, 650)},
ball: {x: 0, y: 0, speed: 0}
};
return update;
}
export {gameUpdate}

View File

@@ -3,25 +3,13 @@ import http from "http";
import url from "url";
import fs from "fs";
import path from "path";
import { WebSocketServer, WebSocket } from "ws";
import { v4 as uuidv4 } from 'uuid';
import { random } from "../shared_js/utils.js";
import {EventTypes, EventData, EventGameUpdate} from "../shared_js/class/Event.js"
import {Rectangle, MovingRectangle, Player, Ball, Line} from "../shared_js/class/Rectangle.js";
import { Vector } from "../shared_js/class/Vector.js";
import {wsServer} from "./wsServer.js"; wsServer; // no-op
const hostname = "localhost";
const port = 8080;
const wsPort = 8042; // pas indispensable d'avoir un autre port si le WebSocket est limité à certaines routes
const root = "../../www/";
class CanvasRenderingContext2D {} // Empty object replacement to the web-API (web-API useless on server-side)
const mockCtx = new CanvasRenderingContext2D;
// @ts-ignore
const player1 = new Player(mockCtx, new Vector(), "white", 1, 1, 1);
const server = http.createServer((req, res) => {
// var q = new URL(req.url, `http://${req.getHeaders().host}`)
// @ts-ignore
@@ -46,84 +34,3 @@ const server = http.createServer((req, res) => {
server.listen(port, hostname, () => {
console.log(`Pong running at http://${hostname}:${port}/pong.html`);
});
const wsServer = new WebSocketServer({port: wsPort, path: "/pong"});
class Client {
socket: WebSocket;
id: string;
isAlive: boolean = true;
constructor(socket: WebSocket, id: string) {
this.socket = socket;
this.id = id;
}
}
const clientsArr: Client[] = [];
wsServer.on("connection", (socket, request) => {
const id = uuidv4();
const client = new Client(socket, id);
clientsArr.push(client);
socket.on("pong", function heartbeat() {
client.isAlive = true;
console.log("client %s alive at %i", client.id, Date.now());
});
socket.on("message", function log(data) {
console.log("received: %s", data);
});
socket.on("message", clientInputListener);
socket.send(JSON.stringify({type: EventTypes.start}));
// socket.send("connection success, bravo client " + id);
// socket.send("start");
// socket.send("json/20");
});
function clientInputListener(data: string)
{
return;
}
function deleteClient(client: Client)
{
var i = clientsArr.indexOf(client);
if (i != -1) {
clientsArr.splice(i, 1);
}
}
const pingInterval = setInterval( () => {
clientsArr.forEach( (client) => {
if (client.isAlive === false) {
client.socket.terminate();
console.log("client %s is no more at %i :'(", client.id, Date.now());
deleteClient(client);
return;
}
client.isAlive = false;
client.socket.ping();
});
}, 5000);
const gameUpdateInterval = setInterval( () => {
clientsArr.forEach( (client) => {
const update = {
type: EventTypes.gameUpdate,
player1: {y: random(50, 650)},
player2: {y: random(50, 650)},
ball: {x: 0, y: 0, speed: 0}
};
client.socket.send(JSON.stringify(update));
});
}, 500);
wsServer.on('close', () => {
clearInterval(pingInterval);
clearInterval(gameUpdateInterval);
});

156
src/server/wsServer.ts Normal file
View File

@@ -0,0 +1,156 @@
import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws";
class WebSocket extends BaseLibWebSocket {
id?: string;
}
import { IncomingMessage } from "http";
import { v4 as uuidv4 } from 'uuid';
import * as en from "../shared_js/enums.js"
import * as ev from "../shared_js/class/Event.js"
import {Client, ClientPlayer} from "./class/Client.js"
import {GameSession} from "./class/GameSession.js"
// pas indispensable d'avoir un autre port si le WebSocket est limité à certaines routes
// (et relié à un serveur http préexistant ?)
const wsPort = 8042;
export const wsServer = new WebSocketServer<WebSocket>({port: wsPort, path: "/pong"});
const clientsMap: Map<string, Client> = new Map; // socket.id/Client (unique Client)
const gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession (duplicates GameSession)
wsServer.on("connection", connectionListener);
wsServer.on("error", errorListener);
wsServer.on("close", closeListener);
function connectionListener(socket: WebSocket, request: IncomingMessage)
{
const id = uuidv4();
const client = new Client(socket, id);
clientsMap.set(id, client);
socket.id = id;
socket.on("pong", function heartbeat() {
client.isAlive = true;
console.log("%i: client %s is alive", Date.now(), client.id);
});
socket.on("message", function log(data) {
console.log("data: " + data);
});
socket.once("message", clientAnnounceListener);
}
function clientAnnounceListener(this: WebSocket, data: string)
{
try {
const msg : ev.ClientAnnounce = JSON.parse(data);
if (msg.type === en.EventTypes.clientAnnounce) {
// TODO: reconnection with msg.id
// TODO: spectator/player distinction with msg.type
this.send(JSON.stringify( new ev.EventAssignId(this.id) ))
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ))
matchmaking(this);
}
else {
console.log("Invalid ClientAnnounce");
}
return;
}
catch (e) {
console.log("Invalid JSON");
}
this.once("message", clientAnnounceListener);
}
function matchmaking(socket: WebSocket)
{
// TODO Actual Matchmaking
// TODO: Only once
const id = uuidv4();
const gameSession = new GameSession(id);
gameSessionsMap.set(id, gameSession);
// TODO: Per player
const player: ClientPlayer = clientsMap.get(socket.id) as ClientPlayer;
player.gameSession = gameSession;
gameSession.playersMap.set(socket.id, player);
gameSession.unreadyPlayersMap.set(socket.id, player);
socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) ));
socket.once("message", playerReadyConfirmationListener);
// socket.on("message", clientInputListener);
// setinterval gameloop
}
function playerReadyConfirmationListener(this: WebSocket, data: string)
{
try {
const msg : ev.ClientEvent = JSON.parse(data);
if (msg.type === en.EventTypes.clientPlayerReady) {
const client = clientsMap.get(this.id);
const gameSession = client.gameSession;
gameSession.unreadyPlayersMap.delete(this.id);
if (gameSession.unreadyPlayersMap.size === 0) {
gameSession.start();
}
}
else {
console.log("Invalid PlayerReady confirmation");
}
return;
}
catch (e) {
console.log("Invalid JSON");
}
this.once("message", playerReadyConfirmationListener);
}
function clientInputListener(this: WebSocket, data: string)
{
try {
const input: ev.ClientEvent = JSON.parse(data);
if (input.type === en.EventTypes.clientInput) {
console.log("Valid EventInput");
}
else {
console.log("Invalid EventInput");
}
}
catch (e) {
console.log("Invalid JSON");
}
}
////////////
////////////
const pingInterval = setInterval( () => {
clientsMap.forEach( (client, key, map) => {
if (client.isAlive === false) {
client.socket.terminate();
map.delete(key);
console.log("%i: client %s is no more :'(", Date.now(), key);
return;
}
client.isAlive = false;
client.socket.ping();
});
}, 5000);
function closeListener()
{
clearInterval(pingInterval);
// clearInterval(gameUpdateInterval); // TODO: Per Game Session
}
function errorListener(error: Error)
{
console.log("Error: " + JSON.stringify(error));
}

View File

@@ -1,37 +1,66 @@
/* Server */
enum EventTypes {
gameUpdate = 1,
start,
pause,
resume
import * as en from "../enums.js"
/* From Server */
class ServerEvent {
type: en.EventTypes;
constructor(type: en.EventTypes = 0) {
this.type = type;
}
}
interface EventData {
type: EventTypes;
class EventAssignId extends ServerEvent {
id: string;
constructor(id: string) {
super(en.EventTypes.assignId);
this.id = id;
}
}
class EventGameUpdate implements EventData {
type: EventTypes = EventTypes.gameUpdate;
player1 = {y: 0};
player2 = {y: 0};
class EventMatchmakingComplete extends ServerEvent {
side: en.PlayerSide;
constructor(side: en.PlayerSide) {
super(en.EventTypes.matchmakingComplete);
this.side = side;
}
}
class EventGameUpdate extends ServerEvent {
playerLeft = {y: 0};
playerRight = {y: 0};
ball = {x: 0, y: 0, speed: 0};
constructor() {
super(en.EventTypes.gameUpdate);
}
}
/* Client */
enum InputEnum {
up = 1,
down
/* From Client */
class ClientEvent {
type: en.EventTypes;
constructor(type: en.EventTypes = 0) {
this.type = type;
}
}
class EventInput {
input: InputEnum;
constructor(input: InputEnum = 0) {
class ClientAnnounce extends ClientEvent {
role: en.ClientRole;
id: string;
constructor(role: en.ClientRole, id: string = "") {
super(en.EventTypes.clientAnnounce);
this.role = role;
}
}
class EventInput extends ClientEvent {
input: en.InputEnum;
constructor(input: en.InputEnum = 0) {
super(en.EventTypes.clientInput);
this.input = input;
}
}
export {
EventTypes, EventData, EventGameUpdate,
InputEnum, EventInput
ServerEvent, EventAssignId, EventMatchmakingComplete, EventGameUpdate,
ClientEvent, ClientAnnounce, EventInput
}

View File

@@ -0,0 +1,33 @@
import * as c from "../constants.js"
import {VectorInteger} from "./Vector.js";
import {Rectangle, Racket, Ball} from "./Rectangle.js";
class GameComponents {
wallTop: Rectangle;
wallBottom: Rectangle;
playerLeft: Racket;
playerRight: Racket;
ball: Ball;
constructor(ctx?: CanvasRenderingContext2D)
{
let pos = new VectorInteger;
pos.assign(0, 0);
this.wallTop = new Rectangle(ctx, pos, "grey", c.w, c.wallSize);
pos.assign(0, c.h-c.wallSize);
this.wallBottom = new Rectangle(ctx, pos, "grey", c.w, c.wallSize);
pos.assign(0+c.pw, c.h_mid-c.ph/2);
this.playerLeft = new Racket(ctx, pos, "white", c.pw, c.ph, c.playerSpeed);
pos.assign(c.w-c.pw-c.pw, c.h_mid-c.ph/2);
this.playerRight = new Racket(ctx, pos, "white", c.pw, c.ph, c.playerSpeed);
pos.assign(c.w_mid-c.ballSize/2, c.h_mid-c.ballSize/2);
this.ball = new Ball(ctx, pos, "white", c.ballSize, c.ballSpeed);
this.ball.dir.assign(-0.8, +0.2);
}
}
export {GameComponents}

View File

@@ -77,7 +77,7 @@ class MovingRectangle extends Rectangle implements Moving {
}
}
class Player extends MovingRectangle {
class Racket extends MovingRectangle {
constructor(ctx: CanvasRenderingContext2D, pos: VectorInteger, color: string, width: number, height: number, baseSpeed: number) {
super(ctx, pos, color, width, height, baseSpeed);
}
@@ -88,9 +88,9 @@ class Ball extends MovingRectangle {
super(ctx, pos, color, size, size, baseSpeed);
}
bounce(collider?: Rectangle) {
/* Could be more generic, but testing only player is enough,
because in Pong collider can only be Player or Wall. */
if (collider instanceof Player) {
/* Could be more generic, but testing only Racket is enough,
because in Pong collider can only be Racket or Wall. */
if (collider instanceof Racket) {
this._bouncePlayer(collider);
}
else {
@@ -112,8 +112,8 @@ class Ball extends MovingRectangle {
private _bounceWall() { // Should be enough for Wall
this.dir.y = this.dir.y * -1;
}
private _bouncePlayer(collider: Player) { // WIP
// Bounce for Player need to be more complexe than this
private _bouncePlayer(collider: Racket) { // WIP
// Bounce for Racket need to be more complexe than this
this.speed += this.baseSpeed/20;
this.dir.x = this.dir.x * -1;
}
@@ -154,7 +154,7 @@ class Line extends Rectangle {
}
}
export {Rectangle, MovingRectangle, Player, Ball, Line}
export {Rectangle, MovingRectangle, Racket, Ball, Line}
// How to handle const export in initGame ?
// example for class Rectangle

37
src/shared_js/enums.ts Normal file
View File

@@ -0,0 +1,37 @@
enum EventTypes {
// Class Implemented
gameUpdate = 1,
assignId,
matchmakingComplete,
// Generic
matchmakingInProgress,
matchNewRound, // unused
matchStart, // unused
matchPause, // unused
matchResume, // unused
// Client
clientAnnounce,
clientPlayerReady,
clientInput,
}
enum InputEnum {
up = 1,
down
}
enum PlayerSide {
left = 1,
right
}
enum ClientRole {
player = 1,
spectator
}
export {EventTypes, InputEnum, PlayerSide, ClientRole}

View File

@@ -1,31 +0,0 @@
import * as c from "./constants.js"
import {Vector, VectorInteger} from "./class/Vector.js";
import {Rectangle, MovingRectangle, Player, Ball, Line} from "./class/Rectangle.js";
export let wall_top: Rectangle;
export let wall_bottom: Rectangle;
export let player1: Player;
export let player2: Player;
export let ball: Ball;
function initGame(ctx: CanvasRenderingContext2D)
{
let pos = new VectorInteger;
// Component
pos.assign(0, 0);
wall_top = new Rectangle(ctx, pos, "grey", c.w, c.wallSize);
pos.assign(0, c.h-c.wallSize);
wall_bottom = new Rectangle(ctx, pos, "grey", c.w, c.wallSize);
pos.assign(0+c.pw, c.h_mid-c.ph/2);
player1 = new Player(ctx, pos, "white", c.pw, c.ph, c.playerSpeed);
pos.assign(c.w-c.pw-c.pw, c.h_mid-c.ph/2);
player2 = new Player(ctx, pos, "white", c.pw, c.ph, c.playerSpeed);
pos.assign(c.w_mid-c.ballSize/2, c.h_mid-c.ballSize/2);
ball = new Ball(ctx, pos, "white", c.ballSize, c.ballSpeed);
ball.dir.assign(-0.8, +0.2);
}
export {initGame}