commit
8f52472a6f
17 changed files with 3477 additions and 0 deletions
@ -0,0 +1,4 @@ |
||||
/js |
||||
/json_data |
||||
/logs |
||||
/node_modules |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@ |
||||
{ |
||||
"name": "benjocraeft-web", |
||||
"version": "1.1.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"compile-typescript": "npx tsc", |
||||
"start": "node js/server.js" |
||||
}, |
||||
"dependencies": { |
||||
"@types/node": "^17.0.18", |
||||
"@types/socket.io-client": "^1.4.36", |
||||
"https": "^1.0.0", |
||||
"ini": "^2.0.0", |
||||
"socket.io": "^4.4.1", |
||||
"socket.io-client": "^4.4.1", |
||||
"socket.io-p2p": "^1.2.0", |
||||
"socket.io-p2p-server": "^1.2.0" |
||||
}, |
||||
"devDependencies": { |
||||
"typescript": "^4.5.5" |
||||
} |
||||
} |
@ -0,0 +1,168 @@ |
||||
import {Room} from "./room" |
||||
import {ConnectionManager, serializeObject} from "./manager" |
||||
import {log} from "./logger"; |
||||
import SocketIO = require("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,76 @@ |
||||
declare namespace Serialized { |
||||
interface Game { |
||||
players: Player[] |
||||
balls: Ball[] |
||||
finished: boolean |
||||
paused: boolean |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
interface Ball { |
||||
pos: Vector |
||||
vel: Vector |
||||
color: Color |
||||
radius: number |
||||
runningUp: boolean |
||||
nextStep: Vector |
||||
} |
||||
|
||||
interface Player { |
||||
id: string |
||||
pos: Vector |
||||
dim: Vector |
||||
vel: Vector |
||||
color: Color |
||||
points: number |
||||
} |
||||
|
||||
interface Vector { |
||||
x: number, |
||||
y: number |
||||
} |
||||
|
||||
interface Color { |
||||
stroke: string |
||||
fill: string |
||||
} |
||||
|
||||
module Player { |
||||
interface Input { |
||||
up: boolean |
||||
down: boolean |
||||
} |
||||
|
||||
interface Side { |
||||
x: number |
||||
y: number |
||||
} |
||||
} |
||||
|
||||
enum Directions { |
||||
Top, |
||||
Right, |
||||
Bottom, |
||||
Left, |
||||
Forward, |
||||
Back, |
||||
Center |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
declare module Settings { |
||||
interface Global { |
||||
project: Project |
||||
frameWork: FrameWork |
||||
game: Game |
||||
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,104 @@ |
||||
import {Room} from "../room" |
||||
import {ServerGame} from "./game_standard" |
||||
import {Client} from "../client" |
||||
|
||||
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,37 @@ |
||||
import {Room} from "../room" |
||||
import {Client} from "../client" |
||||
|
||||
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,149 @@ |
||||
import {ServerGame} from "./game_standard" |
||||
import {Room} from "../room" |
||||
import {Client} from "../client" |
||||
import {log} from "../logger"; |
||||
|
||||
import fs = require("fs"); |
||||
|
||||
export class GlobalDraw extends ServerGame { |
||||
|
||||
lines: any[]; |
||||
pixels: any[][]; |
||||
|
||||
linesPath = "json_data/global_draw/lines.json"; |
||||
pixelsPath = "json_data/global_draw/pixels.json"; |
||||
|
||||
pixelCount = 1000; |
||||
|
||||
constructor(lobby: Room, settings: Settings.Global) { |
||||
super(lobby, settings); |
||||
|
||||
this.lines = []; |
||||
this.pixels = []; |
||||
|
||||
for (let x = 0; x < this.pixelCount; x++) { |
||||
let column = []; |
||||
for (let y = 0; y < this.pixelCount; y++) { |
||||
column.push({x: x, y: y, c: "#ffffff"}); |
||||
} |
||||
this.pixels.push(column); |
||||
} |
||||
|
||||
let linesLoaded = false; |
||||
let pixelsLoaded = false; |
||||
|
||||
this.loadDrawingsFromFile(this.linesPath, (data: any[]) => { |
||||
this.lines = data; |
||||
}, () => { |
||||
linesLoaded = true; |
||||
if (pixelsLoaded) { |
||||
this.startSaveInterval(); |
||||
} |
||||
}); |
||||
this.loadDrawingsFromFile(this.pixelsPath, (data: any[]) => { |
||||
for (let x = 0; x < this.pixelCount; x++) { |
||||
for (let y = 0; y < this.pixelCount; y++) { |
||||
if (data[x]) |
||||
if (data[x][y]) |
||||
this.pixels[x][y].c = data[x][y].c |
||||
} |
||||
} |
||||
}, () => { |
||||
pixelsLoaded = true; |
||||
if (linesLoaded) { |
||||
this.startSaveInterval(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
startSaveInterval() { |
||||
this.saveAllDrawingsToFile(); |
||||
|
||||
//Saves once every day
|
||||
setInterval(() => this.saveAllDrawingsToFile(), 1000 * 60 * 60 * 24); |
||||
} |
||||
|
||||
addLine(line: any) { |
||||
this.lines.push(line); |
||||
this.room.toAll('add-line', line) |
||||
} |
||||
|
||||
fillPixel(pixel: any) { |
||||
this.pixels[pixel.x][pixel.y].c = pixel.c; |
||||
this.room.toAll('fill-pixel', pixel) |
||||
} |
||||
|
||||
loadDrawingsFromFile(drawingsPath: string, successs: (data: any[]) => void, done: () => void) { |
||||
fs.readFile(drawingsPath, 'utf8', (err, data) => { |
||||
if (err) |
||||
log('load-error', null, this.room, err.message); |
||||
else { |
||||
try { |
||||
let parsed = JSON.parse(data); |
||||
log('load-success', null, this.room); |
||||
successs(parsed); |
||||
} catch (e) { |
||||
log('parse-error', null, this.room, e.message); |
||||
} |
||||
} |
||||
done(); |
||||
}); |
||||
} |
||||
|
||||
saveDrawingsToFile(drawings: any[], drawingsPath: string, callback: (err: any) => void) { |
||||
let splits = drawingsPath.split('/'); |
||||
let path = splits.slice(0, splits.length - 1).reduce((prev, curr) => prev + '/' + curr); |
||||
let name = splits[splits.length - 1]; |
||||
if (!fs.existsSync(path)) { |
||||
fs.mkdirSync(path, {recursive: true}); |
||||
} |
||||
fs.writeFile(drawingsPath, JSON.stringify(drawings), callback); |
||||
} |
||||
|
||||
saveAllDrawingsToFile() { |
||||
let linesSaved = false; |
||||
let pixelsSaved = false; |
||||
|
||||
this.saveDrawingsToFile(this.lines, this.linesPath, (err) => { |
||||
if (err) |
||||
log('save-error', null, this.room, err.message); |
||||
else { |
||||
linesSaved = true; |
||||
if (pixelsSaved) { |
||||
this.room.toAll('all-saved'); |
||||
linesSaved = false; |
||||
pixelsSaved = false |
||||
} |
||||
log('save-success', null, this.room, 'Successfully saved lines to file') |
||||
} |
||||
}); |
||||
this.saveDrawingsToFile(this.pixels, this.pixelsPath, (err) => { |
||||
if (err) |
||||
log('save-error', null, this.room, err.message); |
||||
else { |
||||
pixelsSaved = true; |
||||
if (linesSaved) { |
||||
this.room.toAll('all-saved'); |
||||
pixelsSaved = false; |
||||
linesSaved = false |
||||
} |
||||
log('save-success', null, this.room, 'Successfully saved pixels to file') |
||||
} |
||||
}); |
||||
} |
||||
|
||||
addClient(client: Client): void { |
||||
this.setEvents(client); |
||||
} |
||||
|
||||
setEvents(client: Client): void { |
||||
super.setEvents(client); |
||||
let socket = client.socket; |
||||
socket.on('add-line', (line) => this.addLine(line)); |
||||
socket.on('fill-pixel', (pixel) => this.fillPixel(pixel)); |
||||
socket.on('request-all-lines', () => socket.emit('add-all', this.lines)); |
||||
socket.on('request-all-pixels', () => socket.emit('fill-all', this.pixels)); |
||||
socket.on('save-all', () => this.saveAllDrawingsToFile()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
import {Room} from "../room" |
||||
import {ServerGame} from "./game_standard" |
||||
|
||||
export class Memory extends ServerGame { |
||||
|
||||
constructor(room: Room, settings: Settings.Global) { |
||||
super(room, settings); |
||||
} |
||||
|
||||
gameAction(action: string, ...args: any[]) { |
||||
this.room.toAll('game-action', action, ...args); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@ |
||||
import {ServerGame} from "./game_standard" |
||||
import {Room} from "../room"; |
||||
|
||||
export class Pong extends ServerGame { |
||||
|
||||
constructor(lobby: Room, settings: Settings.Global) { |
||||
super(lobby, settings) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,90 @@ |
||||
export class Vector { |
||||
|
||||
x: number; |
||||
y: number; |
||||
|
||||
constructor(x: number, y: number) { |
||||
this.x = x; |
||||
this.y = y |
||||
} |
||||
|
||||
static fromAngle(angle: number) { |
||||
return new Vector(Math.cos(angle), Math.sin(angle)) |
||||
} |
||||
|
||||
static sub(v1: Vector, v2: Vector) { |
||||
return new Vector(v1.x - v2.x, v1.y - v2.y) |
||||
} |
||||
|
||||
static div(v1: Vector, divider: number): Vector { |
||||
return new Vector(v1.x / divider, v1.y / divider) |
||||
} |
||||
|
||||
add(other: Vector): Vector { |
||||
this.x += other.x; |
||||
this.y += other.y; |
||||
return this |
||||
} |
||||
|
||||
mult(scalar: number): Vector { |
||||
this.x *= scalar; |
||||
this.y *= scalar; |
||||
return this |
||||
} |
||||
|
||||
addMag(length: number): Vector { |
||||
this.setMag(this.mag() + length); |
||||
return this |
||||
} |
||||
|
||||
setMag(length: number): Vector { |
||||
let mag = this.mag(); |
||||
this.x /= mag; |
||||
this.y /= mag; |
||||
this.x *= length; |
||||
this.y *= length; |
||||
return this |
||||
} |
||||
|
||||
rotate(rad: number): Vector { |
||||
let r = this.rotated(rad); |
||||
this.x = r.x; |
||||
this.y = r.y; |
||||
return this |
||||
} |
||||
|
||||
copy(): Vector { |
||||
return new Vector(this.x, this.y) |
||||
} |
||||
|
||||
heading(): number { |
||||
let r = this.rotated(Math.PI / -2); |
||||
return Math.atan2(r.x, -r.y) |
||||
} |
||||
|
||||
mag() { |
||||
return Math.sqrt(this.x * this.x + this.y * this.y) |
||||
} |
||||
|
||||
serialized(): Serialized.Vector { |
||||
return { |
||||
x: this.x, |
||||
y: this.y |
||||
}; |
||||
} |
||||
|
||||
private rotated(rad: number): Vector { |
||||
let x = Math.cos(rad) * this.x - Math.sin(rad) * this.y, |
||||
y = Math.sin(rad) * this.x + Math.cos(rad) * this.y; |
||||
return new Vector(x, y) |
||||
} |
||||
} |
||||
|
||||
export let p = { |
||||
createVector: (x: number, y: number): Vector => { |
||||
return new Vector(x, y) |
||||
}, |
||||
dist: (x1: number, y1: number, x2: number, y2: number): number => { |
||||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) |
||||
} |
||||
}; |
@ -0,0 +1,105 @@ |
||||
import {Room} from "./room" |
||||
import {Client} from "./client" |
||||
|
||||
import fs = require('fs'); |
||||
import util = require('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,149 @@ |
||||
import {Room} from "./room" |
||||
import {Client} from "./client" |
||||
import {log} from "./logger" |
||||
import fs = require("fs"); |
||||
import SocketIO = require("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); |
||||
// @ts-ignore
|
||||
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,150 @@ |
||||
import {Client} from "./client" |
||||
import {ServerGame} from "./games/game_standard" |
||||
import {Memory} from "./games/memory" |
||||
import {Pong} from "./games/pong" |
||||
import {GlobalDraw} from "./games/global_draw" |
||||
import {Chainreact} from "./games/chainreact" |
||||
import {serializeObject} from "./manager"; |
||||
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; |
||||
|
||||
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 { |
||||
switch (this.gameName) { |
||||
case 'memory': |
||||
this.runningGame = new Memory(this, this.settings); |
||||
break; |
||||
case 'pong': |
||||
this.runningGame = new Pong(this, this.settings); |
||||
break; |
||||
case 'global-draw': |
||||
this.runningGame = new GlobalDraw(this, this.settings); |
||||
break; |
||||
case 'chainreact': |
||||
this.runningGame = new Chainreact(this, this.settings); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
toAll(event: string, ...args: any[]): void { |
||||
this.io.to(this.id).emit(event, serializeObject(this), ...serializeObject(args)) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,35 @@ |
||||
'use strict'; |
||||
|
||||
import {log} from "./logger"; |
||||
import {ConnectionManager} from "./manager"; |
||||
|
||||
import https = require('https'); |
||||
import {Server} from 'socket.io'; |
||||
const p2p = require('socket.io-p2p-server').Server; |
||||
import fs = require('fs'); |
||||
import ini = require('ini'); |
||||
|
||||
let rootDir = __dirname + '/../..'; |
||||
|
||||
let httpsPort = ini.parse(fs.readFileSync(rootDir + '/env_config.ini', 'utf-8'))['nodejs_port']; |
||||
|
||||
let cert = fs.readFileSync(rootDir + '/ssl_certificate/cert.pem'); |
||||
let key = fs.readFileSync(rootDir + '/ssl_certificate/key.pem'); |
||||
|
||||
let httpsServer = https.createServer({key: key, cert: cert}); |
||||
|
||||
let sIO = new Server(httpsServer, { |
||||
cors: { |
||||
origin: ["https://play.benjamin-kraft.local", "https://dev.play.benjamin-kraft.eu", "https://play.benjamin-kraft.eu"] |
||||
} |
||||
}); |
||||
sIO.use(p2p); |
||||
|
||||
httpsServer.listen(httpsPort); |
||||
|
||||
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,13 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"target": "es6", |
||||
"module": "commonjs", |
||||
"sourceMap": true, |
||||
"alwaysStrict": true, |
||||
"outDir": "./js", |
||||
"removeComments": true |
||||
}, |
||||
"include": [ |
||||
"./ts" |
||||
] |
||||
} |
Loading…
Reference in new issue