WIP, tout est en chantier, très content :)
This commit is contained in:
51
src/client/class/GameComponentsClient.ts
Normal file
51
src/client/class/GameComponentsClient.ts
Normal 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}
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
@@ -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
15
src/client/utils.ts
Normal 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}
|
||||
@@ -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}
|
||||
|
||||
32
src/server/class/Client.ts
Normal file
32
src/server/class/Client.ts
Normal 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}
|
||||
17
src/server/class/GameComponentsServer.ts
Normal file
17
src/server/class/GameComponentsServer.ts
Normal 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}
|
||||
30
src/server/class/GameSession.ts
Normal file
30
src/server/class/GameSession.ts
Normal 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
2
src/server/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export * from "../shared_js/constants.js"
|
||||
26
src/server/gameUpdate.ts
Normal file
26
src/server/gameUpdate.ts
Normal 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}
|
||||
@@ -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
156
src/server/wsServer.ts
Normal 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));
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
33
src/shared_js/class/GameComponents.ts
Normal file
33
src/shared_js/class/GameComponents.ts
Normal 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}
|
||||
@@ -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
37
src/shared_js/enums.ts
Normal 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}
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user