diff --git a/src/lib/games/quantum-tictactoe/AiApp.svelte b/src/lib/games/quantum-tictactoe/AiApp.svelte
new file mode 100644
index 0000000..c367a4a
--- /dev/null
+++ b/src/lib/games/quantum-tictactoe/AiApp.svelte
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/utils/getRandomInt.ts b/src/lib/utils/getRandomInt.ts
new file mode 100644
index 0000000..4799287
--- /dev/null
+++ b/src/lib/utils/getRandomInt.ts
@@ -0,0 +1,49 @@
+type GetRandomInt = (prop: { min?: number; max: number; excepts?: number[] }) => number;
+
+const getRandomInt: GetRandomInt = ({ min, max, excepts }) => {
+ if (min === undefined) min = 0;
+ if (!Number.isInteger(min) || !Number.isInteger(max))
+ console.error('getRandomInt: arguments min and max should be integers.');
+
+ if (max < min) console.error('getRandomInt: argument max should be greater than min.');
+
+ if (excepts && !Array.isArray(excepts))
+ console.error('getRandomInt: argument excepts should be an array.');
+ if (excepts && Array.isArray(excepts)) {
+ while (excepts.includes(min)) min++;
+ while (excepts.includes(max)) max--;
+ if (max < min)
+ console.error(
+ 'getRandomInt: range [min, max] must have at least one integer not included in excepts.'
+ );
+ }
+
+ let ret = Math.floor(Math.random() * (max - min + 1)) + min;
+ while (excepts && Array.isArray(excepts) && excepts.includes(ret))
+ ret = Math.floor(Math.random() * (max - min + 1)) + min;
+
+ return ret;
+};
+
+export { getRandomInt };
+
+// test
+if (import.meta.vitest) {
+ const { test, expect } = import.meta.vitest;
+ test('getRandomInt', () => {
+ const min = 1;
+ const max = 10;
+ const n = getRandomInt({ min, max });
+ expect(n).toBeGreaterThanOrEqual(min);
+ expect(n).toBeLessThanOrEqual(max);
+ });
+ test('getRandomInt with excepts', () => {
+ const min = 1;
+ const max = 10;
+ const excepts = [1, 2, 3, 8, 10];
+ const n = getRandomInt({ min, max, excepts });
+ expect(n).toBeGreaterThanOrEqual(4);
+ expect(n).toBeLessThanOrEqual(9);
+ expect(excepts.includes(n)).toBe(false);
+ });
+}
diff --git a/src/lib/utils/sleep.ts b/src/lib/utils/sleep.ts
new file mode 100644
index 0000000..414dfc7
--- /dev/null
+++ b/src/lib/utils/sleep.ts
@@ -0,0 +1,16 @@
+const sleep = async (ms: number): Promise =>
+ new Promise((resolve) => setTimeout(resolve, ms));
+
+export { sleep };
+
+// test
+if (import.meta.vitest) {
+ const { test, expect } = import.meta.vitest;
+ test('sleep', async () => {
+ const sleepTime = 100;
+ const start = Date.now();
+ await sleep(sleepTime);
+ const end = Date.now();
+ expect(end - start).toBeGreaterThanOrEqual(sleepTime);
+ });
+}
diff --git a/src/routes/games/quantum-tictactoe/+page.svelte b/src/routes/games/quantum-tictactoe/+page.svelte
index 40b023d..052376e 100644
--- a/src/routes/games/quantum-tictactoe/+page.svelte
+++ b/src/routes/games/quantum-tictactoe/+page.svelte
@@ -19,6 +19,10 @@ let footerHeight: number;
>チュートリアル
+
+ AI対局
+
オフライン対局
+import AiApp from '$lib/games/quantum-tictactoe/AiApp.svelte';
+
+
+
+
+ Quantum Tic-Tac-Toe - Quantum Game Arena
+
+
+
+
+
+
+ Quantum Tic-Tac-Toe
+
+
+
+
diff --git a/static/sitemap.xml b/static/sitemap.xml
index 7236442..8e53a4c 100644
--- a/static/sitemap.xml
+++ b/static/sitemap.xml
@@ -9,7 +9,7 @@
https://qgame.app/games/quantum-tictactoe
- 2022-09-29
+ 2024-05-04
yearly
0.8
@@ -20,6 +20,13 @@
yearly
+
+ https://qgame.app/games/quantum-tictactoe/play/ai
+ 2024-05-04
+ monthly
+ 1.0
+
+
https://qgame.app/games/quantum-tictactoe/play/human
2023-12-22