commit f51e999744f459bf9e212428560413f662c493f9
parent a6887112071801c1356856a444ee92359fbfa90e
Author: brookjeynes <me@brookjeynes.dev>
Date: Mon, 1 Jun 2026 19:32:07 +1000
feat: add time questions
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
| M | index.html | | | 9 | +++++---- |
| M | src/store.js | | | 132 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------- |
2 files changed, 103 insertions(+), 38 deletions(-)
diff --git a/index.html b/index.html
@@ -17,7 +17,8 @@
@click="$store.app.changeTab('sino')">한자어</button>
<button role="tab" :aria-selected="$store.app.tab === 'native'" id="panel-native"
@click="$store.app.changeTab('native')">고유어</button>
- <button role="tab" id="panel-time" disabled>시간</button>
+ <button role="tab" :aria-selected="$store.app.tab === 'time'" id="panel-time"
+ @click="$store.app.changeTab('time')">시간</button>
<button role="tab" id="panel-date" disabled>날짜</button>
<button role="tab" id="panel-counter" disabled>단위</button>
</div>
@@ -85,12 +86,12 @@
<label class="prompt">
<template x-if="$store.app.mode === 'number'">
<span>다음을 한글로 쓰세요.
- <span class="question" x-text="$store.app.number"></span>
+ <span class="question" x-text="$store.app.question"></span>
</span>
</template>
<template x-if="$store.app.mode === 'hangeul'">
<span>다음을 숫자로 쓰세요.
- <span class="question" x-text="$store.app.string"></span>
+ <span class="question" x-text="$store.app.question"></span>
</span>
</template>
<br>
@@ -120,7 +121,7 @@
<footer>
<a href="index.html">숫자마스터</a>
·
- <a href="https://tangled.org/brookjeynes.dev/number-master">source</a>
+ <a href="https://git.brookjeynes.dev/number-master">source</a>
</footer>
<script type="module" src="src/main.js"></script>
diff --git a/src/store.js b/src/store.js
@@ -33,6 +33,12 @@ import { sinoStringFromNumber } from "./sino.js";
* @property {number} max
*/
+/**
+ * @typedef {Object} Time
+ * @property {number} hour
+ * @property {number} minute
+ */
+
const DEFAULT_CONFIG = {
mode: "number",
min: 1,
@@ -41,7 +47,7 @@ const DEFAULT_CONFIG = {
window.Alpine.store("app", {
/** @type {number} */
- number: 0,
+ num: 0,
/** @type {number} */
min: DEFAULT_CONFIG.min,
/** @type {number} */
@@ -56,56 +62,91 @@ window.Alpine.store("app", {
rangeError: null,
/** @type {boolean} */
configSaved: false,
+ /** @type {Time} */
+ time: {
+ hour: 12,
+ minute: 30,
+ },
init() {
this.configSaved = false;
this.rangeError = null;
+ this.parseConfig();
+ this.onRangeChange();
+ this.newQuestion();
+ },
+ parseConfig() {
const persistentConfig = localStorage.getItem("numbermasterconfig");
- if (persistentConfig) {
- const config = JSON.parse(persistentConfig);
-
- const min = parseInt(config.min, 10);
- const max = parseInt(config.max, 10);
- switch (config.mode) {
- case "hangeul":
- this.mode = "hangeul";
- break;
- case "number":
- this.mode = "number";
- break;
- default:
- this.mode = DEFAULT_CONFIG.mode;
- console.error("unknown mode selected");
- }
-
- this.min = isNaN(min) ? DEFAULT_CONFIG.min : min;
- this.max = isNaN(max) ? DEFAULT_CONFIG.max : max;
+ if (!persistentConfig) return
+
+ /** @type {Config} */
+ const config = JSON.parse(persistentConfig);
+
+ /** @type {number} */
+ const min = parseInt(config.min, 10);
+ /** @type {number} */
+ const max = parseInt(config.max, 10);
+ switch (config.mode) {
+ case "hangeul":
+ this.mode = "hangeul";
+ break;
+ case "number":
+ this.mode = "number";
+ break;
+ default:
+ this.mode = DEFAULT_CONFIG.mode;
+ console.error("unknown mode selected");
}
- this.onRangeChange()
- this.number = this.randomNumber;
+ this.min = isNaN(min) ? DEFAULT_CONFIG.min : min;
+ this.max = isNaN(max) ? DEFAULT_CONFIG.max : max;
+ },
+
+ /** @returns {string} */
+ get question() {
+ switch (this.mode) {
+ case "number":
+ return this.number;
+ case "hangeul":
+ return this.string;
+ }
+ },
+
+ /** @returns {string} */
+ get answer() {
+ switch (this.mode) {
+ case "number":
+ return this.string;
+ case "hangeul":
+ return this.number;
+ }
},
/** @returns {string} */
get string() {
switch (this.tab) {
case "sino":
- return sinoStringFromNumber(this.number);
+ return sinoStringFromNumber(this.num);
case "native":
- return nativeStringFromNumber(this.number);
+ return nativeStringFromNumber(this.num);
+ case "time":
+ return `${nativeStringFromNumber(this.time.hour)}시간 ${sinoStringFromNumber(this.time.minute)}분`;
default:
throw new Error("unknown tab selected");
}
},
/** @returns {string} */
- get answer() {
- switch (this.mode) {
- case "number":
- return this.string;
- case "hangeul":
- return this.number.toString();
+ get number() {
+ switch (this.tab) {
+ case "sino":
+ case "native":
+ return this.num.toString();
+ case "time":
+ return `${this.time.hour.toString().padStart(2, "0")}:${this.time.minute.toString().padStart(2, "0")}`;
+ default:
+ throw new Error("unknown tab selected");
}
},
@@ -118,7 +159,7 @@ window.Alpine.store("app", {
const sanitised = input.trim().split(" ").join("");
if (this.questionState !== "undecided") {
- this.number = this.randomNumber;
+ this.newQuestion();
event.target.input.value = "";
this.questionState = "undecided";
return
@@ -127,7 +168,10 @@ window.Alpine.store("app", {
/** @type {string} */
let answer = this.answer.split(" ").join("");
- if (answer === sanitised) {
+ if (
+ answer === sanitised ||
+ (this.tab === "time" && answer.slice(1) === sanitised) // "07:52".slice(1) == "7:52"
+ ) {
this.questionState = "correct";
} else {
const el = document.getElementById('input');
@@ -148,7 +192,7 @@ window.Alpine.store("app", {
}
this.rangeError = null;
- this.number = this.randomNumber;
+ this.newQuestion();
this.questionState = "undecided";
},
@@ -169,15 +213,35 @@ window.Alpine.store("app", {
changeTab(tab) {
this.questionState = "undecided";
this.tab = tab;
- this.number = this.randomNumber;
+ this.newQuestion();
+ },
+
+ newQuestion() {
+ switch (this.tab) {
+ case "sino":
+ case "native":
+ this.num = this.randomNumber;
+ break;
+ case "time":
+ this.time = this.randomNumber;
+ break;
+ default:
+ throw new Error("unknown tab selected");
+ }
},
+ /** @returns {number | Time} */
get randomNumber() {
switch (this.tab) {
case "sino":
return random(this.min, this.max * 10);
case "native":
return random(1, 100);
+ case "time":
+ return {
+ hour: random(1, 13),
+ minute: random(0, 60),
+ };
default:
throw new Error("unknown tab selected");
}