@ -0,0 +1,2 @@ |
|||||||
|
.idea |
||||||
|
|
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 407 KiB |
After Width: | Height: | Size: 401 KiB |
After Width: | Height: | Size: 489 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 124 KiB |
After Width: | Height: | Size: 158 KiB |
@ -0,0 +1,125 @@ |
|||||||
|
class Card{ |
||||||
|
|
||||||
|
constructor(id, src){ |
||||||
|
this.id = id; |
||||||
|
this.src = src; |
||||||
|
this.revealed = true; |
||||||
|
this.createDomElement(); |
||||||
|
} |
||||||
|
|
||||||
|
static SortDomElements(){ |
||||||
|
function byIndex(a, b){ |
||||||
|
return $(a).data("index") - $(b).data("index"); |
||||||
|
} |
||||||
|
for (let c of cards){ |
||||||
|
$(c.dom).data("index", cards.indexOf(c)); |
||||||
|
} |
||||||
|
let list = $("#table img").get(); |
||||||
|
list.sort(byIndex); |
||||||
|
$("#table").append(list); |
||||||
|
} |
||||||
|
|
||||||
|
static getById(id){ |
||||||
|
for (let c of cards){ |
||||||
|
if (c.id === id) return c; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static hideTime(){ |
||||||
|
return 300; |
||||||
|
} |
||||||
|
|
||||||
|
reveal(){ |
||||||
|
if (this.revealed) return; |
||||||
|
let card = this; |
||||||
|
this.dom.delay(Card.hideTime() / 2).queue(function(next){ |
||||||
|
$(this).attr("src", card.src); |
||||||
|
next(); |
||||||
|
}); |
||||||
|
this.dom.css("transform", "rotateY(0deg)"); |
||||||
|
this.revealed = true; |
||||||
|
} |
||||||
|
|
||||||
|
hide(){ |
||||||
|
if (!this.revealed) return; |
||||||
|
this.dom.delay(Card.hideTime() / 2).queue(function(next){ |
||||||
|
$(this).attr("src", hidden); |
||||||
|
next(); |
||||||
|
}); |
||||||
|
this.dom.css("transform", "rotateY(180deg)"); |
||||||
|
this.revealed = false; |
||||||
|
} |
||||||
|
|
||||||
|
savePosition(){ |
||||||
|
let x = this.dom.position().left; |
||||||
|
let y = this.dom.position().top; |
||||||
|
this.dom.css({ |
||||||
|
"left": x, |
||||||
|
"top": y |
||||||
|
}); |
||||||
|
this.pos = {left: x, top: y}; |
||||||
|
} |
||||||
|
|
||||||
|
moveToPosition(type, staple){ |
||||||
|
let card = this; |
||||||
|
this.dom.css("position", "absolute"); |
||||||
|
this.moving = true; |
||||||
|
this.dom.animate({ |
||||||
|
left: this.pos.left, |
||||||
|
top: this.pos.top |
||||||
|
}, 750, function(){ |
||||||
|
card.moving = false; |
||||||
|
if (type === "collect") { |
||||||
|
card.appendToStaple(staple); |
||||||
|
} |
||||||
|
for (let c of cards) if (c.moving) return; |
||||||
|
if (type === "collect"){ |
||||||
|
|
||||||
|
} else { |
||||||
|
for (let c of cards) c.dom.css("position", "static"); |
||||||
|
Card.SortDomElements(); |
||||||
|
game.shuffled = true; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
appendToStaple(staple){ |
||||||
|
this.dom.remove(); |
||||||
|
this.dom.css({ |
||||||
|
"position": "", |
||||||
|
"top": "", |
||||||
|
"left": "" |
||||||
|
}); |
||||||
|
this.dom.attr("class", "card card_on_staple"); |
||||||
|
staple.append(this.dom); |
||||||
|
} |
||||||
|
|
||||||
|
createDomElement(){ |
||||||
|
this.dom = $("<img></img>"); |
||||||
|
this.dom.data("card", this); |
||||||
|
this.dom.attr({ |
||||||
|
"class": "card_on_table card", |
||||||
|
"id": this.id, |
||||||
|
"src": this.revealed ? this.src : hidden |
||||||
|
}); |
||||||
|
this.dom.click(() => cardClicked(this)); |
||||||
|
let card = this; |
||||||
|
this.dom.hover(() => card.debugShow(), () => card.debugHide()); |
||||||
|
$("#table").append(this.dom); |
||||||
|
} |
||||||
|
|
||||||
|
equals(other){ |
||||||
|
return this.src === other.src; |
||||||
|
} |
||||||
|
|
||||||
|
debugShow(){ |
||||||
|
if (this.revealed || !debug) return; |
||||||
|
this.dom.attr("src", this.src); |
||||||
|
} |
||||||
|
|
||||||
|
debugHide(){ |
||||||
|
if (this.revealed || !debug) return; |
||||||
|
this.dom.attr("src", hidden); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,190 @@ |
|||||||
|
class Game{ |
||||||
|
|
||||||
|
constructor(playercount){ |
||||||
|
this.players = []; |
||||||
|
this.online = false; |
||||||
|
for (let i = 0; i < playercount; i++){ |
||||||
|
this.players.push(new Player(i, null, "Player")); |
||||||
|
} |
||||||
|
playerDomSetup(this.players.length, this.players); |
||||||
|
this.cards = []; |
||||||
|
this.cardSize = $($(".card_on_table")[0]).width(); |
||||||
|
$("#interface, #showInterface_btn").show(); |
||||||
|
} |
||||||
|
|
||||||
|
static startShuffle(){ |
||||||
|
while (!Game.allPaired()){ |
||||||
|
let c1 = random(cards); |
||||||
|
let c2 = random(cards); |
||||||
|
if (!c1.equals(c2) && !c2.hasSwitchPartner){ |
||||||
|
Game.switchPartner(c1, c2); |
||||||
|
} |
||||||
|
} |
||||||
|
for (let c of cards){ |
||||||
|
c.moveToPosition(); |
||||||
|
c.hasSwitchPartner = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static switchPartner(c1, c2){ |
||||||
|
c1.hasSwitchPartner = true; |
||||||
|
c2.hasSwitchPartner = true; |
||||||
|
let temp = c1.pos; |
||||||
|
c1.pos = c2.pos; |
||||||
|
c2.pos = temp; |
||||||
|
let index = {c1: cards.indexOf(c1), |
||||||
|
c2: cards.indexOf(c2)}; |
||||||
|
temp = cards[index.c1]; |
||||||
|
cards[index.c1] = cards[index.c2]; |
||||||
|
cards[index.c2] = temp; |
||||||
|
} |
||||||
|
|
||||||
|
static allPaired(){ |
||||||
|
for (let c of cards){ |
||||||
|
if (!c.hasSwitchPartner){ |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
start(){ |
||||||
|
if (this.running || !this.shuffled) return; |
||||||
|
this.running = true; |
||||||
|
random(game.players).hasTurn(true); |
||||||
|
} |
||||||
|
|
||||||
|
currentPlayer(){ |
||||||
|
for (let p of this.players){ |
||||||
|
if (p.hasTurn()) return p; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
revealCard(calledByServer, cardId){ |
||||||
|
if (this.nextTurn){ |
||||||
|
if (!this.pairFound()) for (let c of this.cards) c.hide(); |
||||||
|
this.cards = []; |
||||||
|
this.nextTurn = false; |
||||||
|
} |
||||||
|
let card = Card.getById(cardId); |
||||||
|
card.reveal(); |
||||||
|
this.cards.push(card); |
||||||
|
if (this.cards.length == 2) this.next(); |
||||||
|
} |
||||||
|
|
||||||
|
pairFound(){ |
||||||
|
if (this.cards.length < 2) return false; |
||||||
|
return this.cards[0].equals(this.cards[1]); |
||||||
|
} |
||||||
|
|
||||||
|
next(){ |
||||||
|
this.nextTurn = true; |
||||||
|
if (this.pairFound()) this.thisPlayer(); |
||||||
|
else this.nextPlayer(); |
||||||
|
} |
||||||
|
|
||||||
|
nextPlayer(){ |
||||||
|
let p = this.currentPlayer(); |
||||||
|
p.hasTurn(false); |
||||||
|
let i = this.players.indexOf(p) + 1; |
||||||
|
if (i == this.players.length) i = 0; |
||||||
|
this.players[i].hasTurn(true); |
||||||
|
} |
||||||
|
|
||||||
|
thisPlayer(){ |
||||||
|
this.collectPair(); |
||||||
|
} |
||||||
|
|
||||||
|
getLeader(){ |
||||||
|
return this.players[0]; |
||||||
|
} |
||||||
|
|
||||||
|
collectPair(){ |
||||||
|
let p = this.currentPlayer(); |
||||||
|
let stapleOffset = 2; |
||||||
|
let staple = p.dom.find(".staple"); |
||||||
|
p.cards.push(this.cards[0], this.cards[1]); |
||||||
|
p.addPairs(1); |
||||||
|
let last = staple.find("img").last(); |
||||||
|
let count = 1; |
||||||
|
for (let c of this.cards) { |
||||||
|
let x = (p.pairs - 1) ? last.offset().left + stapleOffset * count : staple.offset().left; |
||||||
|
let y = (p.pairs - 1) ? last.offset().top : staple.offset().top; |
||||||
|
count++; |
||||||
|
c.savePosition(); |
||||||
|
c.pos = {left: x, top: y}; |
||||||
|
c.moveToPosition("collect", p.dom.find(".staple")); |
||||||
|
c.dom.css("z-index", p.cards.length - 2 + this.cards.indexOf(c)); |
||||||
|
c.dom.after(createEmpty()); |
||||||
|
cards.splice(cards.indexOf(c), 1); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
shuffle(){ |
||||||
|
for (let c of cards){ |
||||||
|
c.savePosition(); |
||||||
|
c.hide(); |
||||||
|
} |
||||||
|
setTimeout(Game.startShuffle, Card.hideTime()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class OnlineGame extends Game{ |
||||||
|
|
||||||
|
constructor(players, seed){ |
||||||
|
super(0); |
||||||
|
this.online = true; |
||||||
|
randomSeed(seed); |
||||||
|
for (let p of players){ |
||||||
|
this.players.push(new Player(players.indexOf(p), p.id, p.name));
|
||||||
|
} |
||||||
|
playerDomSetup(this.players.length, this.players); |
||||||
|
if (socket.id === this.players[0].id){ |
||||||
|
$("#interface, #showInterface_btn").show(); |
||||||
|
} else { |
||||||
|
$("#interface, #showInterface_btn").hide(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
start(calledByServer){ |
||||||
|
if (calledByServer){ |
||||||
|
super.start(); |
||||||
|
} else
|
||||||
|
socket.emit("game-action", "start"); |
||||||
|
} |
||||||
|
|
||||||
|
revealCard(calledByServer, cardId){ |
||||||
|
if (calledByServer){ |
||||||
|
super.revealCard(false, cardId); |
||||||
|
} else { |
||||||
|
if (socket.id !== this.currentPlayer().id) return; |
||||||
|
socket.emit("game-action", "revealCard", cardId); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
shuffle(calledByServer){ |
||||||
|
if (calledByServer){ |
||||||
|
super.shuffle(); |
||||||
|
} else
|
||||||
|
socket.emit("game-action", "shuffle"); |
||||||
|
} |
||||||
|
|
||||||
|
setPlayers(players){ |
||||||
|
/*let newPlayers = []; |
||||||
|
for (let p of this.players){ |
||||||
|
if (p.hasTurn() && !p.isIn(players)){ |
||||||
|
this.nextPlayer(); |
||||||
|
} |
||||||
|
if (p.isIn(players)) newPlayers.push(p); |
||||||
|
else if (p.hasTurn()) |
||||||
|
} |
||||||
|
playerDomSetup(this.players.length, this.players); |
||||||
|
if (socket.id === this.players[0].id){ |
||||||
|
$("#interface, #showInterface_btn").show(); |
||||||
|
}*/ |
||||||
|
//TODO
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,149 @@ |
|||||||
|
let formState = "type", lobby; |
||||||
|
|
||||||
|
function joinLobby(dom){ |
||||||
|
loader(dom); |
||||||
|
let name = playerNameEntered(); |
||||||
|
if (name === '') return; |
||||||
|
let codeTyped = $('#join-lobby > input').val(); |
||||||
|
socket.emit('join-lobby', codeTyped, name); |
||||||
|
} |
||||||
|
|
||||||
|
function leaveLobby(dom){ |
||||||
|
loader(dom); |
||||||
|
socket.emit('leave-lobby', lobby.id); |
||||||
|
|
||||||
|
if (game){ |
||||||
|
$('#initialize-wrapper, ' +
|
||||||
|
'.setup:eq(1), .setup:eq(2), #join-lobby').show(); |
||||||
|
game = null; |
||||||
|
} |
||||||
|
|
||||||
|
lobby = null; |
||||||
|
$('#interface').hide(); |
||||||
|
$('#initialize').show(); |
||||||
|
$('#initialize *').attr('disabled', false); |
||||||
|
$('#lobby').hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function createLobby(dom){ |
||||||
|
loader(dom); |
||||||
|
let name = playerNameEntered(); |
||||||
|
if (name === '') return; |
||||||
|
socket.emit('create-lobby', settings); |
||||||
|
showLobby(); |
||||||
|
} |
||||||
|
|
||||||
|
function addSocketEvents(){ |
||||||
|
socket.on('member-joined', (lobby) => setLobby(lobby)); |
||||||
|
socket.on('member-left', (lobby) => setLobby(lobby)); |
||||||
|
socket.on('join-failed', (message) => joinFailed(message)); |
||||||
|
socket.on('start-game', (lobby, seed) => startOnlineGame(lobby, seed)); |
||||||
|
socket.on('game-action', (lobby, action, argument) => startAction(action, argument)); |
||||||
|
} |
||||||
|
|
||||||
|
function startAction(action, argument){ |
||||||
|
game[action].call(game, true, argument); |
||||||
|
} |
||||||
|
|
||||||
|
function startLobby(){ |
||||||
|
socket.emit('start-game', lobby.id); |
||||||
|
} |
||||||
|
|
||||||
|
function startOnlineGame(lobby, seed){ |
||||||
|
setLobby(lobby); |
||||||
|
$("#initialize-wrapper").hide(); |
||||||
|
console.log(seed); |
||||||
|
game = new OnlineGame(lobby.clients, seed); |
||||||
|
} |
||||||
|
|
||||||
|
function continueForm(){ |
||||||
|
|
||||||
|
if (formState === "type"){ |
||||||
|
let type = $('input[name="game-type"]:checked').val(); |
||||||
|
$("#game-type input").attr("disabled", "disabled"); |
||||||
|
if (type === "online"){ |
||||||
|
$("#game-type").hide(); |
||||||
|
$("#player-name").show(); |
||||||
|
formState = "player-name"; |
||||||
|
} |
||||||
|
if (type === "offline"){ |
||||||
|
$("#player-count").show(); |
||||||
|
formState = "player-count"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
else if (formState === "player-count"){ |
||||||
|
let playerCount = int($("#player-count select").val()); |
||||||
|
$("#initialize-wrapper").hide(); |
||||||
|
game = new Game(playerCount); |
||||||
|
} else if (formState === "player-name"){ |
||||||
|
if (playerNameEntered() != ""){ |
||||||
|
$(".setup:eq(0)").hide(); |
||||||
|
$(".setup:eq(1), .setup:eq(2), #join-lobby").show(); |
||||||
|
$("#player-name").hide(); |
||||||
|
let queries = "?game=memory&name=" + playerNameEntered(); |
||||||
|
$.get('data/settings/get_port.php', port => { |
||||||
|
socket = io('https://' + location.hostname + ':' + port + queries); |
||||||
|
addSocketEvents(); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function joinFailed(reason){ |
||||||
|
$("#join-lobby .error-label").html(reason); |
||||||
|
$("#join-lobby .error-label").show(); |
||||||
|
} |
||||||
|
|
||||||
|
function showLobby(){ |
||||||
|
$("#initialize").hide(); |
||||||
|
$("#lobby").show(); |
||||||
|
} |
||||||
|
|
||||||
|
function setLobby(newLobby){ |
||||||
|
lobby = newLobby; |
||||||
|
let leader = lobby.clients[0]; |
||||||
|
$("#lobby-members").html(""); |
||||||
|
for (let p of lobby.clients){ |
||||||
|
$("#lobby-members").append(createLobbyMember(p, p.id === leader.id)); |
||||||
|
} |
||||||
|
if (lobby.clientCounts.indexOf(lobby.clients.length) === -1){ |
||||||
|
let strCounts = ''; |
||||||
|
for (let c of lobby.clientCounts){ |
||||||
|
let comma = lobby.clientCounts.indexOf(c) === lobby.clientCounts.length - 1 ? '' : ', '; |
||||||
|
strCounts += c + comma; |
||||||
|
} |
||||||
|
$('#lobby > .error-label').html('Only as ' + strCounts); |
||||||
|
$('#lobby > .error-label').show(); |
||||||
|
$("#start-lobby").attr("disabled", "disabled"); |
||||||
|
} else { |
||||||
|
$('#lobby > .error-label').hide(); |
||||||
|
$("#start-lobby").attr("disabled", false); |
||||||
|
} |
||||||
|
if (!(socket.id === leader.id)){ |
||||||
|
$("#start-lobby").attr({"disabled": "disabled"}); |
||||||
|
} else { |
||||||
|
$("#start-lobby").attr("disabled", false); |
||||||
|
} |
||||||
|
$("#lobby-id").html("Id: " + lobby.id); |
||||||
|
showLobby(); |
||||||
|
if (game) game.setPlayers(lobby.clients); |
||||||
|
} |
||||||
|
|
||||||
|
function createLobbyMember(player, isLeader){ |
||||||
|
let member = $("<div></div>"); |
||||||
|
member.attr("class", "lobby-member"); |
||||||
|
member.html(player.name); |
||||||
|
if (isLeader) member.attr("id", "lobby-leader"); |
||||||
|
return member; |
||||||
|
} |
||||||
|
|
||||||
|
function playerNameEntered(){ |
||||||
|
let name = $("#player-name > input").val(); |
||||||
|
if (name === ""){ |
||||||
|
$("#player-name .error-label").html("Please enter a name!"); |
||||||
|
$("#player-name .error-label").show(); |
||||||
|
} |
||||||
|
return name; |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
class Player{ |
||||||
|
|
||||||
|
constructor(i, id, name){ |
||||||
|
this.turn = false; |
||||||
|
this.pairs = 0; |
||||||
|
this.cards = []; |
||||||
|
this.id = id; |
||||||
|
this.index = i; |
||||||
|
this.name = name; |
||||||
|
this.createDom(); |
||||||
|
this.updateDom(); |
||||||
|
} |
||||||
|
|
||||||
|
static getById(id){ |
||||||
|
for (let p of game.players){ |
||||||
|
if (p.id === id) return p; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
addPairs(pairs){ |
||||||
|
this.pairs += pairs; |
||||||
|
this.updateDom(); |
||||||
|
} |
||||||
|
|
||||||
|
updateDom(){ |
||||||
|
this.dom.find(".pairs-label").html("Pairs: " + this.pairs); |
||||||
|
} |
||||||
|
|
||||||
|
createDom(){ |
||||||
|
if (this.index % 2 == 0) this.dom = $("#left > div:eq(" + this.index / 2 + ")"); |
||||||
|
else this.dom = $("#right > div:eq(" + (this.index - 1) / 2 + ")"); |
||||||
|
this.dom.find("span").html(this.name); |
||||||
|
} |
||||||
|
|
||||||
|
isIn(players){ |
||||||
|
for (let p of players){ |
||||||
|
if (p.id === this.id) return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
hasTurn(turn){ |
||||||
|
if (turn == null) return this.turn; |
||||||
|
this.turn = turn; |
||||||
|
if (turn) this.dom.find(".has-turn").css("background-color", "green"); |
||||||
|
else this.dom.find(".has-turn").css("background-color", "rgba(0, 0, 0, 0)"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
"use strict" |
||||||
|
|
||||||
|
function keyPressed(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function keyReleased(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mouseMoved(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mouseDragged(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mousePressed(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mouseReleased(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
window.onresize = function(){ |
||||||
|
let i = 0; |
||||||
|
function resize(){ |
||||||
|
resizeCanvas($("#canvasHolder").outerWidth(), $("#canvasHolder").outerHeight(), true); |
||||||
|
i++; |
||||||
|
if (i < 5) window.setTimeout(resize, 0); |
||||||
|
} |
||||||
|
resize(); |
||||||
|
} |
@ -0,0 +1,443 @@ |
|||||||
|
"use strict" |
||||||
|
|
||||||
|
class Collision{ |
||||||
|
|
||||||
|
static ellipseToEllipse(e1, e2){ |
||||||
|
|
||||||
|
//Colliding angle of ball 1 to ball 2 using arc tan of both x and y differences
|
||||||
|
let collisionAngle = atan2((e2.pos.y - e1.pos.y), (e2.pos.x - e1.pos.x)); |
||||||
|
|
||||||
|
//Converting directions of velocity vector of balls into angles
|
||||||
|
let d1 = atan2(e1.vel.y, e1.vel.x); |
||||||
|
let d2 = atan2(e2.vel.y, e2.vel.x); |
||||||
|
|
||||||
|
//Ignoring mass effects new velocites are simply magnitude multiplied with value of angle differences
|
||||||
|
let newXspeed1 = e1.vel.mag() * cos(d1 - collisionAngle); |
||||||
|
let newYspeed1 = e1.vel.mag() * sin(d1 - collisionAngle); |
||||||
|
let newXspeed2 = e2.vel.mag() * cos(d2 - collisionAngle); |
||||||
|
let newYspeed2 = e2.vel.mag() * sin(d2 - collisionAngle); |
||||||
|
|
||||||
|
//According to the principle of linear momentum, kinetic energy stays the same after collision, so velocities are now related to masses
|
||||||
|
let finalXspeed1 = ((e1.radius - e2.radius) * newXspeed1 + e2.radius * 2 * newXspeed2) / (e1.radius + e2.radius); |
||||||
|
let finalYspeed1 = newYspeed1; |
||||||
|
let finalXspeed2 = (e1.radius * 2 * newXspeed1 + (e2.radius - e1.radius) * newXspeed2) / (e1.radius + e2.radius); |
||||||
|
let finalYspeed2 = newYspeed2; |
||||||
|
|
||||||
|
//Values of collisionAngle
|
||||||
|
let cosAngle = cos(collisionAngle); |
||||||
|
let sinAngle = sin(collisionAngle); |
||||||
|
|
||||||
|
//To also keep velocites relative to pure collisionAngle, subtract sin*x from cos*x and add sin*y to cos*y because coordSystem has y = 0 on the top
|
||||||
|
let u1x = cosAngle * finalXspeed1 - sinAngle * finalYspeed1; |
||||||
|
let u1y = sinAngle * finalXspeed1 + cosAngle * finalYspeed1; |
||||||
|
let u2x = cosAngle * finalXspeed2 - sinAngle * finalYspeed2; |
||||||
|
let u2y = sinAngle * finalXspeed2 + cosAngle * finalYspeed2; |
||||||
|
|
||||||
|
//Set new velocities to both balls
|
||||||
|
e1.vel.x = u1x; |
||||||
|
e1.vel.y = u1y; |
||||||
|
e2.vel.x = u2x; |
||||||
|
e2.vel.y = u2y; |
||||||
|
|
||||||
|
//Move balls one vx/vy forward to avoid double inverting collision detection
|
||||||
|
e1.pos.x += e1.vel.x; |
||||||
|
e1.pos.y += e1.vel.y; |
||||||
|
e2.pos.x += e2.vel.x; |
||||||
|
e2.pos.y += e2.vel.y; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function toTimeString(time, hoursWanted){ |
||||||
|
|
||||||
|
time = floor(time / 10); |
||||||
|
|
||||||
|
let hs = String(floor(time % 100)); |
||||||
|
let fs = String(floor((time / 100) % 60)); |
||||||
|
|
||||||
|
if (hoursWanted){ |
||||||
|
let min = String(floor(((time / 100) / 60) % 60)); |
||||||
|
let hr = String(floor(((time / 100) / 60) / 60)); |
||||||
|
|
||||||
|
if (hs.length < 2) hs = "0" + hs; |
||||||
|
if (fs.length < 2) fs = "0" + fs; |
||||||
|
if (min.length < 2) min = "0" + min; |
||||||
|
if (hr.length < 2) hr = "0" + hr; |
||||||
|
|
||||||
|
let timeString = hr + ":" + min + ":" + fs + ":" + hs; |
||||||
|
} else { |
||||||
|
let min = String(floor(((time / 100) / 60) % 60)); |
||||||
|
|
||||||
|
if (hs.length < 2) hs = "0" + hs; |
||||||
|
if (fs.length < 2) fs = "0" + fs; |
||||||
|
if (min.length < 2) min = "0" + min; |
||||||
|
|
||||||
|
let timeString = min + ":" + fs + ":" + hs; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return timeString; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function setCookie(name, value, years){ |
||||||
|
let expires = ""; |
||||||
|
if (years){ |
||||||
|
let date = new Date(); |
||||||
|
date.setTime(date.getTime() + (years * 365 * 24 * 60 * 60 * 1000)); |
||||||
|
expires = "; expires=" + date.toUTCString(); |
||||||
|
} |
||||||
|
document.cookie = name + "=" + value + expires + "; path=/"; |
||||||
|
} |
||||||
|
|
||||||
|
function getCookie(name){ |
||||||
|
let nameEQ = name + "="; |
||||||
|
let ca = document.cookie.split(';'); |
||||||
|
for (let i = 0; i < ca.length; i++){ |
||||||
|
let c = ca[i]; |
||||||
|
while (c.charAt(0) == ' ') c = c.substring(1, c.length); |
||||||
|
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
function deleteCookies(){ |
||||||
|
for (let i = 0; i < arguments.length; i++) setCookie(arguments[i], "", -1); |
||||||
|
} |
||||||
|
|
||||||
|
function deleteAllCookies(){ |
||||||
|
let cookies = document.cookie.split(";"); |
||||||
|
for (let i = 0; i < cookies.length; i++) deleteCookies(cookies[i].split("=")[0]); |
||||||
|
} |
||||||
|
|
||||||
|
Array.prototype.shuffle = function(){ |
||||||
|
let currentIndex = this.length, temporaryValue, randomIndex; |
||||||
|
while (0 != currentIndex){ |
||||||
|
randomIndex = floor(random() * currentIndex); |
||||||
|
currentIndex -= 1; |
||||||
|
temporaryValue = this[currentIndex]; |
||||||
|
this[currentIndex] = this[randomIndex]; |
||||||
|
this[randomIndex] = temporaryValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Array.prototype.copy = function(){ |
||||||
|
return this.slice(0); |
||||||
|
}; |
||||||
|
|
||||||
|
//Divides big Array into big multidimensional Array
|
||||||
|
Array.prototype.partitiate = function(dimensions){ |
||||||
|
if (!dimensions) return this; |
||||||
|
let parts = []; |
||||||
|
while(this.length) parts.push(this.splice(0, round(pow(this.length, 1 / (1 + 1 / dimensions)))).partitiate(dimensions - 1)); |
||||||
|
return parts; |
||||||
|
} |
||||||
|
|
||||||
|
String.prototype.isValidHEX = function(){ |
||||||
|
return /(^#[0-9A-Fa-f]{6}$)|(^#[0-9A-Fa-f]{3}$)/i.test(this); |
||||||
|
} |
||||||
|
|
||||||
|
String.prototype.capitalize = function(){ |
||||||
|
return this.charAt(0).toUpperCase() + this.slice(1); |
||||||
|
} |
||||||
|
|
||||||
|
function debugInformation(x, y){ |
||||||
|
push(); |
||||||
|
textSize(12); |
||||||
|
textStyle(NORMAL); |
||||||
|
stroke(255); |
||||||
|
strokeWeight(1); |
||||||
|
fill(255); |
||||||
|
text("FPS : " + round(frameRate()), 10 + x, 10 + textAscent("FPS : ") + y); |
||||||
|
text("MouseX : " + round(mouseX + x), 10 + x, 10 + textAscent("FPS : ") + 10 + textAscent("MouseX : ") + y); |
||||||
|
text("MouseY : " + round(-mouseY - y), 10 + x, 10 + textAscent("FPS : ") + 10 + textAscent("MouseX : ") + 10 + textAscent("MouseY : ") + y); |
||||||
|
pop(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ColorPicker { |
||||||
|
|
||||||
|
constructor(){ |
||||||
|
this.movingObject = ""; |
||||||
|
} |
||||||
|
|
||||||
|
updateFromGraphical(){ |
||||||
|
this.h = (1 - $("#hue_picker").position().top / $("#hue").height()) * 360; |
||||||
|
this.s = ($("#sb_picker").position().left + 8) / $("#saturation").width() * 100; |
||||||
|
this.v = (1 - ($("#sb_picker").position().top + 8) / $("#value").height()) * 100; |
||||||
|
|
||||||
|
this.r = HSVtoRGB(this.h, this.s, this.v).r; |
||||||
|
this.g = HSVtoRGB(this.h, this.s, this.v).g; |
||||||
|
this.b = HSVtoRGB(this.h, this.s, this.v).b; |
||||||
|
|
||||||
|
this.hex = RGBtoHEX(this.r, this.g, this.b); |
||||||
|
|
||||||
|
this.updateInterface(); |
||||||
|
} |
||||||
|
|
||||||
|
updateFromHSV(){ |
||||||
|
this.h = $($("#color_picker_hsv input")[0]).val(); |
||||||
|
this.s = $($("#color_picker_hsv input")[1]).val(); |
||||||
|
this.v = $($("#color_picker_hsv input")[2]).val(); |
||||||
|
|
||||||
|
this.r = HSVtoRGB(this.h, this.s, this.v).r; |
||||||
|
this.g = HSVtoRGB(this.h, this.s, this.v).g; |
||||||
|
this.b = HSVtoRGB(this.h, this.s, this.v).b; |
||||||
|
|
||||||
|
this.hex = RGBtoHEX(this.r, this.g, this.b); |
||||||
|
|
||||||
|
this.updateFromHEX(null, true); |
||||||
|
|
||||||
|
this.updateInterface(); |
||||||
|
} |
||||||
|
|
||||||
|
updateFromRGB(){ |
||||||
|
this.r = $($("#color_picker_rgb input")[0]).val(); |
||||||
|
this.g = $($("#color_picker_rgb input")[1]).val(); |
||||||
|
this.b = $($("#color_picker_rgb input")[2]).val(); |
||||||
|
|
||||||
|
this.h = RGBtoHSV(this.r, this.g, this.b).h; |
||||||
|
this.s = RGBtoHSV(this.r, this.g, this.b).s; |
||||||
|
this.v = RGBtoHSV(this.r, this.g, this.b).v; |
||||||
|
|
||||||
|
this.hex = RGBtoHEX(this.r, this.g, this.b); |
||||||
|
|
||||||
|
this.updateFromHEX(null, true); |
||||||
|
|
||||||
|
this.updateInterface(); |
||||||
|
} |
||||||
|
|
||||||
|
updateFromHEX(input, otf){ |
||||||
|
if (!otf){ //Not on the fly
|
||||||
|
if ($(input).val().isValidHEX()) this.hex = $(input).val(); |
||||||
|
else { |
||||||
|
alert("Error!"); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.r = HEXtoRGB(this.hex).r; |
||||||
|
this.g = HEXtoRGB(this.hex).g; |
||||||
|
this.b = HEXtoRGB(this.hex).b; |
||||||
|
|
||||||
|
this.h = RGBtoHSV(this.r, this.g, this.b).h; |
||||||
|
this.s = RGBtoHSV(this.r, this.g, this.b).s; |
||||||
|
this.v = RGBtoHSV(this.r, this.g, this.b).v; |
||||||
|
|
||||||
|
this.updateInterface(); |
||||||
|
} |
||||||
|
|
||||||
|
updateInterface(){ |
||||||
|
|
||||||
|
let r = $($("#color_picker_rgb input")[0]), |
||||||
|
g = $($("#color_picker_rgb input")[1]), |
||||||
|
b = $($("#color_picker_rgb input")[2]), |
||||||
|
h = $($("#color_picker_hsv input")[0]), |
||||||
|
s = $($("#color_picker_hsv input")[1]), |
||||||
|
v = $($("#color_picker_hsv input")[2]), |
||||||
|
hex = $("#color_picker_hex"), |
||||||
|
bgColor; |
||||||
|
|
||||||
|
r.val(round(this.r)); |
||||||
|
g.val(round(this.g)); |
||||||
|
b.val(round(this.b)); |
||||||
|
h.val(round(this.h)); |
||||||
|
s.val(round(this.s)); |
||||||
|
v.val(round(this.v)); |
||||||
|
bgColor = color(this.r, 0, 0); |
||||||
|
r.css({ |
||||||
|
"background-color": bgColor.toString(), |
||||||
|
"color": fontColor(bgColor) |
||||||
|
}); |
||||||
|
bgColor = color(0, this.g, 0); |
||||||
|
g.css({ |
||||||
|
"background-color": bgColor.toString(), |
||||||
|
"color": fontColor(bgColor) |
||||||
|
}); |
||||||
|
bgColor = color(0, 0, this.b); |
||||||
|
b.css({ |
||||||
|
"background-color": bgColor.toString(), |
||||||
|
"color": fontColor(bgColor) |
||||||
|
}); |
||||||
|
colorMode(HSL); |
||||||
|
bgColor = color(this.h, 100, 50); |
||||||
|
h.css({ |
||||||
|
"background-color": bgColor.toString(), |
||||||
|
"color": fontColor(bgColor) |
||||||
|
}); |
||||||
|
bgColor = color(this.h, this.s, 100 - this.s / 2); |
||||||
|
s.css({ |
||||||
|
"background-color": bgColor.toString(), |
||||||
|
"color": fontColor(bgColor) |
||||||
|
}); |
||||||
|
bgColor = color(this.h, 100, this.v / 2); |
||||||
|
v.css({ |
||||||
|
"background-color": bgColor.toString(), |
||||||
|
"color": fontColor(bgColor) |
||||||
|
}); |
||||||
|
colorMode(RGB); |
||||||
|
hex.val(this.hex); |
||||||
|
hex.css({ |
||||||
|
"background-color": this.hex, |
||||||
|
"color": fontColor(color(this.hex)) |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
let sRGB = HSVtoRGB(this.h, 100, 100); |
||||||
|
let saturationBackground = "linear-gradient(to right, #FFF 0%, rgb("
|
||||||
|
+ sRGB.r + ","
|
||||||
|
+ sRGB.g + ","
|
||||||
|
+ sRGB.b + ") 100%)"; |
||||||
|
$("#hue_picker").css("top", (1 - this.h / 360) * $("#hue").height()); |
||||||
|
$("#sb_picker").css({ |
||||||
|
"left": this.s / 100 * $("#saturation").width() - 8, |
||||||
|
"top": (1 - this.v / 100) * $("#value").height() - 8 |
||||||
|
}); |
||||||
|
$("#saturation").css("background", saturationBackground); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
mousePressed(){ |
||||||
|
let x = winMouseX - $("#saturation").offset().left; |
||||||
|
let y = winMouseY - $("#value").offset().top; |
||||||
|
if (x > 0 && x < $("#saturation").width() && y > 0 && y < $("#value").height()){ |
||||||
|
this.movingObject = "sb"; |
||||||
|
} |
||||||
|
if (x > $("#saturation").width() + 6 && x < $("#saturation").width() + 6 + $("#hue").width() && y > 0 && y < $("#hue").height()){ |
||||||
|
this.movingObject = "hue"; |
||||||
|
} |
||||||
|
this.mouseDragged(); |
||||||
|
} |
||||||
|
|
||||||
|
mouseDragged(){ |
||||||
|
if (this.movingObject == "hue"){ |
||||||
|
let objH = $("#hue"); |
||||||
|
let picker = $("#hue_picker"); |
||||||
|
let h = winMouseY - objH.offset().top; |
||||||
|
if (h > 0 && h < objH.height()){ |
||||||
|
picker.css("top", h - 1); |
||||||
|
} else if (h > objH.height()){ |
||||||
|
picker.css("top", objH.height() - 1); |
||||||
|
} else if (h < 0){ |
||||||
|
picker.css("top", -1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (this.movingObject == "sb"){ |
||||||
|
let objS = $("#saturation"); |
||||||
|
let objV = $("#value"); |
||||||
|
let picker = $("#sb_picker"); |
||||||
|
let s = winMouseX - objS.offset().left; |
||||||
|
let v = winMouseY - objV.offset().top; |
||||||
|
if (s > 0 && s < objS.width()){ |
||||||
|
picker.css("left", s - 8); |
||||||
|
} else if (s < 0){ |
||||||
|
picker.css("left", -8); |
||||||
|
} else if (s < objS.width()){ |
||||||
|
picker.css("left", objS.width() - 8); |
||||||
|
} |
||||||
|
if (v > 0 && v < objV.height()){ |
||||||
|
picker.css("top", v - 8); |
||||||
|
} else if (v < 0){ |
||||||
|
picker.css("top", -8); |
||||||
|
} else if (v > objV.height()){ |
||||||
|
picker.css("top", objV.height() - 8); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
this.updateFromGraphical(); |
||||||
|
} |
||||||
|
|
||||||
|
mouseReleased(){ |
||||||
|
this.movingObject = ""; |
||||||
|
} |
||||||
|
|
||||||
|
getColor(){ |
||||||
|
return this.hex; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function fontColor(bg){ |
||||||
|
//https://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
|
||||||
|
let o = (red(bg) * 299 + green(bg) * 587 + blue(bg) * 114) / 1000; |
||||||
|
return (o > 125) ? "#000" : "#CCC"; |
||||||
|
} |
||||||
|
|
||||||
|
//www.stackoverflow.com -->
|
||||||
|
function RGBtoHEX(r, g, b) { |
||||||
|
let rgb = b | (g << 8) | (r << 16); |
||||||
|
return '#' + (0x1000000 + rgb).toString(16).slice(1); |
||||||
|
} |
||||||
|
|
||||||
|
function HEXtoRGB(hex) { |
||||||
|
let shorthandRegex = /^#?([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])$/i; |
||||||
|
hex = hex.replace(shorthandRegex, function(m, r, g, b) { |
||||||
|
return r + r + g + g + b + b; |
||||||
|
}); |
||||||
|
let result = /^#?([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/i.exec(hex); |
||||||
|
return result ? { |
||||||
|
r: parseInt(result[1], 16), |
||||||
|
g: parseInt(result[2], 16), |
||||||
|
b: parseInt(result[3], 16) |
||||||
|
} : null; |
||||||
|
} |
||||||
|
|
||||||
|
function HSVtoRGB(h, s, v) { |
||||||
|
let r, g, b, i, f, p, q, t; |
||||||
|
if (arguments.length === 1) { |
||||||
|
s = h.s, v = h.v, h = h.h; |
||||||
|
} |
||||||
|
h /= 360; |
||||||
|
s /= 100; |
||||||
|
v /= 100; |
||||||
|
i = Math.floor(h * 6); |
||||||
|
f = h * 6 - i; |
||||||
|
p = v * (1 - s); |
||||||
|
q = v * (1 - f * s); |
||||||
|
t = v * (1 - (1 - f) * s); |
||||||
|
switch (i % 6) { |
||||||
|
case 0: r = v, g = t, b = p; break; |
||||||
|
case 1: r = q, g = v, b = p; break; |
||||||
|
case 2: r = p, g = v, b = t; break; |
||||||
|
case 3: r = p, g = q, b = v; break; |
||||||
|
case 4: r = t, g = p, b = v; break; |
||||||
|
case 5: r = v, g = p, b = q; break; |
||||||
|
} |
||||||
|
return { |
||||||
|
r: r * 255, |
||||||
|
g: g * 255, |
||||||
|
b: b * 255 |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function RGBtoHSV(r, g, b) { |
||||||
|
if (arguments.length === 1) { |
||||||
|
g = r.g, b = r.b, r = r.r; |
||||||
|
} |
||||||
|
let max = Math.max(r, g, b), min = Math.min(r, g, b), |
||||||
|
d = max - min, |
||||||
|
h, |
||||||
|
s = (max === 0 ? 0 : d / max), |
||||||
|
v = max / 255; |
||||||
|
|
||||||
|
switch (max) { |
||||||
|
case min: h = 0; break; |
||||||
|
case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break; |
||||||
|
case g: h = (b - r) + d * 2; h /= 6 * d; break; |
||||||
|
case b: h = (r - g) + d * 4; h /= 6 * d; break; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
h: h * 360, |
||||||
|
s: s * 100, |
||||||
|
v: v * 100 |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function ranBool(chance){ |
||||||
|
if (!chance) chance = 2; |
||||||
|
return Math.floor(Math.random() * chance + 1) == chance ? true : false; |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
"use strict" |
||||||
|
|
||||||
|
let socket; |
||||||
|
|
||||||
|
let debug = false, |
||||||
|
viewPort = {x: 0, y: 0}, |
||||||
|
font, |
||||||
|
settings; |
||||||
|
|
||||||
|
let userInterface = true, |
||||||
|
cards = [], |
||||||
|
hidden, |
||||||
|
game, |
||||||
|
cardSize; |
||||||
|
|
||||||
|
function preload(){ |
||||||
|
font = loadFont("data/styles/font.ttf"); |
||||||
|
settings = loadJSON('data/settings/settings.json'); |
||||||
|
loadCards(); |
||||||
|
} |
||||||
|
|
||||||
|
function loadCards(){ |
||||||
|
loadJSON("data/settings/sources.json", createCards); |
||||||
|
} |
||||||
|
|
||||||
|
function setup(){ |
||||||
|
let ctx = createCanvas(50, 50); |
||||||
|
ctx.parent("loader"); |
||||||
|
playerDomSetup(0); |
||||||
|
hideDomElements(); |
||||||
|
$("body").show(); |
||||||
|
} |
||||||
|
|
||||||
|
function draw(){ |
||||||
|
clear(); |
||||||
|
let angleStart = frameCount / 10 * PI; |
||||||
|
noFill(); |
||||||
|
stroke("#33AA33"); |
||||||
|
strokeWeight(5); |
||||||
|
arc(25, 25, 45, 45, angleStart, angleStart + PI + HALF_PI); |
||||||
|
} |
||||||
|
|
||||||
|
function loader(dom){ |
||||||
|
//$(dom).attr("disabled", "disabled");
|
||||||
|
//$("#loader").show();
|
||||||
|
} |
||||||
|
|
||||||
|
function createCards(json){ |
||||||
|
cards = []; |
||||||
|
let id = 0; |
||||||
|
hidden = json.hidden; |
||||||
|
for (let src of json.sources){ |
||||||
|
cards.push(new Card(id, src), |
||||||
|
new Card(id + 1, src)); |
||||||
|
id += 2; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function cardClicked(card){ |
||||||
|
if (!game || !game.running || game.cards.includes(card)) return; |
||||||
|
game.revealCard(false, card.id); |
||||||
|
} |
||||||
|
|
||||||
|
function playerDomSetup(playerCount, players){ |
||||||
|
for (let i = 0; i < playerCount; i++){ |
||||||
|
if (i % 2 == 0) $("#left > div:eq(" + i / 2 + ")").show(); |
||||||
|
else $("#right > div:eq(" + (i - 1) / 2 + ")").show(); |
||||||
|
} |
||||||
|
for (let i = playerCount; i < 8; i++){ |
||||||
|
if (i % 2 == 0) $("#left > div:eq(" + i / 2 + ")").hide(); |
||||||
|
else $("#right > div:eq(" + (i - 1) / 2 + ")").hide(); |
||||||
|
} |
||||||
|
if (players != null) for (let p of players) p.hasTurn(false); |
||||||
|
} |
||||||
|
|
||||||
|
function hideDomElements(){ |
||||||
|
//Only leaders shall see those
|
||||||
|
$("#showInterface_btn, #interface").hide(); |
||||||
|
|
||||||
|
//For all
|
||||||
|
$("#player-count, #player-name, .error-label, " +
|
||||||
|
"#join-lobby, #lobby, .setup:eq(1), .setup:eq(2), #p5_loading, #loader").hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function createEmpty(){ |
||||||
|
let empty = $("<div></div>"); |
||||||
|
empty.attr("class", "card empty"); |
||||||
|
return empty; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,2 @@ |
|||||||
|
<?php |
||||||
|
echo parse_ini_file("../../../server/.env")["HTTPS_PORT"]; |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"project": { |
||||||
|
"name": "memory", |
||||||
|
"author": "BenjoCraeft", |
||||||
|
"playerCounts": [2, 3, 4, 5, 6, 7, 8] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
{ |
||||||
|
"sources": [ |
||||||
|
"data/images/airplane.jpeg", |
||||||
|
"data/images/airplane_high.jpeg", |
||||||
|
"data/images/ant.jpeg", |
||||||
|
"data/images/ant_nest.jpeg", |
||||||
|
"data/images/ball.jpg", |
||||||
|
"data/images/ball_2.jpg", |
||||||
|
"data/images/big_ben.jpeg", |
||||||
|
"data/images/bumblebee.jpeg", |
||||||
|
"data/images/butterfly.jpeg", |
||||||
|
"data/images/cars.jpeg", |
||||||
|
"data/images/cat_2.jpeg", |
||||||
|
"data/images/cereal.jpeg", |
||||||
|
"data/images/cereal_making.jpeg", |
||||||
|
"data/images/cliff.jpeg", |
||||||
|
"data/images/cliff_forest.jpeg", |
||||||
|
"data/images/cloudy_sky.jpeg", |
||||||
|
"data/images/cloudy_sky_high.jpeg", |
||||||
|
"data/images/cloudy_sky_sun.jpeg", |
||||||
|
"data/images/cloudy_sky_sun_2.jpeg", |
||||||
|
"data/images/clover.jpeg", |
||||||
|
"data/images/drink.jpeg", |
||||||
|
"data/images/evening_sky.jpeg", |
||||||
|
"data/images/eye.jpeg", |
||||||
|
"data/images/feast.jpeg", |
||||||
|
"data/images/fire.jpeg", |
||||||
|
"data/images/flower.jpeg", |
||||||
|
"data/images/forest.jpeg", |
||||||
|
"data/images/insect.jpeg", |
||||||
|
"data/images/moss.jpeg", |
||||||
|
"data/images/ocean.jpeg", |
||||||
|
"data/images/pancake.jpeg", |
||||||
|
"data/images/pine_trees.jpeg", |
||||||
|
"data/images/rabbit.jpeg", |
||||||
|
"data/images/river.jpeg", |
||||||
|
"data/images/sledge.jpeg", |
||||||
|
"data/images/snow_forest.jpeg", |
||||||
|
"data/images/snowman.jpeg", |
||||||
|
"data/images/snowy_landscape.jpeg", |
||||||
|
"data/images/stage.jpeg", |
||||||
|
"data/images/strawberries.jpg", |
||||||
|
"data/images/street_forest.jpg", |
||||||
|
"data/images/street_ocean.jpeg", |
||||||
|
"data/images/sunrise.jpeg", |
||||||
|
"data/images/tower_bridge.jpeg", |
||||||
|
"data/images/tree.jpeg", |
||||||
|
"data/images/tree_blue_sky.jpeg", |
||||||
|
"data/images/tree_broken.jpeg", |
||||||
|
"data/images/tree_yellow_sky.jpeg", |
||||||
|
"data/images/volcano.jpeg", |
||||||
|
"data/images/wasteland.jpeg" |
||||||
|
], |
||||||
|
"hidden": "data/images/hidden.png" |
||||||
|
} |
@ -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,145 @@ |
|||||||
|
<!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="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.min.js" type="text/javascript"></script> |
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.min.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/lib/benjo_library.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/events.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/sketch.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/Card.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/Game.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/Player.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/Online.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>Memory</title> |
||||||
|
</head> |
||||||
|
<body style="display: none;"> |
||||||
|
<div id="p5_loading"></div> |
||||||
|
<div id="game"> |
||||||
|
<div class="player-holder" id="left"> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div id="table"></div> |
||||||
|
<div class="player-holder" id="right"> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="player"> |
||||||
|
<div class="has-turn"></div> |
||||||
|
<span></span> |
||||||
|
<label class="pairs-label"></label> |
||||||
|
<div class="staple"> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div id="interface"> |
||||||
|
<button onclick="game.shuffle();">Shuffle</button> |
||||||
|
<button onclick="game.start();">Start</button> |
||||||
|
</div> |
||||||
|
<div id="initialize-wrapper"> |
||||||
|
<div class="initialize-lobby" id="initialize"> |
||||||
|
<fieldset 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> |
||||||
|
<fieldset id="player-count"> |
||||||
|
<legend>Player</legend> |
||||||
|
<select> |
||||||
|
<option value="2">2</option> |
||||||
|
<option value="3">3</option> |
||||||
|
<option value="4">4</option> |
||||||
|
<option value="5">5</option> |
||||||
|
<option value="6">6</option> |
||||||
|
<option value="7">7</option> |
||||||
|
<option value="8">8</option> |
||||||
|
</select> |
||||||
|
</fieldset> |
||||||
|
<fieldset id="player-name"> |
||||||
|
<span>Enter your name:</span><br> |
||||||
|
<input type="text"/><br> |
||||||
|
<span class="error-label"></span> |
||||||
|
</fieldset> |
||||||
|
<button class="setup" onclick="continueForm();">OK</button> |
||||||
|
<button class="setup" onclick="createLobby(this);">Create Lobby</button> |
||||||
|
<fieldset id="join-lobby"> |
||||||
|
<span>Enter Lobby Code:</span><br> |
||||||
|
<input type="text"/><br> |
||||||
|
<span class="error-label"></span> |
||||||
|
</fieldset> |
||||||
|
<button class="setup" onclick="joinLobby(this);">Join Lobby</button> |
||||||
|
<div id="loader"></div> |
||||||
|
</div> |
||||||
|
<div class="initialize-lobby" id="lobby"> |
||||||
|
<div id="lobby-members"></div> |
||||||
|
<button class="setup" id="start-lobby" onclick="startLobby(this);">Start</button> |
||||||
|
<button class="setup" id="leave-lobby" onclick="leaveLobby(this);">Leave</button> |
||||||
|
<span id="lobby-id"></span> |
||||||
|
<span class="error-label"></span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,251 @@ |
|||||||
|
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"; |
||||||
|
} |
||||||
|
|
||||||
|
html{ |
||||||
|
background-image: url(data/images/background.jpg); |
||||||
|
background-size: 100% 100%; |
||||||
|
} |
||||||
|
|
||||||
|
#interface{ |
||||||
|
width: 100%; |
||||||
|
position: fixed; |
||||||
|
bottom: 0; |
||||||
|
margin: auto; |
||||||
|
visibility: visible; |
||||||
|
opacity: 1; |
||||||
|
float: left; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
#interface button{ |
||||||
|
width: 100px; |
||||||
|
padding: 5px; |
||||||
|
margin: 2px; |
||||||
|
border-radius: 4px; |
||||||
|
box-shadow: 2px 2px 4px #000; |
||||||
|
background-color: rgb(36, 126, 248); |
||||||
|
float: left; |
||||||
|
} |
||||||
|
|
||||||
|
#interface button:hover{ |
||||||
|
filter: brightness(70%); |
||||||
|
} |
||||||
|
|
||||||
|
#game{ |
||||||
|
display: flex; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background-image: url(data/images/background.jpg); |
||||||
|
background-size: 100% 100%; |
||||||
|
} |
||||||
|
|
||||||
|
#table{ |
||||||
|
margin: auto; |
||||||
|
width: 100vh; |
||||||
|
height: 100vh; |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
justify-content: space-around; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.card_on_table{ |
||||||
|
margin: 0 calc(1vh - 2px); |
||||||
|
transition: filter 150ms ease-in-out, transform 300ms ease-in-out; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
.card_on_table:hover{ |
||||||
|
filter: brightness(70%); |
||||||
|
} |
||||||
|
|
||||||
|
.card_on_staple{ |
||||||
|
position: relative; |
||||||
|
margin-left: calc((-8vh - 2px) + 4px - 1px); |
||||||
|
top: 0px; |
||||||
|
float: left; |
||||||
|
} |
||||||
|
.card_on_staple:first-child{ |
||||||
|
margin-left: 0px; |
||||||
|
} |
||||||
|
|
||||||
|
.card_on_staple:hover{ |
||||||
|
transform: none; |
||||||
|
} |
||||||
|
|
||||||
|
.card{ |
||||||
|
width: 8vh; |
||||||
|
height: 8vh; |
||||||
|
background-size: 100% 100%; |
||||||
|
border-radius: 7px; |
||||||
|
border: 1px solid #000; |
||||||
|
box-shadow: 10px 10px 15px #000; |
||||||
|
} |
||||||
|
|
||||||
|
.empty{ |
||||||
|
margin: 0 calc(1vh - 2px); |
||||||
|
border: 1px solid rgba(0, 0, 0, 0); |
||||||
|
box-shadow: none; |
||||||
|
} |
||||||
|
|
||||||
|
.player-holder{ |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
justify-content: space-around; |
||||||
|
align-items: center; |
||||||
|
width: calc((100vw - 100vh) / 2); |
||||||
|
} |
||||||
|
|
||||||
|
.player{ |
||||||
|
background-color: rgba(0, 0, 0, 0.5); |
||||||
|
width: 90%; |
||||||
|
padding: 10px; |
||||||
|
border: 3px solid #000; |
||||||
|
border-radius: 10px; |
||||||
|
box-shadow: 10px 10px 15px #000; |
||||||
|
} |
||||||
|
|
||||||
|
.staple{ |
||||||
|
width: 95%; |
||||||
|
height: calc(8vh + 2px); |
||||||
|
margin: 50px 2.5% 2.5% 2.5%; |
||||||
|
} |
||||||
|
|
||||||
|
.has-turn{ |
||||||
|
width: 30px; |
||||||
|
height: 30px; |
||||||
|
border: 1px solid #000; |
||||||
|
box-shadow: 2px 2px 5px #000; |
||||||
|
border-radius: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
.pairs-label{ |
||||||
|
font-size: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
#right .pairs-label{ |
||||||
|
float: right; |
||||||
|
} |
||||||
|
|
||||||
|
#left .pairs-label{ |
||||||
|
float: left; |
||||||
|
} |
||||||
|
|
||||||
|
.player > span{ |
||||||
|
font-size: 23px; |
||||||
|
line-height: 30px; |
||||||
|
} |
||||||
|
|
||||||
|
#left .has-turn{ |
||||||
|
margin-left: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
#right .has-turn{ |
||||||
|
margin-right: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
#left .has-turn, #left .player > span{ |
||||||
|
float: right; |
||||||
|
} |
||||||
|
|
||||||
|
#right .has-turn, #right .player > span{ |
||||||
|
float: left; |
||||||
|
} |
||||||
|
|
||||||
|
#initialize-wrapper{ |
||||||
|
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background-color: rgba(0, 0, 0, 0.8); |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.initialize-lobby{ |
||||||
|
padding: 20px; |
||||||
|
background-color: rgb(224, 224, 224); |
||||||
|
border-radius: 10px; |
||||||
|
border: 5px solid #000; |
||||||
|
box-shadow: 5px 5px 10px #000; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
|
||||||
|
#initialize fieldset{ |
||||||
|
border: 2px solid #000; |
||||||
|
border-radius: 5px; |
||||||
|
background-color: rgb(150, 150, 150); |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
#initialize > fieldset{ |
||||||
|
margin: 10px; |
||||||
|
box-shadow: 4px 4px 8px #000; |
||||||
|
} |
||||||
|
|
||||||
|
#loader{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
.setup{ |
||||||
|
width: 200px; |
||||||
|
border-radius: 3px; |
||||||
|
border: 2px solid #000; |
||||||
|
background-color: rgb(53, 204, 53); |
||||||
|
font-size: 20px; |
||||||
|
margin: 10px; |
||||||
|
box-shadow: 3px 3px 6px #000; |
||||||
|
} |
||||||
|
|
||||||
|
.setup:hover{ |
||||||
|
filter: brightness(70%); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.error-label{ |
||||||
|
font-size: 10px; |
||||||
|
color: red; |
||||||
|
} |
||||||
|
|
||||||
|
.lobby-member{ |
||||||
|
padding: 5px; |
||||||
|
margin: 5px; |
||||||
|
border: 1px solid #000; |
||||||
|
background-color: rgb(180, 180, 180); |
||||||
|
border-radius: 5px; |
||||||
|
box-shadow: 4px 4px 6px #000; |
||||||
|
} |
||||||
|
|
||||||
|
#lobby-leader{ |
||||||
|
border-width: 4px; |
||||||
|
border-color: #009a06; |
||||||
|
} |
||||||
|
|
||||||
|
#lobby > span{ |
||||||
|
font-size: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
#start-lobby:disabled{ |
||||||
|
filter: brightness(40%); |
||||||
|
} |
||||||
|
#start-lobby:disabled:hover{ |
||||||
|
filter: brightness(40%); |
||||||
|
} |
After Width: | Height: | Size: 733 KiB |
@ -0,0 +1,4 @@ |
|||||||
|
logs |
||||||
|
node_modules |
||||||
|
out |
||||||
|
.env |
@ -0,0 +1,439 @@ |
|||||||
|
{ |
||||||
|
"name": "chainreact-server", |
||||||
|
"version": "2.0", |
||||||
|
"lockfileVersion": 2, |
||||||
|
"requires": true, |
||||||
|
"packages": { |
||||||
|
"": { |
||||||
|
"name": "chainreact-server", |
||||||
|
"version": "2.0", |
||||||
|
"dependencies": { |
||||||
|
"dotenv": "^16.0.3", |
||||||
|
"https": "^1.0.0", |
||||||
|
"socket.io": "^4.4.1", |
||||||
|
"typescript": "^5.0.2" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/node": "^18.15.3" |
||||||
|
} |
||||||
|
}, |
||||||
|
"base": { |
||||||
|
"name": "game-server", |
||||||
|
"version": "2.0", |
||||||
|
"extraneous": true, |
||||||
|
"dependencies": { |
||||||
|
"dotenv": "^16.0.3", |
||||||
|
"https": "^1.0.0", |
||||||
|
"ini": "^2.0.0", |
||||||
|
"socket.io": "^4.4.1" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/node": "^17.0.18", |
||||||
|
"typescript": "^4.8.4" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/@socket.io/component-emitter": { |
||||||
|
"version": "3.1.0", |
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", |
||||||
|
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" |
||||||
|
}, |
||||||
|
"node_modules/@types/cookie": { |
||||||
|
"version": "0.4.1", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", |
||||||
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" |
||||||
|
}, |
||||||
|
"node_modules/@types/cors": { |
||||||
|
"version": "2.8.13", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", |
||||||
|
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", |
||||||
|
"dependencies": { |
||||||
|
"@types/node": "*" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/@types/node": { |
||||||
|
"version": "18.15.3", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", |
||||||
|
"integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" |
||||||
|
}, |
||||||
|
"node_modules/accepts": { |
||||||
|
"version": "1.3.8", |
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", |
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", |
||||||
|
"dependencies": { |
||||||
|
"mime-types": "~2.1.34", |
||||||
|
"negotiator": "0.6.3" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.6" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/base64id": { |
||||||
|
"version": "2.0.0", |
||||||
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", |
||||||
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", |
||||||
|
"engines": { |
||||||
|
"node": "^4.5.0 || >= 5.9" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/cookie": { |
||||||
|
"version": "0.4.2", |
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", |
||||||
|
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.6" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/cors": { |
||||||
|
"version": "2.8.5", |
||||||
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", |
||||||
|
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", |
||||||
|
"dependencies": { |
||||||
|
"object-assign": "^4", |
||||||
|
"vary": "^1" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.10" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/debug": { |
||||||
|
"version": "4.3.4", |
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", |
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", |
||||||
|
"dependencies": { |
||||||
|
"ms": "2.1.2" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=6.0" |
||||||
|
}, |
||||||
|
"peerDependenciesMeta": { |
||||||
|
"supports-color": { |
||||||
|
"optional": true |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/dotenv": { |
||||||
|
"version": "16.0.3", |
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", |
||||||
|
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", |
||||||
|
"engines": { |
||||||
|
"node": ">=12" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/engine.io": { |
||||||
|
"version": "6.4.1", |
||||||
|
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", |
||||||
|
"integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", |
||||||
|
"dependencies": { |
||||||
|
"@types/cookie": "^0.4.1", |
||||||
|
"@types/cors": "^2.8.12", |
||||||
|
"@types/node": ">=10.0.0", |
||||||
|
"accepts": "~1.3.4", |
||||||
|
"base64id": "2.0.0", |
||||||
|
"cookie": "~0.4.1", |
||||||
|
"cors": "~2.8.5", |
||||||
|
"debug": "~4.3.1", |
||||||
|
"engine.io-parser": "~5.0.3", |
||||||
|
"ws": "~8.11.0" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=10.0.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/engine.io-parser": { |
||||||
|
"version": "5.0.6", |
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", |
||||||
|
"integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", |
||||||
|
"engines": { |
||||||
|
"node": ">=10.0.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/https": { |
||||||
|
"version": "1.0.0", |
||||||
|
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", |
||||||
|
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" |
||||||
|
}, |
||||||
|
"node_modules/mime-db": { |
||||||
|
"version": "1.52.0", |
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.6" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/mime-types": { |
||||||
|
"version": "2.1.35", |
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
||||||
|
"dependencies": { |
||||||
|
"mime-db": "1.52.0" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.6" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/ms": { |
||||||
|
"version": "2.1.2", |
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" |
||||||
|
}, |
||||||
|
"node_modules/negotiator": { |
||||||
|
"version": "0.6.3", |
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", |
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.6" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/object-assign": { |
||||||
|
"version": "4.1.1", |
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", |
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", |
||||||
|
"engines": { |
||||||
|
"node": ">=0.10.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/socket.io": { |
||||||
|
"version": "4.6.1", |
||||||
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", |
||||||
|
"integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", |
||||||
|
"dependencies": { |
||||||
|
"accepts": "~1.3.4", |
||||||
|
"base64id": "~2.0.0", |
||||||
|
"debug": "~4.3.2", |
||||||
|
"engine.io": "~6.4.1", |
||||||
|
"socket.io-adapter": "~2.5.2", |
||||||
|
"socket.io-parser": "~4.2.1" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=10.0.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/socket.io-adapter": { |
||||||
|
"version": "2.5.2", |
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", |
||||||
|
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", |
||||||
|
"dependencies": { |
||||||
|
"ws": "~8.11.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/socket.io-parser": { |
||||||
|
"version": "4.2.2", |
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", |
||||||
|
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", |
||||||
|
"dependencies": { |
||||||
|
"@socket.io/component-emitter": "~3.1.0", |
||||||
|
"debug": "~4.3.1" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=10.0.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/typescript": { |
||||||
|
"version": "5.0.2", |
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", |
||||||
|
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", |
||||||
|
"bin": { |
||||||
|
"tsc": "bin/tsc", |
||||||
|
"tsserver": "bin/tsserver" |
||||||
|
}, |
||||||
|
"engines": { |
||||||
|
"node": ">=12.20" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/vary": { |
||||||
|
"version": "1.1.2", |
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", |
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", |
||||||
|
"engines": { |
||||||
|
"node": ">= 0.8" |
||||||
|
} |
||||||
|
}, |
||||||
|
"node_modules/ws": { |
||||||
|
"version": "8.11.0", |
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", |
||||||
|
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", |
||||||
|
"engines": { |
||||||
|
"node": ">=10.0.0" |
||||||
|
}, |
||||||
|
"peerDependencies": { |
||||||
|
"bufferutil": "^4.0.1", |
||||||
|
"utf-8-validate": "^5.0.2" |
||||||
|
}, |
||||||
|
"peerDependenciesMeta": { |
||||||
|
"bufferutil": { |
||||||
|
"optional": true |
||||||
|
}, |
||||||
|
"utf-8-validate": { |
||||||
|
"optional": true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@socket.io/component-emitter": { |
||||||
|
"version": "3.1.0", |
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", |
||||||
|
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" |
||||||
|
}, |
||||||
|
"@types/cookie": { |
||||||
|
"version": "0.4.1", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", |
||||||
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" |
||||||
|
}, |
||||||
|
"@types/cors": { |
||||||
|
"version": "2.8.13", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", |
||||||
|
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", |
||||||
|
"requires": { |
||||||
|
"@types/node": "*" |
||||||
|
} |
||||||
|
}, |
||||||
|
"@types/node": { |
||||||
|
"version": "18.15.3", |
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", |
||||||
|
"integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" |
||||||
|
}, |
||||||
|
"accepts": { |
||||||
|
"version": "1.3.8", |
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", |
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", |
||||||
|
"requires": { |
||||||
|
"mime-types": "~2.1.34", |
||||||
|
"negotiator": "0.6.3" |
||||||
|
} |
||||||
|
}, |
||||||
|
"base64id": { |
||||||
|
"version": "2.0.0", |
||||||
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", |
||||||
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" |
||||||
|
}, |
||||||
|
"cookie": { |
||||||
|
"version": "0.4.2", |
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", |
||||||
|
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" |
||||||
|
}, |
||||||
|
"cors": { |
||||||
|
"version": "2.8.5", |
||||||
|
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", |
||||||
|
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", |
||||||
|
"requires": { |
||||||
|
"object-assign": "^4", |
||||||
|
"vary": "^1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"debug": { |
||||||
|
"version": "4.3.4", |
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", |
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", |
||||||
|
"requires": { |
||||||
|
"ms": "2.1.2" |
||||||
|
} |
||||||
|
}, |
||||||
|
"dotenv": { |
||||||
|
"version": "16.0.3", |
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", |
||||||
|
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" |
||||||
|
}, |
||||||
|
"engine.io": { |
||||||
|
"version": "6.4.1", |
||||||
|
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", |
||||||
|
"integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", |
||||||
|
"requires": { |
||||||
|
"@types/cookie": "^0.4.1", |
||||||
|
"@types/cors": "^2.8.12", |
||||||
|
"@types/node": ">=10.0.0", |
||||||
|
"accepts": "~1.3.4", |
||||||
|
"base64id": "2.0.0", |
||||||
|
"cookie": "~0.4.1", |
||||||
|
"cors": "~2.8.5", |
||||||
|
"debug": "~4.3.1", |
||||||
|
"engine.io-parser": "~5.0.3", |
||||||
|
"ws": "~8.11.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"engine.io-parser": { |
||||||
|
"version": "5.0.6", |
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", |
||||||
|
"integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" |
||||||
|
}, |
||||||
|
"https": { |
||||||
|
"version": "1.0.0", |
||||||
|
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", |
||||||
|
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" |
||||||
|
}, |
||||||
|
"mime-db": { |
||||||
|
"version": "1.52.0", |
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", |
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" |
||||||
|
}, |
||||||
|
"mime-types": { |
||||||
|
"version": "2.1.35", |
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", |
||||||
|
"requires": { |
||||||
|
"mime-db": "1.52.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"ms": { |
||||||
|
"version": "2.1.2", |
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" |
||||||
|
}, |
||||||
|
"negotiator": { |
||||||
|
"version": "0.6.3", |
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", |
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" |
||||||
|
}, |
||||||
|
"object-assign": { |
||||||
|
"version": "4.1.1", |
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", |
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" |
||||||
|
}, |
||||||
|
"socket.io": { |
||||||
|
"version": "4.6.1", |
||||||
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", |
||||||
|
"integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", |
||||||
|
"requires": { |
||||||
|
"accepts": "~1.3.4", |
||||||
|
"base64id": "~2.0.0", |
||||||
|
"debug": "~4.3.2", |
||||||
|
"engine.io": "~6.4.1", |
||||||
|
"socket.io-adapter": "~2.5.2", |
||||||
|
"socket.io-parser": "~4.2.1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"socket.io-adapter": { |
||||||
|
"version": "2.5.2", |
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", |
||||||
|
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", |
||||||
|
"requires": { |
||||||
|
"ws": "~8.11.0" |
||||||
|
} |
||||||
|
}, |
||||||
|
"socket.io-parser": { |
||||||
|
"version": "4.2.2", |
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", |
||||||
|
"integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", |
||||||
|
"requires": { |
||||||
|
"@socket.io/component-emitter": "~3.1.0", |
||||||
|
"debug": "~4.3.1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"typescript": { |
||||||
|
"version": "5.0.2", |
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", |
||||||
|
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==" |
||||||
|
}, |
||||||
|
"vary": { |
||||||
|
"version": "1.1.2", |
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", |
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" |
||||||
|
}, |
||||||
|
"ws": { |
||||||
|
"version": "8.11.0", |
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", |
||||||
|
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", |
||||||
|
"requires": {} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
{ |
||||||
|
"name": "chainreact-server", |
||||||
|
"version": "2.0", |
||||||
|
"private": true, |
||||||
|
"scripts": { |
||||||
|
"start": "node out/index.js", |
||||||
|
"tsc": "npx tsc" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"dotenv": "^16.0.3", |
||||||
|
"https": "^1.0.0", |
||||||
|
"socket.io": "^4.4.1", |
||||||
|
"typescript": "^5.0.2" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/node": "^18.15.3" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,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 {Memory} from "./memory"; |
||||||
|
import {StartServer} from "./start"; |
||||||
|
|
||||||
|
StartServer({ |
||||||
|
useP2P: false, |
||||||
|
gameClass: Memory |
||||||
|
}); |
@ -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,14 @@ |
|||||||
|
import {Room} from "./room" |
||||||
|
import {ServerGame} from "./game_standard" |
||||||
|
|
||||||
|
export class Memory extends ServerGame { |
||||||
|
|
||||||
|
constructor(room: Room, settings: Settings.Global) { |
||||||
|
super(room, settings); |
||||||
|
} |
||||||
|
|
||||||
|
gameAction(action: string, ...args: any[]) { |
||||||
|
this.room.toAll('game-action', action, ...args); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,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" |
||||||
|
] |
||||||
|
} |