Benjamin Kraft 2 years ago
parent 3906e45f26
commit f58da88801
  1. 1
      .gitignore
  2. 33
      data/scripts/events.js
  3. 104
      data/scripts/init.js
  4. 0
      public/changelog.txt
  5. 0
      public/data/images/favicon.ico
  6. 0
      public/data/images/gem_border.png
  7. 0
      public/data/images/gem_content.png
  8. 0
      public/data/images/gem_full.png
  9. 4
      public/data/lib/socketiop2p.min.js
  10. 14
      public/data/scripts/events.js
  11. 94
      public/data/scripts/game.js
  12. 100
      public/data/scripts/init.js
  13. 10
      public/data/scripts/leaderboard.js
  14. 4
      public/data/scripts/online.js
  15. 3
      public/data/settings/get_port.php
  16. 0
      public/data/settings/libraries.json
  17. 1
      public/data/settings/settings.json
  18. 0
      public/data/styles/Tajawal/OFL.txt
  19. 0
      public/data/styles/Tajawal/Tajawal-Black.ttf
  20. 0
      public/data/styles/Tajawal/Tajawal-Bold.ttf
  21. 0
      public/data/styles/Tajawal/Tajawal-ExtraBold.ttf
  22. 0
      public/data/styles/Tajawal/Tajawal-ExtraLight.ttf
  23. 0
      public/data/styles/Tajawal/Tajawal-Light.ttf
  24. 0
      public/data/styles/Tajawal/Tajawal-Medium.ttf
  25. 0
      public/data/styles/Tajawal/Tajawal-Regular.ttf
  26. 0
      public/data/styles/checkbox.css
  27. 0
      public/data/styles/color_picker.css
  28. 0
      public/data/styles/font.ttf
  29. 0
      public/data/styles/range_input.css
  30. 13
      public/index.html
  31. 0
      public/styles.css
  32. BIN
      public/thumbnail.png
  33. 3
      server/.gitignore
  34. 439
      server/package-lock.json
  35. 18
      server/package.json
  36. 104
      server/src/chainreact.ts
  37. 168
      server/src/client.ts
  38. 20
      server/src/definitions/serialized.d.ts
  39. 74
      server/src/definitions/settings.d.ts
  40. 37
      server/src/game_standard.ts
  41. 7
      server/src/index.ts
  42. 105
      server/src/logger.ts
  43. 148
      server/src/manager.ts
  44. 135
      server/src/room.ts
  45. 39
      server/src/start.ts
  46. 11
      server/tsconfig.json

1
.gitignore vendored

@ -1 +1,2 @@
.idea .idea
.env

@ -1,33 +0,0 @@
'use strict';
function keyPressed(){
}
function keyReleased(){
}
function mouseMoved(){
}
function mouseDragged(){
}
function mousePressed(){
if (game)
if (!game.winId)
game.onMouseDown();
}
function mouseReleased(){
}
window.onresize = () => {
let w = $('#canvas-holder').width();
let h = $('#canvas-holder').height();
resizeCanvas(w, h);
}

@ -1,104 +0,0 @@
'use strict';
let projectName = "chainreact";
let debug = false,
productionMode = false,
font,
localSettings,
loader;
//Only for online games
let socket;
let game;
let gemContentImage;
let gemBorderImage;
let gemContentGraphics = {};
let antiCacheQuery = '?_=' + new Date().getTime();
function preload(){
localSettings = loadJSON('data/settings/settings.json' + antiCacheQuery, json => {
console.log('Local settings loaded: ', json);
}, error => {
console.log('Local settings failed: ', error);
});
font = loadFont('data/styles/Tajawal/Tajawal-Regular.ttf', json => {
console.log('Local font loaded: ', json);
}, error => {
console.log('Local font failed: ', error);
});
gemContentImage = loadImage('data/images/gem_content.png', img => {
console.log('Image loaded: ', img);
}, error => {
console.log('Image failed: ' , error);
});
gemBorderImage = loadImage('data/images/gem_border.png', img => {
console.log('Image loaded: ', img);
}, error => {
console.log('Image failed: ' , error);
});
loadJSON('data/settings/libraries.json' + antiCacheQuery, json => {
loadScripts(json)
console.log('BenjoCraeft library scripts loaded: ', json)
});
}
function setup(){
canvasSetup();
interfaceSetup();
}
function draw(){
background(0, 0, 10);
if (game){
game.display();
game.update();
}
if (loader){
loader.update();
loader.display();
}
if (debug) debugInformation();
}
function canvasSetup(){
setFrameRate(60);
let w = $('#canvas-holder').width(),
h = $('#canvas-holder').height();
let canvas = createCanvas(w, h);
canvas.parent('canvas-holder');
textFont(font);
textAlign(CENTER, CENTER);
imageMode(CENTER);
colorMode(HSB);
}
function interfaceSetup(){
window.onresize();
setInterval(() => window.onresize(), 500);
$('#version').html(localSettings.project.version);
$('#start_feedback, #give_feedback').attr('disabled', 'disabled');
nameTyped($('#main > input'));
$('#main').fadeIn(menuesFadeTime);
}
function loadScripts(libs){
for (let script in libs){
if (libs[script]){
let url = '/lib/benjocraeft/' + script + '.js'
$.getScript(url, () => {
console.log('Successfully loaded script: ', url)
});
}
}
}

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 318 B

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 679 B

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 585 B

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 782 B

File diff suppressed because one or more lines are too long

@ -0,0 +1,14 @@
'use strict';
p.mousePressed = () => {
if (game)
if (!game.winId)
game.onMouseDown();
}
window.onresize = () => {
let w = $('#canvas-holder').width();
let h = $('#canvas-holder').height();
p.resizeCanvas(w, h);
}

@ -17,7 +17,7 @@ function readGameSettings(){
for (let i = 0; i < maxIndex * 0.1; i++){ for (let i = 0; i < maxIndex * 0.1; i++){
let index; let index;
do{ do{
index = floor(random(0, maxIndex)); index = Math.floor(p.random(0, maxIndex));
} while (indices.find(i => i === index) != null); } while (indices.find(i => i === index) != null);
indices.push(index); indices.push(index);
} }
@ -66,17 +66,17 @@ class Game{
} }
get winCount(){ get winCount(){
return round(this.fields.filter(f => !f.isEmpty).length * 0.7); return Math.round(this.fields.filter(f => !f.isEmpty).length * 0.7);
} }
//Width and height of game field //Width and height of game field
get size(){ get size(){
return Math.min(width, height - this.leaderboard.height); return Math.min(p.width, p.height - this.leaderboard.height);
} }
//Upper left corner of game field //Upper left corner of game field
get pos(){ get pos(){
return {x: (width - this.size) / 2, y: (height + this.leaderboard.height - this.size) / 2}; return {x: (p.width - this.size) / 2, y: (p.height + this.leaderboard.height - this.size) / 2};
} }
get hasTurn(){ get hasTurn(){
@ -123,8 +123,8 @@ class Game{
setPlayerHues(colors){ setPlayerHues(colors){
for (let key in colors){ for (let key in colors){
this.playerHues[key] = colors[key]; this.playerHues[key] = colors[key];
let graphics = createGraphics(100, 100); let graphics = p.createGraphics(100, 100);
graphics.colorMode(HSB); graphics.colorMode(p.HSB);
graphics.tint(colors[key], 100, 100); graphics.tint(colors[key], 100, 100);
graphics.image(gemContentImage, 0, 0, 100, 100); graphics.image(gemContentImage, 0, 0, 100, 100);
gemContentGraphics[colors[key]] = graphics; gemContentGraphics[colors[key]] = graphics;
@ -304,8 +304,8 @@ class Field{
} }
get isMouseOver(){ get isMouseOver(){
return mouseX > this.pos.x && mouseX < this.pos.x + this.size return p.mouseX > this.pos.x && p.mouseX < this.pos.x + this.size
&& mouseY > this.pos.y && mouseY < this.pos.y + this.size; && p.mouseY > this.pos.y && p.mouseY < this.pos.y + this.size;
} }
get isNeutral(){ get isNeutral(){
@ -326,9 +326,9 @@ class Field{
setup(allFields){ setup(allFields){
if (!this.isEmpty){ if (!this.isEmpty){
for (let angle = 0; angle < TWO_PI; angle += PI / 2){ for (let angle = 0; angle < Math.PI * 2; angle += Math.PI / 2){
let x = round(sin(angle)); let x = Math.round(Math.sin(angle));
let y = round(cos(angle)); let y = Math.round(Math.cos(angle));
if (this.getPartner(x, y, allFields)) if (this.getPartner(x, y, allFields))
this.slots.push(new Slot(x, y)); this.slots.push(new Slot(x, y));
} }
@ -347,19 +347,19 @@ class Field{
display(){ display(){
if (!this.isEmpty){ if (!this.isEmpty){
noStroke(); p.noStroke();
fill(0, 0, 5); p.fill(0, 0, 5);
let size = this.size / 3; let size = this.size / 3;
rect(this.pos.x + size, this.pos.y + size, size, size); p.rect(this.pos.x + size, this.pos.y + size, size, size);
} }
this.slots.forEach(s => s.display()); this.slots.forEach(s => s.display());
let s = this.isNeutral ? 0 : 50; let s = this.isNeutral ? 0 : 50;
stroke(100); p.stroke(100);
strokeWeight(1); p.strokeWeight(1);
fill(game.playerHues[this.ownerId], s, 60, 0.3); p.fill(game.playerHues[this.ownerId], s, 60, 0.3);
rect(this.pos.x, this.pos.y, this.size, this.size); p.rect(this.pos.x, this.pos.y, this.size, this.size);
} }
onMouseDown(){ onMouseDown(){
@ -389,15 +389,15 @@ class Slot{
} }
get isMouseOver(){ get isMouseOver(){
return mouseX > this.pos.x && mouseX < this.pos.x + this.size return p.mouseX > this.pos.x && p.mouseX < this.pos.x + this.size
&& mouseY > this.pos.y && mouseY < this.pos.y + this.size; && p.mouseY > this.pos.y && p.mouseY < this.pos.y + this.size;
} }
get color(){ get color(){
let h = game.playerHues[this.field.ownerId]; let h = game.playerHues[this.field.ownerId];
let s = this.isFilled ? 100 : 0; let s = this.isFilled ? 100 : 0;
let b = this.isMouseOver && !this.isFilled ? 90 : 70; let b = this.isMouseOver && !this.isFilled ? 90 : 70;
return color(h, s, b) return p.color(h, s, b);
} }
get partner(){ get partner(){
@ -414,24 +414,24 @@ class Slot{
display(){ display(){
fill(this.color); p.fill(this.color);
noStroke(); p.noStroke();
rect(this.pos.x, this.pos.y, this.size, this.size); p.rect(this.pos.x, this.pos.y, this.size, this.size);
if (this.isFilled){ if (this.isFilled){
stroke(0, 0, 0); p.stroke(0, 0, 0);
strokeWeight(3); p.strokeWeight(3);
let x = this.pos.x + this.size / 2; let x = this.pos.x + this.size / 2;
let y = this.pos.y + this.size / 2; let y = this.pos.y + this.size / 2;
line(this.pos.x, y, this.pos.x + this.size, y); p.line(this.pos.x, y, this.pos.x + this.size, y);
line(x, this.pos.y, x, this.pos.y + this.size); p.line(x, this.pos.y, x, this.pos.y + this.size);
} }
if (this.isHighlighted){ if (this.isHighlighted){
stroke(0); p.stroke(0);
fill(0); p.fill(0);
let hs = this.size / 2; let hs = this.size / 2;
ellipse(this.pos.x + hs, this.pos.y + hs, hs, hs); p.ellipse(this.pos.x + hs, this.pos.y + hs, hs, hs);
} }
} }
@ -461,13 +461,13 @@ class Spread{
get startPos(){ get startPos(){
let x = this.slot.field.pos.x + this.slot.size; let x = this.slot.field.pos.x + this.slot.size;
let y = this.slot.field.pos.y + this.slot.size; let y = this.slot.field.pos.y + this.slot.size;
return createVector(x, y); return p.createVector(x, y);
} }
get endPos(){ get endPos(){
let x = this.slot.partner.field.pos.x + this.slot.size; let x = this.slot.partner.field.pos.x + this.slot.size;
let y = this.slot.partner.field.pos.y + this.slot.size; let y = this.slot.partner.field.pos.y + this.slot.size;
return createVector(x, y); return p.createVector(x, y);
} }
get pos(){ get pos(){
@ -481,26 +481,26 @@ class Spread{
display(){ display(){
noStroke(); p.noStroke();
let x = this.pos.x + this.size / 2; let x = this.pos.x + this.size / 2;
let y = this.pos.y + this.size / 2; let y = this.pos.y + this.size / 2;
fill(0, 0, 0); p.fill(0, 0, 0);
ellipse(x, y, this.size * 0.9, this.size * 0.9); p.ellipse(x, y, this.size * 0.9, this.size * 0.9);
let c = frameCount % 10 >= 5 && !this.moving ? color(0, 0, 0) : this.color; let c = p.frameCount % 10 >= 5 && !this.moving ? p.color(0, 0, 0) : this.color;
fill(c); p.fill(c);
ellipse(x, y, this.size * 0.5, this.size * 0.5); p.ellipse(x, y, this.size * 0.5, this.size * 0.5);
} }
update(){ update(){
this.waitTime += 1 / frameRate(); this.waitTime += 1 / p.frameRate();
if (this.waitTime >= 0.2 && !this.moving){ if (this.waitTime >= 0.2 && !this.moving){
this.moving = true; this.moving = true;
this.slot.isFilled = false; this.slot.isFilled = false;
this.slot.field.ownerId = 'neutral'; this.slot.field.ownerId = 'neutral';
} }
if (this.moving){ if (this.moving){
this.moveProgress += 1.5 / frameRate(); this.moveProgress += 1.5 / p.frameRate();
} }
if (this.moveProgress >= 1) if (this.moveProgress >= 1)
this.fillEndSlot(); this.fillEndSlot();
@ -556,14 +556,14 @@ class Countdown{
display(){ display(){
if (!this.isFinished){ if (!this.isFinished){
let panelW = (width - game.size) / 2; let panelW = (p.width - game.size) / 2;
stroke(200); p.stroke(200);
fill(150); p.fill(150);
textSize(panelW / 4); p.textSize(panelW / 4);
let x = panelW / 2; let x = panelW / 2;
let y = game.pos.y + game.size / 2; let y = game.pos.y + game.size / 2;
let rounded = floor(this.time); let rounded = Math.floor(this.time);
text(rounded, x, y); p.text(rounded, x, y);
} }
} }

@ -0,0 +1,100 @@
'use strict';
let projectName = "chainreact";
let debug = false,
productionMode = false,
font,
localSettings,
loader;
//Only for online games
let socket;
let game;
let gemContentImage;
let gemBorderImage;
let gemContentGraphics = {};
let antiCacheQuery = '?_=' + new Date().getTime();
let p;
p = new p5(s => {
s.preload = () => {
localSettings = p.loadJSON('data/settings/settings.json' + antiCacheQuery, json => {
console.log('Local settings loaded: ', json);
}, error => {
console.log('Local settings failed: ', error);
});
font = p.loadFont('data/styles/Tajawal/Tajawal-Regular.ttf', json => {
console.log('Local font loaded: ', json);
}, error => {
console.log('Local font failed: ', error);
});
gemContentImage = p.loadImage('data/images/gem_content.png', img => {
console.log('Image loaded: ', img);
}, error => {
console.log('Image failed: ', error);
});
gemBorderImage = p.loadImage('data/images/gem_border.png', img => {
console.log('Image loaded: ', img);
}, error => {
console.log('Image failed: ', error);
});
p.loadJSON('data/settings/libraries.json' + antiCacheQuery, json => {
loadScripts(json)
console.log('BenjoCraeft library scripts loaded: ', json)
});
};
s.setup = () => {
canvasSetup();
interfaceSetup();
}
s.draw = () => {
p.background(0, 0, 10);
if (game){
game.display();
game.update();
}
}
}, null, null);
function canvasSetup(){
p.setFrameRate(60);
let w = $('#canvas-holder').width(),
h = $('#canvas-holder').height();
let canvas = p.createCanvas(w, h);
canvas.parent('canvas-holder');
p.textFont(font);
p.textAlign(p.CENTER, p.CENTER);
p.imageMode(p.CENTER);
p.colorMode(p.HSB);
}
function interfaceSetup(){
window.onresize();
setInterval(() => window.onresize(), 500);
$('#start_feedback, #give_feedback').attr('disabled', 'disabled');
nameTyped($('#main > input'));
$('#main').fadeIn(menuesFadeTime);
}
function loadScripts(libs){
for (let script in libs){
if (libs[script]){
let url = '/lib/benjocraeft/' + script + '.js'
$.getScript(url, () => {
console.log('Successfully loaded script: ', url)
});
}
}
}

@ -5,7 +5,7 @@ class Leaderboard{
} }
get pos(){ get pos(){
return createVector(game.pos.x, 0); return p.createVector(game.pos.x, 0);
} }
get width(){ get width(){
@ -41,14 +41,14 @@ class Bar{
return game.size / game.winCount; return game.size / game.winCount;
} }
get marginY(){ get marginY(){
return height * 0.005; return p.height * 0.005;
} }
get pos(){ get pos(){
let lb = game.leaderboard; let lb = game.leaderboard;
let x = lb.pos.x + this.marginX; let x = lb.pos.x + this.marginX;
let y = lb.pos.y + this.marginY + lb.bars.findIndex(b => b.id === this.id) * (this.height + this.marginY); let y = lb.pos.y + this.marginY + lb.bars.findIndex(b => b.id === this.id) * (this.height + this.marginY);
return createVector(x, y); return p.createVector(x, y);
} }
get height(){ get height(){
@ -67,11 +67,11 @@ class Bar{
let filled = game.fields.filter(f => f.ownerId === this.id).length; let filled = game.fields.filter(f => f.ownerId === this.id).length;
for (let i = 0; i < count; i++){ for (let i = 0; i < count; i++){
let x = i * this.width / count + this.width / count / 2 + this.pos.x; let x = i * this.width / count + this.width / count / 2 + this.pos.x;
image(gemBorderImage, x, y, imageSize, imageSize); p.image(gemBorderImage, x, y, imageSize, imageSize);
} }
for (let i = 0; i < filled; i++){ for (let i = 0; i < filled; i++){
let x = i * this.width / count + this.width / count / 2 + this.pos.x; let x = i * this.width / count + this.width / count / 2 + this.pos.x;
image(gemContentGraphics[game.playerHues[this.id]], x, y, imageSize, imageSize); p.image(gemContentGraphics[game.playerHues[this.id]], x, y, imageSize, imageSize);
} }
} }

@ -24,7 +24,7 @@ function socketConnect(project, name = playerName){
connecting = true; connecting = true;
let urlQueries = '?game=' + project.name + '&name=' + name; let urlQueries = '?game=' + project.name + '&name=' + name;
$.get('/php/get_nodejs_port.php', port => { $.get('data/settings/get_port.php', port => {
let url = 'https://' + location.hostname + ':' + port + urlQueries; let url = 'https://' + location.hostname + ':' + port + urlQueries;
socket = io.connect(url); socket = io.connect(url);
@ -217,7 +217,7 @@ function serverStartGame(dom){
} }
function startGame(room, seed){ function startGame(room, seed){
randomSeed(seed); p.randomSeed(seed);
buildGame(room); buildGame(room);
console.log('Game started'); console.log('Game started');
socket.emit('ready-for-turn'); socket.emit('ready-for-turn');

@ -0,0 +1,3 @@
<?php
echo parse_ini_file("../../../server/.env")["HTTPS_PORT"];

@ -1,6 +1,5 @@
{ {
"project": { "project": {
"version": "v1.2.3",
"name": "chainreact", "name": "chainreact",
"author": "BenjoCraeft", "author": "BenjoCraeft",
"playerCounts": [1, 2, 3, 4], "playerCounts": [1, 2, 3, 4],

@ -1,16 +1,11 @@
<!-- Web project created by Benjo Craeft (alias) -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<script src="/lib/socket.io/socket.io.min.js" type="text/javascript"></script> <script src="https://cdn.socket.io/4.4.1/socket.io.min.js" type="text/javascript"></script>
<script src="/lib/socket.io/socket.io-p2p.min.js" type="text/javascript"></script> <script src="data/lib/socketiop2p.min.js" type="text/javascript"></script>
<script src="/lib/p5/p5.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js" type="text/javascript"></script>
<script src="/lib/p5/p5.dom.min.js" type="text/javascript"></script> <script src="https://code.jquery.com/jquery-3.6.4.min.js" type="text/javascript"></script>
<script src="/lib/p5/p5.sound.min.js" type="text/javascript"></script>
<script src="/lib/jquery/jquery.min.js" type="text/javascript"></script>
<script src="/lib/jquery/jquery-ui.min.js" type="text/javascript"></script>
<script src="/lib/vue/vue.js" type="text/javascript"></script>
<script src="data/scripts/init.js" type="text/javascript"></script> <script src="data/scripts/init.js" type="text/javascript"></script>
<script src="data/scripts/events.js" type="text/javascript"></script> <script src="data/scripts/events.js" type="text/javascript"></script>
<script src="data/scripts/online.js" type="text/javascript"></script> <script src="data/scripts/online.js" type="text/javascript"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

3
server/.gitignore vendored

@ -0,0 +1,3 @@
out
logs
node_modules

@ -0,0 +1,439 @@
{
"name": "chainreact-server",
"version": "2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "chainreact-server",
"version": "2.0",
"dependencies": {
"dotenv": "^16.0.3",
"https": "^1.0.0",
"socket.io": "^4.4.1",
"typescript": "^5.0.2"
},
"devDependencies": {
"@types/node": "^18.15.3"
}
},
"base": {
"name": "game-server",
"version": "2.0",
"extraneous": true,
"dependencies": {
"dotenv": "^16.0.3",
"https": "^1.0.0",
"ini": "^2.0.0",
"socket.io": "^4.4.1"
},
"devDependencies": {
"@types/node": "^17.0.18",
"typescript": "^4.8.4"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"node_modules/@types/cors": {
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "18.15.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
"integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw=="
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/engine.io": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz",
"integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.3",
"ws": "~8.11.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz",
"integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/https": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/socket.io": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz",
"integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.4.1",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"dependencies": {
"ws": "~8.11.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/typescript": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=12.20"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"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": {
"@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"@types/cors": {
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "18.15.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
"integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw=="
},
"accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"requires": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
}
},
"base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"dotenv": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
},
"engine.io": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz",
"integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==",
"requires": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.3",
"ws": "~8.11.0"
}
},
"engine.io-parser": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz",
"integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw=="
},
"https": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"socket.io": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz",
"integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==",
"requires": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.2",
"engine.io": "~6.4.1",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.1"
}
},
"socket.io-adapter": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"requires": {
"ws": "~8.11.0"
}
},
"socket.io-parser": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz",
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
}
},
"typescript": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
},
"ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"requires": {}
}
}
}

@ -0,0 +1,18 @@
{
"name": "chainreact-server",
"version": "2.0",
"private": true,
"scripts": {
"start": "node out/index.js",
"tsc": "npx tsc"
},
"dependencies": {
"dotenv": "^16.0.3",
"https": "^1.0.0",
"socket.io": "^4.4.1",
"typescript": "^5.0.2"
},
"devDependencies": {
"@types/node": "^18.15.3"
}
}

@ -0,0 +1,104 @@
import {ServerGame} from "./game_standard";
import {Client} from "./client";
import {Room} from "./room";
export class Chainreact extends ServerGame {
readyForTurn: Client[];
currentTurnIndex: number;
currentGameData: any;
colorHues: { [id: string]: number };
constructor(room: Room, settings: Settings.Global) {
super(room, settings);
this.readyForTurn = []
}
setEvents(client: Client) {
let socket = client.socket;
socket.on('ready-for-turn', isDead => {
if (isDead) {
client.isPlayer = false;
client.isSpectator = true;
this.room.toAll('client-list', this.room.clients)
} else {
this.readyForTurn.push(client)
}
let allReady = true;
this.room.players.forEach(c => {
if (this.readyForTurn.find(r => r.id === c.id) == null) {
allReady = false
}
});
if (allReady) {
this.nextTurn();
this.readyForTurn = []
}
});
socket.on('set-slot', (fieldsIndex: number, slotsIndex: number) => {
this.room.toAll('set-slot', fieldsIndex, slotsIndex, socket.id)
});
socket.on('game-data', data => this.currentGameData = data)
}
addClient(client: Client): void {
super.addClient(client);
if (client.isSpectator) {
let room = this.room;
let data = this.currentGameData;
let hues = this.colorHues;
let turnId = '';
if (this.room.players[this.currentTurnIndex])
turnId = this.room.players[this.currentTurnIndex].id;
client.send('start-spectate', room, data, hues, turnId)
}
}
removeClient(client: Client): void {
super.removeClient(client);
if (this.room.players.indexOf(client) === this.currentTurnIndex)
this.nextTurn(true);
let s = client.socket;
s.removeAllListeners('set-slot');
s.removeAllListeners('ready-for-turn');
s.removeAllListeners('game-data')
}
nextTurn(skip?: boolean) {
if (this.currentTurnIndex != null && !skip) {
this.currentTurnIndex++;
if (this.currentTurnIndex >= this.room.players.length) {
this.currentTurnIndex = 0
}
} else if (!skip) {
this.setTurnAndColors()
}
let index = this.currentTurnIndex;
if (skip) {
index = this.currentTurnIndex + 1;
if (index >= this.room.players.length) {
index = 0;
this.currentTurnIndex = 0
}
}
if (this.room.players.length) {
this.room.toAll('current-turn', this.room.players[index].id)
}
}
setTurnAndColors() {
this.currentTurnIndex = Math.floor(Math.random() * this.room.players.length);
let colorHues = [0, 60, 120, 240];
this.colorHues = {};
for (let c of this.room.players) {
let index = Math.floor(Math.random() * colorHues.length);
let hue = colorHues[index];
colorHues.splice(index, 1);
this.colorHues[c.id] = hue
}
this.room.toAll('player-colors', this.colorHues)
}
}

@ -0,0 +1,168 @@
import {Room} from "./room.js"
import {ConnectionManager, serializeObject} from "./manager.js"
import {log} from "./logger.js";
import * as SocketIO from "socket.io";
export class Client {
socket: SocketIO.Socket;
name: string;
game: string;
id: string;
isReady: boolean;
isPlayer: boolean;
isSpectator: boolean;
constructor(socket: SocketIO.Socket, manager: ConnectionManager) {
this.socket = socket;
// @ts-ignore
this.name = socket.handshake.query.name;
// @ts-ignore
this.game = socket.handshake.query.game;
this.id = socket.id;
this.setEvents(manager)
}
get serialized(): Serialized.Client {
return {
id: this.id,
name: this.name,
game: this.game,
isReady: this.isReady,
isPlayer: this.isPlayer,
isSpectator: this.isSpectator
};
}
setEvents(mng: ConnectionManager): void {
let s = this.socket;
s.on('room-list', () => this.sendRoomList());
s.on('client-list', () => this.sendClientList());
s.on('set-ready', ready => this.setReady(ready));
s.on('game-settings', settings => this.setGameSettings(settings));
s.on('create-lobby', (settings, name) => this.createRoom(settings, name));
s.on('join-lobby', roomId => this.joinRoom(roomId));
s.on('leave-lobby', roomId => this.leaveRoom(roomId));
s.on('join-spectators', () => this.joinSpectators());
s.on('join-players', () => this.joinPlayers());
s.on('start-game', lobbyId => mng.startGame(this, lobbyId));
s.on('stop-game', lobbyId => mng.stopGame(this, lobbyId));
s.on('feedback', content => mng.saveFeedbackToFile(this, content));
s.on('disconnect', () => mng.disconnected(this));
this.send('connected')
}
sendRoomList(): void {
let rooms = ConnectionManager.RoomListByGame(this.game);
this.send('room-list', rooms)
}
sendClientList(): void {
let clients = ConnectionManager.ClientListByClientId(this.id);
this.send('client-list', clients)
}
setReady(ready: boolean): void {
let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms);
if (room) {
this.isReady = ready;
room.toAll('client-list', room.clients)
}
}
setGameSettings(settings: any): void {
let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms);
if (room) {
room.gameSettings = settings;
room.toAll('game-settings', settings)
}
}
createRoom(settings: Settings.Global, name: string): void {
let room = ConnectionManager.Instance.createRoom(settings, name);
room.add(this);
this.send('created-lobby', room);
log('lobby-created', this, room)
}
joinRoom(roomId: string): Room {
let room = Room.getByRoomId(roomId, ConnectionManager.Instance.rooms);
if (!room) {
this.send('join-failed', 'Room does not exist!');
log('join-non-existent', this, new Room('not-existent', roomId))
} else if (room.hasStarted && !room.settings.spectators) {
this.send('join-failed', 'Game has started yet!');
log('join-started', this, room)
} else {
room.add(this);
log('member-joined', this, room)
}
return room
}
leaveRoom(_roomId: string): void {
let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms);
if (!room)
return;
this.leave(room.id);
if (room.runningGame)
room.runningGame.removeClient(this);
room.clients.splice(room.clients.indexOf(this), 1);
room.toAll('member-left', this.id, this.name);
room.toAll('client-list', room.clients);
this.send('left-lobby');
log('member-left', this, room);
if (room.isEmpty && !room.settings.always) {
ConnectionManager.Instance.deleteRoom(room)
}
}
joinSpectators() {
let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms);
if (!room)
return;
this.isSpectator = true;
this.isPlayer = false;
room.toAll('client-list', room.clients)
}
joinPlayers() {
let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms);
if (!room)
return;
if (room.hasStarted)
return;
this.isSpectator = false;
this.isPlayer = true;
room.toAll('client-list', room.clients)
}
send(event: string, ...args: any[]): void {
this.socket.emit(event, ...serializeObject(args))
}
join(roomId: string): void {
this.socket.join(roomId)
}
leave(roomId: string): void {
this.socket.leave(roomId)
}
}

@ -0,0 +1,20 @@
declare namespace Serialized {
interface Lobby {
id: string
name: string
game: string
clientCounts: number[]
clients: Client[]
hasStarted: boolean
}
interface Client {
id: string
name: string
game: string
isReady: boolean
isPlayer: boolean
isSpectator: boolean
}
}

@ -0,0 +1,74 @@
declare module Settings {
interface Global {
project: Project
frameWork: FrameWork
game: any
always: boolean
spectators: boolean
}
interface Project {
name: string
author: string
playerCounts: number[]
}
interface FrameWork {
frameRate: number
updateRate: number
width: number
height: number
}
interface Game {
ball: Ball
player: Player
cw: number
ch: number
}
interface Ball {
radius: number
velocity: number
acceleration: number
runUp: Ball.RunUp
color: Color
cw: number
ch: number
}
interface Player {
width: number
height: number
margin: number
points: number
normal: State
weakened: State
enhanced: State
cw: number
ch: number
}
interface Color {
stroke: string
fill: string
}
interface State {
vel: Vector
color: Color
moveMargin: number
}
interface Vector {
x: number
y: number
}
module Ball {
interface RunUp {
min: number
max: number
}
}
}

@ -0,0 +1,37 @@
import {Room} from "./room.js"
import {Client} from "./client.js"
export class ServerGame {
room: Room;
settings: Settings.Global;
game: any;
constructor(room: Room, settings: Settings.Global) {
this.settings = settings;
this.room = room;
this.room.clients.forEach(c => this.addClient(c))
}
addClient(client: Client): void {
this.setEvents(client)
}
removeClient(client: Client): void {
this.removeEvents(client)
}
gameAction(action: string, ...args: any[]): void {
}
setEvents(client: Client): void {
let socket = client.socket;
socket.on('game-action', (action, ...args) => this.gameAction(action, ...args))
}
removeEvents(client: Client): void {
let socket = client.socket;
socket.removeAllListeners('game-action')
}
}

@ -0,0 +1,7 @@
import {Chainreact} from "./chainreact";
import {StartServer} from "./start";
StartServer({
useP2P: false,
gameClass: Chainreact
});

@ -0,0 +1,105 @@
import {Room} from "./room.js"
import {Client} from "./client.js"
import * as fs from "fs";
import * as util from "util";
let logFolder = "./logs";
if (!fs.existsSync(logFolder)) {
fs.mkdirSync(logFolder);
}
let logFile = fs.createWriteStream(logFolder + '/' + new Date().getTime() + '.log', {flags: 'a'});
let logStdout = process.stdout;
console.log = function () {
logFile.write(util.format.apply(null, arguments) + '\n');
logStdout.write(util.format.apply(null, arguments) + '\n');
};
console.error = console.log;
process.on('uncaughtException', err => {
console.error('Uncaught error: ', err);
process.exit(1);
});
process.stdin.pipe(logFile);
export function log(type: string, client: Client, lobby?: Room, msg?: string) {
let now = new Date(Date.now()).toString(), message, name, game;
let date = '[' + now.substring(0, now.indexOf('GMT') - 1) + ']';
if (client) {
game = '[' + client.game + ']';
let short = client.id.substring(0, Math.round(client.id.length / 3));
name = '"' + client.name + '(' + short + '...)"';
} else {
if (type === 'lobby-deleted') {
game = '[' + lobby.gameName + ']';
} else {
game = '[undefined]';
}
name = 'UNKNOWN';
}
if (lobby) {
game = '[' + lobby.gameName + ']';
}
switch (type) {
case 'join-non-existent':
message = name + ' tried to join non-existent lobby "' + lobby.id + '"';
break;
case 'join-started':
message = name + ' tried to join the started game "' + lobby.id + '"';
break;
case 'lobby-created':
message = name + ' created new lobby: "' + lobby.id + '"';
break;
case 'game-started':
message = name + ' started the game: "' + lobby.id + '"';
break;
case 'game-stopped':
message = name + ' stopped the game: "' + lobby.id + '"';
break;
case 'member-joined':
message = name + ' joined the lobby "' + lobby.id + '"';
break;
case 'member-left':
message = name + ' left the lobby "' + lobby.id + '"';
break;
case 'lobby-deleted':
message = 'Lobby "' + lobby.id + '" was deleted';
break;
case 'save-success':
message = msg;
break;
case 'save-error':
message = 'Failed to save contents to file: ' + msg;
break;
case 'load-success':
message = 'Successfully loaded and parsed file contents';
break;
case 'load-error':
message = 'Failed to load file: ' + msg;
break;
case 'parse-error':
message = 'Failed to parse contents: ' + msg;
break;
case 'feedback':
message = 'Saved feedback to file: ' + msg;
break;
case 'connection':
message = name + ' connected';
break;
case 'disconnection':
message = name + ' disconnected';
break;
case 'startup':
message = msg;
break;
}
console.log(date + game + ' ---> {' + message + '}');
}

@ -0,0 +1,148 @@
import {Room} from "./room.js"
import {Client} from "./client.js"
import {log} from "./logger.js"
import * as fs from "fs";
import * as SocketIO from "socket.io"
export class ConnectionManager {
static Instance: ConnectionManager;
io: SocketIO.Server;
rooms: Room[];
constructor(io: SocketIO.Server) {
ConnectionManager.Instance = this;
this.io = io;
this.rooms = [];
/*let drawSettings = {
project: {
name: 'global-draw',
playerCounts: null
},
always: true,
spectators: true
};
let drawRoom = this.createRoom(drawSettings, '');
drawRoom.id = 'global-draw-room';
drawRoom.startGame();
this.rooms.push(drawRoom);*/
}
static RoomListByGame(game: string): Room[] {
return this.Instance.rooms.filter(l => l.gameName === game)
}
static ClientListByClientId(clientId: string): Client[] {
let room = Room.getByClientId(clientId, this.Instance.rooms);
return room.clients
}
newSocket(socket: SocketIO.Socket): void {
let client = new Client(socket, this);
log('connection', client)
}
roomListUpdate(): void {
this.io.sockets.emit('room-list', serializeObject(this.rooms))
}
createRoom(settings: Settings.Global | any, name: string): Room {
let roomId = Room.generateCode(10);
let room = new Room(name, roomId, settings, this.io);
this.rooms.push(room);
this.roomListUpdate();
return room
}
deleteRoom(room: Room): void {
this.rooms.splice(this.rooms.indexOf(room), 1);
this.roomListUpdate();
log('lobby-deleted', null, room)
}
//Starts the game of a room with given id
startGame(client: Client, _roomId: string): void {
let lobby = Room.getByClientId(client.id, this.rooms);
if (!lobby) return;
if (!lobby.hasStarted) {
lobby.startGame();
log('game-started', client, lobby)
}
this.io.sockets.emit('room-list', serializeObject(this.rooms))
}
//Stops the game of a lobby with given id
stopGame(client: Client, lobbyId: string): void {
let lobby = Room.getByRoomId(lobbyId, this.rooms);
if (!lobby) return;
lobby.stopGame(client);
log('game-stopped', client, lobby)
}
//Saves user feedback to a file
saveFeedbackToFile(client: Client, content: string): void {
let date = new Date(Date.now()).toString();
let path = "feedback/" + client.game + '.txt';
let saveToFile = (content: string) => {
fs.writeFile(path, content, (err: any) => {
if (err)
log('save-error', client, null, err.message);
else
log('feedback', client, null, path)
});
};
if (fs.existsSync(path)) {
fs.readFile(path, 'utf8', (err, data) => {
if (err)
log('load-error', client, null, err.message);
else {
log('load-success', client, null);
let newContent = data + '\n\n\n\n' + date + '\n\n' + content;
saveToFile(newContent)
}
})
} else {
saveToFile(date + '\n' + content)
}
}
//Removes a disconnected client from all references
disconnected(client: Client): void {
let room = Room.getByClientId(client.id, this.rooms);
if (room)
client.leaveRoom(room.id);
log('disconnection', client)
}
}
export function serializeObject(object: any): any {
function serialize(obj: any) {
if (!obj)
return obj;
if (obj.serialized)
return obj.serialized;
else if (obj instanceof Array) {
let content = [];
obj.forEach(o => {
content.push(serialize(o))
});
return content
}
return obj
}
return serialize(object)
}

@ -0,0 +1,135 @@
import {Client} from "./client.js"
import {ServerGame} from "./game_standard.js"
import {serializeObject} from "./manager.js";
import {Server} from "socket.io";
export class Room {
id: string;
gameName: string;
clientCounts: number[];
io: Server;
clients: Client[];
runningGame: ServerGame;
settings: Settings.Global;
gameSettings: any;
name: string;
static GameClass: typeof ServerGame
constructor(name: string, id: string, settings?: Settings.Global, io?: Server) {
this.id = id;
this.name = name;
if (!io || !settings) return;
this.settings = settings;
this.gameName = settings.project.name;
this.clientCounts = settings.project.playerCounts;
this.io = io;
this.clients = [];
this.gameSettings = {}
}
get leader(): Client {
return this.players[0]
}
get players(): Client[] {
return this.clients.filter(c => c.isPlayer)
}
get spectators(): Client[] {
return this.clients.filter(c => c.isSpectator)
}
get serialized(): Serialized.Lobby {
return {
id: this.id,
name: this.name,
game: this.gameName,
clientCounts: this.clientCounts,
clients: serializeObject(this.clients),
hasStarted: this.hasStarted
};
}
get isEmpty(): boolean {
return !(this.clients.length)
}
get hasStarted(): boolean {
return this.runningGame != null
}
static getByRoomId(id: string, lobbies: Room[]): Room {
for (let l of lobbies) {
if (l.id === id)
return l
}
return null;
}
static getByClientId(id: string, lobbies: Room[]): Room {
for (let l of lobbies) {
for (let c of l.clients) {
if (c.id === id)
return l
}
}
return null;
}
static generateCode(elements: number): string {
let code = '';
let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
while (elements--) {
code += possible.charAt(Math.floor(Math.random() * possible.length))
}
return code
}
startGame(): void {
let seed = Math.random() * 10000;
this.toAll('start-game', seed);
this.runGame()
}
stopGame(client: Client): void {
this.toAll('stop-game', client);
this.runningGame = null
}
add(client: Client): void {
this.clients.push(client);
let isPlayer = !this.hasStarted && this.hasValidPlayerCount();
client.isPlayer = isPlayer;
client.isSpectator = !isPlayer;
client.isReady = false;
client.join(this.id);
this.toAll('member-joined', client.id, client.name);
this.toAll('client-list', this.clients);
this.toAll('game-settings', this.gameSettings);
if (this.hasStarted)
this.runningGame.addClient(client)
}
hasValidPlayerCount(): boolean {
let valid = false;
this.clientCounts.forEach(c => {
if (c === this.clients.length)
valid = true
});
return valid
}
runGame(): void {
this.runningGame = new Room.GameClass(this, this.settings);
}
toAll(event: string, ...args: any[]): void {
this.io.to(this.id).emit(event, serializeObject(this), ...serializeObject(args))
}
}

@ -0,0 +1,39 @@
import {ConnectionManager} from "./manager.js";
import {log} from "./logger.js";
import {Server} from 'socket.io';
import {Room} from "./room.js";
import * as https from "https";
import * as fs from "fs";
export function StartServer(settings: any){
require("dotenv").config();
const httpsPort = parseInt(process.env.HTTPS_PORT);
let cert = fs.readFileSync(`${process.env.SSL_PATH}/cert.pem`);
let key = fs.readFileSync(`${process.env.SSL_PATH}/key.pem`);
let httpsServer = https.createServer({key: key, cert: cert});
let sIO = new Server(httpsServer, {
cors: {
origin: ["https://play.benjamin-kraft.local", "https://play.benjamin-kraft.eu"]
}
});
if (settings.useP2P){
const p2p = require('socket.io-p2p-server').Server;
sIO.use(p2p);
}
httpsServer.listen(httpsPort);
Room.GameClass = settings.gameClass;
let connectionManager = new ConnectionManager(sIO);
// On new connection
sIO.on('connection', socket => connectionManager.newSocket(socket));
log('startup', null, null, 'Server is listening on port ' + httpsPort);
}

@ -0,0 +1,11 @@
{
"compilerOptions": {
"module": "CommonJS",
"outDir": "./out",
"sourceMap": true,
"alwaysStrict": true
},
"include": [
"./src"
]
}
Loading…
Cancel
Save