Add barrier type. Transition into playing phase properly.
This commit is contained in:
parent
8afe512062
commit
8d1a7e14e3
@ -23,3 +23,17 @@
|
|||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
color: green;
|
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");
|
let list = document.querySelector("#playerList");
|
||||||
list.replaceChildren();
|
list.replaceChildren();
|
||||||
|
|
||||||
let newDom = document.createElement("li");
|
for (let playerId of Object.keys(players).sort()) {
|
||||||
newDom.textContent = ID + " (you)";
|
let player = players[playerId];
|
||||||
newDom.style.color = "grey";
|
|
||||||
list.appendChild(newDom);
|
|
||||||
|
|
||||||
for (let playerId of Object.keys(players)) {
|
let statusSpan = document.createElement("div");
|
||||||
if (playerId !== ID) {
|
statusSpan.classList.add("status-span");
|
||||||
let newDom = document.createElement("li");
|
if (game_state === WAITING) {
|
||||||
newDom.textContent = playerId;
|
if (player.ready) {
|
||||||
list.appendChild(newDom);
|
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.classList.toggle("active");
|
||||||
ev.target.textContent = nowReady ? "Ready" : "Not ready";
|
ev.target.textContent = nowReady ? "Ready" : "Not ready";
|
||||||
|
|
||||||
socket.emit("message", {
|
socket.emit("message", Packet.createSetReady(nowReady));
|
||||||
type: "SYNC",
|
|
||||||
author: ID,
|
updatePlayerDom();
|
||||||
ready: nowReady,
|
|
||||||
name: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (allPlayersReady()) {
|
if (allPlayersReady()) {
|
||||||
await startPregame();
|
await startPregame();
|
||||||
|
@ -14,29 +14,21 @@ let game_state = WAITING;
|
|||||||
|
|
||||||
let socket;
|
let socket;
|
||||||
let random;
|
let random;
|
||||||
|
let barrier;
|
||||||
|
|
||||||
// Not totally reliable but better than nothing.
|
// Not totally reliable but better than nothing.
|
||||||
window.addEventListener("beforeunload", () => {
|
window.addEventListener("beforeunload", () => {
|
||||||
socket.emit("message", {
|
socket.emit("message", Packet.createDisconnect());
|
||||||
type: "DISCONNECT",
|
|
||||||
id: window.crypto.randomUUID(),
|
|
||||||
author: ID,
|
|
||||||
name: "",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
socket = io();
|
socket = io();
|
||||||
random = new Random();
|
random = new Random();
|
||||||
|
barrier = new Barrier();
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
console.log("Connected!");
|
console.log("Connected!");
|
||||||
socket.emit("message", {
|
socket.emit("message", Packet.createAnnounce());
|
||||||
type: "ANNOUNCE",
|
|
||||||
id: window.crypto.randomUUID(),
|
|
||||||
author: ID,
|
|
||||||
name: "",
|
|
||||||
});
|
|
||||||
// Create self
|
// Create self
|
||||||
players[ID] = new Player(ID, name);
|
players[ID] = new Player(ID, name);
|
||||||
us = players[ID];
|
us = players[ID];
|
||||||
@ -61,23 +53,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
keepAlive(data);
|
keepAlive(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "SYNC":
|
case "READY":
|
||||||
await sync(data);
|
await setReady(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "RANDOM":
|
case "RANDOM":
|
||||||
await random.processCooperativeRandom(data);
|
await random.processCooperativeRandom(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "BARRIER":
|
||||||
|
barrier.resolve(data);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emit keepalive messages to inform other players we are still here
|
// Emit keepalive messages to inform other players we are still here
|
||||||
window.setInterval(() => {
|
window.setInterval(() => {
|
||||||
socket.emit("message", {
|
socket.emit("message", Packet.createKeepAlive());
|
||||||
type: "KEEPALIVE",
|
|
||||||
id: window.crypto.randomUUID(),
|
|
||||||
author: ID,
|
|
||||||
});
|
|
||||||
}, TIMEOUT / 5);
|
}, TIMEOUT / 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,12 +87,7 @@ function playerConnected(data) {
|
|||||||
// When a new player is seen, all announce to ensure they know all players.
|
// When a new player is seen, all announce to ensure they know all players.
|
||||||
if (players[data.author] === undefined) {
|
if (players[data.author] === undefined) {
|
||||||
players[data.author] = new Player(data.author, data.name);
|
players[data.author] = new Player(data.author, data.name);
|
||||||
socket.emit("message", {
|
socket.emit("message", Packet.createAnnounce());
|
||||||
type: "ANNOUNCE",
|
|
||||||
id: window.crypto.randomUUID(),
|
|
||||||
author: ID,
|
|
||||||
name: "",
|
|
||||||
});
|
|
||||||
players[data.author].resetTimeout();
|
players[data.author].resetTimeout();
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
@ -128,10 +115,12 @@ function keepAlive(data) {
|
|||||||
*
|
*
|
||||||
* @param data Packet received
|
* @param data Packet received
|
||||||
*/
|
*/
|
||||||
async function sync(data) {
|
async function setReady(data) {
|
||||||
players[data.author].name = data.name;
|
players[data.author].name = data.name;
|
||||||
players[data.author].ready = data.ready;
|
players[data.author].ready = data.ready;
|
||||||
|
|
||||||
|
updatePlayerDom();
|
||||||
|
|
||||||
if (allPlayersReady()) {
|
if (allPlayersReady()) {
|
||||||
await startPregame();
|
await startPregame();
|
||||||
}
|
}
|
||||||
@ -148,11 +137,20 @@ function allPlayersReady() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startPregame() {
|
async function startPregame() {
|
||||||
console.log("All players ready.");
|
console.log("all players ready.");
|
||||||
|
|
||||||
game_state = PRE_GAME;
|
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.timeout = null;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
|
this.isPlaying = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetTimeout() {
|
resetTimeout() {
|
||||||
|
@ -27,7 +27,6 @@ class Random {
|
|||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
await navigator.locks.request(`random-${sessionId}`, () => {
|
await navigator.locks.request(`random-${sessionId}`, () => {
|
||||||
console.log("in lock now");
|
|
||||||
if (this.sessions[sessionId].finalValue === null) {
|
if (this.sessions[sessionId].finalValue === null) {
|
||||||
let session = this.sessions[sessionId];
|
let session = this.sessions[sessionId];
|
||||||
let resolver;
|
let resolver;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
<script src="{{ url_for('static', filename='js/player.js') }}"></script>
|
<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/dom.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/random.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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -21,6 +23,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="turn-title">
|
||||||
|
<span class="current-player"></span>'s Turn!
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="ready">
|
<div id="ready">
|
||||||
<button id="ready-button">Not ready</button>
|
<button id="ready-button">Not ready</button>
|
||||||
</div>
|
</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}
|
\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}
|
\bibliography{Dissertation}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user