Newer
Older
import DataChannel from "./networking/DataChannel.js";
import Signaler from "./networking/Signaler.js";

Ben Eltschig
committed
/**
* Format of the messages send to the server by the clients.
* @typedef {{action:"join",name:string}|{action:"startGame"}|{action:"changeSettings",rounds:number,drawTime:number}} MessageToServer

Ben Eltschig
committed
*/
/**
* Format of the messages send to the clients by the server.
* @typedef {"yup"|"nope"|{action:"stateUpdate",newState:GameState}|{action:"settingsUpdate",rounds:number,drawTime:number}} MessageToClient
*/
/**
* Format in which the game state is send to the clients by the server.
* @typedef {{hasGameStarted:false,host:boolean,players:{name:string}[]}|{hasGameStarted:true,round:number,rounds:number,host:boolean,players:{name:string,points:number}[]}} GameState

Ben Eltschig
committed
*/
/**
* A local server. Handles all the important game logic, and communicates with clients via DataChannels.
*/
export default class SkribblServer {
/**
* Starts a new SkribblServer.
*/
constructor(){
this._readyPromise = (async()=>{
/** @type {{name:string,dataChannel:DataChannel<MessageToClient,MessageToServer>,points:number}[]} */

Ben Eltschig
committed
this._clients = [];
this._round = 0;
this._rounds = 3;
this._drawTime = 180;
this._id = await Signaler.host(dataChannel=>{

Ben Eltschig
committed
this.connect(DataChannel.from(dataChannel,JSON.stringify,JSON.parse));

Ben Eltschig
committed
/** @type {[DataChannel<MessageToServer,MessageToClient>,DataChannel<MessageToClient,MessageToServer>]} */

Ben Eltschig
committed
let [endpointA,endpointB] = DataChannel.createPair();
this._dataChannel = endpointA;
this.connect(endpointB);
})();
}
/**
* Waits until the server is ready.
*/
async waitUntilReady(){
return this._readyPromise;
}

Ben Eltschig
committed
/**
* 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
*/
get id(){
return this._id;
}
/**
* Returns the full url others can use to connect to this server.
* @readonly
*/
get url(){
return document.location.host+document.location.pathname+"#"+this._id;
}
/**
* Adds an incoming connection as a client.

Ben Eltschig
committed
* @param {DataChannel<MessageToClient,MessageToServer>} dataChannel
*/
connect(dataChannel){

Ben Eltschig
committed
(async()=>{
let message = await dataChannel.next();
console.log("message: ",message);

Ben Eltschig
committed
if (message.action==="join"){
let name = message.name;

Ben Eltschig
committed
if (name.length<30&&name.length>=1){
dataChannel.send("yup");
this._clients.push({name,dataChannel,points:0});
this._sendStateUpdate();
if (!this._hasGameStarted){
dataChannel.send({action:"settingsUpdate",rounds:this._rounds,drawTime:this._drawTime});
}
dataChannel.onMessage(message=>{
let isHost = this._clients[0].dataChannel = dataChannel;
if (message.action=="startGame"){
if (isHost&&!this._hasGameStarted){
this._startGame();
}else if(message.action=="changeSettings"){
if (isHost&&!this._hasGameStarted){
this._rounds = message.rounds;
this._drawTime = message.drawTime;
this._clients.forEach(({dataChannel})=>{
dataChannel.send({action:"settingsUpdate",rounds:this._rounds,drawTime:this._drawTime});
});
}

Ben Eltschig
committed
}else{
dataChannel.send("nope");
dataChannel.close();
}
}else{
dataChannel.send("nope");
dataChannel.close();
}
})();
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
* Starts the game if it hasn't already started.
*/
_startGame(){
if (this._hasGameStarted){
console.warn("Tried to start a game that has already started.");
}else{
this._hasGameStarted = true;
(async()=>{
for (this._round=1;this._round<=this._rounds;this._round++){
this._sendStateUpdate();
await new Promise(resolve=>setTimeout(resolve,2000));
}
this._hasGameStarted = false;
this._sendStateUpdate();
})();
}
}
/**
* Sends a state update to all connected clients (that is, clients that have already joined the game).
*/
_sendStateUpdate(){
if (this._hasGameStarted){
let players = this._clients.map(({name,points})=>({name,points}));
this._clients.forEach(({dataChannel},index)=>{
dataChannel.send({action:"stateUpdate",newState:{players,host:index==0,hasGameStarted:true,rounds:this._rounds,round:this._round}});
});
}else{
let players = this._clients.map(({name})=>({name}));
this._clients.forEach(({dataChannel},index)=>{
dataChannel.send({action:"stateUpdate",newState:{players,host:index==0,hasGameStarted:false}});
});
}
}