game workflow corrected
This commit is contained in:
@@ -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,25 +137,60 @@ 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
|
||||
};
|
||||
}
|
||||
|
||||
const words = card.answer.split(' ').filter(word => word.trim() !== '');
|
||||
const scrambledWords = this.scrambleArray([...words]);
|
||||
// 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]);
|
||||
|
||||
return {
|
||||
...baseData,
|
||||
words: scrambledWords
|
||||
};
|
||||
return {
|
||||
...baseData,
|
||||
words: scrambledWords
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Sentence pairing card answer must be array of pairs or string');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,29 +243,80 @@ 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')}`
|
||||
};
|
||||
}
|
||||
|
||||
// Handle both array of words and joined string
|
||||
const reconstructed = Array.isArray(playerAnswer)
|
||||
? playerAnswer.join(' ').toLowerCase().trim()
|
||||
: playerAnswer.toLowerCase().trim();
|
||||
// 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()
|
||||
: (typeof playerAnswer === 'string' ? playerAnswer.toLowerCase().trim() : '');
|
||||
|
||||
const correctSentence = card.answer.toLowerCase().trim();
|
||||
const isCorrect = reconstructed === correctSentence;
|
||||
const correctSentence = card.answer.toLowerCase().trim();
|
||||
const isCorrect = reconstructed === correctSentence;
|
||||
|
||||
return {
|
||||
isCorrect,
|
||||
submittedAnswer: reconstructed,
|
||||
correctAnswer: card.answer,
|
||||
explanation: isCorrect
|
||||
? '✅ Perfect! You arranged the sentence correctly!'
|
||||
: `❌ Wrong order! Correct sentence: "${card.answer}"`
|
||||
};
|
||||
return {
|
||||
isCorrect,
|
||||
submittedAnswer: reconstructed,
|
||||
correctAnswer: card.answer,
|
||||
explanation: isCorrect
|
||||
? '✅ Perfect! You arranged the sentence correctly!'
|
||||
: `❌ Wrong order! Correct sentence: "${card.answer}"`
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Sentence pairing card answer must be array of pairs or string');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user