From c0d4c678f805c158cfcc5be772c0b6679b87d6b6 Mon Sep 17 00:00:00 2001 From: Taejong Yoo Date: Sat, 6 Apr 2024 06:04:52 +0900 Subject: [PATCH] feat: implement settings panel --- css.css | 2 +- index.html | 2 +- js.js | 2 +- js.js.map | 2 +- src/css.css | 48 +++++++++++++++- src/index.html | 15 +++-- src/js.js | 153 +++++++++++++++++++++++++++++++++++++++++++++---- 7 files changed, 200 insertions(+), 24 deletions(-) diff --git a/css.css b/css.css index af4e7cc..6a8d048 100644 --- a/css.css +++ b/css.css @@ -1 +1 @@ -body{flex-direction:column;justify-content:center;align-items:center;display:flex}.hidden{pointer-events:none;opacity:0;-webkit-user-select:none;-ms-user-select:none;user-select:none;width:0;height:0;position:absolute;overflow:hidden}#word-list{grid-template-columns:repeat(4,1fr);gap:1rem;margin:0;padding:0;list-style:none;display:grid}#word-list>li{text-align:center;border-radius:1lh}#word-list>li.found{background-color:oklch(75% 75% calc(var(--hue)*1deg))}#jamo-board{grid-template-columns:repeat(var(--width),1fr);gap:var(--gap);aspect-ratio:1;touch-action:none;-webkit-tap-highlight-color:transparent;margin:0;padding:0;display:grid;position:relative}#jamo-board>i{text-align:center;width:calc(var(--size));height:calc(var(--size));font-size:var(--size);-webkit-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;font-style:normal;line-height:1;display:inline-block;position:relative}#jamo-board>i:after{content:attr(data-jamo);display:inline-block}:root[data-mode=dark] #jamo-board>i:before{background-color:#fff}:root[data-mode=light] #jamo-board>i:before{background-color:#000}:root[data-mode=light] #jamo-board>.completion-bar:before{opacity:.6}@media (prefers-color-scheme:dark){:root[data-mode=system] #jamo-board>i:before{background-color:#fff}}@media (prefers-color-scheme:light){:root[data-mode=system] #jamo-board>i:before{background-color:#000}:root[data-mode=system] #jamo-board>.completion-bar:before{opacity:.6}}#jamo-board>i:before{content:"";width:calc(var(--size)*1.414);height:calc(var(--size)*1.414);pointer-events:none;opacity:0;z-index:-1;border-radius:50%;display:inline-block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#jamo-board:not(:active)>i:hover:before{opacity:.15}#jamo-board>.completion-bar{top:calc(var(--top));left:calc(var(--left));width:calc(var(--width));height:calc(var(--height));pointer-events:none;position:absolute}#jamo-board>.completion-bar:before{content:"";background-color:oklch(75% 75% calc(var(--hue)*1deg));height:calc(var(--thick));width:calc(var(--hypot));transform:translate(-50%,-50%)rotate(var(--angle));transform-origin:50%;mix-blend-mode:overlay;opacity:.5;z-index:-2;border-radius:calc(var(--thick));position:absolute;top:50%;left:50%}nav{height:2rem}#dark-mode-toggle{z-index:1;cursor:pointer;view-transition-name:none;-webkit-tap-highlight-color:transparent;border-radius:50%;justify-content:center;align-items:center;width:2rem;height:2rem;padding:0;display:flex;position:absolute;top:1rem;right:1rem;overflow:hidden;transform:rotate(45deg)}.no-transition,.no-transition>*{transition:none!important}#dark-mode-toggle>#sun,#dark-mode-toggle>#moon{text-align:center;width:100%;height:100%;font-size:1.5rem;line-height:1;transition:-webkit-clip-path .5s,clip-path .5s;position:absolute;top:0;left:0;overflow:hidden}#dark-mode-toggle>#sun{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background-color:buttonface}#dark-mode-toggle>#moon{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background-color:buttonface}#dark-mode-toggle>#sun:before,#dark-mode-toggle>#moon:before{display:inline-block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)rotate(-45deg)}#dark-mode-toggle>#sun:before{content:"β˜€οΈ"}#dark-mode-toggle>#moon:before{content:"πŸŒ™"}:root[data-mode=system]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root[data-mode=system]{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-mode=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:root[data-mode=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}#dark-mode-toggle[data-mode=system]>#sun{clip-path:rect(0 50% 100% 0)}#dark-mode-toggle[data-mode=system]>#moon{clip-path:rect(0 100% 100% 50%)}#dark-mode-toggle[data-mode=dark]>#sun{clip-path:rect(0 0 100% 0)}#dark-mode-toggle[data-mode=dark]>#moon,#dark-mode-toggle[data-mode=light]>#sun{clip-path:rect(0 100% 100% 0)}#dark-mode-toggle[data-mode=light]>#moon{clip-path:rect(0 100% 100% 100%)}#currentJamoCompletions{position:absolute}@media screen and (min-width:720px){#word-list{animation:none}}main{font-size:0}main>*{font-size:1rem}.noresize{resize:none}#file-wrapper{text-align:center;border:1px solid buttonborder;border-radius:2px;width:150px;height:100px;display:inline-block;position:relative}#file-wrapper:where(:focus-visible,:focus-within){outline:-webkit-focus-ring-color auto 1px}#select-game-state-file{cursor:pointer;opacity:0;width:100%;height:100%;position:absolute;top:0;left:0}#file-wrapper:before{content:"πŸ“";margin-bottom:10px;font-size:50px;display:block}#file-wrapper:after{content:attr(data-text);font-size:14px;display:block}::-ms-backdrop{background:rgba(0,0,0,.267)}::backdrop{background:rgba(0,0,0,.267)}#clear-game-state{--pressed:0;position:relative}#clear-game-state:active{--pressed:100%}#clear-game-state:active:after{transition:-webkit-clip-path 2s linear,clip-path 2s linear}#clear-game-state:after{content:"";opacity:.5;clip-path:xywh(0 0 var(--pressed)100%);background:currentColor;width:100%;height:100%;display:inline-block;position:absolute;top:0;left:0}.br{width:0;height:1rem;min-height:1rem;display:block}nav>div:has(#stage){white-space:nowrap} \ No newline at end of file +body{flex-direction:column;justify-content:center;align-items:center;display:flex}.hidden{pointer-events:none;opacity:0;-webkit-user-select:none;-ms-user-select:none;user-select:none;width:0;height:0;position:absolute;overflow:hidden}#word-list{grid-template-columns:repeat(4,1fr);gap:1rem;margin:0;padding:0;list-style:none;display:grid}#word-list>li{text-align:center;border-radius:1lh}#word-list>li.found{background-color:oklch(75% 75% calc(var(--hue)*1deg))}#jamo-board{grid-template-columns:repeat(var(--width),1fr);gap:var(--gap);aspect-ratio:1;touch-action:none;-webkit-tap-highlight-color:transparent;margin:0;padding:0;display:grid;position:relative}#jamo-board>i{text-align:center;width:calc(var(--size));height:calc(var(--size));font-size:var(--size);-webkit-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;font-style:normal;line-height:1;display:inline-block;position:relative}#jamo-board>i:after{content:attr(data-jamo);display:inline-block}:root[data-mode=dark] #jamo-board>i:before{background-color:#fff}:root[data-mode=light] #jamo-board>i:before{background-color:#000}:root[data-mode=light] #jamo-board>.completion-bar:before{opacity:.6}@media (prefers-color-scheme:dark){:root[data-mode=system] #jamo-board>i:before{background-color:#fff}}@media (prefers-color-scheme:light){:root[data-mode=system] #jamo-board>i:before{background-color:#000}:root[data-mode=system] #jamo-board>.completion-bar:before{opacity:.6}}#jamo-board>i:before{content:"";width:calc(var(--size)*1.414);height:calc(var(--size)*1.414);pointer-events:none;opacity:0;z-index:-1;border-radius:50%;display:inline-block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#jamo-board:not(:active)>i:hover:before{opacity:.15}#jamo-board>.completion-bar{top:calc(var(--top));left:calc(var(--left));width:calc(var(--width));height:calc(var(--height));pointer-events:none;position:absolute}#jamo-board>.completion-bar:before{content:"";background-color:oklch(75% 75% calc(var(--hue)*1deg));height:calc(var(--thick));width:calc(var(--hypot));transform:translate(-50%,-50%)rotate(var(--angle));transform-origin:50%;mix-blend-mode:overlay;opacity:.5;z-index:-2;border-radius:calc(var(--thick));position:absolute;top:50%;left:50%}nav{height:2rem}#dark-mode-toggle{z-index:1;cursor:pointer;view-transition-name:none;-webkit-tap-highlight-color:transparent;border-radius:50%;justify-content:center;align-items:center;width:2rem;height:2rem;padding:0;display:flex;position:absolute;top:1rem;right:1rem;overflow:hidden;transform:rotate(45deg)}.no-transition,.no-transition>*{transition:none!important}#dark-mode-toggle>#sun,#dark-mode-toggle>#moon{text-align:center;width:100%;height:100%;font-size:1.5rem;line-height:1;transition:-webkit-clip-path .5s,clip-path .5s;position:absolute;top:0;left:0;overflow:hidden}#dark-mode-toggle>#sun{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;background-color:buttonface}#dark-mode-toggle>#moon{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;background-color:buttonface}#dark-mode-toggle>#sun:before,#dark-mode-toggle>#moon:before{display:inline-block;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)rotate(-45deg)}#dark-mode-toggle>#sun:before{content:"β˜€οΈ"}#dark-mode-toggle>#moon:before{content:"πŸŒ™"}:root[data-mode=system]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark}@media (prefers-color-scheme:dark){:root[data-mode=system]{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-mode=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}:root[data-mode=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}#dark-mode-toggle[data-mode=system]>#sun{clip-path:rect(0 50% 100% 0)}#dark-mode-toggle[data-mode=system]>#moon{clip-path:rect(0 100% 100% 50%)}#dark-mode-toggle[data-mode=dark]>#sun{clip-path:rect(0 0 100% 0)}#dark-mode-toggle[data-mode=dark]>#moon,#dark-mode-toggle[data-mode=light]>#sun{clip-path:rect(0 100% 100% 0)}#dark-mode-toggle[data-mode=light]>#moon{clip-path:rect(0 100% 100% 100%)}#currentJamoCompletions{position:absolute}@media screen and (min-width:720px){#word-list{animation:none}}main{font-size:0}main>*{font-size:1rem}.noresize{resize:none}#file-wrapper{text-align:center;border:1px solid buttonborder;border-radius:2px;width:150px;height:100px;display:inline-block;position:relative}#file-wrapper:where(:focus-visible,:focus-within){outline:-webkit-focus-ring-color auto 1px}#select-game-state-file{cursor:pointer;opacity:0;width:100%;height:100%;position:absolute;top:0;left:0}#file-wrapper:before{content:"πŸ“";margin-bottom:10px;font-size:50px;display:block}#file-wrapper:after{content:attr(data-text);width:100%;font-size:14px;display:block;position:absolute;bottom:50%;translate:0 calc(50% + 1.5lh)}#file-wrapper[data-filename]:after{content:attr(data-filename)}::-ms-backdrop{background:rgba(0,0,0,.267)}::backdrop{background:rgba(0,0,0,.267)}#clear-game-state{-webkit-user-select:none;-ms-user-select:none;user-select:none;--pressed:0;position:relative}#clear-game-state:active{--pressed:100%}#clear-game-state:active:after{transition:-webkit-clip-path 2s linear,clip-path 2s linear}#clear-game-state:after{content:"";opacity:.5;clip-path:xywh(0 0 var(--pressed)100%);background:currentColor;width:100%;height:100%;display:inline-block;position:absolute;top:0;left:0}.br{width:0;height:1rem;min-height:1rem;display:block}nav>div:has(#stage){white-space:nowrap}body:has(dialog[open]){scrollbar-gutter:stable;overflow:hidden}dialog[open].bump{animation:.2s linear bump}@keyframes bump{0%{transform:scale(1.01)}to{transform:scale(1)}}:has(input.success-report){position:relative}input.success-report{opacity:0;pointer-events:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;text-align:center;position:absolute;top:0;bottom:0;left:0;right:0} \ No newline at end of file diff --git a/index.html b/index.html index 4605270..ae5ea20 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -γ…γ…—γ…‡γ…γ…Šγ…γ…ˆγ„±γ…£
1판

κ²Œμž„ μ„€μ •

κ²Œμž„ μƒνƒœ 내보내기
κ²Œμž„ μƒνƒœ 뢈러였기
λ˜λŠ”

κ²Œμž„ μƒνƒœ μ§€μš°κΈ°κ²Œμž„ 진행 쀑 였λ₯˜κ°€ λ°œμƒν•œ 경우, μ €μž₯된 κ²Œμž„ 진행도λ₯Ό μ΄ˆκΈ°ν™”ν•˜μ—¬ μƒˆλ‘œ μ‹œμž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ‘ 찾음!

λ‹€μŒ 판으둜 λ„˜μ–΄κ°€μ‹œκ² μŠ΅λ‹ˆκΉŒ?

\ No newline at end of file +γ…γ…—γ…‡γ…γ…Šγ…γ…ˆγ„±γ…£
1판

κ²Œμž„ μ„€μ •

κ²Œμž„ μƒνƒœ 내보내기
κ²Œμž„ μƒνƒœ 뢈러였기
λ˜λŠ”

κ²Œμž„ μƒνƒœ μ§€μš°κΈ°κ²Œμž„ 진행 쀑 였λ₯˜κ°€ λ°œμƒν•œ 경우, μ €μž₯된 κ²Œμž„ 진행도λ₯Ό μ΄ˆκΈ°ν™”ν•˜μ—¬ μƒˆλ‘œ μ‹œμž‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ‘ 찾음!

λ‹€μŒ 판으둜 λ„˜μ–΄κ°€μ‹œκ² μŠ΅λ‹ˆκΉŒ?

\ No newline at end of file diff --git a/js.js b/js.js index 4d0aa8d..1aab43a 100644 --- a/js.js +++ b/js.js @@ -1,3 +1,3 @@ -function t(t,e,n,r,o,a,l){try{var i=t[a](l),s=i.value}catch(t){n(t);return}i.done?e(s):Promise.resolve(s).then(r,o)}function e(e){return function(){var n=this,r=arguments;return new Promise(function(o,a){var l=e.apply(n,r);function i(e){t(l,o,a,i,s,"next",e)}function s(e){t(l,o,a,i,s,"throw",e)}i(void 0)})}}e(function*(){let t=t=>t,n=(performance.now(),document.currentScript.outerHTML),r=document.body.innerHTML.replace(n,""),o=0;function a(t){return t>=0?2*t:-2*t-1}function l(t){return t%2==0?t/2:-(t+1)/2}function i(){return(i=e(function*(t){let e=yield fetch(t);if(!e.ok)throw Error(`HTTP error! status: ${e.status}`);return yield e.text()})).apply(this,arguments)}function s(t){return u.apply(this,arguments)}function u(){return(u=e(function*(t){return(yield function(t){return i.apply(this,arguments)}(t)).split("\n").filter(t=>!t.startsWith("#")).map(t=>t.trim())})).apply(this,arguments)}t(function(t){let e=function(t){let e=Math.floor((Math.sqrt(8*t+1)-1)/2),n=t-(e*e+e)/2;return[e-n,n]}(a(42));return[l(e[0]),l(e[1])]}(0));let c=yield s("/data/wordsets.txt"),d=[];function h(){return(h=e(function*(t){let e=yield s(`/data/wordsets/${t}.txt`);return d.push(e),e})).apply(this,arguments)}function m(t){return f.apply(this,arguments)}function f(){return(f=e(function*(t){var e;let n=t%c.length;return null!==(e=d[n])&&void 0!==e?e:yield function(t){return h.apply(this,arguments)}(c[n])})).apply(this,arguments)}let p=[],g={s1:0,s2:0,setSeed(t){this.s1=t,this.s2=t},random(){return this.s1=1103515245*this.s1+12345&2147483647,this.s2^=this.s2<<13,this.s2^=this.s2>>17,this.s2^=this.s2<<5,t("random.random"),t(((this.s1^this.s2)+2147483648)/4294967295)}};function y(){return v.apply(this,arguments)}function v(){return(v=e(function*(){let e;if(window.hwgInitialized)throw Error("Game was already initialized.");window.hwgInitialized=!0;let n=document.getElementById("stage"),r=[[3,2,1],[4,-1,0],[5,6,7]],[i,s,u]=[..."각".normalize("NFD")],c=12623-s.charCodeAt(0),d=Object.fromEntries(Object.entries({γ„±γ„±:"γ„²",γ„±γ……:"γ„³",γ„΄γ…ˆ:"γ„΅",γ„΄γ…Ž:"γ„Ά",γ„·γ„·:"γ„Έ",γ„Ήγ„±:"γ„Ί",ㄹㅁ:"γ„»",γ„Ήγ…‚:"γ„Ό",γ„Ήγ……:"γ„½",γ„Ήγ…Œ:"γ„Ύ",ㄹㅍ:"γ„Ώ",γ„Ήγ…Ž:"γ…€",γ…‚γ…‚:"γ…ƒ",γ…‚γ……:"γ…„",γ……γ……:"γ…†",γ…ˆγ…ˆ:"γ…‰",ㅏㅣ:"ㅐ",γ…‘γ…£:"γ…’",γ…“γ…£:"γ…”",γ…•γ…£:"γ…–",ㅗㅏ:"γ…˜",ㅗㅏㅣ:"γ…™",γ…—γ…£:"γ…š",γ…œγ…“:"ㅝ",γ…œγ…“γ…£:"γ…ž",γ…œγ…£:"γ…Ÿ",γ…‘γ…£:"γ…’"}).map(([t,e])=>[e,t])),h=-1,f=1;try{(e=function(t){if(null===t)return null;let[e,n]=t.split("|");if(N(e)!==1*n)throw Error("saved game state is corrupted");let r=JSON.parse(e);if(4!==r.GAME_VERSION)throw Error("The saved game state is from a different version of the game.");return r.completions?r.completions=r.completions.split(",").map(Number).reduce((t,e)=>(t.length&&4!==t.at(-1).length||t.push([]),t.at(-1).push(e),t),[]):r.completions=[],r}(w("gameState")))&&(f=e.stageNumber,n.textContent=f)}catch(t){console.error(t),M("gameState")}n.textContent=f,p.length=0,p.push(...yield m(f));let y=function(t,e){let n=a(t),r=a(e);return l(.5*(n+r)*(n+r+1)+r)}(f,o);g.setSeed(y);let v=[...p];function S(t){return[...t.normalize("NFC")].flatMap(B)}let O=[];for(let t=0;t<16;t++)O.push(...v.splice(K(0,v.length-1),1));v.length=0;let P=document.getElementById("word-list"),j=document.getElementById("word-template");O.forEach(t=>{let e=j.content.cloneNode(!0).querySelector("li");e.textContent=t,e.dataset.word=t,P.appendChild(e)});let L=document.getElementById("jamo-board"),C=document.getElementById("jamo-template");L.addEventListener("selectstart",t=>{t.preventDefault()});let $=!1;L.addEventListener("contextmenu",t=>{$||t.preventDefault(),$=!1});let x="γ„±γ„΄γ„·γ„Ήγ…γ…‚γ……γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Žγ…γ…‘γ…“γ…•γ…—γ…›γ…œγ… γ…‘γ…£";function N(t){return[...t].map(t=>t.charCodeAt()).reduce((t,e)=>(t>>>1|(1&t)<<15)^e,0)}function A(){return{GAME_VERSION:4,width:12,height:12,completions:Array.from(L.querySelectorAll(".completion-bar")).filter(t=>t!==ts).map(t=>`${t.dataset.start},${t.dataset.end}`).join(),stageNumber:f}}function I(t=A()){let e=JSON.stringify(t);return`${e}|${N(e)}`}function B(t){let[e,n,r]=[...t.normalize("NFD")].concat(["","",""]).slice(0,3);return["γ„±γ„²γ„΄γ„·γ„Έγ„Ήγ…γ…‚γ…ƒγ……γ…†γ…‡γ…ˆγ…‰γ…Šγ…‹γ…Œγ…γ…Ž"[e.charCodeAt(0)-i.charCodeAt(0)],String.fromCharCode(n.charCodeAt(0)+c),r.length?"γ„±γ„²γ„³γ„΄γ„΅γ„Άγ„·γ„Ήγ„Ίγ„»γ„Όγ„½γ„Ύγ„Ώγ…€γ…γ…‚γ…„γ……γ…†γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Ž"[r.charCodeAt(0)-u.charCodeAt(0)]:""].flatMap(t=>{var e;return[...null!==(e=d[t])&&void 0!==e?e:t]})}let q=()=>(t(q.name),t(x[K(0,x.length-1)])),k=O.flatMap(t=>[...t.normalize("NFC")].flatMap(B)),z=()=>(t(z.name),t(k[K(0,k.length-1)]));function T(t,e){e.classList.add("no-transition"),t(),requestAnimationFrame(()=>{requestAnimationFrame(()=>{e.classList.remove("no-transition")})})}L.style.setProperty("--gap","0.75rem"),L.style.setProperty("--width",12),L.style.setProperty("--height",12),T(()=>{for(let t=0;t<144;t++){let t=C.content.cloneNode(!0).querySelector("i"),e=K(0,1)?q():z();t.dataset.jamo=e,L.appendChild(t)}},L);let R=Array.from({length:144},()=>null),D=(t,e,n,o)=>{if(n<0||n>7)throw RangeError("direction must be 0 to 7");for(let a=-1;a<=1;a++)for(let l=-1;l<=1;l++)if(r[a+1][l+1]===n)return[t+l*o,e+a*o]},F=L.querySelectorAll("#jamo-board>i");F.forEach((t,e)=>{t.dataset.index=e});let H=(t,e,n,r)=>{let o=S(t);if(o.length>12&&o.length>12)throw RangeError("word too long for board");for(let t=0;t=12||l<0||l>=12)return!1;let i=R[12*l+a];if(i&&i!==o[t])return!1}for(let t=0;tdocument.getElementById("completion-bar-template")),l=null!=o?o:a.content.cloneNode(!0).querySelector(".completion-bar");l.dataset.start=`${t},${e}`,l.dataset.end=`${n},${r}`;let i=Math.min(t,n),s=Math.max(t,n),u=Math.min(e,r),c=Math.max(e,r),d=(s-i+1)*2+.75*(s-i),m=(c-u+1)*2+.75*(c-u);return l.style.setProperty("--top",`${2*u+.75*u}rem`),l.style.setProperty("--left",`${2*i+.75*i}rem`),l.style.setProperty("--width",`${d}rem`),l.style.setProperty("--height",`${m}rem`),l.style.setProperty("--thick","2.25rem"),l.style.setProperty("--hypot",`${Math.hypot(d,m)+.25}rem`),l.style.setProperty("--angle",`${180*Math.atan2(Math.sign(r-e)*m,Math.sign(n-t)*d)/Math.PI}deg`),l.style.setProperty("--hue",h+Math.floor(43758.5453*Math.sin(12.9898*t+78.233*e)%1*Q)*Math.floor(360/Q)),l}L.style.setProperty("--size","2rem");let J=0,W=document.getElementById("stage-clear-dialog"),X=W.querySelector("#next-stage"),Y=W.querySelector("#cancel-next-stage");function _(t,e){J++,t.style.setProperty("--hue",e.style.getPropertyValue("--hue")),t.classList.add("found"),16===J&&(W.showModal(),m(f+1))}function K(e,n){return t(K.name),t([e,n]),t(Math.floor(g.random()*(n-e+1))+e)}X.addEventListener("click",()=>{var t,e;o=0;let n=A();E("gameState",I((t=function(t){for(var e=1;e{W.close()},{passive:!0});let Q=16;h=K(0,359);let U=Array.from({length:4},()=>16),Z=64,tt=()=>{let t=K(0,Z-1),e=0;for(let n=0;n<4;n++)if(t<(e+=U[n]))return n};try{O.toSorted((t,e)=>S(e).length-S(t).length).forEach(t=>{let e,n,r;let a=0;for(;;){e=K(0,11),n=K(0,11);let l=((r=tt())+6)%8;if(H(t,e,n,l)){U[r]--,Z--,16-U[r]>16/3&&(Z-=U[r],U[r]=0);break}if(a>256)throw o++,Error("Failed to populate a word to the board. Try increasing the size of the board or reducing the number of words. Retrying...");a++}})}catch(t){console.error(t),b();return}e&&e.completions.forEach(([t,e,n,r])=>{!function(t,e,n,r){let o=G(t,e,n,r),a=th(td(t,e,n,r));_(P.querySelector(`li[data-word="${a}"]`),o),L.appendChild(o)}(t,e,n,r)});let te=document.getElementById("dark-mode-toggle");te.addEventListener("click",()=>{let t;let e=te.dataset.mode,n=te.dataset.modeOptions.split("|"),r=n[(n.indexOf(e)+1)%n.length];t=()=>{te.dataset.mode=r,document.documentElement.dataset.mode=r},document.startViewTransition?document.startViewTransition(t):t(),localStorage.darkMode=r},{passive:!0});let tn=localStorage.darkMode;tn&&T(()=>{te.dataset.mode=tn,document.documentElement.dataset.mode=tn},te);let tr=new Proxy({value:!1},{set:(t,e,n)=>{if("value"===e&&"boolean"==typeof n)return Reflect.set(t,e,n)}}),to=[-1,-1],ta=-1,tl=[-1,-1];function ti(t,e){let[n,o]=t,[a,l]=e;if(!(n===a||o===l||Math.abs(n-a)===Math.abs(o-l)))return -1;let[i,s]=[a-n,l-o];return r[Math.sign(s)+1][Math.sign(i)+1]}let ts=null;function tu(){if(!(-1!==to[0]&&-1!==to[1]))return;let t=null!==ts,[e,n]=to,[r,o]=-1===tl[0]&&-1===tl[1]?[e,n]:tl;ts=G(e,n,r,o,ts),t||L.appendChild(ts)}function tc(t,e){let n=t{var l,i;return[null!==(l=o[r])&&void 0!==l?l:t,null!==(i=a[r])&&void 0!==i?i:e]}).map(([t,e])=>F[12*e+t].dataset.jamo).join("")}function th(t){return O.find(e=>{let[n,r]=V(e,()=>{let t=S(e);return[t.join(""),t.toReversed().join("")]});return n===t||r===t})}function tm(t,e,n){return tn?t-n:0}function tf(t,e){let[n,r,o,a,l,i]=function(){let t=F[0],e=F[1],n=F[12],r=t.getBoundingClientRect(),o=e.getBoundingClientRect(),a=n.getBoundingClientRect(),l=o.left-r.left,i=a.top-r.top,s=r.left;return[s,r.top,l,i,o.left-r.right,a.top-r.bottom]}(),[s,u]=[t-(n-l/2),e-(r-i/2)],[c,d]=[Math.floor(s/o),Math.floor(u/a)];return[c,d]}function tp(t,e,n){return Math.min(n,Math.max(e,t))}L.addEventListener("pointerdown",t=>{if(t.target.matches("#jamo-board>i")){if(tr.value=!0,(to=tf(t.clientX,t.clientY))[0]<0||to[0]>=12||to[1]<0||to[1]>=12){to=[-1,-1];return}tl=[-1,-1],ta=-1,tu()}},{passive:!0}),document.addEventListener("pointerup",t=>{$=2===t.button,tr.value=!1,to[0]===tl[0]&&to[1]===tl[1]?(null==ts||ts.remove(),ts=null):-1!==tl[0]&&-1!==tl[1]&&function(t){try{if(null===t)return;let[e,n]=t.dataset.start.split(",").map(Number),[r,o]=t.dataset.end.split(",").map(Number);if(e===r&&n===o){t.remove();return}let a=ti([e,n],[r,o]);if(-1===a){t.remove();return}let l=td(e,n,r,o),i=th(l);if(i){let e=P.querySelector(`li[data-word="${i}"]`);e&&(e.classList.contains("found")?t.remove():_(e,t))}else t.remove()}finally{ts=null}}(ts)},{passive:!0}),document.addEventListener("pointermove",t=>{if(!tr.value)return;t.preventDefault();let e=tf(t.clientX,t.clientY);if(e[0]=tp(e[0],0,11),e[1]=tp(e[1],0,11),to[0]===e[0]&&to[1]===e[1])return;let n=ti(to,e);if(-1!==n?(tl=e,ta=n):-1!==tl[0]&&-1!==tl[1]&&(tl=function(t,e,n){let[r,o]=t,[a,l]=e,[i,s]=[a-r,l-o],[u,c]=[a-r,l-o],[d,h]=[a-r,l-o];Math.abs(u)=12||o<0||o>=12){let t=ti(to,[r,o]),e=tm(r,0,11),n=tm(o,0,11);[r,o]=D(r,o,t,-Math.max(e,n))}F[12*(tl=[r,o])[1]+tl[0]],tu()}),e||E("gameState",I()),window.serializeGameState=I;let tg=document.querySelector("main"),ty=()=>{let t=screen.availWidth,e=screen.availHeight;tg.style.transform="scale(1)",tg.style.margin="0";let{width:n,height:r}=tg.getBoundingClientRect(),o=Math.min(1,t/n,e/r);tg.style.transform=`scale(${o})`,tg.style.margin=`${r*(o-1)/2}px ${n*(o-1)/2}px`};ty(),window.addEventListener("resize",ty,{passive:!0}),window.addEventListener("beforeunload",()=>{E("beforeunload",w("beforeunload",0)+1),w("gameState")&&E("gameState",I())},{passive:!0}),window.addEventListener("pagehide",()=>{E("pagehide",w("pagehide",0)+1)},{passive:!0}),document.getElementById("show-settings-panel").addEventListener("click",()=>{document.getElementById("settings-panel").showModal()});let tv=null,tb=document.getElementById("clear-game-state");tb.addEventListener("pointerdown",()=>{tv=setTimeout(()=>{M("gameState"),location.reload()},2e3)}),tb.addEventListener("pointerup",()=>{clearTimeout(tv)}),tb.addEventListener("pointerleave",()=>{clearTimeout(tv)}),document.querySelectorAll("form[method=dialog]>button[type=submit]").forEach(t=>{t.addEventListener("click",e=>{var n,r;e.preventDefault(),null===(r=t.closest("dialog"))||void 0===r||null===(n=r.close)||void 0===n||n.call(r)})}),performance.now()})).apply(this,arguments)}function b(){[...document.body.children].filter(t=>t!==document.currentScript).forEach(t=>t.remove()),document.body.innerHTML=r,window.hwgInitialized=!1,y()}function w(t,e){let n=localStorage[t];return"string"==typeof n?JSON.parse(n):null!=e?e:null}function M(t){delete localStorage[t]}function E(t,e){localStorage[t]=JSON.stringify(e)}performance.now(),y()})(); +function e(e,t,n,r,o,a,l){try{var i=e[a](l),s=i.value}catch(e){n(e);return}i.done?t(s):Promise.resolve(s).then(r,o)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(o,a){var l=t.apply(n,r);function i(t){e(l,o,a,i,s,"next",t)}function s(t){e(l,o,a,i,s,"throw",t)}i(void 0)})}}t(function*(){let e=e=>e,n=(performance.now(),document.currentScript.outerHTML),r=document.body.innerHTML.replace(n,""),o=0;function a(e){return e>=0?2*e:-2*e-1}function l(e){return e%2==0?e/2:-(e+1)/2}function i(){return(i=t(function*(e){let t=yield fetch(e);if(!t.ok)throw Error(`HTTP error! status: ${t.status}`);return yield t.text()})).apply(this,arguments)}function s(e){return c.apply(this,arguments)}function c(){return(c=t(function*(e){return(yield function(e){return i.apply(this,arguments)}(e)).split("\n").filter(e=>!e.startsWith("#")).map(e=>e.trim())})).apply(this,arguments)}e(function(e){let t=function(e){let t=Math.floor((Math.sqrt(8*e+1)-1)/2),n=e-(t*t+t)/2;return[t-n,n]}(a(42));return[l(t[0]),l(t[1])]}(0));let d=yield s("/data/wordsets.txt"),u=[];function m(){return(m=t(function*(e){let t=yield s(`/data/wordsets/${e}.txt`);return u.push(t),t})).apply(this,arguments)}function p(e){return f.apply(this,arguments)}function f(){return(f=t(function*(e){var t;let n=e%d.length;return null!==(t=u[n])&&void 0!==t?t:yield function(e){return m.apply(this,arguments)}(d[n])})).apply(this,arguments)}let h=[],g={s1:0,s2:0,setSeed(e){this.s1=e,this.s2=e},random(){return this.s1=1103515245*this.s1+12345&2147483647,this.s2^=this.s2<<13,this.s2^=this.s2>>17,this.s2^=this.s2<<5,e("random.random"),e(((this.s1^this.s2)+2147483648)/4294967295)}};function y(){requestIdleCallback(()=>{v()})}function v(){S("gameState")&&O("gameState",serializeGameState())}let b=["keyup","pointerup","pointercancel","scrollend"];function E(){return w.apply(this,arguments)}function w(){return(w=t(function*(){let t;if(window.hwgInitialized)throw Error("Game was already initialized.");window.hwgInitialized=!0;let n=document.getElementById("stage"),r=[[3,2,1],[4,-1,0],[5,6,7]],[i,s,c]=[..."각".normalize("NFD")],d=12623-s.charCodeAt(0),u=Object.fromEntries(Object.entries({γ„±γ„±:"γ„²",γ„±γ……:"γ„³",γ„΄γ…ˆ:"γ„΅",γ„΄γ…Ž:"γ„Ά",γ„·γ„·:"γ„Έ",γ„Ήγ„±:"γ„Ί",ㄹㅁ:"γ„»",γ„Ήγ…‚:"γ„Ό",γ„Ήγ……:"γ„½",γ„Ήγ…Œ:"γ„Ύ",ㄹㅍ:"γ„Ώ",γ„Ήγ…Ž:"γ…€",γ…‚γ…‚:"γ…ƒ",γ…‚γ……:"γ…„",γ……γ……:"γ…†",γ…ˆγ…ˆ:"γ…‰",ㅏㅣ:"ㅐ",γ…‘γ…£:"γ…’",γ…“γ…£:"γ…”",γ…•γ…£:"γ…–",ㅗㅏ:"γ…˜",ㅗㅏㅣ:"γ…™",γ…—γ…£:"γ…š",γ…œγ…“:"ㅝ",γ…œγ…“γ…£:"γ…ž",γ…œγ…£:"γ…Ÿ",γ…‘γ…£:"γ…’"}).map(([e,t])=>[t,e])),m=-1,f=1;try{(t=q(S("gameState")))&&(f=t.stageNumber,n.textContent=f)}catch(e){console.error(e),L("gameState")}n.textContent=f,h.length=0,h.push(...yield p(f));let E=function(e,t){let n=a(e),r=a(t);return l(.5*(n+r)*(n+r+1)+r)}(f,o);g.setSeed(E);let w=[...h];function x(e){return[...e.normalize("NFC")].flatMap(T)}let C=[];for(let e=0;e<16;e++)C.push(...w.splice(ee(0,w.length-1),1));w.length=0;let P=document.getElementById("word-list"),j=document.getElementById("word-template");C.forEach(e=>{let t=j.content.cloneNode(!0).querySelector("li");t.textContent=e,t.dataset.word=e,P.appendChild(t)});let I=document.getElementById("jamo-board"),B=document.getElementById("jamo-template");I.addEventListener("selectstart",e=>{e.preventDefault()});let A=!1;I.addEventListener("contextmenu",e=>{A||e.preventDefault(),A=!1});let k="γ„±γ„΄γ„·γ„Ήγ…γ…‚γ……γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Žγ…γ…‘γ…“γ…•γ…—γ…›γ…œγ… γ…‘γ…£";function $(e){return[...e].map(e=>e.charCodeAt()).reduce((e,t)=>(e>>>1|(1&e)<<15)^t,0)}function R(){return{GAME_VERSION:4,width:12,height:12,completions:Array.from(I.querySelectorAll(".completion-bar")).filter(e=>e!==em).map(e=>`${e.dataset.start},${e.dataset.end}`).join(),stageNumber:f}}function N(e=R()){let t=JSON.stringify(e);return`${t}|${$(t)}`}function q(e){if(null===e)return null;let[t,n]=e.split("|");if($(t)!==1*n)throw Error("saved game state is corrupted");let r=JSON.parse(t);if(4!==r.GAME_VERSION)throw Error("The saved game state is from a different version of the game.");return r.completions?r.completions=r.completions.split(",").map(Number).reduce((e,t)=>(e.length&&4!==e.at(-1).length||e.push([]),e.at(-1).push(t),e),[]):r.completions=[],r}function T(e){let[t,n,r]=[...e.normalize("NFD")].concat(["","",""]).slice(0,3);return["γ„±γ„²γ„΄γ„·γ„Έγ„Ήγ…γ…‚γ…ƒγ……γ…†γ…‡γ…ˆγ…‰γ…Šγ…‹γ…Œγ…γ…Ž"[t.charCodeAt(0)-i.charCodeAt(0)],String.fromCharCode(n.charCodeAt(0)+d),r.length?"γ„±γ„²γ„³γ„΄γ„΅γ„Άγ„·γ„Ήγ„Ίγ„»γ„Όγ„½γ„Ύγ„Ώγ…€γ…γ…‚γ…„γ……γ…†γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Ž"[r.charCodeAt(0)-c.charCodeAt(0)]:""].flatMap(e=>{var t;return[...null!==(t=u[e])&&void 0!==t?t:e]})}let D=()=>(e(D.name),e(k[ee(0,k.length-1)])),z=C.flatMap(e=>[...e.normalize("NFC")].flatMap(T)),F=()=>(e(F.name),e(z[ee(0,z.length-1)]));function V(e,t){t.classList.add("no-transition"),e(),requestAnimationFrame(()=>{requestAnimationFrame(()=>{t.classList.remove("no-transition")})})}I.style.setProperty("--gap","0.75rem"),I.style.setProperty("--width",12),I.style.setProperty("--height",12),V(()=>{for(let e=0;e<144;e++){let e=B.content.cloneNode(!0).querySelector("i"),t=ee(0,1)?D():F();e.dataset.jamo=t,I.appendChild(e)}},I);let G=Array.from({length:144},()=>null),H=(e,t,n,o)=>{if(n<0||n>7)throw RangeError("direction must be 0 to 7");for(let a=-1;a<=1;a++)for(let l=-1;l<=1;l++)if(r[a+1][l+1]===n)return[e+l*o,t+a*o]},U=I.querySelectorAll("#jamo-board>i");U.forEach((e,t)=>{e.dataset.index=t});let J=(e,t,n,r)=>{let o=x(e);if(o.length>12&&o.length>12)throw RangeError("word too long for board");for(let e=0;e=12||l<0||l>=12)return!1;let i=G[12*l+a];if(i&&i!==o[e])return!1}for(let e=0;edocument.getElementById("completion-bar-template")),l=null!=o?o:a.content.cloneNode(!0).querySelector(".completion-bar");l.dataset.start=`${e},${t}`,l.dataset.end=`${n},${r}`;let i=Math.min(e,n),s=Math.max(e,n),c=Math.min(t,r),d=Math.max(t,r),u=(s-i+1)*2+.75*(s-i),p=(d-c+1)*2+.75*(d-c);return l.style.setProperty("--top",`${2*c+.75*c}rem`),l.style.setProperty("--left",`${2*i+.75*i}rem`),l.style.setProperty("--width",`${u}rem`),l.style.setProperty("--height",`${p}rem`),l.style.setProperty("--thick","2.25rem"),l.style.setProperty("--hypot",`${Math.hypot(u,p)+.25}rem`),l.style.setProperty("--angle",`${180*Math.atan2(Math.sign(r-t)*p,Math.sign(n-e)*u)/Math.PI}deg`),l.style.setProperty("--hue",m+Math.floor(43758.5453*Math.sin(12.9898*e+78.233*t)%1*et)*Math.floor(360/et)),l}I.style.setProperty("--size","2rem");let W=0,_=document.getElementById("stage-clear-dialog"),K=_.querySelector("#next-stage"),Q=_.querySelector("#cancel-next-stage");function Z(e,t){W++,e.style.setProperty("--hue",t.style.getPropertyValue("--hue")),e.classList.add("found"),16===W&&(_.showModal(),p(f+1))}function ee(t,n){return e(ee.name),e([t,n]),e(Math.floor(g.random()*(n-t+1))+t)}K.addEventListener("click",()=>{var e,t;o=0;let n=R();O("gameState",N((e=function(e){for(var t=1;t{_.close()},{passive:!0});let et=16;m=ee(0,359);let en=Array.from({length:4},()=>16),er=64,eo=()=>{let e=ee(0,er-1),t=0;for(let n=0;n<4;n++)if(e<(t+=en[n]))return n};try{C.toSorted((e,t)=>x(t).length-x(e).length).forEach(e=>{let t,n,r;let a=0;for(;;){t=ee(0,11),n=ee(0,11);let l=((r=eo())+6)%8;if(J(e,t,n,l)){en[r]--,er--,16-en[r]>16/3&&(er-=en[r],en[r]=0);break}if(a>256)throw o++,Error("Failed to populate a word to the board. Try increasing the size of the board or reducing the number of words. Retrying...");a++}})}catch(e){console.error(e),M();return}t&&t.completions.forEach(([e,t,n,r])=>{!function(e,t,n,r){let o=Y(e,t,n,r),a=eg(eh(e,t,n,r));Z(P.querySelector(`li[data-word="${a}"]`),o),I.appendChild(o)}(e,t,n,r)});let ea=document.getElementById("dark-mode-toggle");ea.addEventListener("click",()=>{let e;let t=ea.dataset.mode,n=ea.dataset.modeOptions.split("|"),r=n[(n.indexOf(t)+1)%n.length];e=()=>{ea.dataset.mode=r,document.documentElement.dataset.mode=r},document.startViewTransition?document.startViewTransition(e):e(),localStorage.darkMode=r},{passive:!0});let el=localStorage.darkMode;el&&V(()=>{ea.dataset.mode=el,document.documentElement.dataset.mode=el},ea);let ei=new Proxy({value:!1},{set:(e,t,n)=>{if("value"===t&&"boolean"==typeof n)return Reflect.set(e,t,n)}}),es=[-1,-1],ec=-1,ed=[-1,-1];function eu(e,t){let[n,o]=e,[a,l]=t;if(!(n===a||o===l||Math.abs(n-a)===Math.abs(o-l)))return -1;let[i,s]=[a-n,l-o];return r[Math.sign(s)+1][Math.sign(i)+1]}let em=null;function ep(){if(!(-1!==es[0]&&-1!==es[1]))return;let e=null!==em,[t,n]=es,[r,o]=-1===ed[0]&&-1===ed[1]?[t,n]:ed;em=Y(t,n,r,o,em),e||I.appendChild(em)}function ef(e,t){let n=e{var l,i;return[null!==(l=o[r])&&void 0!==l?l:e,null!==(i=a[r])&&void 0!==i?i:t]}).map(([e,t])=>U[12*t+e].dataset.jamo).join("")}function eg(e){return C.find(t=>{let[n,r]=X(t,()=>{let e=x(t);return[e.join(""),e.toReversed().join("")]});return n===e||r===e})}function ey(e,t,n){return en?e-n:0}function ev(e,t){let[n,r,o,a,l,i]=function(){let e=U[0],t=U[1],n=U[12],r=e.getBoundingClientRect(),o=t.getBoundingClientRect(),a=n.getBoundingClientRect(),l=o.left-r.left,i=a.top-r.top,s=r.left;return[s,r.top,l,i,o.left-r.right,a.top-r.bottom]}(),[s,c]=[e-(n-l/2),t-(r-i/2)],[d,u]=[Math.floor(s/o),Math.floor(c/a)];return[d,u]}function eb(e,t,n){return Math.min(n,Math.max(t,e))}I.addEventListener("pointerdown",e=>{if(e.target.matches("#jamo-board>i")){if(ei.value=!0,(es=ev(e.clientX,e.clientY))[0]<0||es[0]>=12||es[1]<0||es[1]>=12){es=[-1,-1];return}ed=[-1,-1],ec=-1,ep()}},{passive:!0}),document.addEventListener("pointerup",e=>{A=2===e.button,ei.value=!1,es[0]===ed[0]&&es[1]===ed[1]?(null==em||em.remove(),em=null):-1!==ed[0]&&-1!==ed[1]&&function(e){try{if(null===e)return;let[t,n]=e.dataset.start.split(",").map(Number),[r,o]=e.dataset.end.split(",").map(Number);if(t===r&&n===o){e.remove();return}let a=eu([t,n],[r,o]);if(-1===a){e.remove();return}let l=eh(t,n,r,o),i=eg(l);if(i){let t=P.querySelector(`li[data-word="${i}"]`);t&&(t.classList.contains("found")?e.remove():Z(t,e))}else e.remove()}finally{em=null}}(em)},{passive:!0}),document.addEventListener("pointermove",e=>{if(!ei.value)return;e.preventDefault();let t=ev(e.clientX,e.clientY);if(t[0]=eb(t[0],0,11),t[1]=eb(t[1],0,11),es[0]===t[0]&&es[1]===t[1])return;let n=eu(es,t);if(-1!==n?(ed=t,ec=n):-1!==ed[0]&&-1!==ed[1]&&(ed=function(e,t,n){let[r,o]=e,[a,l]=t,[i,s]=[a-r,l-o],[c,d]=[a-r,l-o],[u,m]=[a-r,l-o];Math.abs(c)=12||o<0||o>=12){let e=eu(es,[r,o]),t=ey(r,0,11),n=ey(o,0,11);[r,o]=H(r,o,e,-Math.max(t,n))}U[12*(ed=[r,o])[1]+ed[0]],ep()}),t||O("gameState",N()),window.serializeGameState=N;let eE=document.querySelector("main"),ew=()=>{let e=screen.availWidth,t=screen.availHeight;eE.style.transform="scale(1)",eE.style.margin="0";let{width:n,height:r}=eE.getBoundingClientRect(),o=Math.min(1,e/n,t/r);eE.style.transform=`scale(${o})`,eE.style.margin=`${r*(o-1)/2}px ${n*(o-1)/2}px`};function eM(e){let t=new Uint8Array(atob(e).split("").map(e=>e.charCodeAt(0)));return new TextDecoder().decode(t)}ew(),window.addEventListener("resize",ew,{passive:!0}),b.forEach(e=>{document.addEventListener(e,y,{passive:!0})}),window.addEventListener("beforeunload",()=>{v()},{passive:!0});let eS=document.getElementById("settings-panel"),eL=document.getElementById("export-game-state-plaintext");document.getElementById("show-settings-panel").addEventListener("click",()=>{var e;eS.showModal(),v(),eL.value=(e=S("gameState"),btoa(String.fromCharCode(...new TextEncoder().encode(e))))}),document.querySelectorAll("button[data-success-report]").forEach(e=>{e.addEventListener("click",()=>{if(!e.successReport){let t=document.getElementById("success-report-template").content.cloneNode(!0).querySelector("input");t.setCustomValidity(e.dataset.successReport),e.appendChild(t),e.successReport=t}e.successReport.reportValidity()})}),document.getElementById("copy-game-state-plaintext").addEventListener("click",()=>{navigator.clipboard.writeText(eL.value)}),document.getElementById("download-game-state-plaintext").addEventListener("click",()=>{let e=document.createElement("a"),t=new Blob([eL.value],{type:"text/plain"});e.href=URL.createObjectURL(t),e.download=`save-${new Date().toISOString().replace(/\D/g,"")}.hwgsave`,e.hidden=!0,document.body.appendChild(e),e.click(),e.remove(),URL.revokeObjectURL(e.href)});let eO=document.getElementById("import-game-state-plaintext"),ex=document.getElementById("file-wrapper");function eC(e){eO.setCustomValidity(e),eO.reportValidity()}document.getElementById("select-game-state-file").addEventListener("input",e=>{let t=e.target.files[0];ex.dataset.filename=t.name;let n=new FileReader;n.onload=e=>{let t=e.target.result;try{let e=eM(t);q(e)}catch(e){console.error("Failed to parse game state from file",e),eC("μ„ νƒν•œ 파일의 ꡬ문 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.")}eO.value=t},n.readAsText(t)}),document.getElementById("apply-imported-game-state").addEventListener("click",()=>{try{let e=eM(eO.value);q(e),O("gameState",e),M()}catch(e){console.error("Failed to parse game state from text",e),eC("μ˜¬λ°”λ₯Έ ν˜•μ‹μ˜ κ²Œμž„ μƒνƒœ ν…μŠ€νŠΈκ°€ μ•„λ‹™λ‹ˆλ‹€.")}}),document.querySelectorAll("dialog").forEach(e=>{e.addEventListener("click",t=>{let n=e.getBoundingClientRect();e.open&&(n.left>t.clientX||n.rightt.clientY||n.bottom{e.classList.add("bump")}))}),e.addEventListener("close",()=>{e.classList.remove("bump")})});let eP=null,ej=document.getElementById("clear-game-state");ej.addEventListener("contextmenu",e=>{e.preventDefault()}),ej.addEventListener("pointerdown",()=>{eP=setTimeout(()=>{L("gameState"),location.reload()},2e3)}),ej.addEventListener("pointerup",()=>{clearTimeout(eP)}),ej.addEventListener("pointerleave",()=>{clearTimeout(eP)}),document.querySelectorAll("form[method=dialog]>button[type=submit]").forEach(e=>{e.addEventListener("click",t=>{var n,r;t.preventDefault(),null===(r=e.closest("dialog"))||void 0===r||null===(n=r.close)||void 0===n||n.call(r)})}),performance.now()})).apply(this,arguments)}function M(){[...document.body.children].filter(e=>e!==document.currentScript).forEach(e=>e.remove()),document.body.innerHTML=r,window.hwgInitialized=!1,b.forEach(e=>{document.removeEventListener(e,y)}),E()}function S(e,t){let n=localStorage[e];return"string"==typeof n?JSON.parse(n):null!=t?t:null}function L(e){delete localStorage[e]}function O(e,t){localStorage[e]=JSON.stringify(t)}performance.now(),E()})(); //# sourceMappingURL=js.js.map \ No newline at end of file diff --git a/js.js.map b/js.js.map index dc090a0..be57e34 100644 --- a/js.js.map +++ b/js.js.map @@ -1 +1 @@ -{"version":3,"sources":["src/js.js"],"names":[],"mappings":"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","file":"js.js","sourcesContent":[";(async function () {\r\n /* eslint-disable no-undef */\r\n const GAME_VERSION = 4\r\n const DEBUG = false\r\n const log = DEBUG ? (a) => { console.log(a); return a } : (a) => a\r\n const PROFILE = false\r\n\r\n const documentParsed = performance.now()\r\n\r\n const scriptHTML = document.currentScript.outerHTML\r\n const initialHTMLWithoutThisScript = document.body.innerHTML.replace(scriptHTML, '')\r\n\r\n let boardGenerationRetryCount = 0\r\n\r\n // Mapping integers to non-negative integers and vice versa\r\n function fold (x) {\r\n return x >= 0 ? 2 * x : -2 * x - 1\r\n }\r\n\r\n function unfold (y) {\r\n return y % 2 === 0 ? y / 2 : -(y + 1) / 2\r\n }\r\n\r\n function pairUnsigned (x, y) {\r\n return 0.5 * (x + y) * (x + y + 1) + y\r\n }\r\n\r\n function unpairUnsigned (z) {\r\n const w = Math.floor((Math.sqrt(8 * z + 1) - 1) / 2)\r\n const t = (w * w + w) / 2\r\n const y = z - t\r\n const x = w - y\r\n return [x, y]\r\n }\r\n\r\n function pairSigned (x, y) {\r\n const mappedX = fold(x)\r\n const mappedY = fold(y)\r\n const cantorResult = pairUnsigned(mappedX, mappedY)\r\n return unfold(cantorResult)\r\n }\r\n\r\n function unpairSigned (z) {\r\n const cantorInverse = unpairUnsigned(fold(z))\r\n return [unfold(cantorInverse[0]), unfold(cantorInverse[1])]\r\n }\r\n log(unpairSigned(42))\r\n\r\n async function fetchBody (url) {\r\n const response = await fetch(url)\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! status: ${response.status}`)\r\n }\r\n return await response.text()\r\n }\r\n\r\n async function fetchLines (url) {\r\n return (await fetchBody(url)).split('\\n').filter(s => !s.startsWith('#')).map(s => s.trim())\r\n }\r\n\r\n const wordsetNames = await fetchLines('/data/wordsets.txt')\r\n const wordsets = []\r\n\r\n async function loadWordset (wordsetName) {\r\n const wordset = await fetchLines(`/data/wordsets/${wordsetName}.txt`)\r\n wordsets.push(wordset)\r\n return wordset\r\n }\r\n\r\n async function loadWordsetForStage (stage) {\r\n const index = stage % wordsetNames.length\r\n const wordset = wordsets[index] ?? await loadWordset(wordsetNames[index])\r\n return wordset\r\n }\r\n\r\n const wordListFull = []\r\n\r\n const random = {\r\n s1: 0,\r\n s2: 0,\r\n setSeed (seed) {\r\n this.s1 = seed\r\n this.s2 = seed\r\n },\r\n random () {\r\n this.s1 = (this.s1 * 1103515245 + 12345) & 2147483647\r\n this.s2 ^= this.s2 << 13\r\n this.s2 ^= this.s2 >> 17\r\n this.s2 ^= this.s2 << 5\r\n log('random.random')\r\n return log(((this.s1 ^ this.s2) + 2147483648) / 4294967295)\r\n }\r\n }\r\n\r\n async function init () {\r\n 'use strict'\r\n if (window.hwgInitialized) {\r\n throw new Error('Game was already initialized.')\r\n }\r\n\r\n window.hwgInitialized = true\r\n\r\n const stageElement = document.getElementById('stage')\r\n\r\n const dirMap = [\r\n [3, 2, 1],\r\n [4, -1, 0],\r\n [5, 6, 7]\r\n ]\r\n\r\n const [nfdChoBase, nfdJungBase, nfdJongBase] = [...'각'.normalize('NFD')]\r\n const simpleJungBase = 'ㅏ'\r\n const jungDiff = simpleJungBase.charCodeAt(0) - nfdJungBase.charCodeAt(0)\r\n\r\n const simpleToCompositeJamoMap = {\r\n γ„±γ„±: 'γ„²',\r\n γ„±γ……: 'γ„³',\r\n γ„΄γ…ˆ: 'γ„΅',\r\n γ„΄γ…Ž: 'γ„Ά',\r\n γ„·γ„·: 'γ„Έ',\r\n γ„Ήγ„±: 'γ„Ί',\r\n ㄹㅁ: 'γ„»',\r\n γ„Ήγ…‚: 'γ„Ό',\r\n γ„Ήγ……: 'γ„½',\r\n γ„Ήγ…Œ: 'γ„Ύ',\r\n ㄹㅍ: 'γ„Ώ',\r\n γ„Ήγ…Ž: 'γ…€',\r\n γ…‚γ…‚: 'γ…ƒ',\r\n γ…‚γ……: 'γ…„',\r\n γ……γ……: 'γ…†',\r\n γ…ˆγ…ˆ: 'γ…‰',\r\n ㅏㅣ: 'ㅐ',\r\n γ…‘γ…£: 'γ…’',\r\n γ…“γ…£: 'γ…”',\r\n γ…•γ…£: 'γ…–',\r\n ㅗㅏ: 'γ…˜',\r\n ㅗㅏㅣ: 'γ…™',\r\n γ…—γ…£: 'γ…š',\r\n γ…œγ…“: 'ㅝ',\r\n γ…œγ…“γ…£: 'γ…ž',\r\n γ…œγ…£: 'γ…Ÿ',\r\n γ…‘γ…£: 'γ…’'\r\n }\r\n\r\n const compositeToSimpleJamoMap = Object.fromEntries(Object.entries(simpleToCompositeJamoMap).map(([simple, composite]) => [composite, simple]))\r\n\r\n let randomHueBase = -1\r\n let stageNumber = 1\r\n let previousGameState\r\n try {\r\n previousGameState = deserializeGameState(load('gameState'))\r\n if (previousGameState) {\r\n stageNumber = previousGameState.stageNumber\r\n stageElement.textContent = stageNumber\r\n }\r\n } catch (e) {\r\n console.error(e)\r\n clear('gameState')\r\n }\r\n\r\n stageElement.textContent = stageNumber\r\n\r\n wordListFull.length = 0\r\n wordListFull.push(...await loadWordsetForStage(stageNumber))\r\n\r\n const seed = pairSigned(stageNumber, boardGenerationRetryCount)\r\n\r\n random.setSeed(seed)\r\n\r\n const cloned = [...wordListFull]\r\n\r\n const wordCount = 16\r\n\r\n function simpleJamoBreakdown (word) {\r\n return [...word.normalize('NFC')].flatMap(decomposeIntoSimple)\r\n }\r\n\r\n const wordList = []\r\n for (let i = 0; i < wordCount; i++) {\r\n wordList.push(...cloned.splice(randomInt(0, cloned.length - 1), 1))\r\n }\r\n cloned.length = 0\r\n\r\n const wordListElement = document.getElementById('word-list')\r\n const wordListTemplate = document.getElementById('word-template')\r\n wordList.forEach(word => {\r\n const li = wordListTemplate.content.cloneNode(true).querySelector('li')\r\n li.textContent = word\r\n li.dataset.word = word\r\n wordListElement.appendChild(li)\r\n })\r\n\r\n const jamoBoardElement = document.getElementById('jamo-board')\r\n const jamoBoardTemplate = document.getElementById('jamo-template')\r\n\r\n jamoBoardElement.addEventListener('selectstart', (e) => {\r\n e.preventDefault()\r\n })\r\n let isRightClick = false\r\n jamoBoardElement.addEventListener('contextmenu', (e) => {\r\n if (!isRightClick) {\r\n e.preventDefault()\r\n }\r\n isRightClick = false\r\n })\r\n\r\n const width = 12\r\n const height = 12\r\n\r\n const simpleJamoList = 'γ„±γ„΄γ„·γ„Ήγ…γ…‚γ……γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Žγ…γ…‘γ…“γ…•γ…—γ…›γ…œγ… γ…‘γ…£'\r\n\r\n function calculateChecksum (gs) {\r\n return [...gs].map(c => c.charCodeAt()).reduce((acc, val) => ((acc >>> 1) | ((acc & 1) << 15)) ^ val, 0)\r\n }\r\n\r\n function currentState () {\r\n const completions = Array.from(jamoBoardElement.querySelectorAll('.completion-bar')).filter((elem) => elem !== currentJamoCompletion).map(elem => {\r\n return `${elem.dataset.start},${elem.dataset.end}`\r\n }).join()\r\n\r\n return {\r\n GAME_VERSION,\r\n width,\r\n height,\r\n completions,\r\n stageNumber\r\n }\r\n }\r\n\r\n function serializeGameState (gsObject = currentState()) {\r\n const gsJSONString = JSON.stringify(gsObject)\r\n return `${gsJSONString}|${calculateChecksum(gsJSONString)}`\r\n }\r\n\r\n function groupElements (arr, numElements) {\r\n return arr.reduce((acc, val) => {\r\n if (!acc.length || acc.at(-1).length === numElements) {\r\n acc.push([])\r\n }\r\n acc.at(-1).push(val)\r\n return acc\r\n }, [])\r\n }\r\n\r\n function deserializeGameState (gsStringFull) {\r\n if (gsStringFull === null) {\r\n return null\r\n }\r\n const [gsObjectStr, checksum] = gsStringFull.split('|')\r\n const expectedChecksum = calculateChecksum(gsObjectStr)\r\n if (expectedChecksum !== checksum * 1) {\r\n throw new Error('saved game state is corrupted')\r\n }\r\n const gsObject = JSON.parse(gsObjectStr)\r\n if (gsObject.GAME_VERSION !== GAME_VERSION) {\r\n throw new Error('The saved game state is from a different version of the game.')\r\n }\r\n if (gsObject.completions) {\r\n gsObject.completions = groupElements(gsObject.completions.split(',').map(Number), 4)\r\n } else {\r\n gsObject.completions = []\r\n }\r\n return gsObject\r\n }\r\n\r\n function decomposeIntoSimple (char) {\r\n const [nfdCho, nfdJung, nfdJong] = [...char.normalize('NFD')].concat(['', '', '']).slice(0, 3)\r\n const simpleCho = 'γ„±γ„²γ„΄γ„·γ„Έγ„Ήγ…γ…‚γ…ƒγ……γ…†γ…‡γ…ˆγ…‰γ…Šγ…‹γ…Œγ…γ…Ž'[nfdCho.charCodeAt(0) - nfdChoBase.charCodeAt(0)]\r\n const simpleJung = String.fromCharCode(nfdJung.charCodeAt(0) + jungDiff)\r\n const simpleJong = nfdJong.length ? 'γ„±γ„²γ„³γ„΄γ„΅γ„Άγ„·γ„Ήγ„Ίγ„»γ„Όγ„½γ„Ύγ„Ώγ…€γ…γ…‚γ…„γ……γ…†γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Ž'[nfdJong.charCodeAt(0) - nfdJongBase.charCodeAt(0)] : ''\r\n return [simpleCho, simpleJung, simpleJong].flatMap(jamo => [...(compositeToSimpleJamoMap[jamo] ?? jamo)])\r\n }\r\n\r\n function composeIntoComposite (simpleJamo) { // @TODO: complete this function\r\n simpleJamo = [...simpleJamo]\r\n const hangulImeStateMachine = {\r\n cho: {\r\n γ„±: ['γ„±', 'jung'],\r\n γ„΄γ„Ήγ…γ…‡γ…Šγ…‹γ…Œγ…γ…Ž: ['jung'],\r\n γ„·: ['γ„·', 'jung'],\r\n γ…‚: ['γ…‚', 'jung'],\r\n γ……: ['γ……', 'jung'],\r\n γ…ˆ: ['γ…ˆ', 'jung']\r\n },\r\n jung: {\r\n ㅏㅑㅓㅕㅑ: ['γ…£', 'jong'],\r\n γ…—: ['ㅏ', 'γ…£', 'jong'],\r\n γ…›γ… γ…£: ['jong'],\r\n γ…œ: ['γ…“', 'γ…£', 'jong']\r\n },\r\n jong: {\r\n γ„±: ['γ„±', 'γ……', 'cho'],\r\n γ„΄: ['γ…ˆ', 'γ…Ž', 'cho'],\r\n γ„·γ…γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Ž: ['cho'],\r\n γ„Ή: ['γ„±', 'ㅁ', 'γ…‚', 'γ……', 'γ…Œ', 'ㅍ', 'γ…Ž', 'cho'],\r\n γ…‚: ['γ……', 'cho'],\r\n γ……: ['γ……', 'cho']\r\n }\r\n }\r\n const maxLengths = Object.freeze({\r\n cho: 2,\r\n jung: 3,\r\n jong: 2\r\n })\r\n const currentLengths = {\r\n cho: 0,\r\n jung: 0,\r\n jong: 0\r\n }\r\n // 'γ…‚γ…‚γ…œγ…“γ…£γ„Ήγ„±' -> [['γ…‚γ…‚'], ['γ…œ','γ…“','γ…£'], ['γ„Ή', 'γ„±']]\r\n let currentState = 'cho'\r\n const grouped = []\r\n const group = []\r\n let nextCandidate = null\r\n while (simpleJamo[0]) {\r\n const jamo = simpleJamo.shift() // 'γ…‚'\r\n group.push(jamo)\r\n currentLengths[currentState]++\r\n const transitionOptions = hangulImeStateMachine[currentState] // cho: { ... }\r\n const transition = Object.entries(transitionOptions).find(([jamoOptions, _]) => jamoOptions.includes(jamo))\r\n if (!transition) {\r\n throw new Error('cannot find suitable continuation for jamo sequence')\r\n }\r\n const [, targetStates] = transition // ['γ…‚', 'jung']\r\n if (targetStates.length === 1 || currentLengths[currentState] === maxLengths[currentState]) {\r\n currentLengths[currentState] = 0\r\n currentState = targetStates[0]\r\n grouped.push([...group])\r\n group.length = 0\r\n if (nextCandidate && !nextCandidate.includes(jamo)) {\r\n throw new Error('next candidate mismatch')\r\n }\r\n nextCandidate = null\r\n } else {\r\n nextCandidate = targetStates.slice(0, -1)\r\n }\r\n }\r\n\r\n return grouped\r\n }\r\n if (DEBUG) {\r\n window.composeIntoComposite = composeIntoComposite\r\n }\r\n\r\n const randomJamo = () => {\r\n log(randomJamo.name)\r\n return log(simpleJamoList[randomInt(0, simpleJamoList.length - 1)])\r\n }\r\n\r\n const simpleJamoFromWordList = wordList.flatMap(word => [...word.normalize('NFC')].flatMap(decomposeIntoSimple))\r\n const jamoFromWordList = () => {\r\n log(jamoFromWordList.name)\r\n return log(simpleJamoFromWordList[randomInt(0, simpleJamoFromWordList.length - 1)])\r\n }\r\n\r\n const gap = 0.75\r\n\r\n function noTransitionZone (fn, elem) {\r\n elem.classList.add('no-transition')\r\n fn()\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => { // 1 frame skip does not work in some cases\r\n elem.classList.remove('no-transition')\r\n })\r\n })\r\n }\r\n\r\n const fillJamoBoard = () => {\r\n jamoBoardElement.style.setProperty('--gap', `${gap}rem`)\r\n jamoBoardElement.style.setProperty('--width', width)\r\n jamoBoardElement.style.setProperty('--height', height)\r\n\r\n noTransitionZone(() => {\r\n for (let i = 0; i < width * height; i++) {\r\n const jamoElement = jamoBoardTemplate.content.cloneNode(true).querySelector('i')\r\n const jamo = randomInt(0, 1) ? randomJamo() : jamoFromWordList()\r\n jamoElement.dataset.jamo = jamo\r\n jamoBoardElement.appendChild(jamoElement)\r\n }\r\n }, jamoBoardElement)\r\n }\r\n\r\n fillJamoBoard()\r\n\r\n const jamoWrittenPositions = Array.from({ length: width * height }, () => null)\r\n\r\n const getPosition = (x, y, direction, progress) => {\r\n if (direction < 0 || direction > 7) {\r\n throw new RangeError('direction must be 0 to 7')\r\n }\r\n for (let dy = -1; dy <= 1; dy++) {\r\n for (let dx = -1; dx <= 1; dx++) {\r\n if (dirMap[dy + 1][dx + 1] === direction) {\r\n return [x + dx * progress, y + dy * progress]\r\n }\r\n }\r\n }\r\n }\r\n\r\n const jamoElements = jamoBoardElement.querySelectorAll('#jamo-board>i')\r\n\r\n jamoElements.forEach((jamoElement, jamoIndex) => {\r\n jamoElement.dataset.index = jamoIndex\r\n })\r\n\r\n /**\r\n *\r\n * @param {string} word\r\n * @param {number} x\r\n * @param {number} y\r\n * @param {number} direction 0 to 7, starting from towards east(right) 1/8 turn CCW each step\r\n */\r\n const writeWord = (word, x, y, direction) => {\r\n const breakdown = simpleJamoBreakdown(word)\r\n // first check without writing\r\n if (breakdown.length > width && breakdown.length > height) {\r\n throw new RangeError('word too long for board')\r\n }\r\n for (let i = 0; i < breakdown.length; i++) {\r\n const [targetX, targetY] = getPosition(x, y, direction, i)\r\n if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) {\r\n return false\r\n }\r\n const jamoIndex = targetY * width + targetX\r\n const existingJamo = jamoWrittenPositions[jamoIndex]\r\n if (existingJamo && existingJamo !== breakdown[i]) {\r\n return false\r\n }\r\n }\r\n\r\n for (let i = 0; i < breakdown.length; i++) {\r\n const [targetX, targetY] = getPosition(x, y, direction, i)\r\n const jamo = breakdown[i]\r\n const jamoIndex = targetY * width + targetX\r\n jamoWrittenPositions[jamoIndex] = jamo\r\n const jamoElement = jamoElements[jamoIndex]\r\n jamoElement.dataset.jamo = jamo\r\n }\r\n\r\n return true\r\n }\r\n\r\n const cellSize = 2\r\n jamoBoardElement.style.setProperty('--size', `${cellSize}rem`)\r\n\r\n function memo (key, compute) {\r\n const cache = memo.cache ?? (memo.cache = new Map())\r\n if (cache.has(key)) {\r\n return cache.get(key)\r\n }\r\n const result = compute()\r\n cache.set(key, result)\r\n return result\r\n }\r\n\r\n function createCompletionBarElement (startX, startY, endX, endY, updateElement = null) {\r\n const completionBarTemplate = memo(createCompletionBarElement, () => document.getElementById('completion-bar-template'))\r\n const completionBarElement = updateElement ?? completionBarTemplate.content.cloneNode(true).querySelector('.completion-bar')\r\n const padding = 0.25\r\n\r\n completionBarElement.dataset.start = `${startX},${startY}`\r\n completionBarElement.dataset.end = `${endX},${endY}`\r\n\r\n const sdx = Math.sign(endX - startX)\r\n const sdy = Math.sign(endY - startY)\r\n const xMin = Math.min(startX, endX)\r\n const xMax = Math.max(startX, endX)\r\n const yMin = Math.min(startY, endY)\r\n const yMax = Math.max(startY, endY)\r\n const top = yMin * cellSize + (gap * yMin)\r\n const left = xMin * cellSize + (gap * xMin)\r\n const width = (xMax - xMin + 1) * cellSize + (gap * (xMax - xMin))\r\n const height = (yMax - yMin + 1) * cellSize + (gap * (yMax - yMin))\r\n\r\n completionBarElement.style.setProperty('--top', `${top}rem`)\r\n completionBarElement.style.setProperty('--left', `${left}rem`)\r\n completionBarElement.style.setProperty('--width', `${width}rem`)\r\n completionBarElement.style.setProperty('--height', `${height}rem`)\r\n\r\n const hypot = Math.hypot(width, height) + padding\r\n const angle = Math.atan2(sdy * height, sdx * width) * 180 / Math.PI\r\n completionBarElement.style.setProperty('--thick', `${cellSize + padding}rem`)\r\n completionBarElement.style.setProperty('--hypot', `${hypot}rem`)\r\n completionBarElement.style.setProperty('--angle', `${angle}deg`)\r\n const randomHue = Math.floor(randomFromCoords(startX, startY) * colorSteps) * Math.floor(360 / colorSteps)\r\n completionBarElement.style.setProperty('--hue', randomHueBase + randomHue)\r\n\r\n return completionBarElement\r\n }\r\n\r\n let foundWords = 0\r\n\r\n const stageClearDialog = document.getElementById('stage-clear-dialog')\r\n const yesButton = stageClearDialog.querySelector('#next-stage')\r\n const noButton = stageClearDialog.querySelector('#cancel-next-stage')\r\n yesButton.addEventListener('click', () => {\r\n boardGenerationRetryCount = 0\r\n const currentGameState = currentState()\r\n save('gameState', serializeGameState({ ...currentGameState, completions: '', stageNumber: currentGameState.stageNumber + 1 }))\r\n reset()\r\n }, { passive: true })\r\n noButton.addEventListener('click', () => {\r\n stageClearDialog.close()\r\n }, { passive: true })\r\n\r\n function prefetchNextStageWordset () {\r\n loadWordsetForStage(stageNumber + 1)\r\n }\r\n\r\n function markWordAsFound (wordElement, completionBarElement) {\r\n foundWords++\r\n wordElement.style.setProperty('--hue', completionBarElement.style.getPropertyValue('--hue'))\r\n wordElement.classList.add('found')\r\n if (foundWords === wordCount) {\r\n stageClearDialog.showModal()\r\n prefetchNextStageWordset()\r\n }\r\n }\r\n\r\n function markCompletionAsCompleted (startX, startY, endX, endY) {\r\n const completionBarElement = createCompletionBarElement(startX, startY, endX, endY)\r\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\r\n const foundWord = findWordByJamoSequence(jamoSequence)\r\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\r\n markWordAsFound(wordElement, completionBarElement)\r\n jamoBoardElement.appendChild(completionBarElement)\r\n }\r\n\r\n function randomInt (min, max) {\r\n log(randomInt.name)\r\n log([min, max])\r\n return log(Math.floor(random.random() * (max - min + 1)) + min)\r\n }\r\n\r\n const colorSteps = 16\r\n randomHueBase = randomInt(0, 359)\r\n\r\n function randomFromCoords (x, y) {\r\n const dot = x * 12.9898 + y * 78.233\r\n return (Math.sin(dot) * 43758.5453) % 1\r\n }\r\n\r\n const easyDirection = true\r\n\r\n const numDirections = easyDirection ? 4 : 8\r\n const directionsProbabilityDist = Array.from({ length: numDirections }, () => wordCount)\r\n let sum = wordCount * numDirections\r\n\r\n const getDirection = () => {\r\n const randomUniform = randomInt(0, sum - 1)\r\n let temp = 0\r\n for (let i = 0; i < numDirections; i++) {\r\n temp += directionsProbabilityDist[i]\r\n if (randomUniform < temp) {\r\n return i\r\n }\r\n }\r\n }\r\n\r\n try {\r\n wordList.toSorted((a, b) => {\r\n return simpleJamoBreakdown(b).length - simpleJamoBreakdown(a).length\r\n }).forEach((word) => {\r\n let x\r\n let y\r\n let direction\r\n let repeated = 0\r\n while (true) {\r\n x = randomInt(0, width - 1)\r\n y = randomInt(0, height - 1)\r\n direction = getDirection()\r\n const directionCorrected = easyDirection ? ((direction + 6) % 8) : direction\r\n const success = writeWord(word, x, y, directionCorrected)\r\n if (success) {\r\n directionsProbabilityDist[direction]--\r\n sum--\r\n if (wordCount - directionsProbabilityDist[direction] > wordCount / 3) {\r\n sum -= directionsProbabilityDist[direction]\r\n directionsProbabilityDist[direction] = 0\r\n }\r\n break\r\n }\r\n if (repeated > wordCount ** 2) {\r\n boardGenerationRetryCount++\r\n throw new Error('Failed to populate a word to the board. Try increasing the size of the board or reducing the number of words. Retrying...')\r\n }\r\n repeated++\r\n }\r\n })\r\n } catch (e) {\r\n console.error(e)\r\n reset()\r\n return\r\n }\r\n\r\n if (previousGameState) {\r\n previousGameState.completions.forEach(([startX, startY, endX, endY]) => {\r\n markCompletionAsCompleted(startX, startY, endX, endY)\r\n })\r\n }\r\n\r\n // add event listener for dark mode toggle\r\n const darkModeToggleButton = document.getElementById('dark-mode-toggle')\r\n\r\n const toggleModes = () => {\r\n const mode = darkModeToggleButton.dataset.mode\r\n const options = darkModeToggleButton.dataset.modeOptions.split('|')\r\n const nextMode = options[(options.indexOf(mode) + 1) % options.length]\r\n const startViewTransition = (change) => document.startViewTransition ? document.startViewTransition(change) : change()\r\n startViewTransition(() => {\r\n darkModeToggleButton.dataset.mode = nextMode\r\n document.documentElement.dataset.mode = nextMode\r\n })\r\n localStorage.darkMode = nextMode\r\n }\r\n darkModeToggleButton.addEventListener('click', toggleModes, { passive: true })\r\n const darkMode = localStorage.darkMode\r\n if (darkMode) {\r\n noTransitionZone(() => {\r\n darkModeToggleButton.dataset.mode = darkMode\r\n document.documentElement.dataset.mode = darkMode\r\n }, darkModeToggleButton)\r\n }\r\n\r\n const pointerdown = new Proxy({ value: false }, {\r\n set: (target, prop, value) => {\r\n if (prop === 'value' && typeof value === 'boolean') {\r\n if (DEBUG) {\r\n Array.from(jamoBoardElement.querySelectorAll('.start, .mid, .end')).forEach(elem => elem.classList.remove('start', 'mid', 'end'))\r\n }\r\n return Reflect.set(target, prop, value)\r\n }\r\n }\r\n })\r\n\r\n let dragStartPos = [-1, -1]\r\n let dragDir = -1\r\n let dragEndPos = [-1, -1]\r\n\r\n function isOctilinear (origin, target) {\r\n const [ox, oy] = origin\r\n const [tx, ty] = target\r\n const isOrthogonal = ox === tx || oy === ty\r\n const isDiagonal = Math.abs(ox - tx) === Math.abs(oy - ty)\r\n if (!(isOrthogonal || isDiagonal)) {\r\n return -1\r\n }\r\n const [dx, dy] = [tx - ox, ty - oy]\r\n return dirMap[Math.sign(dy) + 1][Math.sign(dx) + 1]\r\n }\r\n\r\n function getClosestOctilinearPoint (origin, target, dir) {\r\n const [ox, oy] = origin\r\n const [tx, ty] = target\r\n const [dx, dy] = [tx - ox, ty - oy]\r\n let [dxO, dyO] = [tx - ox, ty - oy]\r\n let [dxD, dyD] = [tx - ox, ty - oy]\r\n\r\n // orthogonal\r\n if (Math.abs(dxO) < Math.abs(dyO)) {\r\n dxO = 0\r\n } else {\r\n dyO = 0\r\n }\r\n\r\n // diagonal\r\n if ((dxD + dyD) % 2) {\r\n // parity mismatch adjustment\r\n if (Math.abs(dxD) < Math.abs(dyD)) {\r\n dyD -= Math.sign(dyD)\r\n } else {\r\n dxD -= Math.sign(dxD)\r\n }\r\n }\r\n {\r\n const dist = Math.abs(Math.abs(dxD) - Math.abs(dyD)) / 2\r\n if (Math.abs(dxD) < Math.abs(dyD)) {\r\n dxD += Math.sign(dxD) * dist\r\n dyD -= Math.sign(dyD) * dist\r\n } else {\r\n dxD -= Math.sign(dxD) * dist\r\n dyD += Math.sign(dyD) * dist\r\n }\r\n }\r\n\r\n const distO = Math.abs(dx - dxO) + Math.abs(dy - dyO)\r\n const distD = Math.abs(dx - dxD) + Math.abs(dy - dyD)\r\n\r\n if (distO === distD) {\r\n return dir % 2 === 0 ? [ox + dxO, oy + dyO] : [ox + dxD, oy + dyD]\r\n } else if (distO < distD) {\r\n return [ox + dxO, oy + dyO]\r\n } else {\r\n return [ox + dxD, oy + dyD]\r\n }\r\n }\r\n\r\n let currentJamoCompletion = null\r\n\r\n function updateJamoCompletion () {\r\n if (!(dragStartPos[0] !== -1 && dragStartPos[1] !== -1)) {\r\n return\r\n }\r\n const exists = currentJamoCompletion !== null\r\n const [sx, sy] = dragStartPos\r\n const [ex, ey] = dragEndPos[0] === -1 && dragEndPos[1] === -1 ? [sx, sy] : dragEndPos\r\n currentJamoCompletion = createCompletionBarElement(sx, sy, ex, ey, currentJamoCompletion)\r\n if (!exists) {\r\n jamoBoardElement.appendChild(currentJamoCompletion)\r\n }\r\n }\r\n\r\n function createRange (start, end) {\r\n const inc = start < end ? 1 : -1\r\n const result = []\r\n for (let i = 0; i <= Math.abs(end - start); i++) {\r\n result.push(i * inc + start)\r\n }\r\n return result\r\n }\r\n\r\n function completionToJamoSequence (startX, startY, endX, endY) {\r\n const rangeX = createRange(startX, endX)\r\n const rangeY = createRange(startY, endY)\r\n const longer = Math.max(rangeX.length, rangeY.length)\r\n const coords = Array.from({ length: longer }, (_, i) => [rangeX[i] ?? startX, rangeY[i] ?? startY])\r\n return coords.map(([x, y]) => jamoElements[y * width + x].dataset.jamo).join('')\r\n }\r\n\r\n function findWordByJamoSequence (jamoSequence) {\r\n return wordList.find(word => {\r\n const [simple, reversed] = memo(word, () => {\r\n const simple = simpleJamoBreakdown(word)\r\n return [simple.join(''), simple.toReversed().join('')]\r\n })\r\n return simple === jamoSequence || reversed === jamoSequence\r\n })\r\n }\r\n\r\n function checkJamoCompletion (jamoCompletionElement) {\r\n try {\r\n if (jamoCompletionElement === null) {\r\n return\r\n }\r\n const [startX, startY] = jamoCompletionElement.dataset.start.split(',').map(Number)\r\n const [endX, endY] = jamoCompletionElement.dataset.end.split(',').map(Number)\r\n if (startX === endX && startY === endY) {\r\n jamoCompletionElement.remove()\r\n return\r\n }\r\n const dir = isOctilinear([startX, startY], [endX, endY])\r\n if (dir === -1) {\r\n jamoCompletionElement.remove()\r\n return\r\n }\r\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\r\n const foundWord = findWordByJamoSequence(jamoSequence)\r\n if (foundWord) {\r\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\r\n if (wordElement) {\r\n if (wordElement.classList.contains('found')) {\r\n jamoCompletionElement.remove()\r\n } else {\r\n markWordAsFound(wordElement, jamoCompletionElement)\r\n }\r\n }\r\n } else {\r\n jamoCompletionElement.remove()\r\n }\r\n } finally {\r\n currentJamoCompletion = null\r\n }\r\n }\r\n\r\n jamoBoardElement.addEventListener('pointerdown', (e) => {\r\n if (!e.target.matches('#jamo-board>i')) {\r\n return\r\n }\r\n pointerdown.value = true\r\n dragStartPos = calculateCellPosFromCoords(e.clientX, e.clientY)\r\n if (dragStartPos[0] < 0 || dragStartPos[0] >= width || dragStartPos[1] < 0 || dragStartPos[1] >= height) {\r\n dragStartPos = [-1, -1]\r\n return\r\n }\r\n dragEndPos = [-1, -1]\r\n dragDir = -1\r\n updateJamoCompletion()\r\n }, { passive: true })\r\n\r\n document.addEventListener('pointerup', (e) => {\r\n isRightClick = e.button === 2\r\n pointerdown.value = false\r\n if (dragStartPos[0] === dragEndPos[0] && dragStartPos[1] === dragEndPos[1]) {\r\n currentJamoCompletion?.remove()\r\n currentJamoCompletion = null\r\n } else if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\r\n checkJamoCompletion(currentJamoCompletion)\r\n }\r\n }, { passive: true })\r\n\r\n function calculateOvershoot (value, min, max) {\r\n if (value < min) {\r\n return min - value\r\n }\r\n if (value > max) {\r\n return value - max\r\n }\r\n return 0\r\n }\r\n\r\n function calculateCellSize () {\r\n const firstElement = jamoElements[0]\r\n const rightElement = jamoElements[1]\r\n const bottomElement = jamoElements[width]\r\n const firstRect = firstElement.getBoundingClientRect()\r\n const rightRect = rightElement.getBoundingClientRect()\r\n const bottomRect = bottomElement.getBoundingClientRect()\r\n const cellInteractiveWidth = rightRect.left - firstRect.left\r\n const cellInteractiveHeight = bottomRect.top - firstRect.top\r\n const left = firstRect.left\r\n const top = firstRect.top\r\n const gapX = rightRect.left - firstRect.right\r\n const gapY = bottomRect.top - firstRect.bottom\r\n return [left, top, cellInteractiveWidth, cellInteractiveHeight, gapX, gapY]\r\n }\r\n\r\n function calculateCellPosFromCoords (clientX, clientY) {\r\n const [left, top, cellWidth, cellHeight, gapX, gapY] = calculateCellSize()\r\n const boardLeft = left - gapX / 2\r\n const boardTop = top - gapY / 2\r\n const [relativeX, relativeY] = [clientX - boardLeft, clientY - boardTop]\r\n const [cellX, cellY] = [Math.floor(relativeX / cellWidth), Math.floor(relativeY / cellHeight)]\r\n return [cellX, cellY]\r\n }\r\n\r\n function clamp (value, min, max) {\r\n return Math.min(max, Math.max(min, value))\r\n }\r\n\r\n document.addEventListener('pointermove', (e) => {\r\n if (!pointerdown.value) {\r\n return\r\n }\r\n e.preventDefault()\r\n const pos = calculateCellPosFromCoords(e.clientX, e.clientY)\r\n pos[0] = clamp(pos[0], 0, width - 1)\r\n pos[1] = clamp(pos[1], 0, height - 1)\r\n if (dragStartPos[0] === pos[0] && dragStartPos[1] === pos[1]) {\r\n return\r\n }\r\n const dir = isOctilinear(dragStartPos, pos)\r\n if (dir !== -1) {\r\n dragEndPos = pos\r\n dragDir = dir\r\n } else if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\r\n dragEndPos = getClosestOctilinearPoint(dragStartPos, pos, dragDir)\r\n }\r\n if (!(dragEndPos[0] !== -1 && dragEndPos[1] !== -1)) {\r\n return\r\n }\r\n let [cx, cy] = dragEndPos\r\n if (cx < 0 || cx >= width || cy < 0 || cy >= height) {\r\n const correctedDragDir = isOctilinear(dragStartPos, [cx, cy])\r\n const overshootX = calculateOvershoot(cx, 0, width - 1)\r\n const overshootY = calculateOvershoot(cy, 0, height - 1)\r\n const maxOvershoot = Math.max(overshootX, overshootY);\r\n [cx, cy] = getPosition(cx, cy, correctedDragDir, -maxOvershoot)\r\n }\r\n dragEndPos = [cx, cy]\r\n\r\n const closestIndex = dragEndPos[1] * width + dragEndPos[0]\r\n const closestElement = jamoElements[closestIndex]\r\n if (DEBUG) {\r\n jamoBoardElement.querySelector('.end')?.classList.remove('end')\r\n closestElement.classList.add('end')\r\n }\r\n updateJamoCompletion()\r\n })\r\n\r\n if (!previousGameState) {\r\n save('gameState', serializeGameState())\r\n }\r\n\r\n window.serializeGameState = serializeGameState\r\n\r\n const mainElement = document.querySelector('main')\r\n\r\n const resizeToFit = () => {\r\n const screenWidth = screen.availWidth\r\n const screenHeight = screen.availHeight\r\n\r\n mainElement.style.transform = 'scale(1)'\r\n mainElement.style.margin = '0'\r\n\r\n const { width, height } = mainElement.getBoundingClientRect()\r\n\r\n const zoomFactor = Math.min(1, screenWidth / width, screenHeight / height)\r\n\r\n mainElement.style.transform = `scale(${zoomFactor})`\r\n mainElement.style.margin = `${(height * (zoomFactor - 1)) / 2}px ${(width * (zoomFactor - 1)) / 2}px`\r\n }\r\n resizeToFit()\r\n window.addEventListener('resize', resizeToFit, { passive: true })\r\n\r\n window.addEventListener('beforeunload', () => {\r\n const beforeunload = load('beforeunload', 0)\r\n save('beforeunload', beforeunload + 1)\r\n const currentStateSaved = load('gameState')\r\n if (currentStateSaved) {\r\n save('gameState', serializeGameState())\r\n }\r\n }, { passive: true })\r\n\r\n window.addEventListener('pagehide', () => {\r\n const pagehide = load('pagehide', 0)\r\n save('pagehide', pagehide + 1)\r\n }, { passive: true })\r\n\r\n document.getElementById('show-settings-panel').addEventListener('click', () => {\r\n document.getElementById('settings-panel').showModal()\r\n })\r\n\r\n const minimumPressDuration = 2000\r\n\r\n let timeout = null\r\n\r\n const clearButton = document.getElementById('clear-game-state')\r\n clearButton.addEventListener('pointerdown', () => {\r\n timeout = setTimeout(() => {\r\n clear('gameState')\r\n location.reload()\r\n }, minimumPressDuration)\r\n })\r\n clearButton.addEventListener('pointerup', () => {\r\n clearTimeout(timeout)\r\n })\r\n clearButton.addEventListener('pointerleave', () => {\r\n clearTimeout(timeout)\r\n })\r\n\r\n // fix dialog forms with sandboxed document\r\n const dialogCloseButtons = document.querySelectorAll('form[method=dialog]>button[type=submit]')\r\n dialogCloseButtons.forEach(button => {\r\n button.addEventListener('click', (e) => {\r\n e.preventDefault()\r\n button.closest('dialog')?.close?.()\r\n })\r\n })\r\n\r\n // @TODO: show relevant image for each word after finding it\r\n // requestIdleCallback(() => {\r\n // // preload images\r\n // const imageElements = wordList.map(word => {\r\n // const img = document.createElement('img')\r\n // img.src = `/images/wordsets/0/${word}.webp`\r\n // return img\r\n // })\r\n // imageElements.forEach(img => img.decode().then(() => {\r\n // console.log('decoded', img.src)\r\n // }).catch(() => {\r\n // console.log('failed to decode', img.src)\r\n // }))\r\n // })\r\n\r\n const gameInitialized = performance.now()\r\n\r\n if (PROFILE) {\r\n requestIdleCallback(() => {\r\n const settled = performance.now()\r\n\r\n const t1 = documentParsed - begin\r\n const t2 = jsParsed - documentParsed\r\n const t3 = gameInitialized - jsParsed\r\n const t4 = settled - gameInitialized\r\n const t5 = settled - begin\r\n\r\n const perfHistory = load('perfHistory', [])\r\n perfHistory.push([t1, t2, t3, t4, t5])\r\n save('perfHistory', perfHistory)\r\n })\r\n }\r\n }\r\n\r\n function reset () {\r\n const children = [...document.body.children].filter(elem => elem !== document.currentScript)\r\n children.forEach(child => child.remove())\r\n document.body.innerHTML = initialHTMLWithoutThisScript\r\n window.hwgInitialized = false\r\n init()\r\n }\r\n\r\n function load (key, fallback) {\r\n const data = localStorage[key]\r\n if (typeof data === 'string') {\r\n return JSON.parse(data)\r\n }\r\n return fallback ?? null\r\n }\r\n\r\n function clear (key) {\r\n delete localStorage[key]\r\n }\r\n\r\n function save (key, value) {\r\n localStorage[key] = JSON.stringify(value)\r\n }\r\n\r\n function average (arr) {\r\n return arr.reduce((acc, val) => acc + val, 0) / arr.length\r\n }\r\n if (DEBUG) {\r\n window.average = average\r\n }\r\n\r\n function median (arr) {\r\n const sorted = arr.slice().sort((a, b) => a - b)\r\n const mid = Math.floor(sorted.length / 2)\r\n if (sorted.length % 2 === 0) {\r\n return (sorted[mid - 1] + sorted[mid]) / 2\r\n }\r\n return sorted[mid]\r\n }\r\n if (DEBUG) {\r\n window.median = median\r\n }\r\n\r\n function getCurrentFunctionName () {\r\n const currentStackRaw = new Error().stack\r\n const callerLine = currentStackRaw.split('\\n').slice(1)[1]\r\n return callerLine.match(/at (\\w+)/)[1]\r\n }\r\n if (DEBUG) {\r\n window.getCurrentFunctionName = getCurrentFunctionName\r\n }\r\n\r\n const jsParsed = performance.now()\r\n\r\n init()\r\n\r\n function registerKonamiCodeHandler () {\r\n const [u, d, l, r] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']\r\n const konamiCode = [u, u, d, d, l, r, l, r, 'b', 'a', 'Enter']\r\n let konamiCodeIndex = 0\r\n const handler = async (e) => {\r\n if (e.key === konamiCode[konamiCodeIndex]) {\r\n konamiCodeIndex++\r\n if (konamiCodeIndex === konamiCode.length) {\r\n showCrazyShit()\r\n window.removeEventListener('keydown', handler)\r\n }\r\n } else {\r\n konamiCodeIndex = 0\r\n }\r\n }\r\n window.addEventListener('keydown', handler, { passive: true })\r\n }\r\n if (DEBUG) {\r\n registerKonamiCodeHandler()\r\n }\r\n\r\n function showCrazyShit () {\r\n console.log('showing lunatic text')\r\n }\r\n})()\r\n"]} \ No newline at end of file +{"version":3,"sources":["src/js.js"],"names":[],"mappings":"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","file":"js.js","sourcesContent":[";(async function () {\n /* eslint-disable no-undef */\n const GAME_VERSION = 4\n const DEBUG = false\n const log = DEBUG ? (a) => { console.log(a); return a } : (a) => a\n const PROFILE = false\n\n const documentParsed = performance.now()\n\n const scriptHTML = document.currentScript.outerHTML\n const initialHTMLWithoutThisScript = document.body.innerHTML.replace(scriptHTML, '')\n\n let boardGenerationRetryCount = 0\n\n // Mapping integers to non-negative integers and vice versa\n function fold (x) {\n return x >= 0 ? 2 * x : -2 * x - 1\n }\n\n function unfold (y) {\n return y % 2 === 0 ? y / 2 : -(y + 1) / 2\n }\n\n function pairUnsigned (x, y) {\n return 0.5 * (x + y) * (x + y + 1) + y\n }\n\n function unpairUnsigned (z) {\n const w = Math.floor((Math.sqrt(8 * z + 1) - 1) / 2)\n const t = (w * w + w) / 2\n const y = z - t\n const x = w - y\n return [x, y]\n }\n\n function pairSigned (x, y) {\n const mappedX = fold(x)\n const mappedY = fold(y)\n const cantorResult = pairUnsigned(mappedX, mappedY)\n return unfold(cantorResult)\n }\n\n function unpairSigned (z) {\n const cantorInverse = unpairUnsigned(fold(z))\n return [unfold(cantorInverse[0]), unfold(cantorInverse[1])]\n }\n log(unpairSigned(42))\n\n async function fetchBody (url) {\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n return await response.text()\n }\n\n async function fetchLines (url) {\n return (await fetchBody(url)).split('\\n').filter(s => !s.startsWith('#')).map(s => s.trim())\n }\n\n const wordsetNames = await fetchLines('/data/wordsets.txt')\n const wordsets = []\n\n async function loadWordset (wordsetName) {\n const wordset = await fetchLines(`/data/wordsets/${wordsetName}.txt`)\n wordsets.push(wordset)\n return wordset\n }\n\n async function loadWordsetForStage (stage) {\n const index = stage % wordsetNames.length\n const wordset = wordsets[index] ?? await loadWordset(wordsetNames[index])\n return wordset\n }\n\n const wordListFull = []\n\n const random = {\n s1: 0,\n s2: 0,\n setSeed (seed) {\n this.s1 = seed\n this.s2 = seed\n },\n random () {\n this.s1 = (this.s1 * 1103515245 + 12345) & 2147483647\n this.s2 ^= this.s2 << 13\n this.s2 ^= this.s2 >> 17\n this.s2 ^= this.s2 << 5\n log('random.random')\n return log(((this.s1 ^ this.s2) + 2147483648) / 4294967295)\n }\n }\n\n function saveGameStateWhenIdle () {\n requestIdleCallback(() => {\n saveGameState()\n })\n }\n\n function saveGameState () {\n const currentStateSaved = load('gameState')\n if (currentStateSaved) {\n save('gameState', serializeGameState())\n }\n }\n\n const eventsToSave = ['keyup', 'pointerup', 'pointercancel', 'scrollend']\n\n async function init () {\n 'use strict'\n if (window.hwgInitialized) {\n throw new Error('Game was already initialized.')\n }\n\n window.hwgInitialized = true\n\n const stageElement = document.getElementById('stage')\n\n const dirMap = [\n [3, 2, 1],\n [4, -1, 0],\n [5, 6, 7]\n ]\n\n const [nfdChoBase, nfdJungBase, nfdJongBase] = [...'각'.normalize('NFD')]\n const simpleJungBase = 'ㅏ'\n const jungDiff = simpleJungBase.charCodeAt(0) - nfdJungBase.charCodeAt(0)\n\n const simpleToCompositeJamoMap = {\n γ„±γ„±: 'γ„²',\n γ„±γ……: 'γ„³',\n γ„΄γ…ˆ: 'γ„΅',\n γ„΄γ…Ž: 'γ„Ά',\n γ„·γ„·: 'γ„Έ',\n γ„Ήγ„±: 'γ„Ί',\n ㄹㅁ: 'γ„»',\n γ„Ήγ…‚: 'γ„Ό',\n γ„Ήγ……: 'γ„½',\n γ„Ήγ…Œ: 'γ„Ύ',\n ㄹㅍ: 'γ„Ώ',\n γ„Ήγ…Ž: 'γ…€',\n γ…‚γ…‚: 'γ…ƒ',\n γ…‚γ……: 'γ…„',\n γ……γ……: 'γ…†',\n γ…ˆγ…ˆ: 'γ…‰',\n ㅏㅣ: 'ㅐ',\n γ…‘γ…£: 'γ…’',\n γ…“γ…£: 'γ…”',\n γ…•γ…£: 'γ…–',\n ㅗㅏ: 'γ…˜',\n ㅗㅏㅣ: 'γ…™',\n γ…—γ…£: 'γ…š',\n γ…œγ…“: 'ㅝ',\n γ…œγ…“γ…£: 'γ…ž',\n γ…œγ…£: 'γ…Ÿ',\n γ…‘γ…£: 'γ…’'\n }\n\n const compositeToSimpleJamoMap = Object.fromEntries(Object.entries(simpleToCompositeJamoMap).map(([simple, composite]) => [composite, simple]))\n\n let randomHueBase = -1\n let stageNumber = 1\n let previousGameState\n try {\n previousGameState = deserializeGameState(load('gameState'))\n if (previousGameState) {\n stageNumber = previousGameState.stageNumber\n stageElement.textContent = stageNumber\n }\n } catch (e) {\n console.error(e)\n clear('gameState')\n }\n\n stageElement.textContent = stageNumber\n\n wordListFull.length = 0\n wordListFull.push(...await loadWordsetForStage(stageNumber))\n\n const seed = pairSigned(stageNumber, boardGenerationRetryCount)\n\n random.setSeed(seed)\n\n const cloned = [...wordListFull]\n\n const wordCount = 16\n\n function simpleJamoBreakdown (word) {\n return [...word.normalize('NFC')].flatMap(decomposeIntoSimple)\n }\n\n const wordList = []\n for (let i = 0; i < wordCount; i++) {\n wordList.push(...cloned.splice(randomInt(0, cloned.length - 1), 1))\n }\n cloned.length = 0\n\n const wordListElement = document.getElementById('word-list')\n const wordListTemplate = document.getElementById('word-template')\n wordList.forEach(word => {\n const li = wordListTemplate.content.cloneNode(true).querySelector('li')\n li.textContent = word\n li.dataset.word = word\n wordListElement.appendChild(li)\n })\n\n const jamoBoardElement = document.getElementById('jamo-board')\n const jamoBoardTemplate = document.getElementById('jamo-template')\n\n jamoBoardElement.addEventListener('selectstart', (e) => {\n e.preventDefault()\n })\n let isRightClick = false\n jamoBoardElement.addEventListener('contextmenu', (e) => {\n if (!isRightClick) {\n e.preventDefault()\n }\n isRightClick = false\n })\n\n const width = 12\n const height = 12\n\n const simpleJamoList = 'γ„±γ„΄γ„·γ„Ήγ…γ…‚γ……γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Žγ…γ…‘γ…“γ…•γ…—γ…›γ…œγ… γ…‘γ…£'\n\n function calculateChecksum (gs) {\n return [...gs].map(c => c.charCodeAt()).reduce((acc, val) => ((acc >>> 1) | ((acc & 1) << 15)) ^ val, 0)\n }\n\n function currentState () {\n const completions = Array.from(jamoBoardElement.querySelectorAll('.completion-bar')).filter((elem) => elem !== currentJamoCompletion).map(elem => {\n return `${elem.dataset.start},${elem.dataset.end}`\n }).join()\n\n return {\n GAME_VERSION,\n width,\n height,\n completions,\n stageNumber\n }\n }\n\n function serializeGameState (gsObject = currentState()) {\n const gsJSONString = JSON.stringify(gsObject)\n return `${gsJSONString}|${calculateChecksum(gsJSONString)}`\n }\n\n function groupElements (arr, numElements) {\n return arr.reduce((acc, val) => {\n if (!acc.length || acc.at(-1).length === numElements) {\n acc.push([])\n }\n acc.at(-1).push(val)\n return acc\n }, [])\n }\n\n function deserializeGameState (gsStringFull) {\n if (gsStringFull === null) {\n return null\n }\n const [gsObjectStr, checksum] = gsStringFull.split('|')\n const expectedChecksum = calculateChecksum(gsObjectStr)\n if (expectedChecksum !== checksum * 1) {\n throw new Error('saved game state is corrupted')\n }\n const gsObject = JSON.parse(gsObjectStr)\n if (gsObject.GAME_VERSION !== GAME_VERSION) {\n throw new Error('The saved game state is from a different version of the game.')\n }\n if (gsObject.completions) {\n gsObject.completions = groupElements(gsObject.completions.split(',').map(Number), 4)\n } else {\n gsObject.completions = []\n }\n return gsObject\n }\n\n function decomposeIntoSimple (char) {\n const [nfdCho, nfdJung, nfdJong] = [...char.normalize('NFD')].concat(['', '', '']).slice(0, 3)\n const simpleCho = 'γ„±γ„²γ„΄γ„·γ„Έγ„Ήγ…γ…‚γ…ƒγ……γ…†γ…‡γ…ˆγ…‰γ…Šγ…‹γ…Œγ…γ…Ž'[nfdCho.charCodeAt(0) - nfdChoBase.charCodeAt(0)]\n const simpleJung = String.fromCharCode(nfdJung.charCodeAt(0) + jungDiff)\n const simpleJong = nfdJong.length ? 'γ„±γ„²γ„³γ„΄γ„΅γ„Άγ„·γ„Ήγ„Ίγ„»γ„Όγ„½γ„Ύγ„Ώγ…€γ…γ…‚γ…„γ……γ…†γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Ž'[nfdJong.charCodeAt(0) - nfdJongBase.charCodeAt(0)] : ''\n return [simpleCho, simpleJung, simpleJong].flatMap(jamo => [...(compositeToSimpleJamoMap[jamo] ?? jamo)])\n }\n\n function composeIntoComposite (simpleJamo) { // @TODO: complete this function\n simpleJamo = [...simpleJamo]\n const hangulImeStateMachine = {\n cho: {\n γ„±: ['γ„±', 'jung'],\n γ„΄γ„Ήγ…γ…‡γ…Šγ…‹γ…Œγ…γ…Ž: ['jung'],\n γ„·: ['γ„·', 'jung'],\n γ…‚: ['γ…‚', 'jung'],\n γ……: ['γ……', 'jung'],\n γ…ˆ: ['γ…ˆ', 'jung']\n },\n jung: {\n ㅏㅑㅓㅕㅑ: ['γ…£', 'jong'],\n γ…—: ['ㅏ', 'γ…£', 'jong'],\n γ…›γ… γ…£: ['jong'],\n γ…œ: ['γ…“', 'γ…£', 'jong']\n },\n jong: {\n γ„±: ['γ„±', 'γ……', 'cho'],\n γ„΄: ['γ…ˆ', 'γ…Ž', 'cho'],\n γ„·γ…γ…‡γ…ˆγ…Šγ…‹γ…Œγ…γ…Ž: ['cho'],\n γ„Ή: ['γ„±', 'ㅁ', 'γ…‚', 'γ……', 'γ…Œ', 'ㅍ', 'γ…Ž', 'cho'],\n γ…‚: ['γ……', 'cho'],\n γ……: ['γ……', 'cho']\n }\n }\n const maxLengths = Object.freeze({\n cho: 2,\n jung: 3,\n jong: 2\n })\n const currentLengths = {\n cho: 0,\n jung: 0,\n jong: 0\n }\n // 'γ…‚γ…‚γ…œγ…“γ…£γ„Ήγ„±' -> [['γ…‚γ…‚'], ['γ…œ','γ…“','γ…£'], ['γ„Ή', 'γ„±']]\n let currentState = 'cho'\n const grouped = []\n const group = []\n let nextCandidate = null\n while (simpleJamo[0]) {\n const jamo = simpleJamo.shift() // 'γ…‚'\n group.push(jamo)\n currentLengths[currentState]++\n const transitionOptions = hangulImeStateMachine[currentState] // cho: { ... }\n const transition = Object.entries(transitionOptions).find(([jamoOptions, _]) => jamoOptions.includes(jamo))\n if (!transition) {\n throw new Error('cannot find suitable continuation for jamo sequence')\n }\n const [, targetStates] = transition // ['γ…‚', 'jung']\n if (targetStates.length === 1 || currentLengths[currentState] === maxLengths[currentState]) {\n currentLengths[currentState] = 0\n currentState = targetStates[0]\n grouped.push([...group])\n group.length = 0\n if (nextCandidate && !nextCandidate.includes(jamo)) {\n throw new Error('next candidate mismatch')\n }\n nextCandidate = null\n } else {\n nextCandidate = targetStates.slice(0, -1)\n }\n }\n\n return grouped\n }\n if (DEBUG) {\n window.composeIntoComposite = composeIntoComposite\n }\n\n const randomJamo = () => {\n log(randomJamo.name)\n return log(simpleJamoList[randomInt(0, simpleJamoList.length - 1)])\n }\n\n const simpleJamoFromWordList = wordList.flatMap(word => [...word.normalize('NFC')].flatMap(decomposeIntoSimple))\n const jamoFromWordList = () => {\n log(jamoFromWordList.name)\n return log(simpleJamoFromWordList[randomInt(0, simpleJamoFromWordList.length - 1)])\n }\n\n const gap = 0.75\n\n function noTransitionZone (fn, elem) {\n elem.classList.add('no-transition')\n fn()\n requestAnimationFrame(() => {\n requestAnimationFrame(() => { // 1 frame skip does not work in some cases\n elem.classList.remove('no-transition')\n })\n })\n }\n\n const fillJamoBoard = () => {\n jamoBoardElement.style.setProperty('--gap', `${gap}rem`)\n jamoBoardElement.style.setProperty('--width', width)\n jamoBoardElement.style.setProperty('--height', height)\n\n noTransitionZone(() => {\n for (let i = 0; i < width * height; i++) {\n const jamoElement = jamoBoardTemplate.content.cloneNode(true).querySelector('i')\n const jamo = randomInt(0, 1) ? randomJamo() : jamoFromWordList()\n jamoElement.dataset.jamo = jamo\n jamoBoardElement.appendChild(jamoElement)\n }\n }, jamoBoardElement)\n }\n\n fillJamoBoard()\n\n const jamoWrittenPositions = Array.from({ length: width * height }, () => null)\n\n const getPosition = (x, y, direction, progress) => {\n if (direction < 0 || direction > 7) {\n throw new RangeError('direction must be 0 to 7')\n }\n for (let dy = -1; dy <= 1; dy++) {\n for (let dx = -1; dx <= 1; dx++) {\n if (dirMap[dy + 1][dx + 1] === direction) {\n return [x + dx * progress, y + dy * progress]\n }\n }\n }\n }\n\n const jamoElements = jamoBoardElement.querySelectorAll('#jamo-board>i')\n\n jamoElements.forEach((jamoElement, jamoIndex) => {\n jamoElement.dataset.index = jamoIndex\n })\n\n /**\n *\n * @param {string} word\n * @param {number} x\n * @param {number} y\n * @param {number} direction 0 to 7, starting from towards east(right) 1/8 turn CCW each step\n */\n const writeWord = (word, x, y, direction) => {\n const breakdown = simpleJamoBreakdown(word)\n // first check without writing\n if (breakdown.length > width && breakdown.length > height) {\n throw new RangeError('word too long for board')\n }\n for (let i = 0; i < breakdown.length; i++) {\n const [targetX, targetY] = getPosition(x, y, direction, i)\n if (targetX < 0 || targetX >= width || targetY < 0 || targetY >= height) {\n return false\n }\n const jamoIndex = targetY * width + targetX\n const existingJamo = jamoWrittenPositions[jamoIndex]\n if (existingJamo && existingJamo !== breakdown[i]) {\n return false\n }\n }\n\n for (let i = 0; i < breakdown.length; i++) {\n const [targetX, targetY] = getPosition(x, y, direction, i)\n const jamo = breakdown[i]\n const jamoIndex = targetY * width + targetX\n jamoWrittenPositions[jamoIndex] = jamo\n const jamoElement = jamoElements[jamoIndex]\n jamoElement.dataset.jamo = jamo\n }\n\n return true\n }\n\n const cellSize = 2\n jamoBoardElement.style.setProperty('--size', `${cellSize}rem`)\n\n function memo (key, compute) {\n const cache = memo.cache ?? (memo.cache = new Map())\n if (cache.has(key)) {\n return cache.get(key)\n }\n const result = compute()\n cache.set(key, result)\n return result\n }\n\n function createCompletionBarElement (startX, startY, endX, endY, updateElement = null) {\n const completionBarTemplate = memo(createCompletionBarElement, () => document.getElementById('completion-bar-template'))\n const completionBarElement = updateElement ?? completionBarTemplate.content.cloneNode(true).querySelector('.completion-bar')\n const padding = 0.25\n\n completionBarElement.dataset.start = `${startX},${startY}`\n completionBarElement.dataset.end = `${endX},${endY}`\n\n const sdx = Math.sign(endX - startX)\n const sdy = Math.sign(endY - startY)\n const xMin = Math.min(startX, endX)\n const xMax = Math.max(startX, endX)\n const yMin = Math.min(startY, endY)\n const yMax = Math.max(startY, endY)\n const top = yMin * cellSize + (gap * yMin)\n const left = xMin * cellSize + (gap * xMin)\n const width = (xMax - xMin + 1) * cellSize + (gap * (xMax - xMin))\n const height = (yMax - yMin + 1) * cellSize + (gap * (yMax - yMin))\n\n completionBarElement.style.setProperty('--top', `${top}rem`)\n completionBarElement.style.setProperty('--left', `${left}rem`)\n completionBarElement.style.setProperty('--width', `${width}rem`)\n completionBarElement.style.setProperty('--height', `${height}rem`)\n\n const hypot = Math.hypot(width, height) + padding\n const angle = Math.atan2(sdy * height, sdx * width) * 180 / Math.PI\n completionBarElement.style.setProperty('--thick', `${cellSize + padding}rem`)\n completionBarElement.style.setProperty('--hypot', `${hypot}rem`)\n completionBarElement.style.setProperty('--angle', `${angle}deg`)\n const randomHue = Math.floor(randomFromCoords(startX, startY) * colorSteps) * Math.floor(360 / colorSteps)\n completionBarElement.style.setProperty('--hue', randomHueBase + randomHue)\n\n return completionBarElement\n }\n\n let foundWords = 0\n\n const stageClearDialog = document.getElementById('stage-clear-dialog')\n const yesButton = stageClearDialog.querySelector('#next-stage')\n const noButton = stageClearDialog.querySelector('#cancel-next-stage')\n yesButton.addEventListener('click', () => {\n boardGenerationRetryCount = 0\n const currentGameState = currentState()\n save('gameState', serializeGameState({ ...currentGameState, completions: '', stageNumber: currentGameState.stageNumber + 1 }))\n reset()\n }, { passive: true })\n noButton.addEventListener('click', () => {\n stageClearDialog.close()\n }, { passive: true })\n\n function prefetchNextStageWordset () {\n loadWordsetForStage(stageNumber + 1)\n }\n\n function markWordAsFound (wordElement, completionBarElement) {\n foundWords++\n wordElement.style.setProperty('--hue', completionBarElement.style.getPropertyValue('--hue'))\n wordElement.classList.add('found')\n if (foundWords === wordCount) {\n stageClearDialog.showModal()\n prefetchNextStageWordset()\n }\n }\n\n function markCompletionAsCompleted (startX, startY, endX, endY) {\n const completionBarElement = createCompletionBarElement(startX, startY, endX, endY)\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\n const foundWord = findWordByJamoSequence(jamoSequence)\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\n markWordAsFound(wordElement, completionBarElement)\n jamoBoardElement.appendChild(completionBarElement)\n }\n\n function randomInt (min, max) {\n log(randomInt.name)\n log([min, max])\n return log(Math.floor(random.random() * (max - min + 1)) + min)\n }\n\n const colorSteps = 16\n randomHueBase = randomInt(0, 359)\n\n function randomFromCoords (x, y) {\n const dot = x * 12.9898 + y * 78.233\n return (Math.sin(dot) * 43758.5453) % 1\n }\n\n const easyDirection = true\n\n const numDirections = easyDirection ? 4 : 8\n const directionsProbabilityDist = Array.from({ length: numDirections }, () => wordCount)\n let sum = wordCount * numDirections\n\n const getDirection = () => {\n const randomUniform = randomInt(0, sum - 1)\n let temp = 0\n for (let i = 0; i < numDirections; i++) {\n temp += directionsProbabilityDist[i]\n if (randomUniform < temp) {\n return i\n }\n }\n }\n\n try {\n wordList.toSorted((a, b) => {\n return simpleJamoBreakdown(b).length - simpleJamoBreakdown(a).length\n }).forEach((word) => {\n let x\n let y\n let direction\n let repeated = 0\n while (true) {\n x = randomInt(0, width - 1)\n y = randomInt(0, height - 1)\n direction = getDirection()\n const directionCorrected = easyDirection ? ((direction + 6) % 8) : direction\n const success = writeWord(word, x, y, directionCorrected)\n if (success) {\n directionsProbabilityDist[direction]--\n sum--\n if (wordCount - directionsProbabilityDist[direction] > wordCount / 3) {\n sum -= directionsProbabilityDist[direction]\n directionsProbabilityDist[direction] = 0\n }\n break\n }\n if (repeated > wordCount ** 2) {\n boardGenerationRetryCount++\n throw new Error('Failed to populate a word to the board. Try increasing the size of the board or reducing the number of words. Retrying...')\n }\n repeated++\n }\n })\n } catch (e) {\n console.error(e)\n reset()\n return\n }\n\n if (previousGameState) {\n previousGameState.completions.forEach(([startX, startY, endX, endY]) => {\n markCompletionAsCompleted(startX, startY, endX, endY)\n })\n }\n\n // add event listener for dark mode toggle\n const darkModeToggleButton = document.getElementById('dark-mode-toggle')\n\n const toggleModes = () => {\n const mode = darkModeToggleButton.dataset.mode\n const options = darkModeToggleButton.dataset.modeOptions.split('|')\n const nextMode = options[(options.indexOf(mode) + 1) % options.length]\n const startViewTransition = (change) => document.startViewTransition ? document.startViewTransition(change) : change()\n startViewTransition(() => {\n darkModeToggleButton.dataset.mode = nextMode\n document.documentElement.dataset.mode = nextMode\n })\n localStorage.darkMode = nextMode\n }\n darkModeToggleButton.addEventListener('click', toggleModes, { passive: true })\n const darkMode = localStorage.darkMode\n if (darkMode) {\n noTransitionZone(() => {\n darkModeToggleButton.dataset.mode = darkMode\n document.documentElement.dataset.mode = darkMode\n }, darkModeToggleButton)\n }\n\n const pointerdown = new Proxy({ value: false }, {\n set: (target, prop, value) => {\n if (prop === 'value' && typeof value === 'boolean') {\n if (DEBUG) {\n Array.from(jamoBoardElement.querySelectorAll('.start, .mid, .end')).forEach(elem => elem.classList.remove('start', 'mid', 'end'))\n }\n return Reflect.set(target, prop, value)\n }\n }\n })\n\n let dragStartPos = [-1, -1]\n let dragDir = -1\n let dragEndPos = [-1, -1]\n\n function isOctilinear (origin, target) {\n const [ox, oy] = origin\n const [tx, ty] = target\n const isOrthogonal = ox === tx || oy === ty\n const isDiagonal = Math.abs(ox - tx) === Math.abs(oy - ty)\n if (!(isOrthogonal || isDiagonal)) {\n return -1\n }\n const [dx, dy] = [tx - ox, ty - oy]\n return dirMap[Math.sign(dy) + 1][Math.sign(dx) + 1]\n }\n\n function getClosestOctilinearPoint (origin, target, dir) {\n const [ox, oy] = origin\n const [tx, ty] = target\n const [dx, dy] = [tx - ox, ty - oy]\n let [dxO, dyO] = [tx - ox, ty - oy]\n let [dxD, dyD] = [tx - ox, ty - oy]\n\n // orthogonal\n if (Math.abs(dxO) < Math.abs(dyO)) {\n dxO = 0\n } else {\n dyO = 0\n }\n\n // diagonal\n if ((dxD + dyD) % 2) {\n // parity mismatch adjustment\n if (Math.abs(dxD) < Math.abs(dyD)) {\n dyD -= Math.sign(dyD)\n } else {\n dxD -= Math.sign(dxD)\n }\n }\n {\n const dist = Math.abs(Math.abs(dxD) - Math.abs(dyD)) / 2\n if (Math.abs(dxD) < Math.abs(dyD)) {\n dxD += Math.sign(dxD) * dist\n dyD -= Math.sign(dyD) * dist\n } else {\n dxD -= Math.sign(dxD) * dist\n dyD += Math.sign(dyD) * dist\n }\n }\n\n const distO = Math.abs(dx - dxO) + Math.abs(dy - dyO)\n const distD = Math.abs(dx - dxD) + Math.abs(dy - dyD)\n\n if (distO === distD) {\n return dir % 2 === 0 ? [ox + dxO, oy + dyO] : [ox + dxD, oy + dyD]\n } else if (distO < distD) {\n return [ox + dxO, oy + dyO]\n } else {\n return [ox + dxD, oy + dyD]\n }\n }\n\n let currentJamoCompletion = null\n\n function updateJamoCompletion () {\n if (!(dragStartPos[0] !== -1 && dragStartPos[1] !== -1)) {\n return\n }\n const exists = currentJamoCompletion !== null\n const [sx, sy] = dragStartPos\n const [ex, ey] = dragEndPos[0] === -1 && dragEndPos[1] === -1 ? [sx, sy] : dragEndPos\n currentJamoCompletion = createCompletionBarElement(sx, sy, ex, ey, currentJamoCompletion)\n if (!exists) {\n jamoBoardElement.appendChild(currentJamoCompletion)\n }\n }\n\n function createRange (start, end) {\n const inc = start < end ? 1 : -1\n const result = []\n for (let i = 0; i <= Math.abs(end - start); i++) {\n result.push(i * inc + start)\n }\n return result\n }\n\n function completionToJamoSequence (startX, startY, endX, endY) {\n const rangeX = createRange(startX, endX)\n const rangeY = createRange(startY, endY)\n const longer = Math.max(rangeX.length, rangeY.length)\n const coords = Array.from({ length: longer }, (_, i) => [rangeX[i] ?? startX, rangeY[i] ?? startY])\n return coords.map(([x, y]) => jamoElements[y * width + x].dataset.jamo).join('')\n }\n\n function findWordByJamoSequence (jamoSequence) {\n return wordList.find(word => {\n const [simple, reversed] = memo(word, () => {\n const simple = simpleJamoBreakdown(word)\n return [simple.join(''), simple.toReversed().join('')]\n })\n return simple === jamoSequence || reversed === jamoSequence\n })\n }\n\n function checkJamoCompletion (jamoCompletionElement) {\n try {\n if (jamoCompletionElement === null) {\n return\n }\n const [startX, startY] = jamoCompletionElement.dataset.start.split(',').map(Number)\n const [endX, endY] = jamoCompletionElement.dataset.end.split(',').map(Number)\n if (startX === endX && startY === endY) {\n jamoCompletionElement.remove()\n return\n }\n const dir = isOctilinear([startX, startY], [endX, endY])\n if (dir === -1) {\n jamoCompletionElement.remove()\n return\n }\n const jamoSequence = completionToJamoSequence(startX, startY, endX, endY)\n const foundWord = findWordByJamoSequence(jamoSequence)\n if (foundWord) {\n const wordElement = wordListElement.querySelector(`li[data-word=\"${foundWord}\"]`)\n if (wordElement) {\n if (wordElement.classList.contains('found')) {\n jamoCompletionElement.remove()\n } else {\n markWordAsFound(wordElement, jamoCompletionElement)\n }\n }\n } else {\n jamoCompletionElement.remove()\n }\n } finally {\n currentJamoCompletion = null\n }\n }\n\n jamoBoardElement.addEventListener('pointerdown', (e) => {\n if (!e.target.matches('#jamo-board>i')) {\n return\n }\n pointerdown.value = true\n dragStartPos = calculateCellPosFromCoords(e.clientX, e.clientY)\n if (dragStartPos[0] < 0 || dragStartPos[0] >= width || dragStartPos[1] < 0 || dragStartPos[1] >= height) {\n dragStartPos = [-1, -1]\n return\n }\n dragEndPos = [-1, -1]\n dragDir = -1\n updateJamoCompletion()\n }, { passive: true })\n\n document.addEventListener('pointerup', (e) => {\n isRightClick = e.button === 2\n pointerdown.value = false\n if (dragStartPos[0] === dragEndPos[0] && dragStartPos[1] === dragEndPos[1]) {\n currentJamoCompletion?.remove()\n currentJamoCompletion = null\n } else if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\n checkJamoCompletion(currentJamoCompletion)\n }\n }, { passive: true })\n\n function calculateOvershoot (value, min, max) {\n if (value < min) {\n return min - value\n }\n if (value > max) {\n return value - max\n }\n return 0\n }\n\n function calculateCellSize () {\n const firstElement = jamoElements[0]\n const rightElement = jamoElements[1]\n const bottomElement = jamoElements[width]\n const firstRect = firstElement.getBoundingClientRect()\n const rightRect = rightElement.getBoundingClientRect()\n const bottomRect = bottomElement.getBoundingClientRect()\n const cellInteractiveWidth = rightRect.left - firstRect.left\n const cellInteractiveHeight = bottomRect.top - firstRect.top\n const left = firstRect.left\n const top = firstRect.top\n const gapX = rightRect.left - firstRect.right\n const gapY = bottomRect.top - firstRect.bottom\n return [left, top, cellInteractiveWidth, cellInteractiveHeight, gapX, gapY]\n }\n\n function calculateCellPosFromCoords (clientX, clientY) {\n const [left, top, cellWidth, cellHeight, gapX, gapY] = calculateCellSize()\n const boardLeft = left - gapX / 2\n const boardTop = top - gapY / 2\n const [relativeX, relativeY] = [clientX - boardLeft, clientY - boardTop]\n const [cellX, cellY] = [Math.floor(relativeX / cellWidth), Math.floor(relativeY / cellHeight)]\n return [cellX, cellY]\n }\n\n function clamp (value, min, max) {\n return Math.min(max, Math.max(min, value))\n }\n\n document.addEventListener('pointermove', (e) => {\n if (!pointerdown.value) {\n return\n }\n e.preventDefault()\n const pos = calculateCellPosFromCoords(e.clientX, e.clientY)\n pos[0] = clamp(pos[0], 0, width - 1)\n pos[1] = clamp(pos[1], 0, height - 1)\n if (dragStartPos[0] === pos[0] && dragStartPos[1] === pos[1]) {\n return\n }\n const dir = isOctilinear(dragStartPos, pos)\n if (dir !== -1) {\n dragEndPos = pos\n dragDir = dir\n } else if (dragEndPos[0] !== -1 && dragEndPos[1] !== -1) {\n dragEndPos = getClosestOctilinearPoint(dragStartPos, pos, dragDir)\n }\n if (!(dragEndPos[0] !== -1 && dragEndPos[1] !== -1)) {\n return\n }\n let [cx, cy] = dragEndPos\n if (cx < 0 || cx >= width || cy < 0 || cy >= height) {\n const correctedDragDir = isOctilinear(dragStartPos, [cx, cy])\n const overshootX = calculateOvershoot(cx, 0, width - 1)\n const overshootY = calculateOvershoot(cy, 0, height - 1)\n const maxOvershoot = Math.max(overshootX, overshootY);\n [cx, cy] = getPosition(cx, cy, correctedDragDir, -maxOvershoot)\n }\n dragEndPos = [cx, cy]\n\n const closestIndex = dragEndPos[1] * width + dragEndPos[0]\n const closestElement = jamoElements[closestIndex]\n if (DEBUG) {\n jamoBoardElement.querySelector('.end')?.classList.remove('end')\n closestElement.classList.add('end')\n }\n updateJamoCompletion()\n })\n\n if (!previousGameState) {\n save('gameState', serializeGameState())\n }\n\n window.serializeGameState = serializeGameState\n\n const mainElement = document.querySelector('main')\n\n const resizeToFit = () => {\n const screenWidth = screen.availWidth\n const screenHeight = screen.availHeight\n\n mainElement.style.transform = 'scale(1)'\n mainElement.style.margin = '0'\n\n const { width, height } = mainElement.getBoundingClientRect()\n\n const zoomFactor = Math.min(1, screenWidth / width, screenHeight / height)\n\n mainElement.style.transform = `scale(${zoomFactor})`\n mainElement.style.margin = `${(height * (zoomFactor - 1)) / 2}px ${(width * (zoomFactor - 1)) / 2}px`\n }\n resizeToFit()\n window.addEventListener('resize', resizeToFit, { passive: true })\n\n eventsToSave.forEach(event => {\n document.addEventListener(event, saveGameStateWhenIdle, { passive: true })\n })\n\n window.addEventListener('beforeunload', () => {\n saveGameState()\n }, { passive: true })\n\n function u2b (str) {\n const encoder = new TextEncoder()\n const encodedData = encoder.encode(str)\n const binaryString = String.fromCharCode(...encodedData)\n return btoa(binaryString)\n }\n\n function b2u (base64) {\n const binaryString = atob(base64)\n const encodedData = new Uint8Array(binaryString.split('').map(char => char.charCodeAt(0)))\n const decoder = new TextDecoder()\n return decoder.decode(encodedData)\n }\n\n const settingsPanel = document.getElementById('settings-panel')\n const exportText = document.getElementById('export-game-state-plaintext')\n\n document.getElementById('show-settings-panel').addEventListener('click', () => {\n settingsPanel.showModal()\n // base64 encode the game state\n saveGameState()\n exportText.value = u2b(load('gameState'))\n })\n\n const successReportButtons = document.querySelectorAll('button[data-success-report]')\n successReportButtons.forEach(button => {\n button.addEventListener('click', () => {\n if (!button.successReport) {\n const successReport = document.getElementById('success-report-template').content.cloneNode(true).querySelector('input')\n successReport.setCustomValidity(button.dataset.successReport)\n button.appendChild(successReport)\n button.successReport = successReport\n }\n button.successReport.reportValidity()\n })\n })\n\n document.getElementById('copy-game-state-plaintext').addEventListener('click', () => {\n navigator.clipboard.writeText(exportText.value)\n })\n\n document.getElementById('download-game-state-plaintext').addEventListener('click', () => {\n const a = document.createElement('a')\n const blob = new Blob([exportText.value], { type: 'text/plain' })\n a.href = URL.createObjectURL(blob)\n a.download = `save-${new Date().toISOString().replace(/\\D/g, '')}.hwgsave`\n a.hidden = true\n document.body.appendChild(a)\n a.click()\n a.remove()\n URL.revokeObjectURL(a.href)\n })\n\n const importText = document.getElementById('import-game-state-plaintext')\n\n const fileWrapperLabel = document.getElementById('file-wrapper')\n\n function reportError (message) {\n importText.setCustomValidity(message)\n importText.reportValidity()\n }\n\n document.getElementById('select-game-state-file').addEventListener('input', (e) => {\n const file = e.target.files[0]\n fileWrapperLabel.dataset.filename = file.name\n const reader = new FileReader()\n reader.onload = (e) => {\n const result = e.target.result\n try {\n const gameStateText = b2u(result)\n deserializeGameState(gameStateText)\n } catch (err) {\n console.error('Failed to parse game state from file', err)\n reportError('μ„ νƒν•œ 파일의 ꡬ문 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.')\n }\n importText.value = result\n }\n reader.readAsText(file)\n })\n\n document.getElementById('apply-imported-game-state').addEventListener('click', () => {\n try {\n const gameStateText = b2u(importText.value)\n deserializeGameState(gameStateText)\n save('gameState', gameStateText)\n reset()\n } catch (err) {\n console.error('Failed to parse game state from text', err)\n reportError('μ˜¬λ°”λ₯Έ ν˜•μ‹μ˜ κ²Œμž„ μƒνƒœ ν…μŠ€νŠΈκ°€ μ•„λ‹™λ‹ˆλ‹€.')\n }\n })\n\n document.querySelectorAll('dialog').forEach(dialog => {\n dialog.addEventListener(\n 'click',\n (event) => {\n const rect = dialog.getBoundingClientRect()\n if (!dialog.open) {\n return\n }\n if (rect.left > event.clientX ||\n rect.right < event.clientX ||\n rect.top > event.clientY ||\n rect.bottom < event.clientY\n ) {\n // show bump animation\n dialog.classList.remove('bump')\n requestAnimationFrame(() => {\n dialog.classList.add('bump')\n })\n }\n }\n )\n dialog.addEventListener('close', () => {\n dialog.classList.remove('bump')\n })\n })\n\n const minimumPressDuration = 2000\n\n let timeout = null\n\n const clearButton = document.getElementById('clear-game-state')\n clearButton.addEventListener('contextmenu', (e) => {\n e.preventDefault()\n })\n clearButton.addEventListener('pointerdown', () => {\n timeout = setTimeout(() => {\n clear('gameState')\n location.reload()\n }, minimumPressDuration)\n })\n clearButton.addEventListener('pointerup', () => {\n clearTimeout(timeout)\n })\n clearButton.addEventListener('pointerleave', () => {\n clearTimeout(timeout)\n })\n\n // fix dialog forms with sandboxed document\n const dialogCloseButtons = document.querySelectorAll('form[method=dialog]>button[type=submit]')\n dialogCloseButtons.forEach(button => {\n button.addEventListener('click', (e) => {\n e.preventDefault()\n button.closest('dialog')?.close?.()\n })\n })\n\n // @TODO: show relevant image for each word after finding it\n // requestIdleCallback(() => {\n // // preload images\n // const imageElements = wordList.map(word => {\n // const img = document.createElement('img')\n // img.src = `/images/wordsets/0/${word}.webp`\n // return img\n // })\n // imageElements.forEach(img => img.decode().then(() => {\n // console.log('decoded', img.src)\n // }).catch(() => {\n // console.log('failed to decode', img.src)\n // }))\n // })\n\n const gameInitialized = performance.now()\n\n if (PROFILE) {\n requestIdleCallback(() => {\n const settled = performance.now()\n\n const t1 = documentParsed - begin\n const t2 = jsParsed - documentParsed\n const t3 = gameInitialized - jsParsed\n const t4 = settled - gameInitialized\n const t5 = settled - begin\n\n const perfHistory = load('perfHistory', [])\n perfHistory.push([t1, t2, t3, t4, t5])\n save('perfHistory', perfHistory)\n })\n }\n }\n\n function reset () {\n const children = [...document.body.children].filter(elem => elem !== document.currentScript)\n children.forEach(child => child.remove())\n document.body.innerHTML = initialHTMLWithoutThisScript\n window.hwgInitialized = false\n\n eventsToSave.forEach(event => {\n document.removeEventListener(event, saveGameStateWhenIdle)\n })\n\n init()\n }\n\n function load (key, fallback) {\n const data = localStorage[key]\n if (typeof data === 'string') {\n return JSON.parse(data)\n }\n return fallback ?? null\n }\n\n function clear (key) {\n delete localStorage[key]\n }\n\n function save (key, value) {\n localStorage[key] = JSON.stringify(value)\n }\n\n function average (arr) {\n return arr.reduce((acc, val) => acc + val, 0) / arr.length\n }\n if (DEBUG) {\n window.average = average\n }\n\n function median (arr) {\n const sorted = arr.slice().sort((a, b) => a - b)\n const mid = Math.floor(sorted.length / 2)\n if (sorted.length % 2 === 0) {\n return (sorted[mid - 1] + sorted[mid]) / 2\n }\n return sorted[mid]\n }\n if (DEBUG) {\n window.median = median\n }\n\n function getCurrentFunctionName () {\n const currentStackRaw = new Error().stack\n const callerLine = currentStackRaw.split('\\n').slice(1)[1]\n return callerLine.match(/at (\\w+)/)[1]\n }\n if (DEBUG) {\n window.getCurrentFunctionName = getCurrentFunctionName\n }\n\n const jsParsed = performance.now()\n\n init()\n\n function registerKonamiCodeHandler () {\n const [u, d, l, r] = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']\n const konamiCode = [u, u, d, d, l, r, l, r, 'b', 'a', 'Enter']\n let konamiCodeIndex = 0\n const handler = async (e) => {\n if (e.key === konamiCode[konamiCodeIndex]) {\n konamiCodeIndex++\n if (konamiCodeIndex === konamiCode.length) {\n showCrazyShit()\n window.removeEventListener('keydown', handler)\n }\n } else {\n konamiCodeIndex = 0\n }\n }\n window.addEventListener('keydown', handler, { passive: true })\n }\n if (DEBUG) {\n registerKonamiCodeHandler()\n }\n\n function showCrazyShit () {\n console.log('showing lunatic text')\n }\n})()\n"]} \ No newline at end of file diff --git a/src/css.css b/src/css.css index 9170af3..7dfd719 100644 --- a/src/css.css +++ b/src/css.css @@ -305,18 +305,25 @@ main>* { } #file-wrapper::after { - content: attr(data-text); /* Text below emoji */ + content: attr(data-text); + font-size: 14px; display: block; - font-size: 14px; /* Adjust text size */ + bottom: 50%; + position: absolute; + width: 100%; + translate: 0 calc(50% + 1.5lh); +} +#file-wrapper[data-filename]::after { + content: attr(data-filename); } ::backdrop { background: #0004; } - #clear-game-state { position: relative; + user-select: none; --pressed: 0; } @@ -350,3 +357,38 @@ main>* { nav>div:has(#stage) { white-space: nowrap; } + +body:has(dialog[open]) { + overflow: hidden; + scrollbar-gutter: stable; +} + +dialog[open].bump { + animation: bump linear 0.2s; +} + +@keyframes bump { + 0% { + transform: scale(1.01); + } + to { + transform: scale(1); + } +} + +:has(input.success-report) { + position: relative; +} + +input.success-report { + opacity: 0; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + user-select: none; + -webkit-tap-highlight-color: transparent; + text-align: center; +} diff --git a/src/index.html b/src/index.html index c382c64..36004ee 100644 --- a/src/index.html +++ b/src/index.html @@ -9,7 +9,7 @@ - + @@ -45,20 +45,20 @@

κ²Œμž„ μ„€μ •

κ²Œμž„ μƒνƒœ 내보내기 - +
- +
κ²Œμž„ μƒνƒœ 뢈러였기
λ˜λŠ”
- +
@@ -77,6 +77,9 @@

λͺ¨λ‘ 찾음!

- + + diff --git a/src/js.js b/src/js.js index 5b4d3e0..1fa1ad1 100644 --- a/src/js.js +++ b/src/js.js @@ -92,6 +92,21 @@ } } + function saveGameStateWhenIdle () { + requestIdleCallback(() => { + saveGameState() + }) + } + + function saveGameState () { + const currentStateSaved = load('gameState') + if (currentStateSaved) { + save('gameState', serializeGameState()) + } + } + + const eventsToSave = ['keyup', 'pointerup', 'pointercancel', 'scrollend'] + async function init () { 'use strict' if (window.hwgInitialized) { @@ -902,22 +917,130 @@ resizeToFit() window.addEventListener('resize', resizeToFit, { passive: true }) + eventsToSave.forEach(event => { + document.addEventListener(event, saveGameStateWhenIdle, { passive: true }) + }) + window.addEventListener('beforeunload', () => { - const beforeunload = load('beforeunload', 0) - save('beforeunload', beforeunload + 1) - const currentStateSaved = load('gameState') - if (currentStateSaved) { - save('gameState', serializeGameState()) - } + saveGameState() }, { passive: true }) - window.addEventListener('pagehide', () => { - const pagehide = load('pagehide', 0) - save('pagehide', pagehide + 1) - }, { passive: true }) + function u2b (str) { + const encoder = new TextEncoder() + const encodedData = encoder.encode(str) + const binaryString = String.fromCharCode(...encodedData) + return btoa(binaryString) + } + + function b2u (base64) { + const binaryString = atob(base64) + const encodedData = new Uint8Array(binaryString.split('').map(char => char.charCodeAt(0))) + const decoder = new TextDecoder() + return decoder.decode(encodedData) + } + + const settingsPanel = document.getElementById('settings-panel') + const exportText = document.getElementById('export-game-state-plaintext') document.getElementById('show-settings-panel').addEventListener('click', () => { - document.getElementById('settings-panel').showModal() + settingsPanel.showModal() + // base64 encode the game state + saveGameState() + exportText.value = u2b(load('gameState')) + }) + + const successReportButtons = document.querySelectorAll('button[data-success-report]') + successReportButtons.forEach(button => { + button.addEventListener('click', () => { + if (!button.successReport) { + const successReport = document.getElementById('success-report-template').content.cloneNode(true).querySelector('input') + successReport.setCustomValidity(button.dataset.successReport) + button.appendChild(successReport) + button.successReport = successReport + } + button.successReport.reportValidity() + }) + }) + + document.getElementById('copy-game-state-plaintext').addEventListener('click', () => { + navigator.clipboard.writeText(exportText.value) + }) + + document.getElementById('download-game-state-plaintext').addEventListener('click', () => { + const a = document.createElement('a') + const blob = new Blob([exportText.value], { type: 'text/plain' }) + a.href = URL.createObjectURL(blob) + a.download = `save-${new Date().toISOString().replace(/\D/g, '')}.hwgsave` + a.hidden = true + document.body.appendChild(a) + a.click() + a.remove() + URL.revokeObjectURL(a.href) + }) + + const importText = document.getElementById('import-game-state-plaintext') + + const fileWrapperLabel = document.getElementById('file-wrapper') + + function reportError (message) { + importText.setCustomValidity(message) + importText.reportValidity() + } + + document.getElementById('select-game-state-file').addEventListener('input', (e) => { + const file = e.target.files[0] + fileWrapperLabel.dataset.filename = file.name + const reader = new FileReader() + reader.onload = (e) => { + const result = e.target.result + try { + const gameStateText = b2u(result) + deserializeGameState(gameStateText) + } catch (err) { + console.error('Failed to parse game state from file', err) + reportError('μ„ νƒν•œ 파일의 ꡬ문 뢄석에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.') + } + importText.value = result + } + reader.readAsText(file) + }) + + document.getElementById('apply-imported-game-state').addEventListener('click', () => { + try { + const gameStateText = b2u(importText.value) + deserializeGameState(gameStateText) + save('gameState', gameStateText) + reset() + } catch (err) { + console.error('Failed to parse game state from text', err) + reportError('μ˜¬λ°”λ₯Έ ν˜•μ‹μ˜ κ²Œμž„ μƒνƒœ ν…μŠ€νŠΈκ°€ μ•„λ‹™λ‹ˆλ‹€.') + } + }) + + document.querySelectorAll('dialog').forEach(dialog => { + dialog.addEventListener( + 'click', + (event) => { + const rect = dialog.getBoundingClientRect() + if (!dialog.open) { + return + } + if (rect.left > event.clientX || + rect.right < event.clientX || + rect.top > event.clientY || + rect.bottom < event.clientY + ) { + // show bump animation + dialog.classList.remove('bump') + requestAnimationFrame(() => { + dialog.classList.add('bump') + }) + } + } + ) + dialog.addEventListener('close', () => { + dialog.classList.remove('bump') + }) }) const minimumPressDuration = 2000 @@ -925,6 +1048,9 @@ let timeout = null const clearButton = document.getElementById('clear-game-state') + clearButton.addEventListener('contextmenu', (e) => { + e.preventDefault() + }) clearButton.addEventListener('pointerdown', () => { timeout = setTimeout(() => { clear('gameState') @@ -986,6 +1112,11 @@ children.forEach(child => child.remove()) document.body.innerHTML = initialHTMLWithoutThisScript window.hwgInitialized = false + + eventsToSave.forEach(event => { + document.removeEventListener(event, saveGameStateWhenIdle) + }) + init() }