useGame.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { useState, useCallback } from 'react';
  2. import type { GuessEntry, GuessResponse } from '@wordle/shared';
  3. const BASE = '/api';
  4. interface UseGameReturn {
  5. guesses: GuessEntry[];
  6. gameStatus: 'playing' | 'won' | 'lost';
  7. message: string | null;
  8. lastDifficulty: number | null;
  9. revealedWord: string | null;
  10. submitGuess: (wordId: number, guess: string) => Promise<void>;
  11. dismissMessage: () => void;
  12. resetForNewGame: () => void;
  13. }
  14. export function useGame(initialGuesses: GuessEntry[]): UseGameReturn {
  15. const [guesses, setGuesses] = useState<GuessEntry[]>(initialGuesses);
  16. const [message, setMessage] = useState<string | null>(null);
  17. const [lastDifficulty, setLastDifficulty] = useState<number | null>(null);
  18. const [revealedWord, setRevealedWord] = useState<string | null>(null);
  19. const gameStatus = deriveStatus(guesses);
  20. const dismissMessage = useCallback(() => setMessage(null), []);
  21. const resetForNewGame = useCallback(() => {
  22. setGuesses([]);
  23. setMessage(null);
  24. setLastDifficulty(null);
  25. setRevealedWord(null);
  26. }, []);
  27. const submitGuess = useCallback(async (wordId: number, guess: string) => {
  28. const tryNo = guesses.length + 1;
  29. const lowerGuess = guess.toLowerCase();
  30. const res = await fetch(`${BASE}/guess`, {
  31. method: 'POST',
  32. headers: { 'Content-Type': 'application/json' },
  33. body: JSON.stringify({ wordId, guess: lowerGuess, tryNo }),
  34. });
  35. if (!res.ok) {
  36. const body = await res.json().catch(() => ({}));
  37. setMessage((body as { error?: string }).error ?? `HTTP ${res.status}`);
  38. return;
  39. }
  40. const data: GuessResponse = await res.json();
  41. if (!data.valid) {
  42. setMessage('Unknown word');
  43. return;
  44. }
  45. const entry: GuessEntry = { guess: lowerGuess, colors: data.colors! };
  46. setGuesses((prev) => [...prev, entry]);
  47. if (data.difficulty !== undefined) {
  48. setLastDifficulty(data.difficulty);
  49. }
  50. if (data.word) {
  51. setRevealedWord(data.word);
  52. }
  53. }, [guesses.length]);
  54. return { guesses, gameStatus, message, lastDifficulty, revealedWord, submitGuess, dismissMessage, resetForNewGame };
  55. }
  56. function deriveStatus(guesses: GuessEntry[]): 'playing' | 'won' | 'lost' {
  57. if (guesses.length === 0) return 'playing';
  58. const last = guesses[guesses.length - 1];
  59. if (last.colors.every((c) => c === 'g')) return 'won';
  60. if (guesses.length >= 6) return 'lost';
  61. return 'playing';
  62. }