Skip to content
Snippets Groups Projects
Unverified Commit cafa4242 authored by Ben Eltschig's avatar Ben Eltschig
Browse files

weiter an SkribblClient- und SkribblServer-Klassen gearbeitet, SkribblLobby-Klasse angelegt

parent 758fa1ed
Branches
1 merge request!5Custom server
import DataChannel from "./networking/DataChannel.js";
import Signaler from "./networking/Signaler.js";
/**
* Client/endpoint that manages a connection to a SkribblServer
*/
export default class SkribblClient {
/**
* Constructs a new client for a given game ID or DataChannel.
* @param {string|DataChannel} idOrDataChannel
*/
constructor(idOrDataChannel){
/** @type {string[]} */
this._players = [];
/** @type {((players:string[])=>void)[]} */
this._onPlayersListChangeCallbacks = [];
this._readyPromise = (async()=>{
this._dataChannel = idOrDataChannel instanceof DataChannel?idOrDataChannel:await Signaler.join(idOrDataChannel);
await this._dataChannel.waitUntilReady();
this._dataChannel.send("Hi there :3");
this._dataChannel.onMessage(message=>{
console.log(`message from server: "${message}"`);
})
if (message.startsWith("players add ")){
let name = message.split(" ").slice(2).join(" ");
this._players.push(name);
this._onPlayersListChangeCallbacks.forEach(callback=>callback(this._players));
}else if(message.startsWith("players list ")){
let json = message.split(" ").slice(2).join(" ");
this._players = JSON.parse(json);
this._onPlayersListChangeCallbacks.forEach(callback=>callback(this._players));
}
});
})();
}
......@@ -23,4 +38,25 @@ export default class SkribblClient {
async waitUntilReady(){
return this._readyPromise;
}
/**
* Joins the game with the given name.
* @param {string} name
*/
async join(name){
this._dataChannel.send(`join ${name}`);
let message = await this._dataChannel.next();
if (message!=="yup"){
throw new Error(`Failed to join! Server response: "${message}"`);
}
}
/**
* Registers a callback whenever the list of players in the game changes, and once when it is registered.
* @param {(players:string[])=>void} callback
*/
onPlayersListChange(callback){
this._onPlayersListChangeCallbacks.push(callback);
callback(this._players);
}
}
\ No newline at end of file
import SkribblClient from "./SkribblClient.js";
import SkribblLobby from "./SkribblLobby.js";
import SkribblMenu from "./SkribblMenu.js";
/**
* Custom element `<skribbl-container>` that functions as a root element for the game, containing and managing both the game and all its menus, taking care of high-level state managment.
*/
export default class SkribblContainer extends HTMLElement {
constructor(){
/**
*
* @param {SkribblClient} client
*/
constructor(client){
super();
this.attachShadow({mode:"open"});
this.shadowRoot.innerHTML = `
......@@ -14,11 +22,26 @@ export default class SkribblContainer extends HTMLElement {
right: 0;
bottom: 0;
left: 0;
--background-color: #efefefef;
--background-color: #bfbfefef;
background-image: linear-gradient(var(--background-color),var(--background-color)), url(./res/background.png);
}
skribbl-menu, skribbl-lobby {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
</style>
`;
(async()=>{
let menu = new SkribblMenu();
this.shadowRoot.appendChild(menu);
let {name} = await menu.awaitProceed();
await client.join(name);
menu.remove();
let lobby = new SkribblLobby(client);
this.shadowRoot.appendChild(lobby);
})();
}
}
customElements.define("skribbl-container",SkribblContainer);
\ No newline at end of file
import SkribblClient from "./SkribblClient.js";
/**
* Custom element `<skribbl-lobby>` for the lobby of a game.
*/
export default class SkribblLobby extends HTMLElement {
/**
* @param {SkribblClient} client
*/
constructor(client){
super();
this.attachShadow({mode:"open"});
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
background: #ffffff;
border-radius: 10px;
padding: 15px;
}
</style>
Players: <span></span>
`;
this._jens = this.shadowRoot.querySelector("span");
client.onPlayersListChange(players=>{
this._jens.innerText = players.join(", ");
});
}
}
customElements.define("skribbl-lobby",SkribblLobby);
\ No newline at end of file
/**
* Custom element `<skribbl-menu>` for the name selection menu that appears when first entering a game.
*/
export default class SkribblMenu extends HTMLElement {
constructor(){
super();
this.attachShadow({mode:"open"});
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
background: #ffffff;
border-radius: 10px;
padding: 15px;
}
</style>
<input type="text">
<button>Play</button>
`;
this._nameInput = this.shadowRoot.querySelector("input");
this._playButton = this.shadowRoot.querySelector("button");
/** @type {Promise<{name:string}>} */
this._proceedPromise = new Promise(resolve=>{
this._playButton.addEventListener("click",e=>{
resolve({name:this._nameInput.value});
});
});
}
/**
* Waits until the user clicks play, then returns the name and settings he has chosen.
*/
async awaitProceed(){
return await this._proceedPromise;
}
}
customElements.define("skribbl-menu",SkribblMenu);
\ No newline at end of file
......@@ -10,9 +10,14 @@ export default class SkribblServer {
*/
constructor(){
this._readyPromise = (async()=>{
/** @type {{name:string,dataChannel:DataChannel}[]} */
this._clients = [];
this._id = await Signaler.host(dataChannel=>{
this.connect(dataChannel);
});
let [endpointA,endpointB] = DataChannel.createPair();
this._dataChannel = endpointA;
this.connect(endpointB);
})();
}
......@@ -23,6 +28,14 @@ export default class SkribblServer {
return this._readyPromise;
}
/**
* A dataChannel talking to this server like any other client.
* @readonly
*/
get dataChannel(){
return this._dataChannel;
}
/**
* The ID others can use to connect to this server.
* @readonly
......@@ -44,9 +57,26 @@ export default class SkribblServer {
* @param {DataChannel} dataChannel
*/
connect(dataChannel){
dataChannel.onMessage(message=>{
console.log(`message from client: "${message}"`);
dataChannel.send("Got your message :D");
});
(async()=>{
let message = await dataChannel.next();
console.log("message: ",message);
if (message.startsWith("join ")){
let name = message.split(" ").slice(1).join(" ");
if (name.length<30&&name.length>=1){
dataChannel.send("yup");
this._clients.forEach(({dataChannel})=>{
dataChannel.send("players add "+name);
});
this._clients.push({name,dataChannel});
dataChannel.send("players list "+JSON.stringify(this._clients.map(({name})=>name)))
}else{
dataChannel.send("nope");
dataChannel.close();
}
}else{
dataChannel.send("nope");
dataChannel.close();
}
})();
}
}
\ No newline at end of file
......@@ -114,6 +114,30 @@ export default class DataChannel {
return {send,onMessage};
});
}
/**
* Creates a pair of DataChannel endpoints talking directly to each other.
*
* Whenever one of those sends a message, all onMessage callbacks registered on the other one get queued as a microtask.
* @returns {[DataChannel,DataChannel]}
*/
static createPair(){
/** @type {MessageCallback[]} */
let onMessageCallbacksA = [];
/** @type {MessageCallback[]} */
let onMessageCallbacksB = [];
let endpointA = new DataChannel(()=>Promise.resolve({send:message=>{
onMessageCallbacksB.forEach(callback=>queueMicrotask(()=>callback(message)));
},onMessage:callback=>{
onMessageCallbacksA.push(callback);
}}));
let endpointB = new DataChannel(()=>Promise.resolve({send:message=>{
onMessageCallbacksA.forEach(callback=>queueMicrotask(()=>callback(message)));
},onMessage:callback=>{
onMessageCallbacksB.push(callback);
}}));
return [endpointA,endpointB];
}
}
/**
* Class representing a connection to a server via a WebSocket.
......
......@@ -3,10 +3,12 @@ import SkribblContainer from "./SkribblContainer.js";
import SkribblServer from "./SkribblServer.js";
document.addEventListener("DOMContentLoaded",async()=>{
/** @type {SkribblClient} */
let client;
if (document.location.hash){
document.body.innerHTML = "";
const gameID = document.location.hash.substring(1);
const client = new SkribblClient(gameID);
client = new SkribblClient(gameID);
}else{
/** @type {HTMLButtonElement} *///@ts-ignore
const button = document.getElementById("button");
......@@ -17,8 +19,9 @@ document.addEventListener("DOMContentLoaded",async()=>{
const server = new SkribblServer();
await server.waitUntilReady();
console.log(server.url);
client = new SkribblClient(server.dataChannel);
}
const game = new SkribblContainer();
const game = new SkribblContainer(client);
document.body.innerHTML = "";
document.body.appendChild(game);
});
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment