diff --git a/public_html/ServerEventsHandlers.js b/public_html/ServerEventsHandlers.js new file mode 100644 index 0000000..63fccf2 --- /dev/null +++ b/public_html/ServerEventsHandlers.js @@ -0,0 +1,348 @@ +class ServerEventsHandlers { + constructor(game) { + this.game = game; + } + + version(data) { + const servVersion = data[0]; + if(servVersion == REVISION) { + return; + } + + alert(strings["bad_version"]); + this.game.conn.close(); + this.game.conn = null; + } + + err(data) { + alert(strings[data[0]]); + this.game.conn.close(); + this.game.conn = null; + } + + exploded(data) { + const nickname = data[0]; + this.game.console("" + nickname + " drew a Detonating Cat!"); + + cardHUD("exploding", 1000); + } + + // Legacy ?? + card_left(data) { + $("#remaining-card-count").text(data[0]); + } + + now_playing(data) { + this.game.nowPlaying = data[0]; + this.game.console("It is " + this.game.nowPlaying + "'s turn."); + + this.game.drawPlayerList(); + + // cmp with raw data because name isn't stored encoded + if (data[0] == this.game.name) { + this.game.ourTurn = true; + this.game.assets["atomic.ogg"].play(); + document.title = strings["title_alert"]; + $(".bottom-notify-container").html(strings["your_turn"]); + } else { + this.game.ourTurn = false; + document.title = strings["title_normal"]; // a bit fragile, needs to be same as + $(".bottom-notify-container").html(""); + } + } + + players(data) { + // Update players list + this.game.players = data; + this.game.spectators = this.game.spectators.filter(i => !this.game.players.includes(i)); + this.game.drawPlayerList(); + this.game.drawSpectatorsList(); + } + + spectators(data) { + // Update spectators list + this.game.spectators = data.filter(i => !this.game.players.includes(i)); + this.game.drawSpectatorsList(); + } + + joins(data) { + const nickname = data[0]; + if(nickname == this.game.name && !this.game.started) { + return this.game.start(); + } + + this.game.console("<span class=\"text-success\">" + nickname + " joined as a spectator.</span>"); + this.game.addSpectator(nickname); + } + + upgrades(data) { + const nickname = data[0]; + this.game.console("<span class=\"text-danger\">" + nickname + " is now spectating.</span>"); + this.game.removeSpectator(nickname); + } + + downgrades(data) { + const nickname = data[0]; + this.game.console("<span class=\"text-success\">" + nickname + " is now playing.</span>"); + this.game.addSpectator(nickname); + } + + parts(data) { + const nickname = data[0]; + this.game.console("<span class=\"text-danger\">" + nickname + " left the game.</span>"); + + this.game.removeSpectator(nickname); + } + + chat(data) { + const nickname = data[0]; + const msg = data.join(" ").substring(nickname.length); + this.game.console(`${nickname}: ${msg}`); + } + + message(data) { + // This refers to the 'message' container in the middle of the board + // that can be used to display useful game info + const msg = strings["message_" + data[0]]; + $("#message").html(msg); + $("#message-container").removeClass("reveal"); + } + + clear_message(data) { + $("#message").html(""); + $("#message-container").addClass("reveal"); + } + + bcast(data) { + let msg = strings["bcast_" + data[0]]; + this.game.console(msg); + } + + hand(data) { + this.game.hand = data; + $("#card-deck").empty(); + + for (let i=0; i < this.game.hand.length; i++) { + let card = $("<img class='card' src='assets/card_" + this.game.hand[i] + ".png' />"); + + (function (gameState, cardNo, cardName) { + card.on("click", function() { + if (gameState.locked) { + return; + } + + if (gameState.favouring) { + // Favour NOPE-logic is handled server-side :) + gameState.send("a favour_what "+cardNo.toString()); + gameState.favouring = false; + return; + } + + if (gameState.combo > 1) { + // Do we have enough cards? + let cards = 0; + for (var j=1; j < parts.length; j++) { + if (parts[j] == cardName) { + cards++; + if (cards == gameState.combo) { + break; + } + } + } + if (cards < gameState.combo) { + gameState.console("You don't have enough " + strings["card_"+cardName] + + " cards to do that!"); + return; + } + + if (gameState.ourTurn) { + gameState.send("play_multiple "+gameState.combo.toString()+" "+ + cardName); + gameState.resetButtons(); + return; + } + } + + if ((gameState.ourTurn || cardName === "nope") && !cardName.startsWith("random") + && ( (!gameState.defusing && cardName !== "defuse") + || (gameState.defusing && cardName === "defuse") )) { + gameState.send("play "+cardNo.toString()); + gameState.defusing = false; + } + }); + })(this.game, i, this.game.hand[i]); + + $("#card-deck").append(card); + } + } + + draw_pile(data) { + const flag = data[0] == "yes" ? true : false; + + if(flag) { + $("#draw-pile").html("<img class='card' src='assets/card_back.png' />"); + $("#draw-pile-counter").removeClass("reveal"); + } else { + $("#draw-pile").html(""); + $("#draw-pile-counter").addClass("reveal"); + } + } + + drew(data) { + this.game.console("You drew <span class=\"text-warning\">" + strings["card_" + data[0]] + ".</span>"); + cardHUD(parts[1], 2000); + } + + drew_other(data) { + const nickname = data[0]; + this.game.console("<span class=\"text-primary\">" + nickname + " drew a card.</span>"); + + // Animation + $("#draw-pile-animation").html("<img src='assets/card_back.png' class='card' />"); + animate("#draw-pile-animation", "right", 125, -150, -15, "px"); + } + + wins(data) { + const nickname = data[0]; + this.game.ourTurn = false; + this.game.console("<span class=\"text-primary\"><b>" + nickname + " won!</b></span>"); + this.game.nowPlaying = ""; + } + + defusing(data) { + this.game.console(strings["must_defuse"]); + this.game.defusing = true; + } + + played(data) { + const [ nickname, card ] = data; + this.game.console(nickname + " played " + strings["card_" + card] + "."); + + $("#discard-pile").html("<img class='card' src='assets/card_" + card + ".png' />"); + + if (card != "see3") { + cardHUD(card, 1000); + } + } + + played_multiple(data) { + const [nickname, x, card] = data; + this.game.console(nickname + " played " + x + "x " + strings["card_" + card] + "."); + + $("#discard-pile").html("<img class='card' src='assets/card_" + card + ".png' />"); + + if (x == 2) { + cardHUD3([card, card], 1000); + } else { + cardHUD3([card, card, card], 1000); + } + } + + no_discard(data) { + $("#discard-pile").html(""); + } + + q(data) { + const type = data[0]; + if (type == "favour_what") { + this.game.favouring = true; + let perpetrator = data[1]; + this.game.console("<span class=\"text-primary\">" + perpetrator + + " is asking you for a favour.</span>"); + } else if (type == "favour_who" || type == "random_who" || type == "steal_who") { + (function (gameState) { + modalChoice(function(player) { + gameState.send("a " + type + " " + player); + }, strings["question_" + type], gameState.players, entities(gameState.name), null); + })(this.game); + } else if (type == "steal_what") { + (function (gameState) { + modalChoice(function(card) { + gameState.send("a " + type + " " + card); + }, strings["question_" + type], cards, null, x => strings["card_" + x]); + })(this.game); + } else { + ans = prompt(strings["question_" + type]); + this.game.send("a " + type + " " + ans); + } + } + + q_cancel(data) { + this.game.favouring = false; + } + + seen(data) { + cardHUD3(data, 2000); + this.game.console("You saw " + strings["card_" + data[0]] + ", " + strings["card_" + data[1]] + " and " + + strings["card_" + data[2]] + "."); + } + + favoured(data) { + const [ perpetrator, victim ] = data; + this.game.console(perpetrator + " is asking " + victim + " for a favour."); + } + + favour_complete(data) { + const [ perpetrator, victim ] = data; + this.game.console(victim + " gave " + perpetrator + " a favour."); + } + + favour_recv(data) { + const remotePlayer = data[0]; + const card = strings["card_" + data[1]]; + this.game.console(remotePlayer + " gave you <span class=\"text-info\">" + card + "</span>."); + cardHUD(data[1], 2000); + } + + favour_gave(data) { + const remotePlayer = data[0]; + const card = strings["card_" + data[1]]; + this.game.console("You gave " + remotePlayer + " <span class=\"text-info\">" + card + "</span>."); + } + + randomed(data) { + const [ perpetrator, victim ] = data; + this.game.console(perpetrator + " took a random card from " + victim + "."); + } + + random_n(data) { + const [ perpetrator, victim ] = data; + this.game.console(perpetrator + " asked " + victim + " for a random card, but they had nothing to give away!"); + } + + random_recv(data) { + const remotePlayer = data[0]; + const card = strings["card_" + data[1]]; + + this.game.console("You randomly took <span class=\"text-info\">" + card + "</span> from " + remotePlayer + "."); + cardHUD(data[1], 2000); + } + + random_gave(data) { + const remotePlayer = data[0]; + const card = strings["card_" + data[1]]; + this.game.console(remotePlayer + " randomly took <span class=\"text-info\">" + card + "</span> from you."); + cardHUD(data[1], 2000); + } + + steal_n(data) { + const [ perpetrator, victim, card ] = data; + this.game.console(perpetrator + " asked " + victim + " for <span class=\"text-info\">" + + strings["card_" + card] + "</span>, but ended up empty-handed!"); + } + + steal_y(data) { + const [ perpetrator, victim, card ] = data; + this.game.console(perpetrator + " stole <span class=\"text-info\">" + + strings["card_" + card]+"</span> from " + victim + "!"); + cardHUD(card, 2000); + } + + lock(data) { + this.game.locked = true; + } + + unlock(data) { + this.game.locked = false; + } +} \ No newline at end of file diff --git a/public_html/game.js b/public_html/game.js index e5f86fb..a8266e0 100644 --- a/public_html/game.js +++ b/public_html/game.js @@ -17,6 +17,8 @@ "#89A666", "#7365A6", "#8C1F1F" ]; + this.servEvHandlers = new ServerEventsHandlers(this); + // Player and lobby name this.name = ""; @@ -25,6 +27,7 @@ this.nowPlaying = ""; this.players = []; this.spectators = []; + this.hand = []; // Assets @@ -216,410 +219,19 @@ this.readFromServer = function(ev) { var parts = ev.data.split(" "); - // TODO: cleanup and refactor - - if (parts[0] == "err") { - alert(strings[parts[1]]); - this.conn.close(); - this.conn = null; - return; - } - if (parts[0] == "version" && parts[1] != REVISION) { - alert(strings["bad_version"]); - this.conn.close(); - this.conn = null; - return; - } - - if (parts[0] == "joins" && parts[1] == this.name) { - // We're in! - if (!this.started) { - this.start(); - } - return; - } - - if (parts[0] == "players") { - // Update players list - this.players = parts.slice(1).map(x => entities(x)); - this.spectators = this.spectators.filter(i => !this.players.includes(i)); - this.drawPlayerList(); - this.drawSpectatorsList(); - - return; - } - - if (parts[0] == "spectators") { - // Update spectators list - this.spectators = parts.slice(1).map(x => entities(x)); - this.drawSpectatorsList(); - - return; - } - - if (parts[0] == "joins") { - // Announce in console - let encoded = entities(parts[1]); - this.console("<span class=\"text-success\">"+encoded+" joined as a spectator.</span>"); - - // Update spectators list - $("#spectator-list").append("<li>"+encoded+"</li>"); - - return; - } - - if (parts[0] == "parts") { - // Announce in console - let encoded = entities(parts[1]); - this.console("<span class=\"text-danger\">"+encoded+" left the game.</span>"); - - // Update spectators list if appropriate - $("#spectator-list > li").each( function() { - if ($( this ).html() == encoded) { - $( this ).remove(); - } - } ); - - return; - } - - if (parts[0] == "upgrades") { - // Announce in console - let encoded = entities(parts[1]); - this.console("<span class=\"text-success\">"+encoded+" is now playing.</span>"); - - // Update spectators list - $("#spectator-list > li").each( function() { - if ($( this ).html() == encoded) { - $( this ).remove(); - } - } ); - - // Server will send players list separately, no worries - - return; - } - - if (parts[0] == "downgrades") { - // Announce in console - let encoded = entities(parts[1]); - this.console("<span class=\"text-danger\">"+encoded+" is now spectating.</span>"); - - // Update spectators list - $("#spectator-list").append("<li>"+encoded+"</li>"); - - // Server will send players list separately, no worries - - return; - } - - if (parts[0] == "chat") { - let encodedName = entities(parts[1]); - let encodedMsg = entities(ev.data.substring(parts[1].length + 5)); - this.console(encodedName+": "+encodedMsg); - - return; - } - - if (parts[0] == "message") { - // This refers to the 'message' container in the middle of the board - // that can be used to display useful game info - let msg = strings["message_"+ev.data.substring(8)]; - $("#message").html(msg); - $("#message-container").removeClass("reveal"); - - return; - } - - if (parts[0] == "clear_message") { - $("#message").html(""); - $("#message-container").addClass("reveal"); - - return; - } - - if (parts[0] == "bcast") { - let msg = strings["bcast_"+ev.data.substring(6)]; - this.console(msg); - - return; - } - - if (parts[0] == "hand") { - $("#card-deck").empty(); - - for (var i=1; i < parts.length; i++) { - var card = $("<img class='card' src='assets/card_"+parts[i]+".png' />"); - - (function (gameState, cardNo, cardName) { - card.on("click", function() { - if (gameState.locked) { - return; - } - - if (gameState.favouring) { - // Favour NOPE-logic is handled server-side :) - gameState.send("a favour_what "+cardNo.toString()); - gameState.favouring = false; - return; - } - - if (gameState.combo > 1) { - // Do we have enough cards? - let cards = 0; - for (var j=1; j < parts.length; j++) { - if (parts[j] == cardName) { - cards++; - if (cards == gameState.combo) { - break; - } - } - } - if (cards < gameState.combo) { - gameState.console("You don't have enough " + strings["card_"+cardName] + - " cards to do that!"); - return; - } - - if (gameState.ourTurn) { - gameState.send("play_multiple "+gameState.combo.toString()+" "+ - cardName); - gameState.resetButtons(); - return; - } - } - - if ((gameState.ourTurn || cardName === "nope") && !cardName.startsWith("random") - && ( (!gameState.defusing && cardName !== "defuse") - || (gameState.defusing && cardName === "defuse") )) { - gameState.send("play "+cardNo.toString()); - gameState.defusing = false; - } - }); - })(this, i-1, parts[i]); - - $("#card-deck").append(card); - } - - return; - } - - if (ev.data == "draw_pile yes") { - $("#draw-pile").html("<img class='card' src='assets/card_back.png' />"); - $("#draw-pile-counter").removeClass("reveal"); - return; - } - if (ev.data == "draw_pile no") { - $("#draw-pile").html(""); - $("#draw-pile-counter").addClass("reveal"); - return; - } - if (parts[0] == "cards_left") { - $("#remaining-card-count").text(parts[1]); - return; - } - - if (parts[0] == "now_playing") { - this.nowPlaying = entities(parts[1]); - this.console("<span class=\"text-info\">It is "+this.nowPlaying+"'s turn.</span>"); - - this.drawPlayerList(); - - // cmp with raw data because name isn't stored encoded - if (parts[1] == this.name) { - this.ourTurn = true; - this.assets["atomic.ogg"].play(); - document.title = strings["title_alert"]; - $(".bottom-notify-container").html(strings["your_turn"]); - } else { - this.ourTurn = false; - document.title = strings["title_normal"]; // a bit fragile, needs to be same as <title> - $(".bottom-notify-container").html(""); - } - - return; - } - - if (parts[0] == "drew") { - this.console("You drew <span class=\"text-warning\">"+strings["card_"+parts[1]]+".</span>"); - - // Animation - cardHUD(parts[1], 2000); - - return; - } - if (parts[0] == "drew_other") { - let encoded = entities(parts[1]); - this.console("<span class=\"text-primary\">"+encoded+" drew a card.</span>"); - - // Animation - $("#draw-pile-animation").html("<img src='assets/card_back.png' class='card' />"); - animate("#draw-pile-animation", "right", 125, -150, -15, "px"); - - return; - } - - if (parts[0] == "exploded") { - let encoded = entities(parts[1]); - this.console("<span class=\"text-primary\">"+encoded+" drew a Detonating Cat!</span>"); - - cardHUD("exploding", 1000); - - return; - } - - if (parts[0] == "wins") { - this.ourTurn = false; - let encoded = entities(parts[1]); - this.console("<span class=\"text-primary\"><b>"+encoded+" won!</b></span>"); - this.nowPlaying = ""; - return; - } - - if (parts[0] == "defusing") { - this.console(strings["must_defuse"]); - this.defusing = true; - return; - } - - if (parts[0] == "played") { - let encoded = entities(parts[1]); - this.console(encoded+" played "+strings["card_"+parts[2]]+"."); - - $("#discard-pile").html("<img class='card' src='assets/card_"+parts[2]+".png' />"); - - if (parts[2] != "see3") { - cardHUD(parts[2], 1000); - } - - return; - } - - if (parts[0] == "played_multiple") { - let encoded = entities(parts[1]); - this.console(encoded+" played "+parts[2]+"x "+strings["card_"+parts[3]]+"."); - - $("#discard-pile").html("<img class='card' src='assets/card_"+parts[3]+".png' />"); - - if (parts[2] == 2) { - cardHUD3([parts[3], parts[3]], 1000); - } else { - cardHUD3([parts[3], parts[3], parts[3]], 1000); - } - - return; - } - - if (parts[0] == "no_discard") { - $("#discard-pile").html(""); - return; - } - - if (parts[0] == "q") { - if (parts[1] == "favour_what") { - this.favouring = true; - let perpetrator = entities(parts[2]); - this.console("<span class=\"text-primary\">" + perpetrator + - " is asking you for a favour.</span>"); - } else if (parts[1] == "favour_who" || parts[1] == "random_who" || parts[1] == "steal_who") { - (function (gameState) { - modalChoice(function(player) { - gameState.send("a "+parts[1]+" "+player); - }, strings["question_"+parts[1]], gameState.players, entities(gameState.name), null); - })(this); - } else if (parts[1] == "steal_what") { - (function (gameState) { - modalChoice(function(card) { - gameState.send("a "+parts[1]+" "+card); - }, strings["question_"+parts[1]], cards, null, x => strings["card_"+x]); - })(this); - } else { - ans = prompt(strings["question_"+parts[1]]); - this.send("a "+parts[1]+" "+ans); - } - - return; - } - if (parts[0] == "q_cancel") { - this.favouring = false; - } - - if (parts[0] == "seen") { - cardHUD3(parts.slice(1), 2000); - this.console("You saw "+strings["card_"+parts[1]]+", "+strings["card_"+parts[2]]+" and "+ - strings["card_"+parts[3]]+"."); - return; - } - - if (parts[0] == "favoured" || parts[0] == "favour_complete") { - let perpetrator = entities(parts[1]); - let victim = entities(parts[2]); - if (parts[0] == "favoured") { - this.console(perpetrator+" is asking "+victim+" for a favour."); - } else { - this.console(victim+" gave "+perpetrator+" a favour."); - } - return; - } - if (parts[0] == "favour_recv" || parts[0] == "favour_gave") { - let remotePlayer = entities(parts[1]); - let card = strings["card_"+parts[2]]; - if (parts[0] == "favour_recv") { - this.console(remotePlayer + " gave you <span class=\"text-info\">" + card + "</span>."); - cardHUD(parts[2], 2000); - } else { - this.console("You gave " + remotePlayer + " <span class=\"text-info\">" + card + "</span>."); - } - return; - } - - if (parts[0] == "randomed" || parts[0] == "random_n") { - let perpetrator = entities(parts[1]); - let victim = entities(parts[2]); - if (parts[0] == "randomed") { - this.console(perpetrator+" took a random card from "+victim+"."); - } else { - this.console(perpetrator+" asked "+victim+ - " for a random card, but they had nothing to give away!"); - } - - return; - } - if (parts[0] == "random_recv" || parts[0] == "random_gave") { - let remotePlayer = entities(parts[1]); - let card = strings["card_"+parts[2]]; - if (parts[0] == "random_recv") { - this.console("You randomly took <span class=\"text-info\">"+card+"</span> from "+ - remotePlayer+"."); - } else { - this.console(remotePlayer+" randomly took <span class=\"text-info\">"+card+ - "</span> from you."); - } - cardHUD(parts[2], 2000); - return; - } - - if (parts[0] == "steal_n" || parts[0] == "steal_y") { - let perpetrator = entities(parts[1]); - let victim = entities(parts[2]); - if (parts[0] == "steal_y") { - this.console(perpetrator+" stole <span class=\"text-info\">"+ - strings["card_"+parts[3]]+"</span> from "+victim+"!"); - cardHUD(parts[3], 2000); - } else { - this.console(perpetrator+" asked "+victim+" for <span class=\"text-info\">" - +strings["card_"+parts[3]]+"</span>, but ended up empty-handed!"); - } - return; - } - - if (parts[0] == "lock") { - this.locked = true; - return; - } - if (parts[0] == "unlock") { - this.locked = false; - return; + const eventName = parts[0]; + console.log('Server Event', parts); + + if( + parts.length + && typeof eventName == "string" + && eventName.length + && eventName in this.servEvHandlers + && typeof this.servEvHandlers[eventName] == "function" + ) { + parts.splice(0, 1).map(i => entities(i)); + const eventData = parts; + return this.servEvHandlers[eventName](eventData); } console.warn("WARN: received unknown data from server: "+ev.data); @@ -628,4 +240,27 @@ this.send = function(msg) { this.conn.send(msg); } + + this.addSpectator = function(nickname) { + this.freePlayerColor(nickname); + this.spectators.push(nickname); + this.drawSpectatorsList(); + } + + this.removeSpectator = function(nickname) { + this.spectators.splice(this.spectators.indexOf(nickname), 1); + this.drawSpectatorsList(); + } + + this.addPlayer = function(nickname) { } + + this.removePlayer = function(nickname) { } + + this.freePlayerColor = function(nickname) { + if(nickname in this.playerColorMap) { + const color = this.playerColorMap[nickname]; + delete this.playerColorMap[nickname]; + this.playerColors.push(color); + } + } } diff --git a/public_html/index.html b/public_html/index.html index 775dc9e..0921b76 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -2,6 +2,7 @@ <html lang="en"> <head> <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Detonating Cats @@ -139,6 +140,7 @@ +