From f40356291b8e3ffc0db9bd5197d2f6dee11acd83 Mon Sep 17 00:00:00 2001
From: Ben Eltschig <eltschib@hu-berlin.de>
Date: Sat, 15 May 2021 04:14:51 +0200
Subject: [PATCH] SkribblWords.js an neues JSON-Format angepasst

---
 client/docs/script.js         | 48 +++++++++---------
 client/logic/SkribblServer.js |  9 ++--
 client/logic/SkribblWords.js  | 95 +++++++++++++++++++++--------------
 3 files changed, 86 insertions(+), 66 deletions(-)

diff --git a/client/docs/script.js b/client/docs/script.js
index 25a3ad4..4bf072e 100644
--- a/client/docs/script.js
+++ b/client/docs/script.js
@@ -1,15 +1,16 @@
+import SkribblWords from "../logic/SkribblWords.js";
 import {SkribblTopic,SkribblWord} from "./CustomElements.js";
 
 (async()=>{
 	/**
-	 * @typedef {{word:string,description?:string,synonyms?:string[]}} Word
-	 * @typedef {{word:string,description?:string,synonyms?:string[],relevance?:number}} WordRef
-	 * @typedef {{from:string,words:(string|WordRef)[],relevance?:number}} WordRefs
-	 * @typedef {{name:string,words?:Word[],subtopics?:Topic[]}} Topic
-	 * @typedef {{name:string,words:(WordRef|WordRefs)[],defaultRelevance:number}} Module
-	 * @type {{topics:Topic[],modules:Module[],macros?:{[key:string]:string;}}}
+	 * @typedef {import("../logic/SkribblWords.js").Module} Module
+	 * @typedef {import("../logic/SkribblWords.js").Topic} Topic
+	 * @typedef {import("../logic/SkribblWords.js").WordRefs} WordRefs
+	 * @typedef {import("../logic/SkribblWords.js").WordList} WordList
+	 * @type {WordList}
 	 */
-	const json = await (await fetch("../Skribbl.json")).json();
+	const json = await SkribblWords.fetchJson("../Skribbl.json");
+	const words = new SkribblWords(json);
 	console.log("json:",json);
 	if (document.readyState=="loading"){
 		await new Promise(resolve=>{document.addEventListener("DOMContentLoaded",()=>resolve())})
@@ -22,13 +23,13 @@ import {SkribblTopic,SkribblWord} from "./CustomElements.js";
 	 * @param {HTMLElement} element
 	 * @param {Module[]} modules
 	 */
-	function appendModules(element,modules){
+	async function appendModules(element,modules){
 		if (modules){
 			modules.forEach(module=>{
 				let topicElement = new SkribblTopic(module.name);
-				module.words.forEach(word=>{
+				module.words.forEach(async word=>{
 					if (!("word" in word)){
-						let topic = getTopic(word.from);
+						let topic = words.getTopic(word.from);
 						word.words.forEach(wordRef=>{
 							let word = topic.words.find(word=>word.word===(typeof wordRef=="string"?wordRef:wordRef.word));
 							let wordElement = new SkribblWord(word.word,{description:word.description,synonyms:word.synonyms,macros:json.macros});
@@ -62,21 +63,18 @@ import {SkribblTopic,SkribblWord} from "./CustomElements.js";
 			});
 		}
 	}
-	/** @param {string} id */
-	function getTopic(id){
-		let ids = id.split(">").map(s=>s.trim());
-		return ids.slice(1).reduce((topic,id)=>topic.subtopics.find(topic=>topic.name==id),json.topics.find(topic=>topic.name==ids[0]));
-	}
-	/** @param {Topic} topic */
-	let getWordPaths = (topic,prefix="")=>{
-		let words = topic.words?topic.words.map(word=>prefix+topic.name+">"+word.word):[];
-		if (topic.subtopics){
-			words.push(...topic.subtopics.map(subtopic=>getWordPaths(subtopic,prefix+topic.name+">")).flat());
-		}
-		return words;
-	};
+	/** @param {string} path */
+	let getTopic = (path)=>words.getTopic(path);
+	/** @param {string} path */
+	let getWord = (path)=>words.getWord(path);
+	/**
+	 * @param {Topic} topic
+	 * @param {string} [prefix]
+	 */
+	let getWordPaths = (topic,prefix)=>words.getWordPaths(topic,prefix);
+	let getRandomWord = ()=>words.getRandom();
 	let getUnusedWordIDs = ()=>{
-		let wordIDs = json.topics.map(topic=>getWordPaths(topic)).flat();
+		let wordIDs = json.topics.map(topic=>words.getWordPaths(topic)).flat();
 		return wordIDs.filter(wordID=>!json.modules.some(module=>module.words.some(wordRefs=>"from" in wordRefs&&wordRefs.words.some(word=>wordRefs.from+">"+(typeof word=="string"?word:word.word)==wordID))));
 	};
 	/** @returns {WordRefs[]} */
@@ -120,5 +118,5 @@ import {SkribblTopic,SkribblWord} from "./CustomElements.js";
 		navigator.clipboard.writeText(JSON.stringify(json,null,"\t"));
 	};
 	// @ts-ignore
-	window.consoleTools = {getTopic,getWordPaths,getUnusedWordIDs,importUnusedWords,importWordsFromTopic,copyJson};
+	window.consoleTools = {getTopic,getWord,getWordPaths,getRandomWord,getUnusedWordIDs,importUnusedWords,importWordsFromTopic,copyJson};
 })();
\ No newline at end of file
diff --git a/client/logic/SkribblServer.js b/client/logic/SkribblServer.js
index ba2253f..4cdebdc 100644
--- a/client/logic/SkribblServer.js
+++ b/client/logic/SkribblServer.js
@@ -97,6 +97,7 @@ export default class SkribblServer {
 			 * @type {string|string[]}
 			 */
 			this._word = null;
+			this._words = new SkribblWords(await SkribblWords.fetchJson());
 			this._id = await Signaler.host(dataChannel=>{
 				this.connect(DataChannel.from(dataChannel,JSON.stringify,JSON.parse));
 			});
@@ -214,7 +215,7 @@ export default class SkribblServer {
 			(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()]);
+						let words = await Promise.all([this._words.getRandom(),this._words.getRandom(),this._words.getRandom()]);
 						this._word = words.map(({word})=>word);
 						this._state = "choosing";
 						this._sendStateUpdate();
@@ -246,7 +247,7 @@ export default class SkribblServer {
 						let points = this._awardPoints();
 						this._state = "other";
 						this._sendStateUpdate();
-						this._sendToAll({action:"revealWord",word:this._word,description:words[wordIndex].description,macros:await SkribblWords.macros,points});
+						this._sendToAll({action:"revealWord",word:this._word,description:words[wordIndex].description,macros:this._words.macros,points});
 						await Util.wait(8);
 					}
 				}
@@ -290,7 +291,7 @@ export default class SkribblServer {
 		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"&&SkribblWords._isCorrect(word,this._word)){
+			}else if (typeof this._word=="string"&&SkribblWords.isCorrect(word,this._word)){
 				player.guessedWord = true;
 				player.guessedIndex = Math.max(...this._clients.map(client=>client.guessedIndex))+1;
 				this._clients.forEach((client,index)=>{
@@ -303,7 +304,7 @@ export default class SkribblServer {
 				this._allGuessedPromise.checkCondition();
 			}else{
 				this._clients.forEach((client,index)=>{
-					let close = typeof this._word=="string"?SkribblWords._isClose(word,this._word):false;
+					let close = typeof this._word=="string"?SkribblWords.isClose(word,this._word):false;
 					if (index==playerIndex){
 						client.dataChannel.send({action:"guessedWord",player:playerIndex,correct:false,word,close});
 					}else{
diff --git a/client/logic/SkribblWords.js b/client/logic/SkribblWords.js
index 69e0f05..9a3034a 100644
--- a/client/logic/SkribblWords.js
+++ b/client/logic/SkribblWords.js
@@ -1,51 +1,72 @@
 /**
- * @typedef {object} WordList
- * @property {{[key:string]:string}} [macros]
- * @property {(Topic|Word)[]} words
+ * @typedef {{word:string,description?:string,synonyms?:string[]}} Word
+ * @typedef {{word:string,description?:string,synonyms?:string[],relevance?:number}} WordRef
+ * @typedef {{from:string,words:(string|WordRef)[],relevance?:number}} WordRefs
+ * @typedef {{name:string,words?:Word[],subtopics?:Topic[]}} Topic
+ * @typedef {{name:string,words:(WordRef|WordRefs)[],defaultRelevance:number}} Module
+ * @typedef {{topics:Topic[],modules:Module[],macros?:{[key:string]:string;}}} WordList
  */
 /**
- * @typedef {object} Topic
- * @property {"topic"} type
- * @property {string} name
- * @property {Word[]} words
- */
-/**
- * @typedef {object} Word
- * @property {"word"} [type]
- * @property {string} word
- * @property {string} [context]
- * @property {string} [description]
- * @property {string[]} [synonyms]
- */
-/** @type {Promise<WordList>} */
-let json = (async()=>{
-	let response = await fetch("./Skribbl.json");
-	return await response.json();
-})();
-let words = (async()=>{
-	let words = (await json).words.map(wordOrTopic=>wordOrTopic.type=="topic"?wordOrTopic.words:[wordOrTopic]).flat();
-	return words;
-})();
-/**
- * Wrapper for the word list specified in `Skribbl.json`.
+ * Wrapper for a word list, most commonly the one specified in `Skribbl.json`.
  */
 export default class SkribblWords {
+	/**
+	 * @param {WordList} json 
+	 */
+	constructor(json){
+		this._json = json;
+	}
+
+	static async fetchJson(url="./Skribbl.json"){
+		return await (await fetch(url)).json();
+	}
+
+	/**
+	 * Returns the paths of all words in a given topic recursively.
+	 * @param {Topic} topic
+	 */
+	getWordPaths = (topic,prefix="")=>{
+		let words = topic.words?topic.words.map(word=>prefix+topic.name+">"+word.word):[];
+		if (topic.subtopics){
+			words.push(...topic.subtopics.map(subtopic=>this.getWordPaths(subtopic,prefix+topic.name+">")).flat());
+		}
+		return words;
+	};
+	
+	/**
+	 * Returns the topic with the given path.
+	 * @param {string} path
+	 */
+	getTopic(path){
+		let ids = path.split(">").map(s=>s.trim());
+		return ids.slice(1).reduce((topic,id)=>topic.subtopics.find(topic=>topic.name==id),this._json.topics.find(topic=>topic.name==ids[0]));
+	}
+
+	/**
+	 * Returns the word with the given path.
+	 * @param {string} path 
+	 */
+	getWord(path){
+		let parts = path.split(">");
+		let topic = this.getTopic(parts.slice(0,-1).join(">"));
+		return topic.words.find(word=>word.word==parts[parts.length-1]);
+	}
+
 	/**
 	 * Returns a random word from the list.
 	 */
-	static async get(){
-		let array = await words;
-		return array[Math.floor(Math.random()*array.length)];
+	getRandom(){
+		let array = this._json.topics.map(topic=>this.getWordPaths(topic)).flat();
+		let chosenPath = array[Math.floor(Math.random()*array.length)];
+		return this.getWord(chosenPath);
 	}
 
 	/**
-	 * @type {Promise<{[key:string]:string}>}
+	 * @type {{[key:string]:string}}
 	 * @readonly
 	 */
-	static get macros(){
-		return (async ()=>{
-			return (await json).macros;
-		})();
+	get macros(){
+		return this._json.macros;
 	}
 
 	/**
@@ -54,7 +75,7 @@ export default class SkribblWords {
 	 * @param {string} guess
 	 * @param {string} word
 	 */
-	 static _isClose(guess,word) {
+	static isClose(guess,word) {
 		guess = guess.toLowerCase().replace(/-/g," ").replace(/'/g,'').normalize("NFD").replace(/[\u0300-\u0307\u0309-\u036f]/g, "");
 		word = word.toLowerCase().replace(/-/g," ").replace(/'/g,'').normalize("NFD").replace(/[\u0300-\u0307\u0309-\u036f]/g, "");
 	
@@ -123,7 +144,7 @@ export default class SkribblWords {
 	 * @param {string} guess
 	 * @param {string} word
 	 */
-	static _isCorrect(guess, word) {
+	static isCorrect(guess, word) {
 		guess = guess.toLowerCase().replace(/-/g," ").replace(/'/g,'').normalize("NFD").replace(/[\u0300-\u0307\u0309-\u036f]/g, "");
 		word = word.toLowerCase().replace(/-/g," ").replace(/'/g,'').normalize("NFD").replace(/[\u0300-\u0307\u0309-\u036f]/g, "");
 		return guess==word;
-- 
GitLab