mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
Extracted game state management, share logic, and stats API calls into dedicated modules (game-persistence.svelte.ts, share.ts, stats-client.ts), and moved daily verse loading to client-side to fix timezone issues. Added a guesses column to daily_completions for cross-device state restoration for logged-in users, a new GET /api/stats endpoint, and a staging deploy script. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { bibleBooks, type BibleBook } from '$lib/types/bible';
|
|
|
|
export interface Guess {
|
|
book: BibleBook;
|
|
testamentMatch: boolean;
|
|
sectionMatch: boolean;
|
|
adjacent: boolean;
|
|
firstLetterMatch: boolean;
|
|
}
|
|
|
|
export function getBookById(id: string): BibleBook | undefined {
|
|
return bibleBooks.find((b) => b.id === id);
|
|
}
|
|
|
|
export function isAdjacent(id1: string, id2: string): boolean {
|
|
const b1 = getBookById(id1);
|
|
const b2 = getBookById(id2);
|
|
return !!(b1 && b2 && Math.abs(b1.order - b2.order) === 1);
|
|
}
|
|
|
|
export function getFirstLetter(bookName: string): string {
|
|
const match = bookName.match(/[a-zA-Z]/);
|
|
return match ? match[0] : bookName[0];
|
|
}
|
|
|
|
export function evaluateGuess(guessBookId: string, correctBookId: string): Guess | null {
|
|
const book = getBookById(guessBookId);
|
|
const correctBook = getBookById(correctBookId);
|
|
if (!book || !correctBook) return null;
|
|
|
|
const testamentMatch = book.testament === correctBook.testament;
|
|
const sectionMatch = book.section === correctBook.section;
|
|
const adjacent = isAdjacent(guessBookId, correctBookId);
|
|
|
|
// Special case: if correct book is in the Epistles + starts with "1",
|
|
// any guess starting with "1" counts as first letter match
|
|
const correctIsEpistlesWithNumber =
|
|
(correctBook.section === "Pauline Epistles" ||
|
|
correctBook.section === "General Epistles") &&
|
|
correctBook.name[0] === "1";
|
|
const guessIsEpistlesWithNumber =
|
|
(book.section === "Pauline Epistles" ||
|
|
book.section === "General Epistles") &&
|
|
book.name[0] === "1";
|
|
|
|
const firstLetterMatch =
|
|
correctIsEpistlesWithNumber && guessIsEpistlesWithNumber
|
|
? true
|
|
: getFirstLetter(book.name).toUpperCase() ===
|
|
getFirstLetter(correctBook.name).toUpperCase();
|
|
|
|
return {
|
|
book,
|
|
testamentMatch,
|
|
sectionMatch,
|
|
adjacent,
|
|
firstLetterMatch,
|
|
};
|
|
}
|
|
|
|
export function getGrade(numGuesses: number): string {
|
|
if (numGuesses === 1) return "S+";
|
|
if (numGuesses === 2) return "A+";
|
|
if (numGuesses === 3) return "A";
|
|
if (numGuesses >= 4 && numGuesses <= 6) return "B+";
|
|
if (numGuesses >= 7 && numGuesses <= 10) return "B";
|
|
if (numGuesses >= 11 && numGuesses <= 15) return "C+";
|
|
return "C";
|
|
}
|
|
|
|
export function getNextGradeMessage(numGuesses: number): string {
|
|
if (numGuesses === 1) return "";
|
|
if (numGuesses === 2) return "Next grade: 1 guess or less";
|
|
if (numGuesses === 3) return "Next grade: 2 guesses or less";
|
|
if (numGuesses >= 4 && numGuesses <= 6) return "Next grade: 3 guesses or less";
|
|
if (numGuesses >= 7 && numGuesses <= 10) return "Next grade: 6 guesses or less";
|
|
if (numGuesses >= 11 && numGuesses <= 15) return "Next grade: 10 guesses or less";
|
|
return "Next grade: 15 guesses or less";
|
|
}
|
|
|
|
export function toOrdinal(n: number): string {
|
|
if (n >= 11 && n <= 13) {
|
|
return `${n}th`;
|
|
}
|
|
const mod = n % 10;
|
|
const suffix = mod === 1 ? "st" : mod === 2 ? "nd" : mod === 3 ? "rd" : "th";
|
|
return `${n}${suffix}`;
|
|
}
|
|
|
|
export function generateUUID(): string {
|
|
if (typeof window !== 'undefined' && typeof (window as any).crypto?.randomUUID === "function") {
|
|
return (window as any).crypto.randomUUID();
|
|
}
|
|
// Fallback
|
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
const r = ((window as any).crypto.getRandomValues(new Uint8Array(1))[0] % 16) | 0;
|
|
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
return v.toString(16);
|
|
});
|
|
}
|