kripke: refactor
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
import type { SeoProps } from "$components/seo";
|
||||
import { dailySeed } from "./lib/system";
|
||||
|
||||
export async function load() {
|
||||
const seed = dailySeed();
|
||||
|
||||
return {
|
||||
seed,
|
||||
seo: {
|
||||
title: "KRIPKE - cannorin.net",
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
|
||||
@@ -6,15 +6,17 @@ import LuRotateCw from "lucide-svelte/icons/rotate-cw";
|
||||
import LuX from "lucide-svelte/icons/x";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import FrameInput from "./frame-input.svelte";
|
||||
import Game, { type GameStatus, type Move } from "./game.svelte";
|
||||
import Rules from "./rules.svelte";
|
||||
import Share from "./share.svelte";
|
||||
import FrameInput from "./components/frame-input.svelte";
|
||||
import Game, { type GameStatus } from "./components/game.svelte";
|
||||
import Rules from "./components/rules.svelte";
|
||||
import Share from "./components/share.svelte";
|
||||
|
||||
import { getFrameBySeed, getTimeUntilNextGame } from "./lib/system";
|
||||
import { daily } from "./store";
|
||||
import { getDailyFrame, getTimeUntilNextGame } from "./system";
|
||||
|
||||
const { id, frame } = getDailyFrame();
|
||||
let { data } = $props();
|
||||
const seed = data.seed;
|
||||
const { id, frame } = getFrameBySeed(seed);
|
||||
const guess = (frameId: number) => isomorphic[frameId] === id;
|
||||
const check = (formula: Formula) => validWorlds(frame, formula).length;
|
||||
const getAnswer = () => id;
|
||||
@@ -46,7 +48,10 @@ onMount(() => {
|
||||
<span class="font-bold">Daily Challenge: </span>
|
||||
{timeUntilNextGame.hours}:{timeUntilNextGame.minutes}:{timeUntilNextGame.seconds} until the next game.
|
||||
</h2>
|
||||
<Game bind:moves={$daily.moves} bind:status relationSize={relationSize} guess={guess} check={check} getAnswer={getAnswer} />
|
||||
<Game
|
||||
bind:moves={$daily.moves} bind:status relationSize={relationSize}
|
||||
guess={guess} check={check} getAnswer={getAnswer}
|
||||
onShare={() => { dialogOpen = true; }} />
|
||||
</section>
|
||||
|
||||
<section class="w-[300px] prose prose-sm">
|
||||
@@ -80,7 +85,7 @@ onMount(() => {
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="flex flex-col items-center w-fit rounded bg-background mx-auto">
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}, seed: <a class="text-primary underline font-medium" href="/kripke/random/{seed}">{seed}</a></span>
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
||||
</div>
|
||||
|
||||
@@ -88,8 +93,7 @@ onMount(() => {
|
||||
<div class="flex flex-col md:flex-row gap-2 w-full justify-end">
|
||||
<Share date={$daily.date} moves={$daily.moves} status={status} />
|
||||
<Button
|
||||
href="/kripke/random"
|
||||
data-sveltekit-reload>
|
||||
href="/kripke/random">
|
||||
<LuRotateCw class="w-4 h-4 mt-[2px]" /> Play Random Challenge
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
worlds,
|
||||
} from "@cannorin/kripke";
|
||||
import type { SVGAttributes } from "svelte/elements";
|
||||
import { type Vector, add, degree, rotate, sub, theta } from "./vector";
|
||||
import { type Vector, add, degree, rotate, sub, theta } from "../lib/vector";
|
||||
|
||||
export interface FrameInputProps extends SVGAttributes<SVGElement> {
|
||||
frame?: Frame | undefined;
|
||||
@@ -116,28 +116,30 @@ function getPath(rel: Relation) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.node {
|
||||
fill: rgb(var(--background));
|
||||
stroke: rgb(var(--foreground));
|
||||
stroke-width: 1;
|
||||
}
|
||||
.node.selected {
|
||||
stroke: rgb(var(--primary));
|
||||
stroke-width: 3;
|
||||
}
|
||||
.edge {
|
||||
stroke: rgb(var(--foreground));
|
||||
stroke-width: 1;
|
||||
fill: none;
|
||||
marker-end: url(#arrowhead);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<svg width={width ?? 250} height={height ?? 250} {...rest} viewBox="0,0,250,250" onclick={handleSvgClick}>
|
||||
<defs>
|
||||
<svg width={width ?? 250} height={height ?? 250} {...rest} viewBox="0,0,250,250" onclick={handleSvgClick} xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.node {
|
||||
fill: rgb(var(--background));
|
||||
stroke: rgb(var(--foreground));
|
||||
stroke-width: 1;
|
||||
}
|
||||
.node.selected {
|
||||
stroke: rgb(var(--primary));
|
||||
stroke-width: 3;
|
||||
}
|
||||
.edge {
|
||||
stroke: rgb(var(--foreground));
|
||||
stroke-width: 1;
|
||||
fill: none;
|
||||
marker-end: url(#arrowhead);
|
||||
}
|
||||
.arrowhead {
|
||||
fill: rgb(var(--foreground));
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<marker
|
||||
id="arrowhead"
|
||||
viewBox="0 0 10 10"
|
||||
@@ -148,7 +150,7 @@ function getPath(rel: Relation) {
|
||||
orient="auto"
|
||||
markerUnits="strokeWidth"
|
||||
>
|
||||
<path d="M0,0 L0,10 L10,5 z" fill="rgb(var(--foreground))" />
|
||||
<path class="arrowhead" d="M0,0 L0,10 L10,5 z" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
@@ -31,6 +31,7 @@ export type Props = {
|
||||
guess: (frameId: number) => boolean | Promise<boolean>;
|
||||
check: (formula: Formula) => number | Promise<number>;
|
||||
getAnswer: () => number | Promise<number>;
|
||||
onShare?: () => void;
|
||||
};
|
||||
|
||||
let {
|
||||
@@ -40,6 +41,7 @@ let {
|
||||
guess: guessImpl,
|
||||
check: checkImpl,
|
||||
getAnswer,
|
||||
onShare,
|
||||
}: Props = $props();
|
||||
|
||||
let formula: Formula | undefined = $state(undefined);
|
||||
@@ -200,20 +202,30 @@ const colors: Record<number, string> = {
|
||||
</li>
|
||||
{/if}
|
||||
{#if status === "win"}
|
||||
<li class="flex flex-col items-center gap-2 rounded bg-green-700 text-background p-5 animate-fade-in">
|
||||
<p class="text-xl font-bold">YOU WIN!</p>
|
||||
<li>
|
||||
<button
|
||||
class="flex flex-col items-center gap-2 rounded bg-green-700 text-background p-5 animate-fade-in w-full"
|
||||
onclick={() => onShare?.()}>
|
||||
<p class="text-xl font-bold">YOU WIN!</p>
|
||||
<p class="sr-only">Open result screen</p>
|
||||
</button>
|
||||
</li>
|
||||
{:else if status === "lose"}
|
||||
{#await getAnswer() then answerId}
|
||||
<li class="flex flex-col gap-2 rounded bg-foreground text-background p-5 animate-fade-in">
|
||||
<div>
|
||||
<p class="text-xl font-bold">YOU LOSE!</p>
|
||||
<p class="text-sm">The answer was:</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-center rounded bg-background w-full">
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={getFrame(answerId)} />
|
||||
</div>
|
||||
<li>
|
||||
<button
|
||||
class="flex flex-col gap-2 rounded bg-foreground text-background p-5 animate-fade-in w-full"
|
||||
onclick={() => onShare?.()}>
|
||||
<div>
|
||||
<p class="text-xl font-bold">YOU LOSE!</p>
|
||||
<p class="text-sm">The answer was:</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-center rounded bg-background w-full">
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={getFrame(answerId)} />
|
||||
</div>
|
||||
<p class="sr-only">Open result screen</p>
|
||||
</button>
|
||||
</li>
|
||||
{/await}
|
||||
{/if}
|
||||
@@ -35,7 +35,7 @@ let shareText = $derived.by(() => {
|
||||
|
||||
${history} ${status === "win" ? moves.length : "X"}/10
|
||||
|
||||
https://www.cannorin.net/kripke${seed ? `/random?seed=${seed}` : ""}`;
|
||||
https://www.cannorin.net/kripke${seed ? `/random/${seed}` : ""}`;
|
||||
|
||||
return encodeURIComponent(text);
|
||||
});
|
||||
@@ -20,26 +20,17 @@ function cyrb53(str: string, seed = 0): number {
|
||||
|
||||
export const date = () => new Date().toISOString().split("T")[0];
|
||||
|
||||
export function getDailyFrame() {
|
||||
// Use UTC ISO string so it's consistent across time zones
|
||||
export const dailySeed = () => {
|
||||
const dateStr = date();
|
||||
const hash = cyrb53(dateStr);
|
||||
const index = hash % nontrivials.length;
|
||||
return {
|
||||
date: dateStr,
|
||||
id: nontrivials[index],
|
||||
frame: getFrame(nontrivials[index]),
|
||||
};
|
||||
}
|
||||
return cyrb53(dateStr) % 0x100000000;
|
||||
};
|
||||
|
||||
export const randomSeed = () => Math.floor(Math.random() * 0x100000000);
|
||||
|
||||
export function getRandomFrame(seed?: number) {
|
||||
const actualSeed = seed ?? randomSeed();
|
||||
export function getFrameBySeed(seed: number) {
|
||||
const hash = cyrb53("random", seed);
|
||||
const index = hash % nontrivials.length;
|
||||
return {
|
||||
seed: actualSeed,
|
||||
id: nontrivials[index],
|
||||
frame: getFrame(nontrivials[index]),
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SeoProps } from "$components/seo";
|
||||
import { randomSeed } from "../system.js";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import { randomSeed } from "../lib/system";
|
||||
|
||||
export async function load({ url }) {
|
||||
const seedStr = url.searchParams.get("seed");
|
||||
@@ -13,20 +13,5 @@ export async function load({ url }) {
|
||||
return randomSeed();
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
seed,
|
||||
seo: {
|
||||
title: "KRIPKE (random challenge) - cannorin.net",
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
openGraph: {
|
||||
title: "KRIPKE (random challenge) - cannorin.net",
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
},
|
||||
twitter: {
|
||||
title: "KRIPKE (random challenge) - cannorin.net",
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
},
|
||||
} as SeoProps,
|
||||
};
|
||||
redirect(302, `/kripke/random/${seed}`);
|
||||
}
|
||||
|
||||
31
apps/web/src/routes/kripke/random/[seed]/+page.server.ts
Normal file
31
apps/web/src/routes/kripke/random/[seed]/+page.server.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { SeoProps } from "$components/seo";
|
||||
import { error } from "@sveltejs/kit";
|
||||
|
||||
export async function load({ params }) {
|
||||
const seedStr = params.seed;
|
||||
const seed = (() => {
|
||||
try {
|
||||
const seed = Number.parseInt(seedStr);
|
||||
if (!Number.isSafeInteger(seed)) throw error(400);
|
||||
return seed;
|
||||
} catch {
|
||||
throw error(400);
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
seed,
|
||||
seo: {
|
||||
title: `KRIPKE (seed ${seed}) - cannorin.net`,
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
openGraph: {
|
||||
title: `KRIPKE (seed ${seed}) - cannorin.net`,
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
},
|
||||
twitter: {
|
||||
title: `KRIPKE (seed ${seed}) - cannorin.net`,
|
||||
description: "KRIPKE - WORDLE, but for Kripke frames!",
|
||||
},
|
||||
} as SeoProps,
|
||||
};
|
||||
}
|
||||
@@ -5,15 +5,15 @@ import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
||||
import LuRotateCw from "lucide-svelte/icons/rotate-cw";
|
||||
import LuX from "lucide-svelte/icons/x";
|
||||
|
||||
import FrameInput from "../frame-input.svelte";
|
||||
import Game, { type GameStatus, type Move } from "../game.svelte";
|
||||
import Rules from "../rules.svelte";
|
||||
import Share from "../share.svelte";
|
||||
import { getRandomFrame } from "../system";
|
||||
import FrameInput from "../../components/frame-input.svelte";
|
||||
import Game, { type GameStatus, type Move } from "../../components/game.svelte";
|
||||
import Rules from "../../components/rules.svelte";
|
||||
import Share from "../../components/share.svelte";
|
||||
import { getFrameBySeed } from "../../lib/system";
|
||||
|
||||
let { data } = $props();
|
||||
const seed = data.seed;
|
||||
const { id, frame } = getRandomFrame(seed);
|
||||
const { id, frame } = getFrameBySeed(seed);
|
||||
const guess = (frameId: number) => isomorphic[frameId] === id;
|
||||
const check = (formula: Formula) => validWorlds(frame, formula).length;
|
||||
const getAnswer = () => id;
|
||||
@@ -29,7 +29,7 @@ $effect(() => {
|
||||
</script>
|
||||
|
||||
{#snippet seedNumber()}
|
||||
<a class="text-primary font-medium underline" href={`/kripke/random?seed=${seed}`}>{seed}</a>
|
||||
<a class="text-primary font-medium underline" href={`/kripke/random/${seed}`}>{seed}</a>
|
||||
{/snippet}
|
||||
|
||||
<main class="flex flex-col min-h-screen max-w-full items-center gap-12 lg:gap-16 py-8">
|
||||
@@ -41,7 +41,10 @@ $effect(() => {
|
||||
<span class="font-bold">Random Challenge</span>
|
||||
<span>(seed: {@render seedNumber()})</span>
|
||||
</h2>
|
||||
<Game bind:moves bind:status relationSize={relationSize} guess={guess} check={check} getAnswer={getAnswer} />
|
||||
<Game
|
||||
bind:moves bind:status relationSize={relationSize}
|
||||
guess={guess} check={check} getAnswer={getAnswer}
|
||||
onShare={() => { dialogOpen = true; }}/>
|
||||
</section>
|
||||
|
||||
<section class="w-[300px] prose prose-sm">
|
||||
@@ -76,7 +79,7 @@ $effect(() => {
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="flex flex-col items-center w-fit rounded bg-background mx-auto">
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}, seed: <a class="text-primary underline font-medium" href="/kripke/random/{seed}">{seed}</a></span>
|
||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
||||
</div>
|
||||
|
||||
@@ -84,8 +87,7 @@ $effect(() => {
|
||||
<div class="flex flex-col md:flex-row gap-2 w-full justify-end">
|
||||
<Share seed={seed} moves={moves} status={status} />
|
||||
<Button
|
||||
href="/kripke/random"
|
||||
data-sveltekit-reload>
|
||||
href="/kripke/random">
|
||||
<LuRotateCw class="w-4 h-4 mt-[2px]" /> Play New Game
|
||||
</Button>
|
||||
<Button
|
||||
@@ -1,6 +1,6 @@
|
||||
import { persisted } from "svelte-persisted-store";
|
||||
import type { Move } from "./game.svelte";
|
||||
import { date } from "./system";
|
||||
import type { Move } from "./components/game.svelte";
|
||||
import { date } from "./lib/system";
|
||||
|
||||
export type Daily = {
|
||||
date: string;
|
||||
|
||||
Reference in New Issue
Block a user