@ -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" |
||||||
|
] |
||||||
|
} |