basic Websocket client-server
This commit is contained in:
75
package-lock.json
generated
75
package-lock.json
generated
@@ -4,8 +4,14 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.5.3",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
},
|
||||
@@ -15,6 +21,21 @@
|
||||
"integrity": "sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
||||
@@ -27,6 +48,34 @@
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
|
||||
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -36,11 +85,37 @@
|
||||
"integrity": "sha512-3JRwhbjI+cHLAkUorhf8RnqUbFXajvzX4q6fMn5JwkgtuwfYtRQYI3u4V92vI6NJuTsbBQWWh3RZjFsuevyMGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
|
||||
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.5",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/ws": "^8.5.3",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@ class GameArea {
|
||||
deleteKey(key: string) {
|
||||
key = key.toLowerCase();
|
||||
var i = this.keys.indexOf(key);
|
||||
if (i != -1)
|
||||
if (i != -1) {
|
||||
this.keys.splice(i, 1);
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class MovingRectangle extends Rectangle implements Moving {
|
||||
this.speed = baseSpeed;
|
||||
}
|
||||
move(delta: number) { // Math.floor WIP until VectorInteger debug
|
||||
console.log("delta: "+ delta);
|
||||
// console.log("delta: "+ delta);
|
||||
// console.log("speed: "+ this.speed);
|
||||
// console.log("speed*delta: "+ this.speed * delta);
|
||||
this.pos.x += Math.floor(this.dir.x * this.speed * delta);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
#canvas-container {
|
||||
text-align: center;
|
||||
/* border: dashed red 5px; */
|
||||
border: dashed red 5px;
|
||||
/* max-height: 80vh; */
|
||||
/* overflow: hidden; */
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
background-color: #333333;
|
||||
max-width: 75vw;
|
||||
/* max-height: 100vh; */
|
||||
/* width: 80%; */
|
||||
width: 80%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -6,6 +6,8 @@ import {TextElem, TextNumericValue} from "./class/Text.js";
|
||||
import * as d from "./draw.js";
|
||||
import {gameLoop, newRound} from "./gameLoop.js"
|
||||
import {random} from "./utils.js";
|
||||
import {socket} from "./ws.js";
|
||||
|
||||
|
||||
/* Keys
|
||||
Player 1: W/S
|
||||
@@ -31,10 +33,56 @@ export let h_grid_mid: Rectangle;
|
||||
export let h_grid_u1: Rectangle;
|
||||
export let h_grid_d1: Rectangle;
|
||||
|
||||
function init()
|
||||
{
|
||||
initGame();
|
||||
initGameClientOnly();
|
||||
console.log("socket state %i", socket.readyState);
|
||||
}
|
||||
|
||||
function startGame()
|
||||
{
|
||||
pong = new GameArea();
|
||||
// Start
|
||||
d.drawInit();
|
||||
window.addEventListener('keydown', function (e) {
|
||||
pong.addKey(e.key);
|
||||
});
|
||||
window.addEventListener('keyup', function (e) {
|
||||
pong.deleteKey(e.key);
|
||||
});
|
||||
pong.interval = window.setInterval(gameLoop, 15); // min interval on Firefox seems to be 15. Chrome can go lower.
|
||||
setTimeout(newRound, 1000);
|
||||
}
|
||||
|
||||
function initGameClientOnly()
|
||||
{
|
||||
let pos = new VectorInteger;
|
||||
|
||||
// Const
|
||||
const w = pong.canvas.width;
|
||||
const h = pong.canvas.height;
|
||||
const w_mid = Math.floor(w/2);
|
||||
const h_mid = Math.floor(h/2);
|
||||
const gridSize = Math.floor(w/500);
|
||||
|
||||
// Grid
|
||||
pos.assign(0, h_mid);
|
||||
w_grid_mid = new Rectangle(pong.ctx, pos, "darkgreen", w, gridSize);
|
||||
pos.assign(0, h/4);
|
||||
w_grid_u1 = new Rectangle(pong.ctx, pos, "darkgreen", w, gridSize);
|
||||
pos.assign(0, h-h/4);
|
||||
w_grid_d1 = new Rectangle(pong.ctx, pos, "darkgreen", w, gridSize);
|
||||
pos.assign(w_mid, 0);
|
||||
h_grid_mid = new Rectangle(pong.ctx, pos, "darkgreen", gridSize, h);
|
||||
pos.assign(w/4, 0);
|
||||
h_grid_u1 = new Rectangle(pong.ctx, pos, "darkgreen", gridSize, h);
|
||||
pos.assign(w-w/4, 0);
|
||||
h_grid_d1 = new Rectangle(pong.ctx, pos, "darkgreen", gridSize, h);
|
||||
}
|
||||
|
||||
function initGame()
|
||||
{
|
||||
pong = new GameArea();
|
||||
// Const
|
||||
const w = pong.canvas.width;
|
||||
const h = pong.canvas.height;
|
||||
@@ -46,7 +94,6 @@ function startGame()
|
||||
const scoreSize = Math.floor(w/16);
|
||||
const midLineSize = Math.floor(w/150);
|
||||
const wallSize = Math.floor(w/100);
|
||||
const gridSize = Math.floor(w/500);
|
||||
const playerSpeed = Math.floor(w/1.5); // pixel per second
|
||||
const ballSpeed = Math.floor(w/1.5); // pixel per second
|
||||
|
||||
@@ -75,34 +122,11 @@ function startGame()
|
||||
|
||||
pos.assign(w_mid-midLineSize/2, 0+wallSize);
|
||||
midLine = new Line(pong.ctx, pos, "white", midLineSize, h-wallSize*2, 15);
|
||||
|
||||
// Grid
|
||||
pos.assign(0, h_mid);
|
||||
w_grid_mid = new Rectangle(pong.ctx, pos, "darkgreen", w, gridSize);
|
||||
pos.assign(0, h/4);
|
||||
w_grid_u1 = new Rectangle(pong.ctx, pos, "darkgreen", w, gridSize);
|
||||
pos.assign(0, h-h/4);
|
||||
w_grid_d1 = new Rectangle(pong.ctx, pos, "darkgreen", w, gridSize);
|
||||
pos.assign(w_mid, 0);
|
||||
h_grid_mid = new Rectangle(pong.ctx, pos, "darkgreen", gridSize, h);
|
||||
pos.assign(w/4, 0);
|
||||
h_grid_u1 = new Rectangle(pong.ctx, pos, "darkgreen", gridSize, h);
|
||||
pos.assign(w-w/4, 0);
|
||||
h_grid_d1 = new Rectangle(pong.ctx, pos, "darkgreen", gridSize, h);
|
||||
|
||||
// Start
|
||||
d.drawInit();
|
||||
window.addEventListener('keydown', function (e) {
|
||||
pong.addKey(e.key);
|
||||
});
|
||||
window.addEventListener('keyup', function (e) {
|
||||
pong.deleteKey(e.key);
|
||||
});
|
||||
pong.interval = window.setInterval(gameLoop, 15); // min interval on Firefox seems to be 15. Chrome can go lower.
|
||||
setTimeout(newRound, 1000);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
startGame();
|
||||
init();
|
||||
|
||||
export {startGame}
|
||||
|
||||
@@ -3,4 +3,8 @@ function random(min: number = 0, max: number = 1) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
export {random}
|
||||
function sleep (ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export {random, sleep}
|
||||
|
||||
81
src/client/ws.ts
Normal file
81
src/client/ws.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
|
||||
import * as g from "./pong.js"
|
||||
import {startGame} from "./pong.js";
|
||||
|
||||
const wsPort = 8042;
|
||||
const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong";
|
||||
const socket = new WebSocket(wsUrl, "json");
|
||||
|
||||
enum EventTypes {
|
||||
gameUpdate = 1,
|
||||
start,
|
||||
pause,
|
||||
resume
|
||||
}
|
||||
|
||||
interface EventData {
|
||||
type: EventTypes;
|
||||
}
|
||||
|
||||
class EventGameUpdate implements EventData {
|
||||
type: EventTypes = EventTypes.gameUpdate;
|
||||
player1 = {y: 0};
|
||||
player2 = {y: 0};
|
||||
ball = {x: 0, y: 0, speed: 0};
|
||||
}
|
||||
|
||||
socket.addEventListener("message", logListener);
|
||||
socket.addEventListener("message", matchmakingListener);
|
||||
|
||||
|
||||
function logListener(event: MessageEvent) {
|
||||
console.log("data: " + event.data + " | [" + Date.now() + "]");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function inGameListener(event: MessageEvent)
|
||||
{
|
||||
console.log("inGameListener");
|
||||
const data: EventData = JSON.parse(event.data);
|
||||
switch (data.type) {
|
||||
case EventTypes.gameUpdate:
|
||||
console.log("Event type = gameUpdate");
|
||||
serverGameUpdate(data as EventGameUpdate);
|
||||
break;
|
||||
case EventTypes.pause:
|
||||
console.log("Event type = pause");
|
||||
break;
|
||||
case EventTypes.resume:
|
||||
console.log("Event type = resume");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function serverGameUpdate(data: EventGameUpdate)
|
||||
{
|
||||
g.player1.clear();
|
||||
g.player1.pos.y = Math.floor(data.player1.y);
|
||||
g.player1.update();
|
||||
|
||||
g.player2.clear();
|
||||
g.player2.pos.y = Math.floor(data.player2.y);
|
||||
g.player2.update();
|
||||
}
|
||||
|
||||
// socket.addEventListener("open", (event) => {
|
||||
// console.log("socket state %i", socket.readyState);
|
||||
// });
|
||||
|
||||
export {socket}
|
||||
@@ -1,17 +1,16 @@
|
||||
|
||||
// var http = require("http");
|
||||
// var url = require("url");
|
||||
// var fs = require("fs");
|
||||
// var path = require("path");
|
||||
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 "../client/utils.js";
|
||||
|
||||
const hostname = "localhost";
|
||||
const port = 8080;
|
||||
const root = "/mnt/c/Users/Lucky/Desktop/code/ft_transcendence/www/";
|
||||
const wsPort = 8042; // pas indispensable d'avoir un autre port si le WebSocket est limité à certaines routes
|
||||
const root = "../../www/";
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
// var q = new URL(req.url, `http://${req.getHeaders().host}`)
|
||||
@@ -37,3 +36,78 @@ 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 message(data) {
|
||||
console.log("received: %s", data);
|
||||
});
|
||||
|
||||
socket.send(JSON.stringify({type: 2})); // start
|
||||
// socket.send("connection success, bravo client " + id);
|
||||
// socket.send("start");
|
||||
// socket.send("json/20");
|
||||
});
|
||||
|
||||
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: 1,
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user