Initial crossword addition and arcade redesign
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m41s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m41s
This commit is contained in:
parent
611a585757
commit
5bec95fb30
32 changed files with 1635 additions and 56 deletions
|
|
@ -1,12 +1,16 @@
|
|||
import { prisma } from "@/lib/db";
|
||||
import { parseConnectionsImportBatch } from "@/lib/arcade/connectionsImport";
|
||||
import { replayConnectionsAttempt } from "@/lib/arcade/connectionsEngine";
|
||||
import type { ArcadeAttemptCreateInput } from "@/lib/validation/arcadeSchemas";
|
||||
import { parseCrosswordImportBatch } from "@/lib/arcade/crosswordImport";
|
||||
import { replayCrosswordAttempt } from "@/lib/arcade/crosswordAttempt";
|
||||
import type { ArcadeAttemptCreateInput, CrosswordAttemptCreateInput } from "@/lib/validation/arcadeSchemas";
|
||||
import type {
|
||||
ArcadeAttemptSummary,
|
||||
ArcadeGameKey,
|
||||
ArcadePackSummary,
|
||||
NormalizedArcadePack,
|
||||
NormalizedConnectionsPack,
|
||||
NormalizedCrosswordPack,
|
||||
} from "@/types/arcade";
|
||||
|
||||
function attemptSummary(attempt: {
|
||||
|
|
@ -31,8 +35,12 @@ function attemptSummary(attempt: {
|
|||
};
|
||||
}
|
||||
|
||||
export function previewArcadeImport(rawJson: string) {
|
||||
return parseConnectionsImportBatch(rawJson);
|
||||
function parseImport(gameType: ArcadeGameKey, rawJson: string) {
|
||||
return gameType === "connections" ? parseConnectionsImportBatch(rawJson) : parseCrosswordImportBatch(rawJson);
|
||||
}
|
||||
|
||||
export function previewArcadeImport(gameType: ArcadeGameKey, rawJson: string) {
|
||||
return parseImport(gameType, rawJson);
|
||||
}
|
||||
|
||||
export async function createArcadePacks(input: {
|
||||
|
|
@ -46,7 +54,7 @@ export async function createArcadePacks(input: {
|
|||
const classItem = await prisma.class.findUnique({ where: { id: input.classId }, select: { id: true } });
|
||||
if (!classItem) throw new Error("Class not found");
|
||||
|
||||
const batch = parseConnectionsImportBatch(input.rawJson);
|
||||
const batch = parseImport(input.gameType, input.rawJson);
|
||||
if (input.names && input.names.length !== batch.count) {
|
||||
throw new Error("A name is required for every imported pack.");
|
||||
}
|
||||
|
|
@ -67,7 +75,7 @@ export async function createArcadePacks(input: {
|
|||
name: input.names?.[index]?.trim() || (batch.count === 1 ? input.name?.trim() : undefined) || preview.name,
|
||||
description: preview.description,
|
||||
schemaVersion: preview.normalized.schemaVersion,
|
||||
sourceJson: JSON.stringify(preview.normalized),
|
||||
sourceJson: JSON.stringify(preview.source),
|
||||
normalizedJson: JSON.stringify(preview.normalized),
|
||||
validationReportJson: JSON.stringify({ wasRepaired: preview.wasRepaired, warnings: preview.warnings }),
|
||||
sortOrder: (maxOrder._max.sortOrder ?? -1) + index + 1,
|
||||
|
|
@ -85,7 +93,8 @@ export async function listArcadePacks(classId: string, gameType: ArcadeGameKey):
|
|||
include: { attempts: { orderBy: { completedAt: "desc" } } },
|
||||
});
|
||||
return packs.map((pack) => {
|
||||
const normalized = JSON.parse(pack.normalizedJson) as NormalizedConnectionsPack;
|
||||
const normalized = JSON.parse(pack.normalizedJson) as NormalizedArcadePack;
|
||||
const isConnections = normalized.type === "connections";
|
||||
return {
|
||||
id: pack.id,
|
||||
classId: pack.classId,
|
||||
|
|
@ -93,8 +102,10 @@ export async function listArcadePacks(classId: string, gameType: ArcadeGameKey):
|
|||
name: pack.name,
|
||||
description: pack.description,
|
||||
schemaVersion: pack.schemaVersion,
|
||||
itemCount: normalized.content.length * 4,
|
||||
defaultAllowedMistakes: normalized.settings.allowedMistakes,
|
||||
itemCount: isConnections ? normalized.content.length * 4 : normalized.content.length,
|
||||
defaultAllowedMistakes: isConnections ? normalized.settings.allowedMistakes : 4,
|
||||
defaultInstantCheck: isConnections ? false : normalized.settings.allowInstantCheck,
|
||||
defaultAllowHints: isConnections ? true : normalized.settings.allowHints,
|
||||
bestScore: pack.attempts.length ? Math.max(...pack.attempts.map((attempt) => attempt.score)) : null,
|
||||
latestAttempt: pack.attempts[0] ? attemptSummary(pack.attempts[0]) : null,
|
||||
};
|
||||
|
|
@ -110,7 +121,7 @@ export async function getArcadePack(id: string) {
|
|||
return {
|
||||
...pack,
|
||||
gameType: pack.gameType as ArcadeGameKey,
|
||||
normalized: JSON.parse(pack.normalizedJson) as NormalizedConnectionsPack,
|
||||
normalized: JSON.parse(pack.normalizedJson) as NormalizedArcadePack,
|
||||
validationReport: JSON.parse(pack.validationReportJson),
|
||||
};
|
||||
}
|
||||
|
|
@ -141,6 +152,7 @@ export async function createArcadeAttempt(arcadePackId: string, input: ArcadeAtt
|
|||
const pack = await prisma.arcadePack.findUnique({ where: { id: arcadePackId } });
|
||||
if (!pack) return null;
|
||||
const normalized = JSON.parse(pack.normalizedJson) as NormalizedConnectionsPack;
|
||||
if (normalized.type !== "connections") throw new Error("This attempt does not match the pack game type.");
|
||||
const replay = replayConnectionsAttempt(
|
||||
normalized,
|
||||
input.submissions,
|
||||
|
|
@ -174,3 +186,43 @@ export async function createArcadeAttempt(arcadePackId: string, input: ArcadeAtt
|
|||
return attemptSummary({ ...attempt, resultsJson: attempt.resultsJson });
|
||||
});
|
||||
}
|
||||
|
||||
export async function createCrosswordAttempt(arcadePackId: string, input: CrosswordAttemptCreateInput) {
|
||||
const pack = await prisma.arcadePack.findUnique({ where: { id: arcadePackId } });
|
||||
if (!pack) return null;
|
||||
const normalized = JSON.parse(pack.normalizedJson) as NormalizedCrosswordPack;
|
||||
if (normalized.type !== "crossword") throw new Error("This attempt does not match the pack game type.");
|
||||
const result = replayCrosswordAttempt(
|
||||
normalized,
|
||||
input.seed,
|
||||
input.settings,
|
||||
input.finalAnswers,
|
||||
input.actions,
|
||||
input.durationSeconds,
|
||||
input.gaveUp
|
||||
);
|
||||
const correctWords = result.entries.filter((entry) => !entry.omitted && entry.correct && !entry.revealedWord).length;
|
||||
return prisma.$transaction(async (transaction) => {
|
||||
const attempt = await transaction.arcadeAttempt.create({
|
||||
data: {
|
||||
arcadePackId,
|
||||
mode: input.mode,
|
||||
score: result.score,
|
||||
maxScore: result.maxScore,
|
||||
accuracy: result.accuracy,
|
||||
durationSeconds: result.durationSeconds,
|
||||
mistakes: result.mistakes,
|
||||
hintsUsed: result.hintsUsed,
|
||||
settingsJson: JSON.stringify(input.settings),
|
||||
resultsJson: JSON.stringify(result),
|
||||
seed: input.seed,
|
||||
},
|
||||
});
|
||||
if (correctWords > 0) {
|
||||
await transaction.studyActivity.createMany({
|
||||
data: Array.from({ length: correctWords }, () => ({ type: "ARCADE_WORD" })),
|
||||
});
|
||||
}
|
||||
return attemptSummary({ ...attempt, resultsJson: attempt.resultsJson });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { prisma } from "@/lib/db";
|
||||
import { ARCADE_SERVER_REGISTRY } from "@/lib/arcade/registry";
|
||||
|
||||
export type LlmInstructionType = "flashcards" | "quizzes" | "connections";
|
||||
export type LlmInstructionType = "flashcards" | "quizzes" | "connections" | "crossword";
|
||||
|
||||
const DEFAULT_LLM_INSTRUCTIONS_FLASHCARDS = `You are generating study materials in a strict JSON format for import into a
|
||||
personal study app. The overall output must be raw JSON with no wrapping
|
||||
|
|
@ -54,7 +54,8 @@ Material:
|
|||
function instructionConfig(type: LlmInstructionType) {
|
||||
if (type === "flashcards") return { key: "llmInstructionsFlashcards", value: DEFAULT_LLM_INSTRUCTIONS_FLASHCARDS };
|
||||
if (type === "quizzes") return { key: "llmInstructionsQuizzes", value: DEFAULT_LLM_INSTRUCTIONS_QUIZZES };
|
||||
return { key: "llmInstructionsConnections", value: ARCADE_SERVER_REGISTRY.connections.defaultInstructions };
|
||||
if (type === "connections") return { key: "llmInstructionsConnections", value: ARCADE_SERVER_REGISTRY.connections.defaultInstructions };
|
||||
return { key: "llmInstructionsCrossword", value: ARCADE_SERVER_REGISTRY.crossword.defaultInstructions };
|
||||
}
|
||||
|
||||
export async function getLlmInstructions(type: LlmInstructionType): Promise<string> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue