From 64d622ea8f488b7fdf7bc71151cb1f6a9209279d Mon Sep 17 00:00:00 2001 From: cannorin Date: Wed, 19 Feb 2025 21:12:39 +0900 Subject: [PATCH] kripke: add daily and random challenges --- apps/web/package.json | 1 + apps/web/src/routes/kripke/+page.svelte | 133 +++++++++++------- apps/web/src/routes/kripke/game.svelte | 109 +++++--------- .../src/routes/kripke/random/+page.server.ts | 18 +++ .../web/src/routes/kripke/random/+page.svelte | 99 +++++++++++++ apps/web/src/routes/kripke/rules.svelte | 46 ++++++ apps/web/src/routes/kripke/store.ts | 24 ++++ apps/web/src/routes/kripke/system.ts | 37 ++++- yarn.lock | 1 + 9 files changed, 341 insertions(+), 127 deletions(-) create mode 100644 apps/web/src/routes/kripke/random/+page.server.ts create mode 100644 apps/web/src/routes/kripke/random/+page.svelte create mode 100644 apps/web/src/routes/kripke/rules.svelte create mode 100644 apps/web/src/routes/kripke/store.ts diff --git a/apps/web/package.json b/apps/web/package.json index 0b1bf51..bda8e81 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,6 +13,7 @@ "fix": "biome check --fix" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "@poppanator/sveltekit-svg": "5.0.0", "@sveltejs/adapter-auto": "3.3.1", "@sveltejs/enhanced-img": "0.4.4", diff --git a/apps/web/src/routes/kripke/+page.svelte b/apps/web/src/routes/kripke/+page.svelte index 19fc97b..3c11208 100644 --- a/apps/web/src/routes/kripke/+page.svelte +++ b/apps/web/src/routes/kripke/+page.svelte @@ -1,66 +1,95 @@

KRiPkE

- +
+

+ Daily Challenge: + {timeUntilNextGame.hours}:{timeUntilNextGame.minutes}:{timeUntilNextGame.seconds} until the next game. +

+ +
-
-

Rules

+
+

Daily Challenge!

    -
  • - A Kripke frame with 4 worlds is generated. -
  • -
  • - The game tells you how many accessibility relations are in the frame, but not the exact shape of it. -
  • -
  • - You have a total of 10 moves (). - In each move you can do one of the following: -
      -
    • - Enter a modal formula. - The game tells you in how many worlds the formula is valid (for every valuation). -
    • -
    • - Guess the Kripke frame. - If your frame is equal or isomorphic to the secret frame, you win. -
    • -
    -
  • -
  • - You lose when you run out of moves. -
  • +
  • The answer of the game changes every day.
  • +
  • The progress of the game persists until the next day ({timeUntilNextGame.hours}:{timeUntilNextGame.minutes}:{timeUntilNextGame.seconds} from now).
  • +
  • You can also play Random Challenge.
-

Syntax

-

You may use the following symbols:

-
    -
  • propositional variables: p, q, r, s
  • -
  • verum: , T, 1, \top
  • -
  • falsum: , F, 0, \bot
  • -
  • negation: ¬, ~, \neg, \lnot
  • -
  • box modality: , [], !, L, \Box
  • -
  • diamond modality: , <>, ?, M, \Diamond
  • -
  • conjunction: , ^, &, \wedge, \land
  • -
  • disjunction: , v, |, \vee, \lor
  • -
  • implication: , ->, >, \rightarrow, \to
  • -
  • equivalence: , <->, =, \leftrightarrow, \equiv
  • -
  • parentheses: (, )
  • -
-
+ +
+ +{#if status !== "playing"} + {#await getAnswer() then answerId} + + + + + {#if status === "win"} + YOU WIN! + {:else if status === "lose"} + YOU LOSE! + {/if} + + + The answer was: + + + +
+ id: {answerId} + +
+ + + + +
+
+ {/await} +{/if} diff --git a/apps/web/src/routes/kripke/game.svelte b/apps/web/src/routes/kripke/game.svelte index 85bf4c5..3b11de6 100644 --- a/apps/web/src/routes/kripke/game.svelte +++ b/apps/web/src/routes/kripke/game.svelte @@ -1,7 +1,6 @@ + +{#snippet seedNumber()} + {seed} +{/snippet} + +
+

KRiPkE

+ +
+
+

+ Random Challenge + (seed: {@render seedNumber()}) +

+ +
+ +
+

Random Challenge!

+
    +
  • The answer of the game is determined by a seed number {@render seedNumber()}, which changes on every reload.
  • +
  • You can right-click on the seed number to obtain a permalink to this exact game.
  • +
  • Unlike Daily Challenge, the progress of the game does not persist.
  • +
  • You can also play Daily Challenge, if you have not yet.
  • +
+ + +
+
+
+ +{#if status !== "playing"} + {#await getAnswer() then answerId} + + + + + {#if status === "win"} + YOU WIN! + {:else if status === "lose"} + YOU LOSE! + {/if} + + + The answer was: + + + +
+ id: {answerId} + +
+ + + + +
+
+ {/await} +{/if} diff --git a/apps/web/src/routes/kripke/rules.svelte b/apps/web/src/routes/kripke/rules.svelte new file mode 100644 index 0000000..5f6a430 --- /dev/null +++ b/apps/web/src/routes/kripke/rules.svelte @@ -0,0 +1,46 @@ + + +

Rules

+ + +

Syntax

+

You may use the following symbols:

+ diff --git a/apps/web/src/routes/kripke/store.ts b/apps/web/src/routes/kripke/store.ts new file mode 100644 index 0000000..0fb1b30 --- /dev/null +++ b/apps/web/src/routes/kripke/store.ts @@ -0,0 +1,24 @@ +import { persisted } from "svelte-persisted-store"; +import type { Move } from "./game.svelte"; +import { date } from "./system"; + +export type Daily = { + date: string; + moves: Move[]; +}; + +export const daily = persisted( + "daily", + { + date: date(), + moves: [], + }, + { + syncTabs: true, + storage: "local", + beforeRead: (value) => { + if (value.date !== date()) return { date: date(), moves: [] }; + return value; + }, + }, +); diff --git a/apps/web/src/routes/kripke/system.ts b/apps/web/src/routes/kripke/system.ts index 6aa31ac..044fb3d 100644 --- a/apps/web/src/routes/kripke/system.ts +++ b/apps/web/src/routes/kripke/system.ts @@ -18,10 +18,43 @@ function cyrb53(str: string, seed = 0): number { return 4294967296 * (2097151 & h2) + (h1 >>> 0); } +export const date = () => new Date().toISOString().split("T")[0]; + export function getDailyFrame() { // Use UTC ISO string so it's consistent across time zones - const dateStr = new Date().toISOString().split("T")[0]; + const dateStr = date(); const hash = cyrb53(dateStr); const index = hash % nontrivials.length; - return getFrame(nontrivials[index]); + return { + date: dateStr, + id: nontrivials[index], + frame: getFrame(nontrivials[index]), + }; +} + +export const randomSeed = () => Math.floor(Math.random() * 0x100000000); + +export function getRandomFrame(seed?: number) { + const actualSeed = seed ?? randomSeed(); + const hash = cyrb53("random", seed); + const index = hash % nontrivials.length; + return { + seed: actualSeed, + id: nontrivials[index], + frame: getFrame(nontrivials[index]), + }; +} + +export function getTimeUntilNextGame() { + const now = new Date(); + const nextDayUTC = new Date( + Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1), + ); + const deltaSeconds = (nextDayUTC.getTime() - now.getTime()) / 1000; + const pad = (num: number) => `0${num}`.slice(-2); + return { + hours: pad(Math.floor(deltaSeconds / 3600)), + minutes: pad(Math.floor((deltaSeconds % 3600) / 60)), + seconds: pad(Math.floor(deltaSeconds % 60)), + }; } diff --git a/yarn.lock b/yarn.lock index 2aa3b5e..1159b92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3948,6 +3948,7 @@ __metadata: version: 0.0.0-use.local resolution: "web@workspace:apps/web" dependencies: + "@biomejs/biome": "npm:1.9.4" "@cannorin/kripke": "workspace:*" "@fontsource/poiret-one": "npm:5.1.1" "@fontsource/zen-kaku-gothic-new": "npm:5.1.1"