From cec85be7c922d9843536796ad97e94c7cfadc3d3 Mon Sep 17 00:00:00 2001 From: George Powell Date: Mon, 26 Jan 2026 00:25:51 -0500 Subject: [PATCH] feat: Add Imposter game component and update project assets --- .gitignore | 5 +- EnglishNKJBible.xml | 2 +- src/lib/assets/Bluesky_Logo.svg | 4 + src/lib/components/Imposter.svelte | 241 +++++++++++++++++++++++++++++ src/lib/server/xml-bible.ts | 48 ++++++ src/routes/api/imposter/+server.ts | 68 ++++++++ src/routes/imposter/+page.svelte | 221 ++------------------------ src/routes/similarity/+page.svelte | 214 +++++++++++++++++++++++++ todo.md | 4 +- 9 files changed, 593 insertions(+), 214 deletions(-) create mode 100644 src/lib/assets/Bluesky_Logo.svg create mode 100644 src/lib/components/Imposter.svelte create mode 100644 src/routes/api/imposter/+server.ts create mode 100644 src/routes/similarity/+page.svelte diff --git a/.gitignore b/.gitignore index 30287df..c6b584a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,5 @@ vite.config.ts.timestamp-* llms-* -engwebu_usfx.xml -embeddings-cache-L12.json -embeddings-cache-L6.json +embeddings* +*.xml \ No newline at end of file diff --git a/EnglishNKJBible.xml b/EnglishNKJBible.xml index be873df..472ce20 100644 --- a/EnglishNKJBible.xml +++ b/EnglishNKJBible.xml @@ -25056,7 +25056,7 @@ “Behold, I send My messenger, And he will prepare the way before Me. And the Lord, whom you seek, Will suddenly come to His temple, Even the Messenger of the covenant, In whom you delight. Behold, He is coming,” Says the Lord of hosts. “But who can endure the day of His coming? And who can stand when He appears? For He is like a refiner’s fire And like launderers’ soap. - He will sit as a refiner and a purifier of silver; He will purify the sons of Levi, And purge them as gold and silver, That they may offer to the LordAn offering in righteousness. + He will sit as a refiner and a purifier of silver; He will purify the sons of Levi, and purge them as gold and silver, that they may offer to the Lord an offering in righteousness. “Then the offering of Judah and Jerusalem Will be pleasant to the Lord, As in the days of old, As in former years. And I will come near you for judgment; I will be a swift witness Against sorcerers, Against adulterers, Against perjurers, Against those who exploit wage earners and widows and orphans, And against those who turn away an alien— Because they do not fear Me,” Says the Lord of hosts. “For I am the Lord, I do not change; Therefore you are not consumed, O sons of Jacob. diff --git a/src/lib/assets/Bluesky_Logo.svg b/src/lib/assets/Bluesky_Logo.svg new file mode 100644 index 0000000..c71e201 --- /dev/null +++ b/src/lib/assets/Bluesky_Logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/lib/components/Imposter.svelte b/src/lib/components/Imposter.svelte new file mode 100644 index 0000000..6159e79 --- /dev/null +++ b/src/lib/components/Imposter.svelte @@ -0,0 +1,241 @@ + + +
+ {#if loading} +

Loading verses...

+ {:else if error} +
+

Error: {error}

+ +
+ {:else if data} + +
+ {#each data.verses as verse, i} +
+ + {#if gameOver} +
{data.refs[i]}
+ {/if} +
+ {/each} +
+ {#if gameOver} +
+ +
+ {/if} + {/if} +
+ + diff --git a/src/lib/server/xml-bible.ts b/src/lib/server/xml-bible.ts index 2f01328..32216e3 100644 --- a/src/lib/server/xml-bible.ts +++ b/src/lib/server/xml-bible.ts @@ -353,6 +353,54 @@ export function getRandomGreekVerses(count: number = 3): { return null; } +/** + * Get a random set of verses from a specific book + * Returns `count` consecutive verses by default + */ +export function getRandomVersesFromBook( + bookNumber: number, + count: number = 1 +): { + bookId: string; + bookName: string; + chapter: number; + startVerse: number; + endVerse: number; + verses: string[]; +} | null { + const book = getBookByNumber(bookNumber); + if (!book) { + return null; + } + + // Try up to 10 times to find a valid passage + for (let attempt = 0; attempt < 10; attempt++) { + const chapterNumber = getRandomChapterNumber(bookNumber); + const verseCount = getVerseCount(bookNumber, chapterNumber); + + // Skip chapters that don't have enough verses + if (verseCount < count) { + continue; + } + + const startVerse = getRandomStartVerse(bookNumber, chapterNumber, count); + const verses = extractVerses(bookNumber, chapterNumber, startVerse, count); + + if (verses.length === count) { + return { + bookId: book.id, + bookName: book.name, + chapter: chapterNumber, + startVerse, + endVerse: startVerse + count - 1, + verses + }; + } + } + + return null; +} + /** * Format a reference string from verse data */ diff --git a/src/routes/api/imposter/+server.ts b/src/routes/api/imposter/+server.ts new file mode 100644 index 0000000..8c6cd48 --- /dev/null +++ b/src/routes/api/imposter/+server.ts @@ -0,0 +1,68 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { getRandomVersesFromBook } from '$lib/server/xml-bible'; + +interface VerseOption { + text: string; + isImposter: boolean; + ref: string; +} + +export const GET: RequestHandler = async () => { + try { + // Select two different random books (1-66) + let book1Num = Math.floor(Math.random() * 66) + 1; + let book2Num = Math.floor(Math.random() * 66) + 1; + while (book2Num === book1Num) { + book2Num = Math.floor(Math.random() * 66) + 1; + } + + // Randomly decide which is majority + const majorityBookNum = Math.random() < 0.5 ? book1Num : book2Num; + const imposterBookNum = majorityBookNum === book1Num ? book2Num : book1Num; + + // Get 3 random verses from majority book + const options: VerseOption[] = []; + for (let i = 0; i < 3; i++) { + const verseData = getRandomVersesFromBook(majorityBookNum, 1); + if (!verseData) { + throw new Error('Failed to get majority verse'); + } + options.push({ + text: verseData.verses[0], + isImposter: false, + ref: `${verseData.bookName} ${verseData.chapter}:${verseData.startVerse}` + }); + } + + // Get 1 random verse from imposter book + const imposterVerseData = getRandomVersesFromBook(imposterBookNum, 1); + if (!imposterVerseData) { + throw new Error('Failed to get imposter verse'); + } + options.push({ + text: imposterVerseData.verses[0], + isImposter: true, + ref: `${imposterVerseData.bookName} ${imposterVerseData.chapter}:${imposterVerseData.startVerse}` + }); + + // Fisher-Yates shuffle + for (let i = options.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [options[i], options[j]] = [options[j], options[i]]; + } + + const verses = options.map(o => o.text); + const refs = options.map(o => o.ref); + const imposterIndex = options.findIndex(o => o.isImposter); + + return json({ + verses, + refs, + imposterIndex + }); + } catch (error) { + console.error('Imposter API error:', error); + return json({ error: 'Failed to generate imposter game' }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/routes/imposter/+page.svelte b/src/routes/imposter/+page.svelte index ac064fa..7f0f553 100644 --- a/src/routes/imposter/+page.svelte +++ b/src/routes/imposter/+page.svelte @@ -1,214 +1,17 @@ -
-

Similar Verse Finder

+ + Bibdle (imposter mode) + -
- - -
+ + +

Imposter Mode

+

Click the verse that doesn't belong

+
- {#if results.length > 0} -
- {#each results as result, i (i)} -
-
- {result.book} {result.chapter}:{result.verse} - Score: {result.score.toFixed(3)} -
-

{result.text}

-
- {/each} -
- {:else if sentence.trim() && !loading} -

No similar verses found. Try another sentence!

- {/if} -
- - + + diff --git a/src/routes/similarity/+page.svelte b/src/routes/similarity/+page.svelte new file mode 100644 index 0000000..ac064fa --- /dev/null +++ b/src/routes/similarity/+page.svelte @@ -0,0 +1,214 @@ + + +
+

Similar Verse Finder

+ +
+ + +
+ + {#if results.length > 0} +
+ {#each results as result, i (i)} +
+
+ {result.book} {result.chapter}:{result.verse} + Score: {result.score.toFixed(3)} +
+

{result.text}

+
+ {/each} +
+ {:else if sentence.trim() && !loading} +

No similar verses found. Try another sentence!

+ {/if} +
+ + diff --git a/todo.md b/todo.md index d42d9d3..8322f97 100644 --- a/todo.md +++ b/todo.md @@ -1,7 +1,8 @@ # in progress - Show new/old testament after 3 guesses and section after 7 guesses - +- Add sections for "first letter", "Canonical/deutero", etc... +- Make the UI more "wordle-like" () - How do you balance rewarding knowledge vs incentivising learning? # todo @@ -55,6 +56,7 @@ I created Bibdle from a combination of two things. The first is my lifelong desi ## january 5th +- created Imposter Mode with four options, identify the verse that is not in the same book as the other three. Needs more testing... - Verses ending in semicolons, commas, etc. will be replaced with "..." ## january 4th