milwright commited on
Commit
baad690
Β·
1 Parent(s): b48eb70

Add enhanced game logic improvements

Browse files

- Enhanced content filtering with additional formatting pattern detection
- Improved word matching algorithm with fallback mechanisms
- Better base word matching for inflected forms
- Fallback selection to ensure expected number of blanks
- More robust passage quality scoring for non-narrative content

Files changed (1) hide show
  1. src/clozeGameEngine.js +50 -1
src/clozeGameEngine.js CHANGED
@@ -168,6 +168,16 @@ class ClozeGame {
168
  const dashSequences = (passage.match(/[-—–]{3,}/g) || []).length;
169
  const totalDashes = (passage.match(/[-—–]/g) || []).length;
170
 
 
 
 
 
 
 
 
 
 
 
171
  // Check for repetitive patterns (common in indexes/TOCs)
172
  const repeatedPhrases = ['CONTENTS', 'CHAPTER', 'Volume', 'Vol.', 'Part', 'Book'];
173
  const repetitionCount = repeatedPhrases.reduce((count, phrase) =>
@@ -187,6 +197,8 @@ class ClozeGame {
187
  const repetitionRatio = repetitionCount / totalWords;
188
  const titleLineRatio = titleLines / Math.max(1, lines.length);
189
  const dashRatio = totalDashes / totalWords;
 
 
190
 
191
  // Stricter thresholds for higher levels
192
  const capsThreshold = this.currentLevel >= 3 ? 0.03 : 0.05;
@@ -205,6 +217,13 @@ class ClozeGame {
205
  if (titleLineRatio > 0.2) { qualityScore += 5; issues.push(`title-lines: ${Math.round(titleLineRatio * 100)}%`); }
206
  if (dashSequences > 0) { qualityScore += dashSequences * 3; issues.push(`dash-sequences: ${dashSequences}`); }
207
  if (dashRatio > 0.02) { qualityScore += dashRatio * 25; issues.push(`dashes: ${Math.round(dashRatio * 100)}%`); }
 
 
 
 
 
 
 
208
 
209
  // Reject if quality score indicates technical/non-narrative content
210
  if (qualityScore > 3) {
@@ -268,7 +287,7 @@ class ClozeGame {
268
  const words = this.originalText.split(/(\s+)/);
269
  const wordsOnly = words.filter(w => w.trim() !== '');
270
 
271
- // Find indices of selected words using exact matching
272
  const selectedIndices = [];
273
  selectedWords.forEach(word => {
274
  // First try exact match (cleaned)
@@ -285,6 +304,18 @@ class ClozeGame {
285
  );
286
  }
287
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  if (index !== -1) {
289
  selectedIndices.push(index);
290
  } else {
@@ -292,6 +323,24 @@ class ClozeGame {
292
  }
293
  });
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  // Create blanks
296
  this.blanks = [];
297
  this.hints = [];
 
168
  const dashSequences = (passage.match(/[-—–]{3,}/g) || []).length;
169
  const totalDashes = (passage.match(/[-—–]/g) || []).length;
170
 
171
+ // Count additional formatting patterns
172
+ const asteriskSequences = (passage.match(/\*{3,}/g) || []).length;
173
+ const asteriskLines = (passage.match(/^\s*\*+\s*$/gm) || []).length;
174
+ const underscoreSequences = (passage.match(/_{3,}/g) || []).length;
175
+ const equalSequences = (passage.match(/={3,}/g) || []).length;
176
+ const pipeCount = (passage.match(/\|/g) || []).length;
177
+ const numberedLines = (passage.match(/^\s*\d+[\.\)]\s/gm) || []).length;
178
+ const parenthesesCount = (passage.match(/[()]/g) || []).length;
179
+ const squareBrackets = (passage.match(/[\[\]]/g) || []).length;
180
+
181
  // Check for repetitive patterns (common in indexes/TOCs)
182
  const repeatedPhrases = ['CONTENTS', 'CHAPTER', 'Volume', 'Vol.', 'Part', 'Book'];
183
  const repetitionCount = repeatedPhrases.reduce((count, phrase) =>
 
197
  const repetitionRatio = repetitionCount / totalWords;
198
  const titleLineRatio = titleLines / Math.max(1, lines.length);
199
  const dashRatio = totalDashes / totalWords;
200
+ const parenthesesRatio = parenthesesCount / totalWords;
201
+ const squareBracketRatio = squareBrackets / totalWords;
202
 
203
  // Stricter thresholds for higher levels
204
  const capsThreshold = this.currentLevel >= 3 ? 0.03 : 0.05;
 
217
  if (titleLineRatio > 0.2) { qualityScore += 5; issues.push(`title-lines: ${Math.round(titleLineRatio * 100)}%`); }
218
  if (dashSequences > 0) { qualityScore += dashSequences * 3; issues.push(`dash-sequences: ${dashSequences}`); }
219
  if (dashRatio > 0.02) { qualityScore += dashRatio * 25; issues.push(`dashes: ${Math.round(dashRatio * 100)}%`); }
220
+ if (asteriskSequences > 0 || asteriskLines > 0) { qualityScore += (asteriskSequences + asteriskLines) * 2; issues.push(`asterisk-separators: ${asteriskSequences + asteriskLines}`); }
221
+ if (underscoreSequences > 0) { qualityScore += underscoreSequences * 2; issues.push(`underscore-lines: ${underscoreSequences}`); }
222
+ if (equalSequences > 0) { qualityScore += equalSequences * 2; issues.push(`equal-lines: ${equalSequences}`); }
223
+ if (pipeCount > 5) { qualityScore += 3; issues.push(`table-formatting: ${pipeCount} pipes`); }
224
+ if (numberedLines > 3) { qualityScore += 2; issues.push(`numbered-list: ${numberedLines} items`); }
225
+ if (parenthesesRatio > 0.05) { qualityScore += 2; issues.push(`excessive-parentheses: ${Math.round(parenthesesRatio * 100)}%`); }
226
+ if (squareBracketRatio > 0.02) { qualityScore += 2; issues.push(`excessive-brackets: ${Math.round(squareBracketRatio * 100)}%`); }
227
 
228
  // Reject if quality score indicates technical/non-narrative content
229
  if (qualityScore > 3) {
 
287
  const words = this.originalText.split(/(\s+)/);
288
  const wordsOnly = words.filter(w => w.trim() !== '');
289
 
290
+ // Find indices of selected words using flexible matching
291
  const selectedIndices = [];
292
  selectedWords.forEach(word => {
293
  // First try exact match (cleaned)
 
304
  );
305
  }
306
 
307
+ // Enhanced fallback: try base word matching (remove common suffixes)
308
+ if (index === -1) {
309
+ const baseWord = word.replace(/[^\w]/g, '').toLowerCase().replace(/(ed|ing|s|es|er|est)$/, '');
310
+ if (baseWord.length > 2) {
311
+ index = wordsOnly.findIndex((w, idx) => {
312
+ const cleanW = w.replace(/[^\w]/g, '').toLowerCase();
313
+ const baseW = cleanW.replace(/(ed|ing|s|es|er|est)$/, '');
314
+ return baseW === baseWord && !selectedIndices.includes(idx);
315
+ });
316
+ }
317
+ }
318
+
319
  if (index !== -1) {
320
  selectedIndices.push(index);
321
  } else {
 
323
  }
324
  });
325
 
326
+ // Ensure we have at least the expected number of blanks
327
+ if (selectedIndices.length < expectedBlanks) {
328
+ console.warn(`Only found ${selectedIndices.length} words, need ${expectedBlanks}. Using fallback selection.`);
329
+ const fallbackWords = this.selectWordsManually(wordsOnly, expectedBlanks - selectedIndices.length);
330
+
331
+ // Add fallback word indices
332
+ fallbackWords.forEach(fallbackWord => {
333
+ const cleanFallback = fallbackWord.toLowerCase().replace(/[^\w]/g, '');
334
+ const index = wordsOnly.findIndex((w, idx) => {
335
+ const cleanW = w.replace(/[^\w]/g, '').toLowerCase();
336
+ return cleanW === cleanFallback && !selectedIndices.includes(idx);
337
+ });
338
+ if (index !== -1) {
339
+ selectedIndices.push(index);
340
+ }
341
+ });
342
+ }
343
+
344
  // Create blanks
345
  this.blanks = [];
346
  this.hints = [];