Add barrier type. Transition into playing phase properly.
This commit is contained in:
parent
8afe512062
commit
8d1a7e14e3
@ -23,3 +23,17 @@
|
||||
font-size: 2em;
|
||||
color: green;
|
||||
}
|
||||
|
||||
.status-span {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.ready {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.not-ready {
|
||||
color: red;
|
||||
}
|
||||
|
30
static/js/barrier.js
Normal file
30
static/js/barrier.js
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Typical barrier type.
|
||||
*
|
||||
* Block all clients until everyone has hit the barrier.
|
||||
*/
|
||||
class Barrier {
|
||||
constructor() {
|
||||
let resolver;
|
||||
this.promise = new Promise((resolve) => {
|
||||
resolver = resolve;
|
||||
});
|
||||
this.resolver = resolver;
|
||||
this.hits = new Set();
|
||||
}
|
||||
|
||||
wait() {
|
||||
socket.emit("message", Packet.createBarrierSignal());
|
||||
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
resolve(data) {
|
||||
this.hits.add(data.author);
|
||||
|
||||
if (this.hits.size === Object.keys(players).length - 1) {
|
||||
this.hits = new Set();
|
||||
this.resolver();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,17 +2,36 @@ function updatePlayerDom() {
|
||||
let list = document.querySelector("#playerList");
|
||||
list.replaceChildren();
|
||||
|
||||
let newDom = document.createElement("li");
|
||||
newDom.textContent = ID + " (you)";
|
||||
newDom.style.color = "grey";
|
||||
list.appendChild(newDom);
|
||||
for (let playerId of Object.keys(players).sort()) {
|
||||
let player = players[playerId];
|
||||
|
||||
for (let playerId of Object.keys(players)) {
|
||||
if (playerId !== ID) {
|
||||
let newDom = document.createElement("li");
|
||||
newDom.textContent = playerId;
|
||||
list.appendChild(newDom);
|
||||
let statusSpan = document.createElement("div");
|
||||
statusSpan.classList.add("status-span");
|
||||
if (game_state === WAITING) {
|
||||
if (player.ready) {
|
||||
statusSpan.textContent = "R";
|
||||
statusSpan.classList.add("ready");
|
||||
} else {
|
||||
statusSpan.textContent = "N";
|
||||
statusSpan.classList.add("not-ready");
|
||||
}
|
||||
} else {
|
||||
if (player.isPlaying) {
|
||||
statusSpan.textContent = "P";
|
||||
}
|
||||
}
|
||||
|
||||
let idSpan = document.createElement("span");
|
||||
if (playerId === ID) {
|
||||
idSpan.textContent = `${playerId} (you)`;
|
||||
} else {
|
||||
idSpan.textContent = playerId;
|
||||
}
|
||||
|
||||
let newDom = document.createElement("li");
|
||||
newDom.appendChild(statusSpan);
|
||||
newDom.appendChild(idSpan);
|
||||
list.appendChild(newDom);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,12 +43,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
ev.target.classList.toggle("active");
|
||||
ev.target.textContent = nowReady ? "Ready" : "Not ready";
|
||||
|
||||
socket.emit("message", {
|
||||
type: "SYNC",
|
||||
author: ID,
|
||||
ready: nowReady,
|
||||
name: "",
|
||||
});
|
||||
socket.emit("message", Packet.createSetReady(nowReady));
|
||||
|
||||
updatePlayerDom();
|
||||
|
||||
if (allPlayersReady()) {
|
||||
await startPregame();
|
||||
|
@ -14,29 +14,21 @@ let game_state = WAITING;
|
||||
|
||||
let socket;
|
||||
let random;
|
||||
let barrier;
|
||||
|
||||
// Not totally reliable but better than nothing.
|
||||
window.addEventListener("beforeunload", () => {
|
||||
socket.emit("message", {
|
||||
type: "DISCONNECT",
|
||||
id: window.crypto.randomUUID(),
|
||||
author: ID,
|
||||
name: "",
|
||||
});
|
||||
socket.emit("message", Packet.createDisconnect());
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
socket = io();
|
||||
random = new Random();
|
||||
barrier = new Barrier();
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log("Connected!");
|
||||
socket.emit("message", {
|
||||
type: "ANNOUNCE",
|
||||
id: window.crypto.randomUUID(),
|
||||
author: ID,
|
||||
name: "",
|
||||
});
|
||||
socket.emit("message", Packet.createAnnounce());
|
||||
// Create self
|
||||
players[ID] = new Player(ID, name);
|
||||
us = players[ID];
|
||||
@ -61,23 +53,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
keepAlive(data);
|
||||
break;
|
||||
|
||||
case "SYNC":
|
||||
await sync(data);
|
||||
case "READY":
|
||||
await setReady(data);
|
||||
break;
|
||||
|
||||
case "RANDOM":
|
||||
await random.processCooperativeRandom(data);
|
||||
break;
|
||||
|
||||
case "BARRIER":
|
||||
barrier.resolve(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Emit keepalive messages to inform other players we are still here
|
||||
window.setInterval(() => {
|
||||
socket.emit("message", {
|
||||
type: "KEEPALIVE",
|
||||
id: window.crypto.randomUUID(),
|
||||
author: ID,
|
||||
});
|
||||
socket.emit("message", Packet.createKeepAlive());
|
||||
}, TIMEOUT / 5);
|
||||
});
|
||||
|
||||
@ -95,12 +87,7 @@ function playerConnected(data) {
|
||||
// When a new player is seen, all announce to ensure they know all players.
|
||||
if (players[data.author] === undefined) {
|
||||
players[data.author] = new Player(data.author, data.name);
|
||||
socket.emit("message", {
|
||||
type: "ANNOUNCE",
|
||||
id: window.crypto.randomUUID(),
|
||||
author: ID,
|
||||
name: "",
|
||||
});
|
||||
socket.emit("message", Packet.createAnnounce());
|
||||
players[data.author].resetTimeout();
|
||||
} else {
|
||||
}
|
||||
@ -128,10 +115,12 @@ function keepAlive(data) {
|
||||
*
|
||||
* @param data Packet received
|
||||
*/
|
||||
async function sync(data) {
|
||||
async function setReady(data) {
|
||||
players[data.author].name = data.name;
|
||||
players[data.author].ready = data.ready;
|
||||
|
||||
updatePlayerDom();
|
||||
|
||||
if (allPlayersReady()) {
|
||||
await startPregame();
|
||||
}
|
||||
@ -148,11 +137,20 @@ function allPlayersReady() {
|
||||
}
|
||||
|
||||
async function startPregame() {
|
||||
console.log("All players ready.");
|
||||
console.log("all players ready.");
|
||||
|
||||
game_state = PRE_GAME;
|
||||
|
||||
let player1 = await random.get(Object.keys(players).length, "first-player");
|
||||
let firstPlayerIndex = await random.get(Object.keys(players).length, "first-player");
|
||||
|
||||
console.log(player1);
|
||||
let firstPlayer = Object.values(players).sort((a, b) => (a.id < b.id ? -1 : 1))[
|
||||
firstPlayerIndex
|
||||
];
|
||||
|
||||
firstPlayer.isPlaying = true;
|
||||
game_state = PLAYING;
|
||||
|
||||
await barrier.wait();
|
||||
|
||||
updatePlayerDom();
|
||||
}
|
||||
|
35
static/js/packet.js
Normal file
35
static/js/packet.js
Normal file
@ -0,0 +1,35 @@
|
||||
class Packet {
|
||||
static _createBase(name) {
|
||||
return {
|
||||
type: name,
|
||||
id: window.crypto.randomUUID(),
|
||||
author: ID,
|
||||
};
|
||||
}
|
||||
|
||||
static createAnnounce() {
|
||||
return {
|
||||
...this._createBase("ANNOUNCE"),
|
||||
name: "",
|
||||
};
|
||||
}
|
||||
|
||||
static createDisconnect() {
|
||||
return this._createBase("DISCONNECT");
|
||||
}
|
||||
|
||||
static createKeepAlive() {
|
||||
return this._createBase("KEEPALIVE");
|
||||
}
|
||||
|
||||
static createSetReady(nowReady) {
|
||||
return {
|
||||
...this._createBase("READY"),
|
||||
ready: nowReady,
|
||||
};
|
||||
}
|
||||
|
||||
static createBarrierSignal() {
|
||||
return this._createBase("BARRIER");
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ class Player {
|
||||
this.timeout = null;
|
||||
this.id = id;
|
||||
this.ready = false;
|
||||
this.isPlaying = false;
|
||||
}
|
||||
|
||||
resetTimeout() {
|
||||
|
@ -27,7 +27,6 @@ class Random {
|
||||
|
||||
let promise;
|
||||
await navigator.locks.request(`random-${sessionId}`, () => {
|
||||
console.log("in lock now");
|
||||
if (this.sessions[sessionId].finalValue === null) {
|
||||
let session = this.sessions[sessionId];
|
||||
let resolver;
|
||||
|
@ -12,6 +12,8 @@
|
||||
<script src="{{ url_for('static', filename='js/player.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/dom.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/random.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/barrier.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/packet.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -21,6 +23,10 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="turn-title">
|
||||
<span class="current-player"></span>'s Turn!
|
||||
</div>
|
||||
|
||||
<div id="ready">
|
||||
<button id="ready-button">Not ready</button>
|
||||
</div>
|
||||
|
Binary file not shown.
@ -236,7 +236,17 @@ RSA blinding can incur a security risk, as by using the same keys to sign and en
|
||||
|
||||
\section{Implementation}
|
||||
|
||||
The implementation provided uses WebSockets as the communication primitive. Whilst this is therefore a centralised implementation, no verification occurs in the server code, which instead simply "echoes" messages received to all connected clients.
|
||||
The implementation provided uses WebSockets as the communication primitive. This is therefore a centralised implementation. However, no verification occurs in the server code, which instead simply "echoes" messages received to all connected clients.
|
||||
|
||||
Despite this approach being centralised, it does emulate a fully peer-to-peer environment, and has notable benefits: \begin{itemize}
|
||||
\item It is faster to develop, use, and test than using a physical system such as mail;
|
||||
\item There is no need for hole-punching or port-forwarding;
|
||||
\item WebSockets are highly flexible in how data is structured and interpreted.
|
||||
\end{itemize}
|
||||
|
||||
In particular, the final point allows for the use of purely JSON messages, which are readily parsed and processed by the client-side JavaScript.
|
||||
|
||||
|
||||
|
||||
\bibliography{Dissertation}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user