play-again.ts 1.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import type { TargetWord, CachedResult } from '@wordle/shared';
  2. /**
  3. * Select a target word weighted by proximity to the player's skill level.
  4. *
  5. * Algorithm:
  6. * 1. Compute a weight for each target based on how close its difficulty
  7. * is to the player's skill (Gaussian-like: 1 / (1 + (diff - skill)^2))
  8. * 2. Pick randomly, weighted by those weights.
  9. *
  10. * Uncached words get default difficulty 3.0 — they converge to real values
  11. * as words get played and cached.
  12. */
  13. export function selectPlayAgainWord(
  14. targets: TargetWord[],
  15. skillMetric: number,
  16. cache: Map<number, CachedResult>,
  17. ): TargetWord {
  18. if (targets.length === 0) {
  19. throw new Error('No target words available');
  20. }
  21. // Compute weights: closer difficulty = higher weight
  22. const weights = targets.map((t) => {
  23. const diff = cache.get(t.id)?.difficulty ?? 3.0;
  24. const delta = diff - skillMetric;
  25. return 1 / (1 + delta * delta);
  26. });
  27. // Weighted random selection
  28. const totalWeight = weights.reduce((sum, w) => sum + w, 0);
  29. let r = Math.random() * totalWeight;
  30. for (let i = 0; i < targets.length; i++) {
  31. r -= weights[i];
  32. if (r <= 0) {
  33. return targets[i];
  34. }
  35. }
  36. // Fallback (floating point edge case)
  37. return targets[targets.length - 1];
  38. }