@ -0,0 +1,4 @@ |
||||
.idea |
||||
*.js |
||||
*.js.map |
||||
node_modules |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"display_name": "Pong remastered online", |
||||
"info_text": "My new version of the game Pong, also playable against friends on the internet", |
||||
"visible": true, |
||||
"tags": ["Game", "Multiplayer"] |
||||
} |
After Width: | Height: | Size: 303 KiB |
After Width: | Height: | Size: 959 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1,88 @@ |
||||
declare module Serialized{ |
||||
interface GameObject{ |
||||
id: string |
||||
} |
||||
interface Game extends GameObject{ |
||||
players: Player[] |
||||
balls: Ball[] |
||||
boosts: Boost[] |
||||
wormholes: Wormhole[] |
||||
newBalls: NewBall[] |
||||
} |
||||
interface Lobby extends GameObject{ |
||||
game: string |
||||
clientCounts: number[] |
||||
clients: Client[] |
||||
} |
||||
interface Client extends GameObject{ |
||||
name: string |
||||
game: string |
||||
} |
||||
interface Ball extends GameObject{ |
||||
pos: Vector |
||||
vel: Vector |
||||
color: Color |
||||
radius: number |
||||
runningUp: boolean |
||||
nextStep: Vector |
||||
} |
||||
interface Player extends GameObject{ |
||||
pos: Vector |
||||
dim: Vector |
||||
vel: Vector |
||||
margin: number |
||||
moveMargin: number |
||||
color: Color |
||||
points: number |
||||
lobby: Lobby |
||||
input: Player.Input |
||||
boosts: ActiveBoost[] |
||||
frameRate: number |
||||
vision: boolean |
||||
} |
||||
interface Item extends GameObject{ |
||||
pos: Vector |
||||
radius: number |
||||
color: Color |
||||
time: TimeProcess |
||||
currentRadius: number |
||||
} |
||||
interface Wormhole extends Item{ |
||||
|
||||
} |
||||
interface Boost extends Item{ |
||||
type: string |
||||
bool: string |
||||
} |
||||
interface NewBall extends Item{ |
||||
|
||||
} |
||||
interface ActiveBoost extends GameObject{ |
||||
type: string |
||||
bool: string |
||||
positive: boolean |
||||
time: TimeProcess |
||||
color: Color |
||||
} |
||||
interface Vector{ |
||||
x: number |
||||
y: number |
||||
} |
||||
interface Color{ |
||||
stroke: string |
||||
fill: string |
||||
} |
||||
interface TimeProcess{ |
||||
max: number |
||||
now: number |
||||
finished: boolean |
||||
} |
||||
module Player{ |
||||
interface Input{ |
||||
up: boolean |
||||
down: boolean |
||||
left: boolean |
||||
right: boolean |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,116 @@ |
||||
declare module Settings{ |
||||
interface Global{ |
||||
project: Project |
||||
frameWork: FrameWork |
||||
game: Game |
||||
} |
||||
interface Project{ |
||||
name: string |
||||
author: string |
||||
playerCounts: number[] |
||||
online: Online |
||||
} |
||||
interface Online{ |
||||
iceServers: ICEServer[] |
||||
} |
||||
interface ICEServer{ |
||||
url: string |
||||
username: string |
||||
credential: string |
||||
} |
||||
interface FrameWork{ |
||||
frameRate: number |
||||
updateRate: number |
||||
width: number |
||||
height: number |
||||
} |
||||
interface Game{ |
||||
ball: Ball |
||||
player: Player |
||||
item: { |
||||
wormhole: Wormhole |
||||
boost: Boost |
||||
newBall: NewBall |
||||
} |
||||
} |
||||
interface Ball{ |
||||
radius: number |
||||
velocity: Ball.Velocity |
||||
acceleration: Ball.Acceleration |
||||
runUp: Ball.RunUp |
||||
color: Color |
||||
safeRadius: number |
||||
} |
||||
interface Player{ |
||||
length: number |
||||
depth: number |
||||
margin: number |
||||
points: number |
||||
absVel: number |
||||
color: Color |
||||
thisStroke: string |
||||
moveMargin: number |
||||
blinkTime: number |
||||
} |
||||
interface Item{ |
||||
radius: number |
||||
spawnTime: number |
||||
color: Color |
||||
duration: number |
||||
fadeTime: number |
||||
} |
||||
interface Boost extends Item{ |
||||
types: string[] |
||||
effect: { |
||||
[effect: string]: Effect |
||||
} |
||||
} |
||||
interface Wormhole extends Item{ |
||||
power: number |
||||
minRadius: number |
||||
maxRadius: number |
||||
} |
||||
interface NewBall extends Item{ |
||||
|
||||
} |
||||
interface Color{ |
||||
stroke: string |
||||
fill: string |
||||
} |
||||
interface Effect{ |
||||
duration: number |
||||
positive: { |
||||
[key: string]: number |
||||
} |
||||
negative: { |
||||
[key: string]: number |
||||
} |
||||
standard : { |
||||
[key: string]: number |
||||
} |
||||
} |
||||
interface Vector{ |
||||
x: number |
||||
y: number |
||||
} |
||||
interface TimeProcess{ |
||||
max: number |
||||
now: number |
||||
} |
||||
module Ball{ |
||||
interface RunUp{ |
||||
min: number |
||||
max: number |
||||
} |
||||
interface Velocity{ |
||||
start: number |
||||
min: number |
||||
runUpMax: number |
||||
resetMultiplier: number |
||||
} |
||||
interface Acceleration{
|
||||
permanent: number |
||||
max: number |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
enum Direction{ |
||||
Top, |
||||
Right, |
||||
Bottom, |
||||
Left, |
||||
Forward, |
||||
Back, |
||||
Center |
||||
}; |
@ -0,0 +1,84 @@ |
||||
function myKeyPressed() { |
||||
if ($('input').is(':focus')){ |
||||
if (p.keyCode === 13 && $('#chat-input > input').is(':focus')){ |
||||
onlineManager.sendMessage() |
||||
} |
||||
return true |
||||
} |
||||
if (game instanceof OnlineGame){ |
||||
switch (p.keyCode){ |
||||
case 38: |
||||
case 87: |
||||
game.input.up = true |
||||
break |
||||
case 40: |
||||
case 83: |
||||
game.input.down = true |
||||
break |
||||
case 65: |
||||
case 37: |
||||
game.input.left = true |
||||
break |
||||
case 68: |
||||
case 39: |
||||
game.input.right = true |
||||
break |
||||
} |
||||
game.sendInput() |
||||
} |
||||
switch (p.keyCode){ |
||||
case 37: |
||||
case 38: |
||||
case 39: |
||||
case 40: |
||||
case 65: |
||||
case 68: |
||||
case 83: |
||||
case 87: |
||||
return false |
||||
} |
||||
} |
||||
|
||||
function myKeyReleased() { |
||||
if ($('input').is(':focus')) return true |
||||
if (game instanceof OnlineGame){ |
||||
switch (p.keyCode){ |
||||
case 38: |
||||
case 87: |
||||
game.input.up = false |
||||
break |
||||
case 40: |
||||
case 83: |
||||
game.input.down = false |
||||
break |
||||
case 65: |
||||
case 37: |
||||
game.input.left = false |
||||
break |
||||
case 68: |
||||
case 39: |
||||
game.input.right = false |
||||
break |
||||
} |
||||
game.sendInput() |
||||
}
|
||||
|
||||
switch (p.keyCode){ |
||||
case 32: |
||||
game.togglePlay(true, false) |
||||
return false |
||||
case 37: |
||||
case 38: |
||||
case 39: |
||||
case 40: |
||||
case 65: |
||||
case 68: |
||||
case 83: |
||||
case 87: |
||||
return false |
||||
} |
||||
} |
||||
|
||||
window.onresize = () => { |
||||
adjustDOMSizes() |
||||
} |
@ -0,0 +1,367 @@ |
||||
class Game{ |
||||
|
||||
//Absolute Main class for everything that is happening in pong
|
||||
//Contains everything that appears in the gameplay
|
||||
//Update method applies frame updates to every entity and/or process
|
||||
|
||||
//Settings
|
||||
settings: Settings.Game |
||||
|
||||
//Entities
|
||||
players: Player[] |
||||
balls: Ball[] |
||||
|
||||
//Items
|
||||
boosts: Boost[] |
||||
wormholes: Wormhole[] |
||||
newBalls: NewBall[] |
||||
|
||||
//Gameplay
|
||||
finished: boolean |
||||
paused: boolean |
||||
|
||||
constructor(settings: Settings.Game){ |
||||
this.settings = settings |
||||
} |
||||
|
||||
init(lobby?: Lobby): void{ |
||||
this.players = this.createPlayers(['0', '1']) |
||||
this.balls = this.createBalls('0') |
||||
this.boosts = [] |
||||
this.wormholes = [] |
||||
this.newBalls = [] |
||||
|
||||
this.play() |
||||
} |
||||
|
||||
createPlayer(side: Direction, id: string, otherCount: number): Player{ |
||||
return new Player(side, id, this.settings.player, otherCount) |
||||
} |
||||
|
||||
createPlayers(ids: string[]): Player[]{ |
||||
switch (ids.length){ |
||||
case 2: |
||||
return [ |
||||
this.createPlayer(Direction.Left, ids[0], 2), |
||||
this.createPlayer(Direction.Right, ids[1], 2) |
||||
] |
||||
case 3: |
||||
return [ |
||||
this.createPlayer(Direction.Left, ids[0], 3), |
||||
this.createPlayer(Direction.Right, ids[1], 3), |
||||
this.createPlayer(Direction.Top, ids[2], 3) |
||||
] |
||||
case 4: |
||||
return [ |
||||
this.createPlayer(Direction.Left, ids[0], 4), |
||||
this.createPlayer(Direction.Right, ids[1], 4), |
||||
this.createPlayer(Direction.Top, ids[2], 4), |
||||
this.createPlayer(Direction.Bottom, ids[3], 4) |
||||
] |
||||
} |
||||
} |
||||
|
||||
createBall(id: string): Ball{ |
||||
return new Ball(this.settings.ball, id) |
||||
} |
||||
|
||||
createBalls(...ids: string[]): Ball[]{ |
||||
let balls = [] |
||||
for (let id of ids) |
||||
balls.push(this.createBall(id)) |
||||
return balls |
||||
} |
||||
|
||||
finish(): void{ |
||||
this.pause() |
||||
this.finished = true |
||||
} |
||||
|
||||
restart(): void{ |
||||
this.finished = false |
||||
this.init() |
||||
} |
||||
|
||||
play(): void{ |
||||
if (this.finished) this.restart() |
||||
this.paused = false |
||||
$('#toggle-play').addClass('paused') |
||||
} |
||||
|
||||
pause(): void{ |
||||
if (this.finished) return |
||||
this.paused = true |
||||
$('#toggle-play').removeClass('paused') |
||||
} |
||||
|
||||
togglePlay(fromClient?: boolean, fromInitiator?: boolean): void{ |
||||
$('#toggle-play').blur() |
||||
if (this.paused){ |
||||
this.play() |
||||
} else { |
||||
this.pause() |
||||
} |
||||
} |
||||
|
||||
update(online?: boolean): void{ |
||||
if (this.paused) return |
||||
|
||||
for (let p of this.players) p.update(online) |
||||
for (let b of this.balls) b.update(this.balls, this.players, this.boosts, this.wormholes, this.newBalls) |
||||
for (let b of this.boosts) b.update() |
||||
for (let b of this.wormholes) b.update() |
||||
for (let n of this.newBalls) n.update() |
||||
|
||||
this.tryCreateItem(Boost, this.settings.item.boost, this.boosts) |
||||
this.tryCreateItem(Wormhole, this.settings.item.wormhole, this.wormholes) |
||||
this.tryCreateItem(NewBall, this.settings.item.newBall, this.newBalls) |
||||
|
||||
for (let p of this.players) if (p.hasLost) this.finish() |
||||
} |
||||
|
||||
tryCreateItem(Type: typeof Item, settings: Settings.Item, items: Item[]){ |
||||
if (ranBool(p.int(p.frameRate()) * settings.spawnTime)){ |
||||
items.push(Item.fromNew(this.players, settings, Type)) |
||||
} |
||||
} |
||||
|
||||
display(): void{ |
||||
this.deadZone() |
||||
for (let p of this.players) p.show() |
||||
for (let b of this.balls) b.show() |
||||
for (let b of this.boosts) b.show() |
||||
for (let w of this.wormholes) w.show() |
||||
for (let n of this.newBalls) n.show() |
||||
} |
||||
|
||||
deadZone(): void{ |
||||
p.noFill() |
||||
p.stroke(255, 0, 0) |
||||
p.strokeWeight(3) |
||||
for (let pl of this.players){ |
||||
p.beginShape() |
||||
if (pl.isLeft || pl.isRight){ |
||||
for (let y = pl.boxTopSide; y < pl.boxBottomSide; y++){ |
||||
let x = p.map(p.noise(p.random(p.frameCount)), 0, 1, -pl.width / 2, pl.width / 2) + pl.centerX |
||||
p.vertex(x, y) |
||||
} |
||||
} |
||||
if (pl.isTop || pl.isBottom){ |
||||
for (let x = pl.boxLeftSide; x < pl.boxRightSide; x++){ |
||||
let y = p.map(p.noise(p.random(p.frameCount)), 0, 1, -pl.height / 2, pl.height / 2) + pl.centerY |
||||
p.vertex(x, y) |
||||
} |
||||
} |
||||
p.endShape() |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
class OnlineGame extends Game{ |
||||
|
||||
//Main class for a game that is played via WebRTC Connection
|
||||
|
||||
id: string |
||||
|
||||
lobby: Lobby |
||||
|
||||
socket: any |
||||
p2p: any |
||||
|
||||
input: Serialized.Player.Input |
||||
initiator: boolean |
||||
|
||||
packetManager: { |
||||
count: number |
||||
lastTime: number |
||||
deltaTime: number |
||||
time: number |
||||
} |
||||
|
||||
constructor(socket: any, p2p: any, settings: Settings.Game){ |
||||
super(settings) |
||||
this.socket = socket |
||||
this.p2p = p2p |
||||
this.packetManager = { |
||||
count: 0, |
||||
lastTime: Date.now(), |
||||
deltaTime: 0, |
||||
time: 0 |
||||
} |
||||
} |
||||
|
||||
set data(data: Serialized.Game){ |
||||
let pm = this.packetManager |
||||
pm.count++ |
||||
pm.deltaTime = Date.now() - pm.lastTime |
||||
pm.lastTime = Date.now() |
||||
pm.time += pm.deltaTime |
||||
if (pm.time > 5000){ |
||||
console.log(pm.count + ' Data packs received from leading Peer, last one: ', data) |
||||
pm.count = 0 |
||||
pm.time = 0 |
||||
} |
||||
|
||||
//Function to apply new values to objects which can disappear or appear
|
||||
function applyValues(Type: typeof GameObject | typeof Player | typeof Ball | typeof Item, |
||||
objects1: Serialized.GameObject[], objects2: GameObject[], |
||||
settings: any): void{ |
||||
for (let o1 of objects1){ |
||||
let found = false |
||||
for (let o2 of objects2){ |
||||
if (o1.id === o2.id){ |
||||
o2.applyValues(o1) |
||||
found = true |
||||
} |
||||
} |
||||
if (!found){ |
||||
let newObject |
||||
switch (Type){ |
||||
case Boost: |
||||
case Wormhole: |
||||
newObject = Item.fromSerialized(o1, settings, Type) |
||||
break |
||||
case Ball: |
||||
newObject = Ball.fromSerialized(o1, settings) |
||||
break |
||||
case NewBall: |
||||
newObject = Item.fromSerialized(o1, settings, Type) |
||||
break |
||||
} |
||||
objects2.push(newObject) |
||||
} |
||||
} |
||||
for (let o2 of objects2){ |
||||
let found = false |
||||
for (let o1 of objects1){ |
||||
if (o1.id === o2.id) found = true |
||||
} |
||||
if (!found){ |
||||
let index = objects2.indexOf(o2) |
||||
objects2.splice(index, 1) |
||||
} |
||||
} |
||||
} |
||||
|
||||
applyValues(Player, data.players, this.players, this.settings.player) |
||||
applyValues(Ball, data.balls, this.balls, this.settings.ball) |
||||
applyValues(Boost, data.boosts, this.boosts, this.settings.item.boost) |
||||
applyValues(Wormhole, data.wormholes, this.wormholes, this.settings.item.wormhole) |
||||
applyValues(NewBall, data.newBalls, this.newBalls, this.settings.item.newBall) |
||||
|
||||
} |
||||
|
||||
init(lobby: Lobby): void{ |
||||
|
||||
this.input = {up: false, down: false, left: false, right: false} |
||||
this.lobby = lobby |
||||
this.id = this.lobby.id |
||||
|
||||
//The initiator runs the actual game
|
||||
//and sends the game to other peers
|
||||
this.initiator = (this.socket.id === this.lobby.leader.id) |
||||
|
||||
let ids = [] |
||||
for (let p of this.lobby.clients) ids.push(p.id) |
||||
this.players = this.createPlayers(ids) |
||||
this.balls = this.createBalls(this.lobby.leader.id) |
||||
this.boosts = [] |
||||
this.wormholes = [] |
||||
this.newBalls = [] |
||||
} |
||||
|
||||
createPlayer(side: Direction, id: string, otherCount: number): Player{ |
||||
return new Player(side, id, this.settings.player, otherCount, this.lobby) |
||||
} |
||||
|
||||
setPlayerInput(player: any): void{ |
||||
for (let p of this.players){ |
||||
if (p.id === player.id){ |
||||
p.input = player.input |
||||
} |
||||
} |
||||
} |
||||
|
||||
sendInput(): void{ |
||||
let player = { |
||||
id: this.getThisPlayer().id, |
||||
input: this.input, |
||||
lobby: this.getThisPlayer().lobby |
||||
} |
||||
if (this.initiator){ |
||||
this.setPlayerInput(player) |
||||
} else { |
||||
this.p2p.emit('player-input', player) |
||||
} |
||||
} |
||||
|
||||
getThisPlayer(): Player{ |
||||
for (let p of this.players){ |
||||
if (p.id === this.socket.id) |
||||
return p |
||||
} |
||||
} |
||||
|
||||
togglePlay(fromClient: boolean, fromInitiator: boolean): void{ |
||||
if (this.initiator){ |
||||
super.togglePlay() |
||||
this.emitAction('togglePlay') |
||||
} else { |
||||
if (fromInitiator){ |
||||
super.togglePlay() |
||||
} else { |
||||
if (fromClient){ |
||||
this.emitAction('togglePlay') |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
update(): void{ |
||||
if (this.initiator){ |
||||
super.update(true) |
||||
this.p2p.emit('game-data', this.serialized()) |
||||
} |
||||
} |
||||
|
||||
emitAction(action: string){ |
||||
this.p2p.emit('game-action', { |
||||
action: action,
|
||||
lobby: this.lobby.serialized(), |
||||
fromInitiator: this.initiator |
||||
}) |
||||
} |
||||
|
||||
finish(): void{ |
||||
super.finish() |
||||
if (this.initiator) |
||||
this.emitAction('finish') |
||||
} |
||||
|
||||
restart(){ |
||||
this.finished = false |
||||
this.init(this.lobby) |
||||
} |
||||
|
||||
serialized(): Serialized.Game{ |
||||
let players: Serialized.Player[] = [],
|
||||
balls: Serialized.Ball[] = [], |
||||
boosts: Serialized.Boost[] = [], |
||||
wormholes: Serialized.Wormhole[] = [], |
||||
newBalls: Serialized.NewBall[] = [] |
||||
for (let b of this.balls) balls.push(b.serialized()) |
||||
for (let p of this.players) players.push(p.serialized()) |
||||
for (let b of this.boosts) boosts.push(b.serialized()) |
||||
for (let w of this.wormholes) wormholes.push(w.serialized()) |
||||
for (let n of this.newBalls) newBalls.push(n.serialized()) |
||||
return { |
||||
players: players, |
||||
balls: balls, |
||||
boosts: boosts, |
||||
wormholes: wormholes, |
||||
newBalls: newBalls, |
||||
id: this.id |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,536 @@ |
||||
class GameObject{ |
||||
|
||||
id: string |
||||
|
||||
constructor(id: string){ |
||||
this.id = id |
||||
} |
||||
|
||||
applyValues(state: Serialized.GameObject | {[key: string]: number}): void{ |
||||
let recurse = (objIn: any, objOut: any): void => { |
||||
for (let p in objIn){ |
||||
if (typeof objIn[p] == 'object' && objIn[p] != null){ |
||||
if (!objOut[p]) objOut[p] = {}; |
||||
recurse(objIn[p], objOut[p]) |
||||
} else objOut[p] = objIn[p] |
||||
} |
||||
} |
||||
recurse(state, this) |
||||
} |
||||
} |
||||
|
||||
class Ball extends GameObject{ |
||||
|
||||
//Main class for the moving ball object and the gameboard
|
||||
|
||||
//Settings
|
||||
settings: Settings.Ball |
||||
|
||||
//General
|
||||
color: Settings.Color |
||||
runUp: RunUp |
||||
runningUp: boolean |
||||
timeAfterRunUp: number |
||||
lastPlayerHit: Player |
||||
collides: boolean |
||||
|
||||
//Position and dimensions
|
||||
pos: Vector |
||||
radius: number |
||||
|
||||
//Movement
|
||||
vel: Vector |
||||
acc: Vector |
||||
maxAcc: number |
||||
permaAcc: number |
||||
nextStep: Vector |
||||
|
||||
constructor(settings: Settings.Ball, id: string){ |
||||
super(id) |
||||
this.settings = settings |
||||
this.initSetup(true) |
||||
} |
||||
|
||||
get canCollide(): boolean{ |
||||
return this.dist(p.width / 2, p.height / 2) > this.settings.safeRadius |
||||
} |
||||
|
||||
get future(): Ball{ |
||||
let future = this.copy() |
||||
future.move() |
||||
return future |
||||
} |
||||
|
||||
get nextPosition(): Vector{ |
||||
let future = this.pos.copy() |
||||
future.add(this.vel) |
||||
return future |
||||
} |
||||
|
||||
get downSide(): number{return this.pos.y + this.radius} |
||||
|
||||
get upSide(): number{return this.pos.y - this.radius} |
||||
|
||||
get leftSide(): number{return this.pos.x - this.radius} |
||||
|
||||
get rightSide(): number{return this.pos.x + this.radius} |
||||
|
||||
get centerX(): number{return this.pos.x} |
||||
|
||||
get centerY(): number{return this.pos.y} |
||||
|
||||
get size(): number{return this.radius * 2} |
||||
|
||||
static fromSerialized(ball: Serialized.GameObject, settings: Settings.Ball): Ball{ |
||||
let b = new Ball(settings, ball.id) |
||||
b.applyValues(ball) |
||||
return b |
||||
} |
||||
|
||||
initSetup(hardReset: boolean): void{ |
||||
let s = this.settings |
||||
this.setup(s.radius, s.velocity, s.acceleration, s.runUp, s.color, hardReset) |
||||
} |
||||
|
||||
setup(r: number, v: Settings.Ball.Velocity, a: Settings.Ball.Acceleration, s: Settings.Ball.RunUp, c: Settings.Color, hardReset: boolean): void{ |
||||
this.pos = new Vector(p.width / 2, p.height / 2) |
||||
this.acc = new Vector(0, 0) |
||||
this.maxAcc = a.max |
||||
this.permaAcc = a.permanent |
||||
if (hardReset) this.vel = new Vector(0, v.start) |
||||
else if (this.vel.mag() * v.resetMultiplier > v.min) this.vel.mult(v.resetMultiplier) |
||||
this.radius = r |
||||
this.color = c |
||||
this.runUp = new RunUp(s, v) |
||||
this.runningUp = true |
||||
} |
||||
|
||||
reset(hardReset: boolean): void{ |
||||
this.initSetup(hardReset) |
||||
} |
||||
|
||||
update(balls: Ball[], players: Player[], |
||||
boosts: Boost[], wormholes: Wormhole[], newBalls: NewBall[]): void{ |
||||
if (this.runningUp){ |
||||
this.runUp.update(this) |
||||
} else { |
||||
let items = (boosts as Item[]).concat(newBalls) |
||||
this.collision(balls, players, items) |
||||
this.checkWormholes(wormholes) |
||||
this.move() |
||||
} |
||||
} |
||||
|
||||
move(){ |
||||
this.acc.limit(this.maxAcc) |
||||
this.vel.addMag(this.permaAcc) |
||||
this.vel.add(this.acc) |
||||
if (this.nextStep){ |
||||
this.pos.add(this.nextStep) |
||||
this.nextStep = null |
||||
} else { |
||||
this.pos.add(this.vel) |
||||
} |
||||
this.acc.mult(0) |
||||
} |
||||
|
||||
applyForce(force: Vector){ |
||||
this.acc.add(force.div(this.radius)) |
||||
} |
||||
|
||||
checkWormholes(wormholes: Wormhole[]){ |
||||
for (let w of wormholes){ |
||||
w.attractBall(this) |
||||
} |
||||
} |
||||
|
||||
collision(balls: Ball[], players: Player[], items: Item[]): void{ |
||||
this.collides = false |
||||
if (this.itemCollision(items)) return |
||||
if (this.playerCollision(players)) return |
||||
if (this.deadZoneCollision(players)) return |
||||
if (this.wallCollision()) return |
||||
if (this.ballCollision(balls)) return |
||||
} |
||||
|
||||
itemCollision(items: Item[]): boolean{ |
||||
let item = this.touchesItems(items) |
||||
if (item){ |
||||
|
||||
//Add new Item interactions with ball collision here
|
||||
if (item instanceof Boost && this.lastPlayerHit) this.lastPlayerHit.applyBoost(item as Boost) |
||||
if (item instanceof NewBall) game.balls.push(new Ball(this.settings, randomToken())) |
||||
|
||||
item.destroy() |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
playerCollision(players: Player[]): boolean{ |
||||
let player = this.touchesPlayers(players) |
||||
if (player){ |
||||
this.turnXY(player) |
||||
this.lastPlayerHit = player |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
deadZoneCollision(players: Player[]): boolean{ |
||||
for (let p of players){ |
||||
if (this.willBeInDeadZone(p) || this.isInDeadZone(p)){ |
||||
if (this.isInDeadZone(p)){ |
||||
p.lost(this) |
||||
return true |
||||
} else { |
||||
this.moveIntoDeadZone(p) |
||||
} |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
wallCollision(): boolean{ |
||||
if (this.willTouchWall() || this.touchesWall()){ |
||||
if (this.touchesWall()) { |
||||
this.turnY() |
||||
return true |
||||
} else { |
||||
this.moveOnWall() |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
ballCollision(balls: Ball[]): boolean{ |
||||
if (!this.canCollide) return false |
||||
for (let b of balls){ |
||||
if (b.id === this.id || b.collides) continue |
||||
if (this.willTouchBall(b) || this.touchesBall(b)){ |
||||
b.collides = true |
||||
if (this.touchesBall(b)){ |
||||
this.performBallCollision(b) |
||||
return true |
||||
} |
||||
this.moveOnBall(b) |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
touchesItems(items: Item[]): Item{ |
||||
for (let i of items){ |
||||
if (this.willTouchItem(i)){ |
||||
if (this.touchesItem(i)){ |
||||
return i |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
touchesPlayers(players: Player[]): Player{ |
||||
for (let p of players){ |
||||
if (this.willTouchPlayer(p) || this.touchesPlayer(p)){ |
||||
if (this.touchesPlayer(p)){ |
||||
return p |
||||
} else { |
||||
this.moveOnPlayer(p) |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
willTouchWall(): boolean{ |
||||
return this.future.touchesWall() |
||||
} |
||||
|
||||
touchesWall(): boolean{ |
||||
return this.downSide >= p.height || this.upSide <= 0 |
||||
} |
||||
|
||||
posTouchesWall(pos: Vector): boolean{ |
||||
let copy = this.copy() |
||||
copy.pos.x = pos.x |
||||
copy.pos.y = pos.y |
||||
return copy.downSide >= p.height || copy.upSide <= 0 |
||||
} |
||||
|
||||
moveOnWall(): void{ |
||||
let pos = this.pos.copy() |
||||
let dir = new Vector(0, 0) |
||||
let vel = Vector.fromAngle(this.vel.heading()).mult(0.5) |
||||
while (!this.posTouchesWall(pos)){ |
||||
pos.add(vel) |
||||
dir.add(vel) |
||||
if (dir.mag() > this.vel.mag()) |
||||
break //error
|
||||
} |
||||
if (!this.nextStep) this.nextStep = dir |
||||
} |
||||
|
||||
willTouchItem(item: Item): boolean{ |
||||
let now = this.copy() |
||||
let then = this.future |
||||
let dist = Vector.sub(then.pos, now.pos).mag() |
||||
let nowDist = 0 |
||||
while(nowDist <= dist){ |
||||
if (now.touchesItem(item)){ |
||||
return true |
||||
} |
||||
now.pos.addMag(1) |
||||
nowDist++ |
||||
} |
||||
return false |
||||
} |
||||
|
||||
touchesItem(item: Item): boolean{ |
||||
let dist = this.dist(item.pos.x, item.pos.y) |
||||
return dist <= this.radius + item.radius |
||||
} |
||||
|
||||
willTouchPlayer(player: Player): boolean{ |
||||
return this.future.touchesPlayer(player.future) |
||||
} |
||||
|
||||
touchesPlayer(player: Player): boolean{ |
||||
if (player.isLeft || player.isRight){ |
||||
for (let y = player.upSide; y <= player.downSide; y++){ |
||||
let touch = this.touchesPos(player.toGameSide, y) |
||||
if (touch) return true |
||||
} |
||||
} |
||||
if (player.isTop || player.isBottom){ |
||||
for (let x = player.leftSide; x <= player.rightSide; x++){ |
||||
let touch = this.touchesPos(x, player.toGameSide) |
||||
if (touch) return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
posTouchesPlayer(pos: Vector, player: Player): boolean{ |
||||
if (player.isLeft || player.isRight){ |
||||
for (let y = player.upSide; y <= player.downSide; y++){ |
||||
if (p.dist(pos.x, pos.y, player.toGameSide, y) - this.radius <= 0.2) return true |
||||
} |
||||
} |
||||
if (player.isTop || player.isBottom){ |
||||
for (let x = player.leftSide; x <= player.rightSide; x++){ |
||||
if (p.dist(pos.x, pos.y, x, player.toGameSide) - this.radius <= 0.2) return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
moveOnPlayer(player: Player): void{ |
||||
let pos = this.pos.copy() |
||||
let dir = new Vector(0, 0) |
||||
let vel = Vector.fromAngle(this.vel.heading()).mult(0.5) |
||||
while (!this.posTouchesPlayer(pos, player)){ |
||||
pos.add(vel) |
||||
dir.add(vel) |
||||
if (dir.mag() > this.vel.mag()) |
||||
break //error
|
||||
} |
||||
this.nextStep = dir |
||||
} |
||||
|
||||
isInDeadZone(player: Player): boolean{ |
||||
if ((this.centerX < player.leftSide && player.isLeft || |
||||
this.centerX > player.rightSide && player.isRight) |
||||
&& this.centerY > player.boxTopSide |
||||
&& this.centerY < player.boxBottomSide){ |
||||
return true |
||||
} |
||||
if ((this.centerY < player.upSide && player.isTop || |
||||
this.centerY > player.downSide && player.isBottom) |
||||
&& this.centerX > player.boxLeftSide |
||||
&& this.centerX < player.boxRightSide){ |
||||
return true |
||||
} |
||||
} |
||||
|
||||
posIsInDeadZone(pos: Vector, player: Player): boolean{ |
||||
let copy = this.copy() |
||||
copy.pos = pos.copy() |
||||
if ((copy.centerX < player.leftSide && player.isLeft || |
||||
copy.centerX > player.rightSide && player.isRight) |
||||
&& copy.centerY > player.boxTopSide |
||||
&& copy.centerY < player.boxBottomSide){ |
||||
return true |
||||
} |
||||
if ((copy.centerY < player.upSide && player.isTop || |
||||
copy.centerY > player.downSide && player.isBottom) |
||||
&& copy.centerX > player.boxLeftSide |
||||
&& copy.centerX < player.boxRightSide){ |
||||
return true |
||||
} |
||||
} |
||||
|
||||
willBeInDeadZone(player: Player): boolean{ |
||||
return this.posIsInDeadZone(this.future.pos.copy(), player) |
||||
} |
||||
|
||||
moveIntoDeadZone(player: Player): void{ |
||||
let pos = this.pos.copy() |
||||
let dir = new Vector(0, 0) |
||||
let vel = Vector.fromAngle(this.vel.heading()).mult(0.5) |
||||
while (!this.posIsInDeadZone(pos, player)){ |
||||
pos.add(vel) |
||||
dir.add(vel) |
||||
if (dir.mag() > this.vel.mag()) |
||||
break //error
|
||||
} |
||||
this.nextStep = dir |
||||
} |
||||
|
||||
touchesBall(ball: Ball): boolean{ |
||||
return this.dist(ball.centerX, ball.centerY) <= this.radius + ball.radius |
||||
} |
||||
|
||||
willTouchBall(ball: Ball): boolean{ |
||||
return this.future.touchesBall(ball.future) |
||||
} |
||||
|
||||
moveOnBall(ball: Ball): void{ |
||||
let pos = this.pos.copy() |
||||
let dir = new Vector(0, 0) |
||||
let vel = Vector.fromAngle(this.vel.heading()).mult(0.5) |
||||
while (!this.posTouchesBall(pos, ball)){ |
||||
pos.add(vel) |
||||
dir.add(vel) |
||||
if (dir.mag() > this.vel.mag()) |
||||
break //error
|
||||
} |
||||
this.nextStep = dir |
||||
} |
||||
|
||||
posTouchesBall(pos: Vector, ball: Ball): boolean{ |
||||
let b = this.copy() |
||||
b.pos = pos.copy() |
||||
return b.touchesBall(ball) |
||||
} |
||||
|
||||
performBallCollision(ball: Ball): void{ |
||||
Collision.ellipseToEllipse(this, ball) |
||||
} |
||||
|
||||
touchesPos(x: number, y: number): boolean{ |
||||
let d = this.dist(x, y) |
||||
return d <= this.radius + 0.2 |
||||
} |
||||
|
||||
dist(x: number, y: number): number{ |
||||
return p.dist(x, y, this.centerX, this.centerY) |
||||
} |
||||
|
||||
turnY(): void{ |
||||
this.vel.y *= -1 |
||||
} |
||||
|
||||
turnX(): void{ |
||||
this.vel.x *= -1 |
||||
} |
||||
|
||||
turnXY(player: Player): void{ |
||||
if (player.isLeft || player.isRight){ |
||||
this.turnX() |
||||
|
||||
// Number between -0.5 (ball on the top) and 0.5 (ball on the bottom of player)
|
||||
let divider = (this.centerY - (player.upSide - this.radius)) / (player.height + this.radius * 2) - 0.5 |
||||
|
||||
this.vel.rotate((player.isLeft ? 1 : -1) * divider) |
||||
} |
||||
if (player.isTop || player.isBottom){ |
||||
this.turnY() |
||||
|
||||
// Number between -0.5 (ball on the left) and 0.5 (ball on the right of player)
|
||||
let divider = (this.centerX - (player.leftSide - this.radius)) / (player.width + this.radius * 2) - 0.5 |
||||
|
||||
this.vel.rotate((player.isBottom ? 1 : -1) * divider) |
||||
} |
||||
} |
||||
|
||||
copy(): Ball{ |
||||
let ball = new Ball(this.settings, this.id) |
||||
ball.pos = this.pos.copy() |
||||
ball.vel = this.vel.copy() |
||||
ball.nextStep = this.nextStep |
||||
ball.radius = this.radius |
||||
return ball |
||||
} |
||||
|
||||
serialized(): Serialized.Ball{ |
||||
return { |
||||
pos: this.pos.serialized(), |
||||
vel: this.vel.serialized(), |
||||
color: this.color, |
||||
radius: this.radius, |
||||
runningUp: this.runningUp, |
||||
nextStep: this.nextStep, |
||||
id: this.id |
||||
} |
||||
} |
||||
|
||||
show(): void{ |
||||
p.push() |
||||
p.fill(this.color.fill) |
||||
p.stroke(this.color.stroke) |
||||
p.translate(this.centerX, this.centerY) |
||||
p.strokeWeight(3) |
||||
p.ellipse(0, 0, this.size, this.size) |
||||
if (this.runningUp){ |
||||
p.stroke(0, 255, 0) |
||||
p.fill(0, 100, 0) |
||||
p.rotate(this.vel.heading()) |
||||
let mag = this.vel.mag() * 15 |
||||
p.strokeWeight(mag / 50) |
||||
p.line(0, 0, mag, 0) |
||||
p.triangle(mag, -mag / 8, mag, mag / 8, mag + mag / 4, 0) |
||||
} |
||||
p.pop() |
||||
} |
||||
|
||||
} |
||||
|
||||
class RunUp{ |
||||
|
||||
time: TimeProcess |
||||
|
||||
rVel = 0 |
||||
mVel = 0 |
||||
rAcc = 0 |
||||
mAcc = 0 |
||||
|
||||
minVel: number |
||||
maxVel: number |
||||
|
||||
constructor(s: Settings.Ball.RunUp, v: Settings.Ball.Velocity){ |
||||
this.time = new TimeProcess(Math.random() * (s.max - s.min) + s.min) |
||||
this.minVel = v.min |
||||
this.maxVel = v.runUpMax |
||||
} |
||||
|
||||
update(b: Ball): void{ |
||||
this.time.update() |
||||
|
||||
this.mAcc = (Math.random() - 0.5) / 50 |
||||
this.rAcc = (Math.random() - 0.5) / 50 |
||||
if (Math.abs(this.mVel) < 0.5) |
||||
this.mVel += this.mAcc |
||||
if (Math.abs(this.rVel) < 0.1) |
||||
this.rVel += this.rAcc |
||||
if (b.vel.mag() < this.maxVel && b.vel.mag() > this.minVel) |
||||
b.vel.addMag(this.mVel) |
||||
b.vel.rotate(this.rVel) |
||||
b.runningUp = !this.time.finished |
||||
if (!b.runningUp){ |
||||
b.lastPlayerHit = null |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,168 @@ |
||||
let debug = false, |
||||
viewPort = {x: 0, y: 0}, |
||||
localFont: any, |
||||
localSettings: Settings.Global; |
||||
|
||||
let state = 'menu', |
||||
game: Game | OnlineGame, |
||||
onlineManager: OnlineManager, |
||||
loader: Loader, |
||||
icons : { |
||||
speed: { |
||||
positive |
||||
negative |
||||
} |
||||
border: { |
||||
positive |
||||
negative |
||||
} |
||||
} |
||||
|
||||
const p = new p5((p: p5) => { |
||||
p.preload = (): void => { |
||||
localSettings = <Settings.Global>p.loadJSON('data/settings/settings.json', {}, "json", (json) => { |
||||
console.log('Local settings loaded: ', json) |
||||
}, (error) => { |
||||
console.log('Local settings failed: ', error) |
||||
}) |
||||
|
||||
localFont = p.loadFont('data/styles/font.ttf', (json) => { |
||||
console.log('Local font loaded: ', json) |
||||
}, error => { |
||||
console.log('Local font failed: ', error) |
||||
}) |
||||
|
||||
p.loadJSON('data/settings/libraries.json', json => { |
||||
loadScripts(json) |
||||
console.log('BenjoCraeft library scripts loaded: ', json) |
||||
}) |
||||
|
||||
icons = {speed: {positive: null, negative: null}, border: {positive: null, negative: null}} |
||||
icons.speed.positive = p.loadImage('data/images/speed_icon_positive.png') |
||||
icons.speed.negative = p.loadImage('data/images/speed_icon_negative.png') |
||||
icons.border.positive = p.loadImage('data/images/border_icon_positive.png') |
||||
icons.border.negative = p.loadImage('data/images/border_icon_negative.png') |
||||
} |
||||
|
||||
p.setup = (): void => { |
||||
canvasSetup(localSettings.frameWork, localFont) |
||||
interfaceSetup() |
||||
} |
||||
|
||||
p.draw = (): void => { |
||||
p.background(90, 90, 120) |
||||
//p.image(img, 0, 0, p.width, p.height)
|
||||
|
||||
if (game){ |
||||
game.update() |
||||
game.display() |
||||
} |
||||
|
||||
if (loader){ |
||||
loader.update() |
||||
loader.display() |
||||
} |
||||
|
||||
if (debug) debugInformation() |
||||
} |
||||
}); |
||||
|
||||
p.keyPressed = myKeyPressed; |
||||
p.keyReleased = myKeyReleased; |
||||
|
||||
declare let debugInformation: () => void |
||||
declare let ranBool: (chance: number) => boolean |
||||
declare let Collision: { |
||||
ellipseToEllipse: (e1: Ball, e2: Ball) => void |
||||
} |
||||
|
||||
|
||||
function canvasSetup(settings: Settings.FrameWork, font): void{ |
||||
p.frameRate(settings.frameRate) |
||||
let canvas = p.createCanvas(settings.width, settings.height) |
||||
canvas.parent('canvasHolder') |
||||
adjustDOMSizes() |
||||
p.textFont(font) |
||||
} |
||||
|
||||
function startOfflineGame(): void{ |
||||
$('#setup-controls').hide() |
||||
$('#game-controls').show() |
||||
game = new Game(localSettings.game) |
||||
game.init() |
||||
|
||||
console.log('Offline Game started:', game) |
||||
} |
||||
|
||||
function adjustDOMSizes(){ |
||||
let w = window.innerWidth |
||||
let h = window.innerHeight |
||||
$('canvas, #canvasHolder').css({ |
||||
width: h * 0.95,
|
||||
height: h * 0.95 |
||||
}) |
||||
$('#interface, #chat').css({ |
||||
width: (w - h * 0.95 - 4) / 2 - 4 - 10, |
||||
height: h * 0.95, |
||||
'margin-top': h * 0.025 - 2, |
||||
'margin-left': 5 |
||||
}) |
||||
$('#chat').css({ |
||||
'margin-right': 5 |
||||
}) |
||||
} |
||||
|
||||
function interfaceSetup(): void{ |
||||
|
||||
function handleRadioInput(): void{ |
||||
let type = $('#game-type input[type=radio]:checked').val() |
||||
$('#game-properties > *').hide() |
||||
if (type === 'offline'){ |
||||
$('.start').show() |
||||
state = 'offline-start' |
||||
} else if (type === 'online'){ |
||||
$('.player-name').show() |
||||
state = 'player-name' |
||||
} |
||||
|
||||
$('#game-properties').show() |
||||
} |
||||
|
||||
$('#lobby, #chat').hide() |
||||
$('#game-type input[type=radio]').change(handleRadioInput) |
||||
handleRadioInput() |
||||
|
||||
} |
||||
|
||||
function continueForm(dom: any): void{ |
||||
$(dom).blur() |
||||
switch(state){ |
||||
case 'offline-start': |
||||
startOfflineGame() |
||||
break; |
||||
case 'player-name': |
||||
let input = getValidInput(state) |
||||
if (input){ |
||||
onlineManager = new OnlineManager(input, localSettings) |
||||
$('.player-name').hide() |
||||
$('.player-actions').show() |
||||
state = 'player-actions' |
||||
} |
||||
break |
||||
} |
||||
} |
||||
|
||||
function randomToken(): string{ |
||||
return Math.floor((1 + Math.random()) * 1e16).toString(16).substring(1) |
||||
} |
||||
|
||||
function loadScripts(libs: string[]){ |
||||
for (let script in libs){ |
||||
if (libs[script]){ |
||||
let url = '/lib/benjocraeft/' + script + '.js' |
||||
$.getScript(url, () => { |
||||
console.log('Successfully loaded script: ', url) |
||||
}) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,242 @@ |
||||
class Item extends GameObject{ |
||||
|
||||
//Superior class for everything apperaring on the gameboard
|
||||
//after a period of time, for a period of time
|
||||
|
||||
//Settings
|
||||
settings: Settings.Item |
||||
|
||||
//General
|
||||
color: Serialized.Color |
||||
time: TimeProcess |
||||
fadeTime: number |
||||
|
||||
//Position and dimensions
|
||||
pos: Vector |
||||
radius: number |
||||
currentRadius: number |
||||
|
||||
|
||||
constructor(settings: Settings.Item){ |
||||
super(randomToken()) |
||||
this.radius = settings.radius |
||||
this.fadeTime = settings.fadeTime |
||||
this.currentRadius = 1 |
||||
this.color = { |
||||
stroke: settings.color.stroke, |
||||
fill: settings.color.fill |
||||
} |
||||
this.time = new TimeProcess(settings.duration) |
||||
} |
||||
|
||||
get centerX(): number{return this.pos.x} |
||||
|
||||
get centerY(): number{return this.pos.y} |
||||
|
||||
get size(): number{return this.currentRadius * 2} |
||||
|
||||
static fromSerialized(item: Serialized.GameObject, settings: Settings.Item, Type: typeof Item): Item{ |
||||
let i = new Type(settings) |
||||
i.pos = new Vector(0, 0) |
||||
i.applyValues(item) |
||||
return i |
||||
} |
||||
|
||||
static fromNew(players: Player[], settings: Settings.Item, Type: typeof Item): Item{ |
||||
let i = new Type(settings) |
||||
i.pos = i.getStartPosition(players) |
||||
return i |
||||
} |
||||
|
||||
getStartPosition(players: Player[]): Vector{ |
||||
let left: number, |
||||
right: number, |
||||
top = this.radius, |
||||
bottom = p.height - this.radius |
||||
for (let p of players){ |
||||
if (p.isLeft) left = p.rightSide + this.radius |
||||
if (p.isRight) right = p.leftSide - this.radius |
||||
if (p.isTop) top = p.downSide + this.radius |
||||
if (p.isBottom) bottom = p.upSide - this.radius |
||||
} |
||||
let x = p.random(left, right), |
||||
y = p.random(top, bottom) |
||||
|
||||
return new Vector(x, y) |
||||
} |
||||
|
||||
update(): void{ |
||||
this.time.update() |
||||
if (this.time.finished) this.destroy() |
||||
|
||||
if (this.time.timeLeft() < this.fadeTime){ |
||||
this.currentRadius = p.pow(this.time.timeLeft() / this.fadeTime, 2) * this.radius |
||||
} else if (this.time.now < this.fadeTime){ |
||||
this.currentRadius = p.pow(this.time.now / this.fadeTime, 2) * this.radius |
||||
} |
||||
} |
||||
|
||||
destroy(): void{} |
||||
|
||||
serialized(): Serialized.Item{ |
||||
return { |
||||
id: this.id, |
||||
radius: this.radius, |
||||
pos: this.pos.serialized(), |
||||
color: this.color, |
||||
time: this.time, |
||||
currentRadius: this.currentRadius |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
class TimeProcess{ |
||||
|
||||
//Numbers in Milliseconds
|
||||
|
||||
max: number |
||||
now: number |
||||
finished: boolean |
||||
|
||||
constructor(max){ |
||||
this.max = max |
||||
this.now = 0 |
||||
this.finished = false |
||||
} |
||||
|
||||
update(): void{ |
||||
this.now += 1000 / p.frameRate() |
||||
if (this.now >= this.max) this.finished = true |
||||
} |
||||
|
||||
progress(): number{ |
||||
return this.now / this.max |
||||
} |
||||
|
||||
timeLeft(): number{ |
||||
return this.max - this.now |
||||
} |
||||
|
||||
serialized(): Serialized.TimeProcess{ |
||||
return { |
||||
max: this.max, |
||||
now: this.now, |
||||
finished: this.finished |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
class Boost extends Item{ |
||||
|
||||
//Class for the boost objects appearing on the game board
|
||||
//Inherits following properties from Item:
|
||||
/* |
||||
id: string |
||||
|
||||
pos: Vector |
||||
radius: number |
||||
color: Serialized.Color |
||||
settings: Settings.Item |
||||
time: TimeProcess |
||||
*/ |
||||
|
||||
type: string |
||||
positive: boolean |
||||
bool: string |
||||
effect: Settings.Effect |
||||
|
||||
constructor(settings: Settings.Boost){ |
||||
super(settings) |
||||
this.type = p.random(settings.types) |
||||
this.positive = ranBool(2) |
||||
this.bool = this.positive ? "positive" : "negative" |
||||
this.effect = settings.effect[this.type] |
||||
this.color = { |
||||
stroke: settings.color[this.bool].stroke, |
||||
fill: settings.color[this.bool].fill |
||||
} |
||||
} |
||||
|
||||
destroy(): void{ |
||||
let index = game.boosts.indexOf(this) |
||||
game.boosts.splice(index, 1) |
||||
} |
||||
|
||||
serialized(): Serialized.Boost{ |
||||
let item = super.serialized() as Serialized.Boost |
||||
item.type = this.type |
||||
item.bool = this.bool |
||||
return item |
||||
} |
||||
|
||||
show(): void{ |
||||
let angle = p.TWO_PI * (1 - this.time.now / this.time.max), |
||||
size = this.size |
||||
|
||||
p.push() |
||||
p.translate(this.centerX, this.centerY) |
||||
p.image(icons[this.type][this.bool], -size / 2, -size / 2, size, size) |
||||
p.pop() |
||||
} |
||||
|
||||
} |
||||
|
||||
class ActiveBoost{ |
||||
|
||||
time: TimeProcess |
||||
type: string |
||||
positive: boolean |
||||
bool: string |
||||
player: Player |
||||
color: Settings.Color |
||||
effect: {[key: string]: number} |
||||
reset: {[key: string]: number} |
||||
|
||||
id: string |
||||
|
||||
constructor(player: Player, boost: Boost){ |
||||
this.id = randomToken() |
||||
this.time = new TimeProcess(boost.effect.duration) |
||||
this.type = boost.type |
||||
this.color = { |
||||
stroke: boost.color.stroke, |
||||
fill: boost.color.fill |
||||
} |
||||
this.positive = boost.positive |
||||
this.bool = boost.bool |
||||
this.player = player |
||||
if (boost.positive) this.effect = boost.effect.positive |
||||
if (!boost.positive) this.effect = boost.effect.negative |
||||
this.reset = boost.effect.standard |
||||
} |
||||
|
||||
serialized(): Serialized.ActiveBoost{ |
||||
return { |
||||
time: this.time.serialized(), |
||||
type: this.type, |
||||
bool: this.bool, |
||||
positive: this.positive, |
||||
color: this.color, |
||||
id: this.id |
||||
} |
||||
} |
||||
|
||||
update(): void{ |
||||
this.player.applyValues(this.effect) |
||||
this.time.update() |
||||
if (this.time.finished){ |
||||
this.player.applyValues(this.reset) |
||||
for (let b of this.player.boosts){ |
||||
if (b.id === this.id){ |
||||
let index = this.player.boosts.indexOf(b) |
||||
this.player.boosts.splice(index, 1) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,37 @@ |
||||
class Loader{ |
||||
|
||||
dim: p5.Vector; |
||||
c: any; |
||||
radius: number; |
||||
center: p5.Vector; |
||||
angle: number; |
||||
|
||||
constructor(dom: any){ |
||||
this.dim = p.createVector($(dom).width(), $(dom).height()); |
||||
this.c = p.createGraphics(this.dim.x, this.dim.y, 'p2d'); |
||||
this.c.parent(dom); |
||||
this.radius = p.min([this.dim.x, this.dim.y]) * 0.4; |
||||
this.center = p.createVector(this.dim.x / 2, this.dim.y / 2); |
||||
$(dom).find('canvas').show(); |
||||
this.angle = 0; |
||||
$(dom).show(); |
||||
} |
||||
|
||||
update(): void{ |
||||
this.angle += p.PI / 10; |
||||
} |
||||
|
||||
display(): void{ |
||||
let c = this.c; |
||||
c.clear(); |
||||
c.noFill(); |
||||
c.stroke(0); |
||||
c.strokeWeight(5); |
||||
c.arc(this.center.x, this.center.y, this.radius * 2, this.radius * 2, this.angle, this.angle + p.PI + p.HALF_PI); |
||||
} |
||||
|
||||
destroy(): void{ |
||||
this.c.remove() |
||||
} |
||||
|
||||
} |
@ -0,0 +1,27 @@ |
||||
class Lobby implements Serialized.Lobby{ |
||||
|
||||
id: string |
||||
clients: Serialized.Client[] |
||||
clientCounts: number[] |
||||
game: string |
||||
|
||||
constructor(lobby: Serialized.Lobby){ |
||||
this.id = lobby.id |
||||
this.clients = lobby.clients |
||||
this.clientCounts = lobby.clientCounts |
||||
this.game = lobby.game |
||||
} |
||||
|
||||
get leader(): Serialized.Client{ |
||||
return this.clients[0] |
||||
} |
||||
|
||||
serialized(): Serialized.Lobby{ |
||||
return { |
||||
id: this.id, |
||||
game: this.game, |
||||
clientCounts: this.clientCounts, |
||||
clients: this.clients |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
class NewBall extends Item{ |
||||
|
||||
//Class for the new ball objects appearing on the game board
|
||||
//Inherits following properties from Item:
|
||||
/* |
||||
id: string |
||||
|
||||
pos: Vector |
||||
radius: number |
||||
color: Serialized.Color |
||||
settings: Settings.Item |
||||
time: TimeProcess |
||||
*/ |
||||
|
||||
constructor(settings: Settings.NewBall){ |
||||
super(settings) |
||||
} |
||||
|
||||
destroy(): void{ |
||||
let index = game.newBalls.indexOf(this) |
||||
game.newBalls.splice(index, 1) |
||||
} |
||||
|
||||
serialized(): Serialized.NewBall{ |
||||
let newBall = super.serialized() as Serialized.NewBall |
||||
return newBall |
||||
} |
||||
|
||||
show(): void{ |
||||
p.push() |
||||
p.translate(this.centerX, this.centerY) |
||||
p.fill(this.color.fill) |
||||
p.stroke(this.color.stroke) |
||||
p.ellipse(0, 0, this.size, this.size) |
||||
p.pop() |
||||
} |
||||
|
||||
} |
@ -0,0 +1,264 @@ |
||||
declare let P2P: any |
||||
declare let io: any |
||||
|
||||
class OnlineManager{ |
||||
|
||||
socket: any |
||||
settings: Settings.Global |
||||
lobby: Lobby |
||||
p2p: any |
||||
|
||||
constructor(name: string, settings: Settings.Global){ |
||||
this.connectToServer(settings.project, name, () => { |
||||
this.connectToPeers(settings.project) |
||||
this.setSocketEvents() |
||||
this.settings = settings |
||||
}); |
||||
} |
||||
|
||||
connectToServer(project: Settings.Project, name: string, cb): void{ |
||||
let urlQueries = '?game=' + project.name + '&name=' + name; |
||||
$.get('data/settings/get_port.php', port => { |
||||
let url = 'https://' + location.hostname + ':' + port + urlQueries |
||||
this.socket = io.connect(url) |
||||
|
||||
this.socket.on('connect', () => |
||||
console.log('Connected to ', url)); |
||||
cb(); |
||||
}); |
||||
} |
||||
|
||||
connectToPeers(project: Settings.Project){ |
||||
let iceServers = project.online.iceServers |
||||
let opts = {peerOpts: {trickle: false, config: {iceServers: iceServers}}} |
||||
this.p2p = new P2P(this.socket, opts) |
||||
this.p2p.usePeerConnection = true |
||||
this.p2p.useSockets = false |
||||
|
||||
console.log('Created WebRTC Connection:', this.p2p) |
||||
} |
||||
|
||||
setSocketEvents(): void{ |
||||
this.socket.on('connected', () =>
|
||||
onlineAnswerFrontend()) |
||||
|
||||
this.socket.on('member-joined', (lobby: Serialized.Lobby) =>
|
||||
this.setLobby(new Lobby(lobby))) |
||||
|
||||
this.socket.on('member-left', (lobby: Serialized.Lobby) =>
|
||||
this.setLobby(new Lobby(lobby))) |
||||
|
||||
this.socket.on('join-failed', (error: string) =>
|
||||
this.joinFailed(error)) |
||||
|
||||
this.socket.on('start-game', () =>
|
||||
this.startOnlineGame()) |
||||
|
||||
} |
||||
|
||||
setPeerEvents(){ |
||||
//Game actions like togglePlay
|
||||
this.p2p.on('game-action', (req: any) => { |
||||
if (req.lobby.id !== this.lobby.id) return |
||||
game[req.action](false, req.fromInitiator) |
||||
}) |
||||
|
||||
//For every non-leader, whole game sent by leader
|
||||
this.p2p.on('game-data', (data: Serialized.Game) => { |
||||
if (data.id !== this.lobby.id) return |
||||
(game as OnlineGame).data = data |
||||
}) |
||||
|
||||
//For leader, inputs sent by others
|
||||
this.p2p.on('player-input', (player: Serialized.Player) => { |
||||
if (player.lobby.id !== this.lobby.id) return |
||||
(game as OnlineGame).setPlayerInput(player) |
||||
}) |
||||
|
||||
this.p2p.on('chat-msg', (req: any) => { |
||||
if (req.lobby.id !== this.lobby.id) return |
||||
this.addChatMessage(req.msg) |
||||
}) |
||||
|
||||
this.p2p.on('peer-error', (data: any) =>
|
||||
console.log('Peer-Error: ', data)) |
||||
} |
||||
|
||||
setLobby(lobby: Lobby): void{ |
||||
if (!this.lobby) this.setPeerEvents() |
||||
|
||||
this.lobby = lobby |
||||
|
||||
onlineAnswerFrontend() |
||||
|
||||
$('.error-label').html('') |
||||
$('#setup-controls').hide() |
||||
$('#lobby-members').html('') |
||||
|
||||
this.setLobbyMembers() |
||||
this.setLeaderAbilites() |
||||
this.checkPlayerCount() |
||||
|
||||
$('#lobby > span:eq(1)').html(this.lobby.id) |
||||
$('#lobby, #chat').show() |
||||
|
||||
console.log('Set lobby with id: ', this.lobby.id) |
||||
} |
||||
setLobbyMembers(): void{ |
||||
for (let c of this.lobby.clients){ |
||||
let dom = $('<div></div>') |
||||
dom.attr('class', 'lobby-member border-node') |
||||
if (c.id === this.socket.id){ |
||||
dom.attr('id', 'this-player') |
||||
} |
||||
if (this.lobby.leader.id === c.id){ |
||||
dom.attr('id', 'lobby-leader') |
||||
} |
||||
dom.html(c.name) |
||||
$('#lobby-members').append(dom) |
||||
} |
||||
|
||||
console.log('Set lobby members: ', this.lobby.clients) |
||||
} |
||||
setLeaderAbilites(): void{ |
||||
if (this.socket.id === this.lobby.leader.id){ |
||||
$('#start-lobby').show() |
||||
|
||||
console.log('Leader: ', true) |
||||
} else { |
||||
$('#start-lobby').hide() |
||||
|
||||
console.log('Leader: ', false) |
||||
} |
||||
} |
||||
|
||||
checkPlayerCount(): void{ |
||||
let wrongCount = true |
||||
let lo = this.lobby |
||||
let pc = lo.clientCounts |
||||
for (let c of pc){ |
||||
if (c === lo.clients.length) wrongCount = false |
||||
} |
||||
if (wrongCount){ |
||||
let error = 'Only as ' |
||||
for (let c of pc){ |
||||
let comma = pc.indexOf(c) === pc.length - 1 ? '' : ', ' |
||||
error += c + comma |
||||
} |
||||
$('#lobby .error-label').html(error) |
||||
$('#start-lobby').prop('disabled', true) |
||||
} else { |
||||
$('#lobby .error-label').html('') |
||||
$('#start-lobby').prop('disabled', false) |
||||
} |
||||
} |
||||
|
||||
createLobby(dom: any): void{ |
||||
onlineRequestFrontend(dom) |
||||
this.socket.emit('create-lobby', this.settings) |
||||
|
||||
console.log('Server request: create lobby') |
||||
} |
||||
|
||||
joinLobby(dom: any): void{ |
||||
let input = getValidInput('join') |
||||
if (input){ |
||||
onlineRequestFrontend(dom) |
||||
this.socket.emit('join-lobby', input) |
||||
|
||||
console.log('Server request: join lobby: ', input) |
||||
} |
||||
} |
||||
|
||||
joinFailed(error: string): void{ |
||||
onlineAnswerFrontend() |
||||
$('.error-label').html(error) |
||||
|
||||
console.log('Joining lobby failed. Reason: ', error) |
||||
} |
||||
|
||||
startLobby(dom: any): void{ |
||||
onlineRequestFrontend(dom) |
||||
this.socket.emit('start-game', this.lobby.id, this.settings) |
||||
|
||||
console.log('Server request: start lobby: ', this.lobby.id) |
||||
} |
||||
|
||||
startOnlineGame(): void{ |
||||
onlineAnswerFrontend() |
||||
$('#lobby').hide() |
||||
$('#game-controls').show() |
||||
game = new OnlineGame(this.socket, this.p2p, this.settings.game) |
||||
game.init(this.lobby) |
||||
|
||||
console.log('Online Game started:', game) |
||||
} |
||||
|
||||
addChatMessage(msg: any): void{ |
||||
let chat = $('#chat-content'),
|
||||
name: string, |
||||
message = $('<div></div>') |
||||
if (chat.find('.message').last().attr('name') === "0"){ |
||||
name = "1" |
||||
} else name = "0" |
||||
message.addClass('message') |
||||
message.attr('name', name) |
||||
let writer = $('<span></span>'), |
||||
content = $('<span></span>') |
||||
writer.html('[' + msg.writer + ']: ') |
||||
content.html(msg.content) |
||||
message.append(writer, content) |
||||
chat.append(message) |
||||
message.get(0).scrollIntoView(false) |
||||
} |
||||
|
||||
sendMessage(): void{ |
||||
let content = $($('#chat-input > input')[0]).val(), |
||||
name: string |
||||
if (content === '')
|
||||
return |
||||
$($('#chat-input > input')[0]).val('') |
||||
for (let c of this.lobby.clients){ |
||||
if (c.id === this.socket.id) |
||||
name = c.name |
||||
} |
||||
let msg = {writer: name, content: content} |
||||
this.addChatMessage(msg) |
||||
this.p2p.emit('chat-msg', { |
||||
lobby: this.lobby.serialized(), |
||||
msg: { |
||||
writer: name,
|
||||
content: content |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
function onlineRequestFrontend(dom: any): void{ |
||||
$(dom).blur() |
||||
$('.setup').prop('disabled', true) |
||||
if (loader) loader.destroy() |
||||
loader = new Loader($('#loader').get(0)) |
||||
} |
||||
|
||||
function onlineAnswerFrontend(): void{ |
||||
$('.setup').prop('disabled', false) |
||||
if (loader) loader.destroy() |
||||
} |
||||
|
||||
function getValidInput(type: string): string{ |
||||
$('.error-label').html('') |
||||
if (type === 'player-name'){ |
||||
let val = $('#player-name > input').val() |
||||
if (val === ''){ |
||||
$('#player-name > .error-label').html('Please enter a name!') |
||||
} else return val as string |
||||
} |
||||
if (type === 'join'){ |
||||
let val = $('#lobby-code > input').val() |
||||
if (val === ''){ |
||||
$('#lobby-code > .error-label').html('Please enter your code!') |
||||
} else return val as string |
||||
} |
||||
return null |
||||
} |
@ -0,0 +1,401 @@ |
||||
class Player extends GameObject{ |
||||
|
||||
//Main class for the Players involved in the game
|
||||
//including real life input and the paddleboard
|
||||
|
||||
//Settings
|
||||
settings: Settings.Player |
||||
|
||||
//Online players
|
||||
lobby: Lobby |
||||
//General
|
||||
color: Settings.Color |
||||
thisStroke: string |
||||
otherCount: number |
||||
//Position and dimensions
|
||||
pos: Vector |
||||
side: Direction |
||||
dim: Vector |
||||
margin: number |
||||
//Movement
|
||||
vel: Vector |
||||
absVel: number |
||||
acc: Vector |
||||
moveMargin: number |
||||
//Competitive
|
||||
points: number |
||||
hasLost: boolean |
||||
//Boosts / Modifications
|
||||
boosts: ActiveBoost[] |
||||
frameRate: number |
||||
vision: boolean |
||||
blinkProcess: TimeProcess |
||||
|
||||
constructor(side: Direction, id: string, settings: Settings.Player, otherCount: number, lobby?: Lobby){ |
||||
super(id) |
||||
this.settings = settings |
||||
this.lobby = lobby |
||||
this.otherCount = otherCount |
||||
this.side = side |
||||
this.setup(settings) |
||||
} |
||||
|
||||
_input: Serialized.Player.Input |
||||
|
||||
get input(): Serialized.Player.Input{ |
||||
let input = {up: null, down: null, right: null, left: null}; |
||||
if (this.isRight){ |
||||
if (p.keyIsDown(38)) input.up = true; |
||||
if (p.keyIsDown(40)) input.down = true; |
||||
} |
||||
if (this.isLeft){ |
||||
if (p.keyIsDown(87)) input.up = true; |
||||
if (p.keyIsDown(83)) input.down = true; |
||||
} |
||||
if (this.isTop){ |
||||
if (p.keyIsDown(65)) input.left = true; |
||||
if (p.keyIsDown(68)) input.right = true; |
||||
} |
||||
if (this.isBottom){ |
||||
if (p.keyIsDown(37)) input.left = true; |
||||
if (p.keyIsDown(39)) input.right = true; |
||||
} |
||||
return input; |
||||
} |
||||
|
||||
set input(input: Serialized.Player.Input){ |
||||
this._input = input |
||||
} |
||||
|
||||
get startPosition(): Vector{ |
||||
let s = this.settings |
||||
let side = this.side, |
||||
d = s.depth, |
||||
l = s.length, |
||||
m = s.margin, |
||||
cw = p.width, |
||||
ch = p.height |
||||
let x = (side === Direction.Left) ? m : |
||||
(side === Direction.Right) ? (cw - m - d) : |
||||
(side === Direction.Top || side === Direction.Bottom) ? (this.boxLeftSide + this.boxWidth / 2 - l / 2) : null |
||||
|
||||
let y = (side === Direction.Left || side === Direction.Right) ? (this.boxTopSide + this.boxHeight / 2 - l / 2) : |
||||
(side === Direction.Top) ? m : |
||||
(side === Direction.Bottom) ? (ch - m - d) : null |
||||
|
||||
if (x == null || y == null){ |
||||
console.error('Something went wrong with computing the players positions. Side:', side) |
||||
} |
||||
return new Vector(x, y) |
||||
} |
||||
|
||||
get startDimension(): Vector{ |
||||
let s = this.settings |
||||
let d = s.depth, |
||||
l = s.length, |
||||
side = this.side |
||||
let x = (side === Direction.Left || side === Direction.Right) ? d : |
||||
(side === Direction.Top || side === Direction.Bottom) ? l : null |
||||
|
||||
let y = (side === Direction.Left || side === Direction.Right) ? l : |
||||
(side === Direction.Top || side === Direction.Bottom) ? d : null |
||||
|
||||
if (x == null || y == null){ |
||||
console.error('Something went wrong with computing the players dimensions. Side:', side) |
||||
} |
||||
return new Vector(x, y) |
||||
} |
||||
|
||||
get future(): Player{ |
||||
let future = new Player(this.side, this.id, this.settings, this.otherCount) |
||||
future.pos = this.pos.copy() |
||||
future.vel = this.vel.copy() |
||||
future.move() |
||||
return future |
||||
} |
||||
|
||||
get moveTopSide(): number{return this.boxTopSide + this.moveMargin} |
||||
|
||||
get moveBottomSide(): number{return this.boxBottomSide - this.moveMargin} |
||||
|
||||
get moveLeftSide(): number{return this.boxLeftSide + this.moveMargin} |
||||
|
||||
get moveRightSide(): number{return this.boxRightSide - this.moveMargin} |
||||
|
||||
get boxTopSide(): number{return (this.isTop ? 0 : (this.isBottom ? (p.height - this.margin) : (this.otherCount === 2 ? 0 : this.margin)))} |
||||
|
||||
get boxBottomSide(): number {return (this.isTop ? this.margin : ((this.isBottom || this.otherCount != 4) ? p.height : p.height - this.margin))} |
||||
|
||||
get boxLeftSide(): number {return this.isLeft ? 0 : (this.isRight ? p.width - this.margin : this.margin)} |
||||
|
||||
get boxRightSide(): number {return this.isLeft ? this.margin : (this.isRight ? p.width : p.width - this.margin)} |
||||
|
||||
get boxCenterX(): number {return (this.boxLeftSide + this.boxRightSide) / 2} |
||||
|
||||
get boxCenterY(): number {return (this.boxTopSide + this.boxBottomSide) / 2} |
||||
|
||||
get boxWidth(): number {return (this.isLeft || this.isRight) ? this.margin : p.width - 2 * this.margin} |
||||
|
||||
get boxHeight(): number {return ((this.isTop || this.isBottom) ? this.margin : (this.otherCount === 2) ? p.height : (this.otherCount === 3) ? p.height - this.margin : p.height - 2 * this.margin)} |
||||
|
||||
get isLeft(): boolean {return this.side === Direction.Left} |
||||
|
||||
get isRight(): boolean {return this.side === Direction.Right} |
||||
|
||||
get isTop(): boolean {return this.side === Direction.Top} |
||||
|
||||
get isBottom(): boolean {return this.side === Direction.Bottom} |
||||
|
||||
get leftSide(): number {return this.pos.x} |
||||
|
||||
get rightSide(): number {return this.pos.x + this.dim.x} |
||||
|
||||
get downSide(): number {return this.pos.y + this.dim.y} |
||||
|
||||
get upSide(): number {return this.pos.y} |
||||
|
||||
get toGameSide(): number {return this.isLeft ? this.rightSide : (this.isRight ? this.leftSide : (this.isTop ? this.downSide : (this.isBottom ? this.upSide : null)))} |
||||
|
||||
get centerX(): number {return this.leftSide + this.width / 2} |
||||
|
||||
get centerY(): number {return this.upSide + this.height / 2} |
||||
|
||||
get width(): number {return this.dim.x} |
||||
|
||||
get height(): number {return this.dim.y} |
||||
|
||||
setup(settings: Settings.Player): void{ |
||||
this._input = {up: false, down: false, left: false, right: false} |
||||
this.color = { |
||||
stroke: settings.color.stroke, |
||||
fill: settings.color.fill |
||||
} |
||||
this.thisStroke = settings.thisStroke |
||||
this.margin = settings.margin |
||||
this.pos = this.startPosition |
||||
this.dim = this.startDimension |
||||
this.vel = new Vector(0, 0) |
||||
this.absVel = settings.absVel |
||||
this.moveMargin = settings.moveMargin |
||||
this.points = settings.points |
||||
this.hasLost = false |
||||
this.boosts = [] |
||||
this.vision = false |
||||
this.frameRate = 60 |
||||
} |
||||
|
||||
applyValues(state: Serialized.GameObject | {[key: string]: number}): void{ |
||||
let recurse = (objIn: any, objOut: any): void => { |
||||
if (Array.isArray(objIn)){ |
||||
for (let o1 of objOut){ |
||||
let found = false |
||||
for (let o2 of objIn){ |
||||
if (o2 === o1){ |
||||
found = true |
||||
break |
||||
} |
||||
} |
||||
if (!found){ |
||||
objOut.splice(objOut.indexOf(o1), 1) |
||||
} |
||||
} |
||||
} |
||||
for (let p in objIn){ |
||||
if (typeof objIn[p] == 'object' && objIn[p] != null){ |
||||
if (!objOut[p]) objOut[p] = {}; |
||||
recurse(objIn[p], objOut[p]) |
||||
} else objOut[p] = objIn[p] |
||||
} |
||||
} |
||||
recurse(state, this) |
||||
} |
||||
|
||||
applyBoost(boost: Boost): void{ |
||||
for (let b of this.boosts){ |
||||
let index = this.boosts.indexOf(b) |
||||
if (b.type === boost.type){ |
||||
this.boosts.splice(index, 1) |
||||
} |
||||
} |
||||
this.boosts.push(new ActiveBoost(this, boost)) |
||||
} |
||||
|
||||
reset(): void{ |
||||
this.setup(this.settings); |
||||
} |
||||
|
||||
checkInputs(online: boolean): void{ |
||||
let input: Serialized.Player.Input |
||||
this.vel.mult(0) |
||||
if (online) |
||||
input = this._input |
||||
else |
||||
input = this.input |
||||
|
||||
if (this.isLeft || this.isRight){ |
||||
if (input.up && this.upSide > this.boxTopSide + this.moveMargin) |
||||
this.vel.y -= this.absVel |
||||
if (input.down && this.downSide < this.boxBottomSide - this.moveMargin) |
||||
this.vel.y += this.absVel |
||||
} |
||||
if (this.isTop || this.isBottom){ |
||||
if (input.left && this.leftSide > this.boxLeftSide + this.moveMargin) |
||||
this.vel.x -= this.absVel |
||||
if (input.right && this.rightSide < this.boxRightSide - this.moveMargin) |
||||
this.vel.x += this.absVel |
||||
} |
||||
} |
||||
|
||||
update(online: boolean): void{ |
||||
this.checkInputs(online) |
||||
this.updateBoosts() |
||||
this.move() |
||||
this.updateBlink() |
||||
} |
||||
|
||||
updateBoosts(): void{ |
||||
for (let b of this.boosts){ |
||||
b.update() |
||||
} |
||||
} |
||||
|
||||
move(): void{ |
||||
this.pos.add(this.vel) |
||||
} |
||||
|
||||
equals(other: Player): boolean{ |
||||
return this === other |
||||
} |
||||
|
||||
lost(ball: Ball): void{ |
||||
this.points-- |
||||
this.blink({ |
||||
stroke: 'rgb(255, 0, 0)', |
||||
fill: 'rgb(100, 0, 0)' |
||||
}) |
||||
ball.reset(false) |
||||
if (!this.points) this.hasLost = true |
||||
} |
||||
|
||||
blink(color: Settings.Color): void{ |
||||
this.color = color |
||||
this.blinkProcess = new TimeProcess(this.settings.blinkTime) |
||||
} |
||||
|
||||
updateBlink(): void{ |
||||
if (this.blinkProcess){ |
||||
this.blinkProcess.update() |
||||
if (this.blinkProcess.finished){ |
||||
this.color = { |
||||
stroke: this.settings.color.stroke, |
||||
fill: this.settings.color.fill |
||||
} |
||||
this.blinkProcess = null |
||||
} |
||||
} |
||||
} |
||||
|
||||
copy(): Player{ |
||||
let copy = new Player(this.side, this.id, this.settings, this.otherCount) |
||||
copy.pos = this.pos.copy() |
||||
copy.vel = this.vel.copy() |
||||
return copy |
||||
} |
||||
|
||||
serialized(): Serialized.Player{ |
||||
let boosts: Serialized.ActiveBoost[] = [] |
||||
for (let b of this.boosts) boosts.push(b.serialized()) |
||||
return { |
||||
id: this.id, |
||||
pos: this.pos.serialized(), |
||||
vel: this.vel.serialized(), |
||||
dim: this.dim.serialized(), |
||||
margin: this.margin, |
||||
moveMargin: this.moveMargin, |
||||
color: this.color, |
||||
points: this.points, |
||||
lobby: this.lobby.serialized(), |
||||
input: this.input, |
||||
boosts: boosts, |
||||
frameRate: this.frameRate, |
||||
vision: this.vision |
||||
} |
||||
} |
||||
|
||||
show(): void{ |
||||
let stroke = this.color.stroke |
||||
if (game instanceof OnlineGame){ |
||||
if (game.getThisPlayer().id === this.id) stroke = this.thisStroke |
||||
}
|
||||
p.fill(this.color.fill) |
||||
p.stroke(stroke) |
||||
p.strokeWeight(2) |
||||
p.rect(this.leftSide, this.upSide, this.width, this.height, p.min([this.width, this.height]) * 0.5) |
||||
p.fill(0, 255, 0) |
||||
p.stroke(0) |
||||
p.strokeWeight(5) |
||||
|
||||
if (this.isLeft || this.isRight){ |
||||
p.line(this.leftSide, this.moveTopSide, this.rightSide, this.moveTopSide) |
||||
p.line(this.leftSide, this.moveBottomSide, this.rightSide, this.moveBottomSide) |
||||
} |
||||
if (this.isTop || this.isBottom){ |
||||
p.line(this.moveLeftSide, this.upSide, this.moveLeftSide, this.downSide) |
||||
p.line(this.moveRightSide, this.upSide, this.moveRightSide, this.downSide) |
||||
} |
||||
|
||||
p.textAlign('center', 'center') |
||||
p.textSize(20) |
||||
p.fill(0) |
||||
p.strokeWeight(3) |
||||
p.stroke(255, 0, 0) |
||||
p.text(p.str(this.points), this.boxCenterX, this.boxCenterY) |
||||
|
||||
//TODO: Boost display for top and bottom players and add boost images
|
||||
let positiveCount = 0, |
||||
negativeCount = 0
|
||||
for (let b of this.boosts){ |
||||
let positive = b.positive |
||||
if (positive) positiveCount++ |
||||
else negativeCount++ |
||||
let boxX, |
||||
boxY, |
||||
boxSize = this.margin * 0.8, |
||||
imgSize = boxSize * 0.7, |
||||
imgX = (boxSize - imgSize) / 2, |
||||
imgY = boxSize * 0.05, |
||||
barW = boxSize * 0.9, |
||||
barH = boxSize * 0.15, |
||||
barX = (boxSize - barW) / 2, |
||||
barY = boxSize - barH - boxSize * 0.05, |
||||
processW = barW * (1 - b.time.now / b.time.max), |
||||
margin = boxSize * 0.1 |
||||
|
||||
if (this.isLeft || this.isRight){ |
||||
boxX = this.boxLeftSide + (this.margin - boxSize) / 2 |
||||
boxY = this.boxCenterY - boxSize / 2 + (boxSize + margin) * (positive ? -positiveCount : negativeCount) |
||||
} |
||||
if (this.isTop || this.isBottom){ |
||||
boxX = this.boxCenterX - boxSize / 2 + (boxSize + margin) * (positive ? -positiveCount : negativeCount) |
||||
boxY = this.boxTopSide + (this.margin - boxSize) / 2 |
||||
} |
||||
|
||||
//Box content
|
||||
|
||||
p.push() |
||||
p.translate(boxX, boxY) |
||||
|
||||
//Bar
|
||||
p.noFill() |
||||
p.stroke(0) |
||||
p.strokeWeight(1) |
||||
p.rect(barX, barY, barW, barH, barH / 5) |
||||
p.fill(b.color.fill) |
||||
p.rect(barX, barY, processW, barH, barH / 5)
|
||||
|
||||
//Image
|
||||
p.image(icons[b.type][b.bool], imgX, imgY, imgSize, imgSize) |
||||
p.pop() |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
class Vector extends p5.Vector { |
||||
|
||||
x: number |
||||
y: number |
||||
|
||||
constructor(x: number, y: number){ |
||||
super(x, y); |
||||
} |
||||
|
||||
addMag(length: number): Vector{ |
||||
this.setMag(this.mag() + length) |
||||
return this |
||||
} |
||||
|
||||
serialized(): Serialized.Vector{ |
||||
return { |
||||
x: this.x,
|
||||
y: this.y |
||||
}; |
||||
} |
||||
|
||||
copy(): Vector{ |
||||
return new Vector(this.x, this.y) |
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
class Wormhole extends Item{ |
||||
|
||||
//Class for the wormhole objects appearing on the game board
|
||||
//Inherits following properties from Item:
|
||||
/* |
||||
id: string |
||||
|
||||
pos: Vector |
||||
radius: number |
||||
color: Serialized.Color |
||||
settings: Settings.Item |
||||
time: TimeProcess |
||||
*/ |
||||
|
||||
power: number |
||||
|
||||
constructor(settings: Settings.Wormhole){ |
||||
super(settings) |
||||
this.power = settings.power |
||||
this.fadeTime = settings.fadeTime |
||||
this.radius = p.random(settings.minRadius, settings.maxRadius) |
||||
} |
||||
|
||||
serialized(): Serialized.Wormhole{ |
||||
let item = super.serialized() as Serialized.Wormhole |
||||
return item |
||||
} |
||||
|
||||
destroy(): void{ |
||||
let index = game.wormholes.indexOf(this) |
||||
game.wormholes.splice(index, 1) |
||||
} |
||||
|
||||
attractBall(ball: Ball): void{ |
||||
let distance = ball.dist(this.centerX, this.centerY) |
||||
let force = (this.power * ball.radius * this.currentRadius) / p.pow(distance, 2) |
||||
let forceVector = Vector.sub(this.pos, ball.pos).setMag(force) as Vector |
||||
ball.applyForce(forceVector) |
||||
} |
||||
|
||||
show(): void{ |
||||
let alpha = 0 |
||||
|
||||
p.push() |
||||
p.noFill() |
||||
p.strokeWeight(2) |
||||
for (let r = this.currentRadius; r > 0; --r){ |
||||
|
||||
let color = p.color(this.color.fill) |
||||
color.setAlpha(p.constrain(alpha, 0, 1) * 255) |
||||
p.stroke(color) |
||||
p.ellipse(this.centerX, this.centerY, r * 2, r * 2) |
||||
|
||||
alpha += 1 / this.currentRadius; |
||||
} |
||||
|
||||
p.pop() |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
<?php |
||||
echo parse_ini_file("../../../server/.env")["HTTPS_PORT"]; |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"collision": true, |
||||
"colorPicker": false, |
||||
"cookie": true, |
||||
"loader": false, |
||||
"prototypes": true, |
||||
"technical": true |
||||
} |
@ -0,0 +1,131 @@ |
||||
{ |
||||
"project": { |
||||
"name": "pong", |
||||
"author": "BenjoCraeft", |
||||
"playerCounts": [2, 3, 4], |
||||
"online": { |
||||
"iceServers": [ |
||||
{"urls": "stun:stun.l.google.com:19302"}, |
||||
{ |
||||
"urls": "turn:numb.viagenie.ca", |
||||
"credential": "muazkh", |
||||
"username": "webrtc@live.com" |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"frameWork": { |
||||
"frameRate": 60, |
||||
"updateRate": 60, |
||||
"width": 1000, |
||||
"height": 1000 |
||||
}, |
||||
"game": { |
||||
"ball": { |
||||
"radius": 10, |
||||
"velocity": { |
||||
"start": 7, |
||||
"min": 5, |
||||
"runUpMax": 9, |
||||
"resetMultiplier": 0.8 |
||||
}, |
||||
"acceleration": { |
||||
"permanent": 0.007, |
||||
"max": 1 |
||||
}, |
||||
"runUp": { |
||||
"min": 1000, |
||||
"max": 3000 |
||||
}, |
||||
"color": { |
||||
"stroke": "rgb(0, 0, 220)", |
||||
"fill": "rgb(50, 50, 255)" |
||||
}, |
||||
"safeRadius": 75 |
||||
}, |
||||
"player": { |
||||
"length": 200, |
||||
"depth": 12, |
||||
"margin": 80, |
||||
"points": 20, |
||||
"absVel": 6, |
||||
"color": { |
||||
"stroke": "rgb(0, 0, 0)", |
||||
"fill": "rgb(0, 0, 0)" |
||||
}, |
||||
"thisStroke": "rgb(255, 255, 0)", |
||||
"moveMargin": 50, |
||||
"blinkTime": 200 |
||||
}, |
||||
"item": { |
||||
"boost": { |
||||
"radius": 30, |
||||
"types": [ |
||||
"speed", |
||||
"border" |
||||
], |
||||
"color": { |
||||
"positive": { |
||||
"stroke": "rgb(0, 255, 0)", |
||||
"fill": "rgb(0, 100, 0)" |
||||
}, |
||||
"negative": { |
||||
"stroke": "rgb(255, 0, 0)", |
||||
"fill": "rgb(100, 0, 0)" |
||||
} |
||||
}, |
||||
"spawnTime": 4, |
||||
"duration": 20000, |
||||
"fadeTime": 500, |
||||
"effect": { |
||||
"speed": { |
||||
"duration": 15000, |
||||
"positive": { |
||||
"absVel": 9 |
||||
}, |
||||
"negative": { |
||||
"absVel": 3 |
||||
}, |
||||
"standard": { |
||||
"absVel": 6 |
||||
} |
||||
}, |
||||
"border": { |
||||
"duration": 10000, |
||||
"positive": { |
||||
"moveMargin": 0 |
||||
}, |
||||
"negative": { |
||||
"moveMargin": 100 |
||||
}, |
||||
"standard": { |
||||
"moveMargin": 50 |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
"wormhole": { |
||||
"minRadius": 80, |
||||
"maxRadius": 120, |
||||
"fadeTime": 1000, |
||||
"power": 100, |
||||
"color": { |
||||
"fill": "rgb(0, 0, 0)", |
||||
"stroke": "rgb(0, 0, 0)" |
||||
}, |
||||
"spawnTime": 20, |
||||
"duration": 5000 |
||||
}, |
||||
"newBall": { |
||||
"radius": 40, |
||||
"fadeTime": 1500, |
||||
"spawnTime": 15, |
||||
"duration": 15000, |
||||
"color": { |
||||
"fill": "rgb(0, 0, 50)", |
||||
"stroke": "rgb(0, 0, 255)" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,88 @@ |
||||
#color_picker{ |
||||
width: 300px; |
||||
height: 25%; |
||||
margin: 20px; |
||||
margin-top: 50px; |
||||
border: 5px solid #000; |
||||
background-color: #000; |
||||
-webkit-user-select: none; |
||||
-moz-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
position: relative; |
||||
} |
||||
#color_picker_numeric{ |
||||
width: 80%; |
||||
padding: 5%; |
||||
margin: 5%; |
||||
background-color: #888; |
||||
border-radius: 10px; |
||||
overflow: hidden; |
||||
} |
||||
.color_picker_rgb{ |
||||
float: left; |
||||
width: 22%; |
||||
height: 35px; |
||||
font-size: 25px; |
||||
color: #000; |
||||
} |
||||
.color_picker_rgb:nth-child(1){ |
||||
margin-right: 10%; |
||||
margin-left: 3%; |
||||
background-color: #F00; |
||||
|
||||
} |
||||
.color_picker_rgb:nth-child(2){ |
||||
background-color: #0F0; |
||||
} |
||||
.color_picker_rgb:nth-child(3){ |
||||
margin-left: 10%; |
||||
background-color: #00F; |
||||
color: #FFF; |
||||
} |
||||
#color_picker_hex{ |
||||
width: 50%; |
||||
height: 30px; |
||||
font-size: 25px; |
||||
margin: 10% 25% 0 25%; |
||||
} |
||||
#saturation{ |
||||
position: relative; |
||||
width: calc(100% - 33px); |
||||
height: 100%; |
||||
background: linear-gradient(to right, #FFF 0%, #F00 100%); |
||||
float: left; |
||||
margin-right: 6px; |
||||
} |
||||
#value { |
||||
width: 100%; |
||||
height: 100%; |
||||
background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%); |
||||
} |
||||
#sb_picker{ |
||||
border: 2px solid; |
||||
border-color: #FFF; |
||||
position: absolute; |
||||
width: 14px; |
||||
height: 14px; |
||||
border-radius: 10px; |
||||
bottom: 50px; |
||||
left: 50px; |
||||
box-sizing: border-box; |
||||
z-index: 10; |
||||
} |
||||
#hue { |
||||
width: 27px; |
||||
height: 100%; |
||||
position: relative; |
||||
float: left; |
||||
background: linear-gradient(to bottom, #F00 0%, #F0F 17%, #00F 34%, #0FF 50%, #0F0 67%, #FF0 84%, #F00 100%); |
||||
} |
||||
#hue_picker { |
||||
position: absolute; |
||||
background: #000; |
||||
border-bottom: 1px solid #000; |
||||
top: 0; |
||||
width: 27px; |
||||
height: 2px; |
||||
} |
@ -0,0 +1,88 @@ |
||||
input[type=range] { |
||||
-webkit-appearance: none; |
||||
margin: 18px 0; |
||||
width: 100%; |
||||
} |
||||
input[type=range]:focus { |
||||
outline: none; |
||||
} |
||||
input[type=range]::-webkit-slider-runnable-track { |
||||
width: 100%; |
||||
height: 8.4px; |
||||
cursor: pointer; |
||||
animate: 0.2s; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
background: #3071a9; |
||||
border-radius: 1.3px; |
||||
border: 0.2px solid #010101; |
||||
} |
||||
input[type=range]::-webkit-slider-thumb { |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
border: 1px solid #000000; |
||||
height: 36px; |
||||
width: 16px; |
||||
border-radius: 3px; |
||||
background: #ffffff; |
||||
cursor: pointer; |
||||
-webkit-appearance: none; |
||||
margin-top: -14px; |
||||
} |
||||
input[type=range]:focus::-webkit-slider-runnable-track { |
||||
background: #367ebd; |
||||
} |
||||
input[type=range]::-moz-range-track { |
||||
width: 100%; |
||||
height: 8.4px; |
||||
cursor: pointer; |
||||
animate: 0.2s; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
background: #3071a9; |
||||
border-radius: 1.3px; |
||||
border: 0.2px solid #010101; |
||||
} |
||||
input[type=range]::-moz-range-thumb { |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
border: 1px solid #000000; |
||||
height: 36px; |
||||
width: 16px; |
||||
border-radius: 3px; |
||||
background: #ffffff; |
||||
cursor: pointer; |
||||
} |
||||
input[type=range]::-ms-track { |
||||
width: 100%; |
||||
height: 8.4px; |
||||
cursor: pointer; |
||||
animate: 0.2s; |
||||
background: transparent; |
||||
border-color: transparent; |
||||
border-width: 16px 0; |
||||
color: transparent; |
||||
} |
||||
input[type=range]::-ms-fill-lower { |
||||
background: #2a6495; |
||||
border: 0.2px solid #010101; |
||||
border-radius: 2.6px; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
} |
||||
input[type=range]::-ms-fill-upper { |
||||
background: #3071a9; |
||||
border: 0.2px solid #010101; |
||||
border-radius: 2.6px; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
} |
||||
input[type=range]::-ms-thumb { |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
border: 1px solid #000000; |
||||
height: 36px; |
||||
width: 16px; |
||||
border-radius: 3px; |
||||
background: #ffffff; |
||||
cursor: pointer; |
||||
} |
||||
input[type=range]:focus::-ms-fill-lower { |
||||
background: #3071a9; |
||||
} |
||||
input[type=range]:focus::-ms-fill-upper { |
||||
background: #367ebd; |
||||
} |
@ -0,0 +1,72 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<script src="https://cdn.socket.io/4.4.1/socket.io.min.js" type="text/javascript"></script> |
||||
<script src="data/lib/socket.io-p2p.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="https://code.jquery.com/jquery-3.6.4.min.js" type="text/javascript"></script> |
||||
<script src="data/scripts/js/main.js" type="text/javascript"></script> |
||||
<link href="styles.css" rel="stylesheet"> |
||||
<link href="data/styles/color_picker.css" rel="stylesheet"> |
||||
<link href="data/styles/range_input.css" rel="stylesheet"> |
||||
<link href="data/images/favicon.ico" rel="icon" type="image/x-icon"> |
||||
<title>Pong remastered online</title> |
||||
</head> |
||||
<body> |
||||
<div id="p5_loading"></div> |
||||
<div id="content"> |
||||
<div class="border-node" id="interface"> |
||||
<div id="game-controls"> |
||||
<button class="paused" id="toggle-play" onclick="game.togglePlay(true, false);"></button> |
||||
</div> |
||||
<div id="setup-controls"> |
||||
<fieldset class="border-node" id="game-type"> |
||||
<legend>Type</legend> |
||||
<form> |
||||
<input id="offline" name="game-type" type="radio" value="offline"> |
||||
<label for="offline">Offline</label><br> |
||||
<input id="online" name="game-type" type="radio" value="online"> |
||||
<label for="online">Online</label> |
||||
</form> |
||||
</fieldset> |
||||
<div id="game-properties" style="display: none;"> |
||||
<fieldset class="player-name border-node" id="player-name"> |
||||
<legend>Your name</legend> |
||||
<input class="border-node" type="text"><br> |
||||
<span class="error-label"></span> |
||||
</fieldset> |
||||
<fieldset class="player-actions border-node" id="lobby-code"> |
||||
<legend>Lobby code</legend> |
||||
<input class="border-node" type="text"><br> |
||||
<span class="error-label"></span> |
||||
</fieldset> |
||||
<div class="player-actions"> |
||||
<button class="setup border-node" onclick="onlineManager.createLobby(this);">Create Lobby</button> |
||||
<span>Or</span> |
||||
<button class="setup border-node" onclick="onlineManager.joinLobby(this);">Join Lobby</button> |
||||
</div> |
||||
<button class="player-name start setup border-node" onclick="continueForm(this)">OK</button> |
||||
</div> |
||||
</div> |
||||
<div class="initialize-lobby" id="lobby"> |
||||
<div class="border-node" id="lobby-members"></div> |
||||
<button class="setup border-node" id="start-lobby" onclick="onlineManager.startLobby(this);">Start</button> |
||||
<span>Code: </span> |
||||
<span></span> |
||||
<br> |
||||
<span class="error-label"></span> |
||||
</div> |
||||
</div> |
||||
<div class="border-node" id="canvasHolder"></div> |
||||
<div class="border-node" id="chat"> |
||||
<div id="chat-content"></div> |
||||
<div id="chat-input"> |
||||
<input type="text"> |
||||
<button onclick="onlineManager.sendMessage()">Send</button> |
||||
</div> |
||||
</div> |
||||
<div id="loader"></div> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,249 @@ |
||||
{ |
||||
"name": "pong-client", |
||||
"version": "1.0.0", |
||||
"lockfileVersion": 2, |
||||
"requires": true, |
||||
"packages": { |
||||
"": { |
||||
"name": "pong-client", |
||||
"version": "1.0.0", |
||||
"dependencies": { |
||||
"socket.io-client": "^4.4.1" |
||||
}, |
||||
"devDependencies": { |
||||
"@types/jquery": "^3.5.16", |
||||
"@types/node": "^18.15.10", |
||||
"typescript": "^5.0.2" |
||||
} |
||||
}, |
||||
"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/jquery": { |
||||
"version": "3.5.16", |
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", |
||||
"integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", |
||||
"dev": true, |
||||
"dependencies": { |
||||
"@types/sizzle": "*" |
||||
} |
||||
}, |
||||
"node_modules/@types/node": { |
||||
"version": "18.15.10", |
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", |
||||
"integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", |
||||
"dev": true |
||||
}, |
||||
"node_modules/@types/sizzle": { |
||||
"version": "2.3.3", |
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", |
||||
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", |
||||
"dev": true |
||||
}, |
||||
"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/engine.io-client": { |
||||
"version": "6.4.0", |
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", |
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", |
||||
"dependencies": { |
||||
"@socket.io/component-emitter": "~3.1.0", |
||||
"debug": "~4.3.1", |
||||
"engine.io-parser": "~5.0.3", |
||||
"ws": "~8.11.0", |
||||
"xmlhttprequest-ssl": "~2.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/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/socket.io-client": { |
||||
"version": "4.6.1", |
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", |
||||
"integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", |
||||
"dependencies": { |
||||
"@socket.io/component-emitter": "~3.1.0", |
||||
"debug": "~4.3.2", |
||||
"engine.io-client": "~6.4.0", |
||||
"socket.io-parser": "~4.2.1" |
||||
}, |
||||
"engines": { |
||||
"node": ">=10.0.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==", |
||||
"dev": true, |
||||
"bin": { |
||||
"tsc": "bin/tsc", |
||||
"tsserver": "bin/tsserver" |
||||
}, |
||||
"engines": { |
||||
"node": ">=12.20" |
||||
} |
||||
}, |
||||
"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 |
||||
} |
||||
} |
||||
}, |
||||
"node_modules/xmlhttprequest-ssl": { |
||||
"version": "2.0.0", |
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", |
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", |
||||
"engines": { |
||||
"node": ">=0.4.0" |
||||
} |
||||
} |
||||
}, |
||||
"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/jquery": { |
||||
"version": "3.5.16", |
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", |
||||
"integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", |
||||
"dev": true, |
||||
"requires": { |
||||
"@types/sizzle": "*" |
||||
} |
||||
}, |
||||
"@types/node": { |
||||
"version": "18.15.10", |
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", |
||||
"integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==", |
||||
"dev": true |
||||
}, |
||||
"@types/sizzle": { |
||||
"version": "2.3.3", |
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", |
||||
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", |
||||
"dev": true |
||||
}, |
||||
"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" |
||||
} |
||||
}, |
||||
"engine.io-client": { |
||||
"version": "6.4.0", |
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz", |
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==", |
||||
"requires": { |
||||
"@socket.io/component-emitter": "~3.1.0", |
||||
"debug": "~4.3.1", |
||||
"engine.io-parser": "~5.0.3", |
||||
"ws": "~8.11.0", |
||||
"xmlhttprequest-ssl": "~2.0.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==" |
||||
}, |
||||
"ms": { |
||||
"version": "2.1.2", |
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" |
||||
}, |
||||
"socket.io-client": { |
||||
"version": "4.6.1", |
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz", |
||||
"integrity": "sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==", |
||||
"requires": { |
||||
"@socket.io/component-emitter": "~3.1.0", |
||||
"debug": "~4.3.2", |
||||
"engine.io-client": "~6.4.0", |
||||
"socket.io-parser": "~4.2.1" |
||||
} |
||||
}, |
||||
"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==", |
||||
"dev": true |
||||
}, |
||||
"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": {} |
||||
}, |
||||
"xmlhttprequest-ssl": { |
||||
"version": "2.0.0", |
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", |
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"name": "pong-client", |
||||
"version": "1.0.0", |
||||
"devDependencies": { |
||||
"@types/jquery": "^3.5.16", |
||||
"@types/node": "^18.15.10", |
||||
"typescript": "^5.0.2" |
||||
}, |
||||
"dependencies": { |
||||
"socket.io-client": "^4.4.1" |
||||
} |
||||
} |
@ -0,0 +1,272 @@ |
||||
a:link, a:hover, a:active, a:visited{color: #000;} |
||||
|
||||
html, body{margin: 0; padding: 0; height: 100%; width: 100%;} |
||||
|
||||
canvas{margin: 0; padding: 0; border: none; display: block;} |
||||
|
||||
button:hover{cursor: pointer;} |
||||
|
||||
@font-face{ |
||||
font-family: "Rametto"; |
||||
src: url("data/styles/font.ttf"); |
||||
} |
||||
|
||||
*{ |
||||
font-family: "Rametto"; |
||||
color: #000; |
||||
font-size: 17px; |
||||
} |
||||
|
||||
:root{ |
||||
--border-width: 2px; |
||||
--width: 1000px; |
||||
--height: 1000px; |
||||
--fieldset-width: 100px; |
||||
--body-background: rgb(60, 60, 100); |
||||
--interface-background: rgb(80, 80, 120); |
||||
--fieldset-background: rgb(100, 100, 130); |
||||
--input-background: rgb(120, 120, 150); |
||||
--lobby-leader-background: rgb(60, 60, 150); |
||||
--button-background: rgb(50, 180, 60); |
||||
--error-label-font: rgb(220, 50, 50); |
||||
--chat-first-background: rgb(50, 50, 170); |
||||
--chat-second-background: rgb(100, 100, 200); |
||||
--chat-writer-color: rgb(243, 130, 0); |
||||
} |
||||
|
||||
body{ |
||||
background-color: var(--body-background); |
||||
overflow: hidden; |
||||
} |
||||
|
||||
#content{ |
||||
position: absolute; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
#canvasHolder{ |
||||
width: var(--width); |
||||
height: var(--height); |
||||
|
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
} |
||||
|
||||
#canvasHolder canvas{ |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
border-radius: inherit; |
||||
} |
||||
|
||||
#loader{ |
||||
width: 70px; |
||||
height: 50px; |
||||
margin-top: 10px; |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
/** |
||||
* While playing |
||||
*/ |
||||
|
||||
#game-controls{ |
||||
position: relative; |
||||
display: none; |
||||
width: 100%; |
||||
height: 100%; |
||||
text-align: center; |
||||
} |
||||
#toggle-play{ |
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
box-sizing: border-box; |
||||
background: transparent; |
||||
border: 0; |
||||
width: 0; |
||||
height: 100px; |
||||
border-color: transparent transparent transparent #000; |
||||
transition: 100ms all ease; |
||||
border-style: solid; |
||||
border-width: 50px 0px 50px 100px; |
||||
filter: drop-shadow(5px 5px 5px #000); |
||||
} |
||||
#toggle-play.paused{ |
||||
border-style: double; |
||||
border-width: 0px 0px 0px 100px; |
||||
} |
||||
#toggle-play:hover{ |
||||
border-color: transparent transparent transparent #111; |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** |
||||
* Interface with all it's controls |
||||
*/ |
||||
#interface{ |
||||
float: left; |
||||
background-color: var(--interface-background); |
||||
width: calc(50% - var(--width) / 2 - 2 * 10px - 4px); |
||||
height: calc(var(--height) - 10px); |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* Setup controls for creating games |
||||
*/ |
||||
|
||||
#setup-controls{ |
||||
display: flex; |
||||
flex-direction: column; |
||||
flex-wrap: wrap; |
||||
justify-content: flex-start; |
||||
align-items: center; |
||||
} |
||||
#setup-controls fieldset{ |
||||
margin: 10px; |
||||
background-color: var(--fieldset-background); |
||||
max-width: var(--fieldset-width); |
||||
} |
||||
#setup-controls > fieldset{ |
||||
margin: 30px; |
||||
} |
||||
#game-properties{ |
||||
display: flex; |
||||
flex-direction: column; |
||||
flex-wrap: nowrap; |
||||
justify-content: flex-end; |
||||
align-items: center; |
||||
max-width: calc(var(--width) - 250px); |
||||
} |
||||
.error-label{ |
||||
color: var(--error-label-font); |
||||
font-size: 13px; |
||||
font-weight: 100; |
||||
} |
||||
.border-node{ |
||||
border: 2px solid #000; |
||||
border-radius: 10px; |
||||
} |
||||
input, select{ |
||||
background-color: var(--input-background); |
||||
max-width: 200px; |
||||
} |
||||
select{ |
||||
padding: 0px 20px 0px 0px; |
||||
font-size: 16px; |
||||
border: 1px solid #ccc; |
||||
height: 34px; |
||||
-webkit-appearance: none; |
||||
-moz-appearance: none; |
||||
appearance: none; |
||||
background: url(data/images/arrow.png) 100% / 50% no-repeat var(--input-background); |
||||
} |
||||
.setup{ |
||||
margin: 10px; |
||||
background-color: var(--button-background); |
||||
} |
||||
.setup:hover, select:hover, input[type=text]:focus{ |
||||
filter: brightness(80%); |
||||
} |
||||
.setup:disabled:hover{ |
||||
filter: brightness(40%); |
||||
cursor: default; |
||||
} |
||||
.setup:disabled{ |
||||
filter: brightness(40%); |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* Lobby |
||||
*/ |
||||
|
||||
#lobby-members{ |
||||
background-color: var(--fieldset-background); |
||||
display: flex; |
||||
flex-direction: column; |
||||
flex-wrap: wrap; |
||||
justify-content: center; |
||||
} |
||||
.lobby-member{ |
||||
margin: 4px; |
||||
padding: 5px; |
||||
background-color: var(--fieldset-background); |
||||
cursor: pointer; |
||||
} |
||||
.lobby-member:hover{ |
||||
filter: brightness(70%); |
||||
} |
||||
#lobby-leader{ |
||||
background-color: var(--lobby-leader-background); |
||||
} |
||||
#lobby > span{ |
||||
margin: 5px; |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* Chat |
||||
*/ |
||||
|
||||
#chat{ |
||||
float: right; |
||||
background-color: var(--interface-background); |
||||
width: calc(50vw - var(--width) / 2 - 2 * 10px - 4px); |
||||
height: calc(var(--height) - 10px); |
||||
} |
||||
#chat-content{ |
||||
border-radius: inherit; |
||||
height: calc(100% - 32px); |
||||
background-color: var(--fieldset-background); |
||||
width: 100%; |
||||
overflow-y: auto; |
||||
} |
||||
#chat-input{ |
||||
width: 100%; |
||||
} |
||||
#chat-input > input[type="text"]{ |
||||
max-width: 100%; |
||||
height: 100%; |
||||
width: calc(100% - 100px - 10px); |
||||
border-radius: 10px; |
||||
border: 1px solid #000; |
||||
} |
||||
#chat-input > button{ |
||||
width: 100px; |
||||
background-color: var(--button-background); |
||||
border-radius: 10px; |
||||
border: 1px solid #000; |
||||
} |
||||
.message > span{ |
||||
word-wrap: break-word; |
||||
font-size: 12px; |
||||
padding: 5px; |
||||
} |
||||
.message[name="0"]{ |
||||
background-color: var(--chat-first-background) |
||||
} |
||||
.message[name="1"]{ |
||||
background-color: var(--chat-second-background) |
||||
} |
||||
.message > span:first-child{ |
||||
color: var(--chat-writer-color) |
||||
} |
||||
|
After Width: | Height: | Size: 128 KiB |
@ -0,0 +1,9 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"target": "es6", |
||||
"module": "amd", |
||||
"sourceMap": true, |
||||
"alwaysStrict": true, |
||||
"outFile": "./data/scripts/js/main.js" |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
HTTPS_PORT=3103 |
||||
SSL_PATH=/home/benjamin/code/web/ssl/benjamin-kraft.local |
@ -0,0 +1,12 @@ |
||||
[Mon Mar 27 2023 17:26:22][undefined] ---> {Server is listening on port 3103} |
||||
[Mon Mar 27 2023 17:31:06][pong] ---> {"beb(6Ya9ADw...)" connected} |
||||
[Mon Mar 27 2023 17:31:17][pong] ---> {"beb2(4XZozzj...)" connected} |
||||
[Mon Mar 27 2023 17:31:20][pong] ---> {"beb2(4XZozzj...)" created new lobby: "2DVLL2WEXI"} |
||||
[Mon Mar 27 2023 17:31:25][undefined] ---> {"beb(6Ya9ADw...)" tried to join non-existent lobby " 2DVLL2WEXI "} |
||||
[Mon Mar 27 2023 17:31:31][pong] ---> {"beb(6Ya9ADw...)" joined the lobby "2DVLL2WEXI"} |
||||
[Mon Mar 27 2023 17:31:36][pong] ---> {"beb2(4XZozzj...)" started the game: "2DVLL2WEXI"} |
||||
[Mon Mar 27 2023 17:33:13][pong] ---> {"beb(6Ya9ADw...)" left the lobby "2DVLL2WEXI"} |
||||
[Mon Mar 27 2023 17:33:13][pong] ---> {"beb(6Ya9ADw...)" disconnected} |
||||
[Mon Mar 27 2023 17:33:18][pong] ---> {"beb2(4XZozzj...)" left the lobby "2DVLL2WEXI"} |
||||
[Mon Mar 27 2023 17:33:18][pong] ---> {Lobby "2DVLL2WEXI" was deleted} |
||||
[Mon Mar 27 2023 17:33:18][pong] ---> {"beb2(4XZozzj...)" disconnected} |
@ -0,0 +1,18 @@ |
||||
{ |
||||
"name": "pong-server", |
||||
"version": "2.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"start": "node out/index.js" |
||||
}, |
||||
"dependencies": { |
||||
"dotenv": "^16.0.3", |
||||
"https": "^1.0.0", |
||||
"socket.io": "^4.4.1", |
||||
"socket.io-p2p-server": "^1.2.0", |
||||
"typescript": "^5.0.2" |
||||
}, |
||||
"devDependencies": { |
||||
"@types/node": "^18.15.3" |
||||
} |
||||
} |
@ -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 {Pong} from "./pong"; |
||||
import {StartServer} from "./start"; |
||||
|
||||
StartServer({ |
||||
useP2P: true, |
||||
gameClass: Pong |
||||
}); |
@ -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,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,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" |
||||
] |
||||
} |