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