From 0fca54479995bb29cad237908da2387c28ceec23 Mon Sep 17 00:00:00 2001 From: Ben Eltschig <eltschib@hu-berlin.de> Date: Sun, 4 Apr 2021 19:43:01 +0200 Subject: [PATCH] Formatierungen in SkribblServer.js wiederhergestellt --- client/logic/SkribblServer.js | 657 +++++++++++++++++----------------- 1 file changed, 328 insertions(+), 329 deletions(-) diff --git a/client/logic/SkribblServer.js b/client/logic/SkribblServer.js index 63392c7..6ae228d 100644 --- a/client/logic/SkribblServer.js +++ b/client/logic/SkribblServer.js @@ -1,12 +1,12 @@ import { SkribblWord } from "../docs/CustomElements.js"; -import Util, { ConditionFulfilledPromise } from "../util/Util.js"; +import Util, {ConditionFulfilledPromise} from "../util/Util.js"; import DataChannel from "./networking/DataChannel.js"; import Signaler from "./networking/Signaler.js"; import SkribblWords from "./SkribblWords.js"; /** * Format of the messages send to the server by the clients. - * @typedef {{action:"join",name:string}|{action:"startGame"}|{action:"changeSettings",rounds:number,drawTime:number}|{action:"chooseWord",word:number}|{action:"guess",word:string}|ClearOrder|BackgroundOrder|DrawOrder} MessageToServer + * @typedef {{action:"join",name:string}|{action:"startGame"}|{action:"changeSettings",rounds:number,drawTime:number}|{action:"chooseWord",word:number}|{action:"guess",word:string}|ClearOrder|DrawOrder|BackgroundOrder} MessageToServer */ /** * Format of the messages send to the clients by the server. @@ -58,6 +58,7 @@ import SkribblWords from "./SkribblWords.js"; * @property {"changeBackgroundColor"} action * @property {string} color */ + /** * @typedef {object} WordReveal * @property {"revealWord"} action @@ -70,343 +71,341 @@ import SkribblWords from "./SkribblWords.js"; * 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,guessedWord:boolean,guessedIndex:number}[]} */ - this._clients = []; - this._hasGameStarted = false; - /** current round, starts counting at 1, only 0 when the game hasn't started yet */ - this._round = 0; - this._rounds = 3; - this._drawTime = 180; - /** index of the currently drawing player in the player list */ - this._drawingPlayer = 0; - /** - * word that is currently being drawn, or list of words a word is currently getting choosen from - * @type {string|string[]} - */ - this._word = null; - this._id = await Signaler.host(dataChannel => { - this.connect(DataChannel.from(dataChannel, JSON.stringify, JSON.parse)); - }); - /** @type {[DataChannel<MessageToServer,MessageToClient>,DataChannel<MessageToClient,MessageToServer>]} */ - let [endpointA, endpointB] = DataChannel.createPair(); - this._dataChannel = endpointA; - this.connect(endpointB); - })(); - } - - /** - * Waits until the server is ready. - */ - async waitUntilReady() { - 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 - */ - 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. - * @param {DataChannel<MessageToClient,MessageToServer>} dataChannel - */ - connect(dataChannel) { - (async() => { - let message = await dataChannel.next(); - console.log("message: ", message); - if (message.action === "join") { - let name = message.name; - if (name.length < 30 && name.length >= 1) { - dataChannel.send("yup"); - this._clients.push({ name, dataChannel, points: 0, guessedWord: false, guessedIndex: 0 }); - this._sendStateUpdate(); - if (!this._hasGameStarted) { - dataChannel.send({ action: "settingsUpdate", rounds: this._rounds, drawTime: this._drawTime }); - } - dataChannel.onMessage(message => { - let playerIndex = this._clients.map(({ dataChannel }) => dataChannel).indexOf(dataChannel); - let isHost = (playerIndex == 0); - 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._sendToAll({ action: "settingsUpdate", rounds: this._rounds, drawTime: this._drawTime }); - } - } else if (message.action == "guess") { - this._handleGuess(playerIndex, message.word); - } else if (message.action == "clearCanvas" || message.action == "changeBackgroundColor" || message.action == "draw" || message.action == "erase") { - // sends the drawing order to all connected clients iff the player is currently drawing and has already chosen a word - if (this._hasGameStarted && this._drawingPlayer == playerIndex && typeof this._word == "string") { - this._sendToAll(message); - } - } - }); - } else { - dataChannel.send("nope"); - dataChannel.close(); - } - } else { - dataChannel.send("nope"); - dataChannel.close(); - } - })(); - } + /** + * Starts a new SkribblServer. + */ + constructor(){ + this._readyPromise = (async()=>{ + /** @type {{name:string,dataChannel:DataChannel<MessageToClient,MessageToServer>,points:number,guessedWord:boolean,guessedIndex:number}[]} */ + this._clients = []; + this._hasGameStarted = false; + /** current round, starts counting at 1, only 0 when the game hasn't started yet */ + this._round = 0; + this._rounds = 3; + this._drawTime = 180; + /** index of the currently drawing player in the player list */ + this._drawingPlayer = 0; + /** + * word that is currently being drawn, or list of words a word is currently getting choosen from + * @type {string|string[]} + */ + this._word = null; + this._id = await Signaler.host(dataChannel=>{ + this.connect(DataChannel.from(dataChannel,JSON.stringify,JSON.parse)); + }); + /** @type {[DataChannel<MessageToServer,MessageToClient>,DataChannel<MessageToClient,MessageToServer>]} */ + let [endpointA,endpointB] = DataChannel.createPair(); + this._dataChannel = endpointA; + this.connect(endpointB); + })(); + } - /** - * Sends a message to all currently connected clients. - * @param {MessageToClient} message - */ - _sendToAll(message) { - this._clients.forEach(({ dataChannel }) => { - dataChannel.send(message); - }) - } + /** + * Waits until the server is ready. + */ + async waitUntilReady(){ + return this._readyPromise; + } - /** - * 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; - this._clients.forEach(client => { - client.points = 0; - client.guessedWord = false; - client.guessedIndex = 0; - }); - (async() => { - for (this._round = 1; this._round <= this._rounds; this._round++) { - for (this._drawingPlayer = 0; this._drawingPlayer < this._clients.length; this._drawingPlayer++) { - let words = await Promise.all([SkribblWords.get(), SkribblWords.get(), SkribblWords.get()]); - this._word = words.map(({ word }) => word); - this._sendStateUpdate(); - /** @type {number} */ - let wordIndex = await new Promise(async resolve => { - let cancelled = false; - setTimeout(() => { - cancelled = true; - resolve(Math.floor(Math.random() * 3)); - }, 10000); - let dataChannel = this._clients[this._drawingPlayer].dataChannel; - while (!cancelled) { - let message = await dataChannel.next(); - if (message.action == "chooseWord") { - resolve(message.word); - } - } - }); - this._word = words[wordIndex].word; - this._sendToAll({ action: "clearCanvas" }); - this._sendStateUpdate(); - this._allGuessedPromise = new ConditionFulfilledPromise(() => { - return this._clients.every((client, index) => client.guessedWord || index == this._drawingPlayer); - }); - let timeOverPromise = Util.wait(this._drawTime); - await Promise.race([this._allGuessedPromise, timeOverPromise]); - let points = this._awardPoints(); - this._sendStateUpdate(); - this._sendToAll({ action: "revealWord", word: this._word, description: words[wordIndex].description, macros: await SkribblWords.macros, points }); - await Util.wait(8); - } - } - this._hasGameStarted = false; - this._sendStateUpdate(); - })(); - } - } + /** + * A dataChannel talking to this server like any other client. + * @readonly + */ + get dataChannel(){ + return this._dataChannel; + } - /** - * Sends a state update to all connected clients (that is, clients that have already joined the game). - */ - _sendStateUpdate() { - console.log("sending state update! players:", this._clients); - if (this._hasGameStarted) { - let players = this._clients.map(({ name, points, guessedWord }) => ({ name, points, guessedWord })); - this._clients.forEach(({ dataChannel }, index) => { - let host = index == 0; - let word = this._drawingPlayer == index ? this._word : (typeof this._word == "string" ? this._word.replace(/[^ -]/g, "_") : null); - /** @type {ActiveGameState} */ - let state = { players, playerIndex: index, host, hasGameStarted: true, rounds: this._rounds, round: this._round, drawingPlayer: this._drawingPlayer, word }; - dataChannel.send({ action: "stateUpdate", newState: state }); - }); - } else { - let players = this._clients.map(({ name }) => ({ name })); - this._clients.forEach(({ dataChannel }, index) => { - dataChannel.send({ action: "stateUpdate", newState: { players, playerIndex: index, host: index == 0, hasGameStarted: false } }); - }); - } - } + /** + * The ID others can use to connect to this server. + * @readonly + */ + get id(){ + return this._id; + } - /** - * Handles a guess send from a given dataChannel. - * @param {number} playerIndex - * @param {string} word - */ - _handleGuess(playerIndex, word) { - let player = this._clients[playerIndex]; - if (this._hasGameStarted) { - if (player.guessedWord || playerIndex == this._drawingPlayer) { - // TODO let people who already know the word send ghost messages to others who already know it too - } else if (typeof this._word == "string" && SkribblServer._isCorrect(word, this._word)) { - player.guessedWord = true; - player.guessedIndex = Math.max(...this._clients.map(client => client.guessedIndex)) + 1; - this._clients.forEach((client, index) => { - if (index == playerIndex) { - client.dataChannel.send({ action: "guessedWord", player: playerIndex, correct: true, word }); - } else { - client.dataChannel.send({ action: "guessedWord", player: playerIndex, correct: true }); - } - }); - this._allGuessedPromise.checkCondition(); - } else { - this._clients.forEach((client, index) => { - let close = typeof this._word == "string" ? SkribblServer._isClose(word, this._word) : false; - if (index == playerIndex) { - client.dataChannel.send({ action: "guessedWord", player: playerIndex, correct: false, word, close }); - } else { - client.dataChannel.send({ action: "guessedWord", player: playerIndex, correct: false, word }); - } - }); - } - } - } + /** + * Returns the full url others can use to connect to this server. + * @readonly + */ + get url(){ + return document.location.host+document.location.pathname+"#"+this._id; + } - /** - * Awards points to each player based on if and when they guessed the word, resets the corresponding properties and then returns the list of points each player got. - * @todo find better formula for the number of points each player should get - */ - _awardPoints() { - let guessingPlayers = this._clients.length - 1; - let successfullPlayers = Math.max(...this._clients.map(client => client.guessedIndex)); - let points = this._clients.map((client, index) => { - let points; - if (this._drawingPlayer == index) { - points = successfullPlayers * 50; - } else if (client.guessedIndex != 0) { - points = ((guessingPlayers - client.guessedIndex + 1) ** 2) / guessingPlayers * 100; - } else { - points = 0; - } - points = Math.round(points / 5) * 5; - client.points += points; - client.guessedWord = false; - client.guessedIndex = 0; - return points; - }); - return points; - } + /** + * Adds an incoming connection as a client. + * @param {DataChannel<MessageToClient,MessageToServer>} dataChannel + */ + connect(dataChannel){ + (async()=>{ + let message = await dataChannel.next(); + console.log("message: ",message); + if (message.action==="join"){ + let name = message.name; + if (name.length<30&&name.length>=1){ + dataChannel.send("yup"); + this._clients.push({name,dataChannel,points:0,guessedWord:false,guessedIndex:0}); + this._sendStateUpdate(); + if (!this._hasGameStarted){ + dataChannel.send({action:"settingsUpdate",rounds:this._rounds,drawTime:this._drawTime}); + } + dataChannel.onMessage(message=>{ + let playerIndex = this._clients.map(({dataChannel})=>dataChannel).indexOf(dataChannel); + let isHost = (playerIndex==0); + 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._sendToAll({action:"settingsUpdate",rounds:this._rounds,drawTime:this._drawTime}); + } + }else if (message.action=="guess"){ + this._handleGuess(playerIndex,message.word); + }else if (message.action=="clearCanvas"||message.action=="draw"||message.action=="erase"){ + // sends the drawing order to all connected clients iff the player is currently drawing and has already chosen a word + if (this._hasGameStarted&&this._drawingPlayer==playerIndex&&typeof this._word=="string"){ + this._sendToAll(message); + } + } + }); + }else{ + dataChannel.send("nope"); + dataChannel.close(); + } + }else{ + dataChannel.send("nope"); + dataChannel.close(); + } + })(); + } - /** - * Checks whether a given guess is close to the given word. A guess counts as close, if one letter is wrong, - * two letters are swapped or the guess has one letter too much or too little. - * @param {string} guess - * @param {string} word - */ - static _isClose(guess, word) { - guess = guess.toLowerCase().replace(/-/g, " "); - word = word.toLowerCase().replace(/-/g, " "); - const wordArray = Array.from(word); - const guessArray = Array.from(guess); + /** + * Sends a message to all currently connected clients. + * @param {MessageToClient} message + */ + _sendToAll(message){ + this._clients.forEach(({dataChannel})=>{ + dataChannel.send(message); + }) + } - //if equal - if (guess == word) { - return true; - } + /** + * 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; + this._clients.forEach(client=>{ + client.points = 0; + client.guessedWord = false; + client.guessedIndex = 0; + }); + (async()=>{ + for (this._round=1;this._round<=this._rounds;this._round++){ + for (this._drawingPlayer=0;this._drawingPlayer<this._clients.length;this._drawingPlayer++){ + let words = await Promise.all([SkribblWords.get(),SkribblWords.get(),SkribblWords.get()]); + this._word = words.map(({word})=>word); + this._sendStateUpdate(); + /** @type {number} */ + let wordIndex = await new Promise(async resolve=>{ + let cancelled = false; + setTimeout(()=>{ + cancelled = true; + resolve(Math.floor(Math.random()*3)); + },10000); + let dataChannel = this._clients[this._drawingPlayer].dataChannel; + while (!cancelled){ + let message = await dataChannel.next(); + if (message.action=="chooseWord"){ + resolve(message.word); + } + } + }); + this._word = words[wordIndex].word; + this._sendToAll({action:"clearCanvas"}); + this._sendStateUpdate(); + this._allGuessedPromise = new ConditionFulfilledPromise(()=>{ + return this._clients.every((client,index)=>client.guessedWord||index==this._drawingPlayer); + }); + let timeOverPromise = Util.wait(this._drawTime); + await Promise.race([this._allGuessedPromise,timeOverPromise]); + let points = this._awardPoints(); + this._sendStateUpdate(); + this._sendToAll({action:"revealWord",word:this._word,description:words[wordIndex].description,macros:await SkribblWords.macros,points}); + await Util.wait(8); + } + } + this._hasGameStarted = false; + this._sendStateUpdate(); + })(); + } + } - //either one letter wrong or two letters swapped - if (guess.length == word.length) { - //Counts the mistakes and their position - let errorCounter = 0; - let errorPos = 0; - for (var i = 0; i < wordArray.length; i++) { - if (wordArray[i] != guessArray[i]) { - if (errorCounter == 0) { - errorPos = i; - } - if (errorCounter == 1) { //if a second mistake occurs, either the letters are swapped or it is not correct - //but there could be a third mistake, so in the case of a swap, true is not directly returned. - if ((wordArray[i] != guessArray[errorPos]) || (wordArray[errorPos] != guessArray[i])) { - return false; - } - } - if (errorCounter >= 2) { //with two or more mistakes, the word is not close - return false; - } - errorCounter++; - } - } - //if it hasnt returned false by now, the guess is close - return true; - } + /** + * Sends a state update to all connected clients (that is, clients that have already joined the game). + */ + _sendStateUpdate(){ + console.log("sending state update! players:",this._clients); + if (this._hasGameStarted){ + let players = this._clients.map(({name,points,guessedWord})=>({name,points,guessedWord})); + this._clients.forEach(({dataChannel},index)=>{ + let host = index==0; + let word = this._drawingPlayer==index?this._word:(typeof this._word=="string"?this._word.replace(/[^ -]/g,"_"):null); + /** @type {ActiveGameState} */ + let state = {players,playerIndex:index,host,hasGameStarted:true,rounds:this._rounds,round:this._round,drawingPlayer:this._drawingPlayer,word}; + dataChannel.send({action:"stateUpdate",newState:state}); + }); + }else{ + let players = this._clients.map(({name})=>({name})); + this._clients.forEach(({dataChannel},index)=>{ + dataChannel.send({action:"stateUpdate",newState:{players,playerIndex:index,host:index==0,hasGameStarted:false}}); + }); + } + } - //if one letter too much - if (guess.length - 1 == word.length) { - let errorCounter = 0; //also the offset - for (var i = 0; i < wordArray.length; i++) { - if (wordArray[i] != guessArray[i + errorCounter]) { - errorCounter++; - if (errorCounter >= 2) { - return false; - } - } - } - return true; - } + /** + * Handles a guess send from a given dataChannel. + * @param {number} playerIndex + * @param {string} word + */ + _handleGuess(playerIndex,word){ + let player = this._clients[playerIndex]; + if (this._hasGameStarted){ + if (player.guessedWord||playerIndex==this._drawingPlayer){ + // TODO let people who already know the word send ghost messages to others who already know it too + }else if (typeof this._word=="string"&&SkribblServer._isCorrect(word,this._word)){ + player.guessedWord = true; + player.guessedIndex = Math.max(...this._clients.map(client=>client.guessedIndex))+1; + this._clients.forEach((client,index)=>{ + if (index==playerIndex){ + client.dataChannel.send({action:"guessedWord",player:playerIndex,correct:true,word}); + }else{ + client.dataChannel.send({action:"guessedWord",player:playerIndex,correct:true}); + } + }); + this._allGuessedPromise.checkCondition(); + }else{ + this._clients.forEach((client,index)=>{ + let close = typeof this._word=="string"?SkribblServer._isClose(word,this._word):false; + if (index==playerIndex){ + client.dataChannel.send({action:"guessedWord",player:playerIndex,correct:false,word,close}); + }else{ + client.dataChannel.send({action:"guessedWord",player:playerIndex,correct:false,word}); + } + }); + } + } + } - //if one letter too little - if (guess.length + 1 == word.length) { - let errorCounter = 0; //also the offset - for (var i = 0; i < guessArray.length; i++) { - if (wordArray[i + errorCounter] != guessArray[i]) { - errorCounter++; - if (errorCounter >= 2) { - return false; - } - } - } - return true; - } + /** + * Awards points to each player based on if and when they guessed the word, resets the corresponding properties and then returns the list of points each player got. + * @todo find better formula for the number of points each player should get + */ + _awardPoints(){ + let guessingPlayers = this._clients.length-1; + let successfullPlayers = Math.max(...this._clients.map(client=>client.guessedIndex)); + let points = this._clients.map((client,index)=>{ + let points; + if (this._drawingPlayer==index){ + points = successfullPlayers*50; + }else if(client.guessedIndex!=0){ + points = ((guessingPlayers-client.guessedIndex+1)**2)/guessingPlayers*100; + }else{ + points = 0; + } + points = Math.round(points/5)*5; + client.points += points; + client.guessedWord = false; + client.guessedIndex = 0; + return points; + }); + return points; + } - return false; - } + /** + * Checks whether a given guess is close to the given word. A guess counts as close, if one letter is wrong, + * two letters are swapped or the guess has one letter too much or too little. + * @param {string} guess + * @param {string} word + */ + static _isClose(guess,word) { + guess = guess.toLowerCase().replace(/-/g," "); + word = word.toLowerCase().replace(/-/g," "); + const wordArray = Array.from(word); + const guessArray = Array.from(guess); + + //if equal + if (guess == word) { + return true; + } + + //either one letter wrong or two letters swapped + if (guess.length == word.length) { + //Counts the mistakes and their position + let errorCounter = 0; + let errorPos = 0; + for (var i = 0; i < wordArray.length; i++) { + if (wordArray[i] != guessArray[i]) { + if (errorCounter == 0) { + errorPos = i; + } if (errorCounter == 1) { //if a second mistake occurs, either the letters are swapped or it is not correct + //but there could be a third mistake, so in the case of a swap, true is not directly returned. + if ((wordArray[i] != guessArray[errorPos]) || (wordArray[errorPos] != guessArray[i])) { + return false; + } + } if (errorCounter >= 2) { //with two or more mistakes, the word is not close + return false; + } + errorCounter++; + } + } + //if it hasnt returned false by now, the guess is close + return true; + } + + //if one letter too much + if (guess.length - 1 == word.length) { + let errorCounter = 0; //also the offset + for (var i = 0; i < wordArray.length; i++) { + if (wordArray[i] != guessArray[i + errorCounter]) { + errorCounter++; + if (errorCounter >= 2) { + return false; + } + } + } + return true; + } + + //if one letter too little + if (guess.length + 1 == word.length) { + let errorCounter = 0; //also the offset + for (var i = 0; i < guessArray.length; i++) { + if (wordArray[i + errorCounter] != guessArray[i]) { + errorCounter++; + if (errorCounter >= 2) { + return false; + } + } + } + return true; + } + + return false; + } - /** - * Checks if the guess is correct. It is not case-sensitive. - * @param {string} guess - * @param {string} word - */ - static _isCorrect(guess, word) { - guess = guess.toLowerCase().replace(/-/g, " "); - word = word.toLowerCase().replace(/-/g, " "); - return guess == word; - } + /** + * Checks if the guess is correct. It is not case-sensitive. + * @param {string} guess + * @param {string} word + */ + static _isCorrect(guess, word) { + guess = guess.toLowerCase().replace(/-/g," "); + word = word.toLowerCase().replace(/-/g," "); + return guess==word; + } } \ No newline at end of file -- GitLab