"use client"; import { useMemo, useState } from "react"; import { EndScreen } from "./components/EndScreen"; import { GameScreen } from "./components/GameScreen"; import { StartScreen } from "./components/StartScreen"; import { usePkAudio } from "./hooks/usePkAudio"; import { usePkSetup } from "./hooks/usePkSetup"; import type { AnswerState, Team, WordItem } from "./types"; import { getFairRoundCount, normalizeOptionCount, shuffleArray } from "./utils"; export default function PkPage() { const { selectedGrade, setSelectedGrade, classTypes, selectedClassTypeId, setSelectedClassTypeId, availableWords, setAvailableWords, loadingSetup, settings, } = usePkSetup(); const [started, setStarted] = useState(false); const [ended, setEnded] = useState(false); const [roundWords, setRoundWords] = useState([]); const [questionIndex, setQuestionIndex] = useState(0); const [currentTeam, setCurrentTeam] = useState("red"); const [scoreRed, setScoreRed] = useState(0); const [scoreBlue, setScoreBlue] = useState(0); const [answerState, setAnswerState] = useState("idle"); const [selectedWord, setSelectedWord] = useState(null); const { speechTip, setSpeechTip, playTone, speakWord } = usePkAudio({ onGeneratedAudio: (word) => { setRoundWords((items) => items.map((item) => (item.id === word.id ? word : item))); setAvailableWords((items) => items.map((item) => (item.id === word.id ? word : item))); }, }); const selectedClassType = classTypes.find((classType) => classType.id === selectedClassTypeId); const currentWord = roundWords[questionIndex] ?? availableWords[0]; const optionCount = normalizeOptionCount(settings.pkOptionCount); const options = useMemo(() => { if (!currentWord) return []; const wrongOptions = availableWords.filter((word) => word.id !== currentWord.id); return shuffleArray([currentWord, ...shuffleArray(wrongOptions).slice(0, optionCount - 1)]); }, [currentWord, availableWords, optionCount]); const roundWordLimit = Math.max(0, Math.floor(settings.pkWordCount || 0)); const plannedRoundCount = getFairRoundCount(availableWords.length, roundWordLimit); const winner = scoreRed > scoreBlue ? "red" : scoreBlue > scoreRed ? "blue" : "draw"; function startGame() { const nextRound = shuffleArray(availableWords).slice(0, plannedRoundCount); if (nextRound.length === 0) return; setRoundWords(nextRound); setQuestionIndex(0); setCurrentTeam("red"); setScoreRed(0); setScoreBlue(0); setAnswerState("idle"); setSelectedWord(null); setStarted(true); setEnded(false); setSpeechTip(settings.autoSpeak ? "准备朗读中..." : "自动朗读已关闭。"); if (settings.autoSpeak) window.setTimeout(() => void speakWord(nextRound[0]), 350); } function checkAnswer(option: WordItem) { if (answerState !== "idle") return; const correct = option.id === currentWord.id; setSelectedWord(option.id); setAnswerState(correct ? "correct" : "wrong"); void playTone(correct); if (correct) { if (currentTeam === "red") setScoreRed((score) => score + settings.scorePerCorrect); else setScoreBlue((score) => score + settings.scorePerCorrect); } else if (settings.scorePerWrong > 0) { if (currentTeam === "red") setScoreRed((score) => Math.max(0, score - settings.scorePerWrong)); else setScoreBlue((score) => Math.max(0, score - settings.scorePerWrong)); } } function nextQuestion() { window.speechSynthesis?.cancel(); const nextIndex = questionIndex + 1; const nextTeam = currentTeam === "red" ? "blue" : "red"; setCurrentTeam(nextTeam); setAnswerState("idle"); setSelectedWord(null); if (nextIndex >= roundWords.length) { setEnded(true); return; } setQuestionIndex(nextIndex); setSpeechTip(settings.autoSpeak ? "准备朗读中..." : "自动朗读已关闭。"); if (settings.autoSpeak) window.setTimeout(() => void speakWord(roundWords[nextIndex]), 350); } if (!started) { return ( ); } if (ended) { return ; } return ( void speakWord(word)} onCheckAnswer={checkAnswer} onNextQuestion={nextQuestion} /> ); }