game workflow corrected #82

Merged
Donat merged 1 commits from Backend_Fix into main 2025-10-30 18:43:55 +01:00
5 changed files with 3726 additions and 147 deletions
File diff suppressed because it is too large Load Diff
@@ -13,13 +13,33 @@ export interface CloserAnswer {
percent: number;
}
/**
* Sentence pair for matching left to right
*/
export interface SentencePair {
id: string; // Unique identifier for this pair
left: string; // Left part to match
right: string; // Right part (scrambled position)
}
/**
* Player's answer for sentence pairing (array of matches)
*/
export interface SentencePairingAnswer {
pairId: string; // ID of the pair
leftText: string; // Left part
rightText: string; // Player's chosen right part
}
export interface CardClientData {
cardid: string;
question: string;
type: CardType;
timeLimit: number;
// Type-specific client data
options?: QuizOption[]; // For QUIZ
words?: string[]; // For SENTENCE_PAIRING (scrambled)
answerOptions?: QuizOption[]; // For QUIZ
words?: string[]; // For SENTENCE_PAIRING (legacy scrambled words)
sentencePairs?: SentencePair[]; // For SENTENCE_PAIRING (left-right matching)
acceptableAnswers?: string[]; // For OWN_ANSWER (not sent to client)
// CLOSER and TRUE_FALSE send only question
}
@@ -50,7 +70,8 @@ export class CardProcessingService {
const baseData: CardClientData = {
cardid: card.cardid,
question: card.question,
type: card.type
type: card.type,
timeLimit: 60 // Default 60 seconds for question cards
};
switch (card.type) {
@@ -116,18 +137,50 @@ export class CardProcessingService {
return {
...baseData,
options: card.answer as QuizOption[]
answerOptions: card.answer as QuizOption[]
};
}
/**
* Prepare SENTENCE_PAIRING card with scrambled words
* Prepare SENTENCE_PAIRING card with scrambled left/right pairs
*
* Expected card.answer format:
* [
* { left: "Apple", right: "Red" },
* { left: "Banana", right: "Yellow" },
* { left: "Orange", right: "Orange color" }
* ]
*
* OR legacy string format: "word1 word2 word3" (will be split and scrambled)
*/
private prepareSentencePairingCard(card: GameCard, baseData: CardClientData): CardClientData {
if (typeof card.answer !== 'string') {
throw new Error('Sentence pairing card answer must be a string');
// NEW FORMAT: Array of pairs (left-right matching)
if (Array.isArray(card.answer)) {
// Validate structure
const pairs = card.answer as Array<{ left: string; right: string }>;
if (!pairs.every(p => p.left && p.right)) {
throw new Error('Sentence pairing card answer must be array of {left, right} objects');
}
// Create pairs with IDs and scramble the right parts
const leftParts = pairs.map((p, idx) => ({ id: `pair_${idx}`, left: p.left, right: p.right }));
const rightParts = this.scrambleArray([...pairs.map(p => p.right)]);
// Send left parts in order, right parts scrambled
const sentencePairs: SentencePair[] = leftParts.map((lp, idx) => ({
id: lp.id,
left: lp.left,
right: rightParts[idx] // Scrambled position
}));
return {
...baseData,
sentencePairs
};
}
// LEGACY FORMAT: Single sentence to reconstruct (backward compatibility)
if (typeof card.answer === 'string') {
const words = card.answer.split(' ').filter(word => word.trim() !== '');
const scrambledWords = this.scrambleArray([...words]);
@@ -137,6 +190,9 @@ export class CardProcessingService {
};
}
throw new Error('Sentence pairing card answer must be array of pairs or string');
}
/**
* Prepare OWN_ANSWER card (only question, acceptable answers hidden)
*/
@@ -187,17 +243,65 @@ export class CardProcessingService {
}
/**
* Validate SENTENCE_PAIRING answer (reconstructed sentence)
* Validate SENTENCE_PAIRING answer
*
* Supports two formats:
* 1. NEW: Array of { pairId, leftText, rightText } matches
* 2. LEGACY: Reconstructed sentence string or array of words
*/
private validateSentencePairingAnswer(card: GameCard, playerAnswer: string[] | string): CardValidationResult {
if (typeof card.answer !== 'string') {
throw new Error('Sentence pairing card answer must be a string');
private validateSentencePairingAnswer(card: GameCard, playerAnswer: any): CardValidationResult {
// NEW FORMAT: Array of pairs (left-right matching)
if (Array.isArray(card.answer) && card.answer.every((p: any) => p.left && p.right)) {
const correctPairs = card.answer as Array<{ left: string; right: string }>;
// Player answer should be array of SentencePairingAnswer objects
if (!Array.isArray(playerAnswer)) {
throw new Error('Player answer must be array of pair matches');
}
const playerMatches = playerAnswer as SentencePairingAnswer[];
// Check if all pairs match correctly
let correctCount = 0;
const results: string[] = [];
for (const correctPair of correctPairs) {
const playerMatch = playerMatches.find(pm =>
pm.leftText.toLowerCase().trim() === correctPair.left.toLowerCase().trim()
);
if (playerMatch) {
const isMatch = playerMatch.rightText.toLowerCase().trim() ===
correctPair.right.toLowerCase().trim();
if (isMatch) {
correctCount++;
results.push(`✓ "${correctPair.left}" → "${correctPair.right}"`);
} else {
results.push(`✗ "${correctPair.left}" → "${playerMatch.rightText}" (should be "${correctPair.right}")`);
}
} else {
results.push(`✗ "${correctPair.left}" → (not matched)`);
}
}
const isCorrect = correctCount === correctPairs.length;
return {
isCorrect,
submittedAnswer: playerMatches,
correctAnswer: correctPairs,
explanation: isCorrect
? `✅ Perfect! All ${correctCount} pairs matched correctly!\n${results.join('\n')}`
: `❌ Only ${correctCount}/${correctPairs.length} pairs correct:\n${results.join('\n')}`
};
}
// LEGACY FORMAT: Single sentence to reconstruct (backward compatibility)
if (typeof card.answer === 'string') {
// Handle both array of words and joined string
const reconstructed = Array.isArray(playerAnswer)
? playerAnswer.join(' ').toLowerCase().trim()
: playerAnswer.toLowerCase().trim();
: (typeof playerAnswer === 'string' ? playerAnswer.toLowerCase().trim() : '');
const correctSentence = card.answer.toLowerCase().trim();
const isCorrect = reconstructed === correctSentence;
@@ -212,6 +316,9 @@ export class CardProcessingService {
};
}
throw new Error('Sentence pairing card answer must be array of pairs or string');
}
/**
* Validate OWN_ANSWER (check against acceptable answers array)
*/
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,7 @@
import { MigrationInterface, QueryRunner } from "typeorm";
<<<<<<<< HEAD:SerpentRace_Backend/src/Infrastructure/Migrationsettings/1758463928499-full.ts
export class Full1758463928499 implements MigrationInterface {
========
export class Full1757939815062 implements MigrationInterface {
>>>>>>>> 83fad59878db015ec8d86bdec1ecbbca0baddfd2:SerpentRace_Backend/src/Infrastructure/Migrationsettings/1757939815062-full.ts
public async up(queryRunner: QueryRunner): Promise<void> {
}
@@ -1,10 +1,5 @@
import { MigrationInterface, QueryRunner } from "typeorm";
<<<<<<<< HEAD:SerpentRace_Backend/src/Infrastructure/Migrationsettings/1758463928499-full.ts
export class Full1758463928499 implements MigrationInterface {
========
export class Full1757939815062 implements MigrationInterface {
>>>>>>>> 83fad59878db015ec8d86bdec1ecbbca0baddfd2:SerpentRace_Backend/src/Infrastructure/Migrationsettings/1757939815062-full.ts
public async up(queryRunner: QueryRunner): Promise<void> {
}