diff --git a/client/docs/script.js b/client/docs/script.js index 25a3ad482c952384edcb3004874a4d2256423514..4bf072e2f15876065a336551bd7b9d5f5591ce52 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 ba2253fd634c5dc29fb620e2561576d148619a57..4cdebdc673b80be659be638cb4069f8513deeaff 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 69e0f059d44b6589a8e077fa07a9932e157ffc77..9a3034a842f6a9c3b14bf7581a495218faf6e5fe 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;