sto-k-odnomu/src/components/Game.jsx

345 lines
11 KiB
React
Raw Normal View History

2025-12-31 17:57:11 +00:00
import { useState, useImperativeHandle, forwardRef, useEffect } from 'react'
2025-12-31 16:53:26 +00:00
import Question from './Question'
2025-12-31 17:05:48 +00:00
import Players from './Players'
import PlayersModal from './PlayersModal'
2025-12-31 17:13:24 +00:00
import QuestionsModal from './QuestionsModal'
2025-12-31 17:57:11 +00:00
import { getCookie, setCookie, deleteCookie } from '../utils/cookies'
2025-12-31 16:53:26 +00:00
import './Game.css'
2025-12-31 17:40:34 +00:00
const Game = forwardRef(({
questions = [],
currentQuestionIndex = 0,
onQuestionIndexChange,
onQuestionsChange,
}, ref) => {
2025-12-31 17:57:11 +00:00
const [players, setPlayers] = useState(() => {
const savedPlayers = getCookie('gamePlayers')
return savedPlayers || []
})
const [currentPlayerId, setCurrentPlayerId] = useState(() => {
const savedId = getCookie('gameCurrentPlayerId')
return savedId !== null ? savedId : null
})
const [playerScores, setPlayerScores] = useState(() => {
const savedScores = getCookie('gamePlayerScores')
return savedScores || {}
})
const [gameOver, setGameOver] = useState(() => {
const savedGameOver = getCookie('gameOver')
return savedGameOver === true
})
const [revealedAnswers, setRevealedAnswers] = useState(() => {
const savedAnswers = getCookie('gameRevealedAnswers')
return savedAnswers || {}
})
// Получаем открытые ответы для текущего вопроса
const getCurrentRevealedAnswers = () => {
return revealedAnswers[currentQuestionIndex] || []
}
// Обновляем открытые ответы для текущего вопроса
const updateRevealedAnswers = (newAnswers) => {
setRevealedAnswers({
...revealedAnswers,
[currentQuestionIndex]: newAnswers,
})
}
2025-12-31 17:05:48 +00:00
const [isPlayersModalOpen, setIsPlayersModalOpen] = useState(false)
2025-12-31 17:13:24 +00:00
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
2025-12-31 16:53:26 +00:00
2025-12-31 17:57:11 +00:00
// Сохраняем состояние в cookies при изменении
useEffect(() => {
if (players.length > 0) {
setCookie('gamePlayers', players)
} else {
deleteCookie('gamePlayers')
}
}, [players])
useEffect(() => {
if (currentPlayerId !== null) {
setCookie('gameCurrentPlayerId', currentPlayerId)
} else {
deleteCookie('gameCurrentPlayerId')
}
}, [currentPlayerId])
useEffect(() => {
if (Object.keys(playerScores).length > 0) {
setCookie('gamePlayerScores', playerScores)
} else {
deleteCookie('gamePlayerScores')
}
}, [playerScores])
useEffect(() => {
setCookie('gameRevealedAnswers', revealedAnswers)
}, [revealedAnswers])
useEffect(() => {
setCookie('gameOver', gameOver)
}, [gameOver])
const currentQuestion = questions[currentQuestionIndex]
const isLastQuestion = currentQuestionIndex === questions.length - 1
const handleShowAllAnswers = () => {
if (!currentQuestion) return
const allAnswerIndices = currentQuestion.answers.map((_, index) => index)
updateRevealedAnswers(allAnswerIndices)
}
2025-12-31 17:40:34 +00:00
useImperativeHandle(ref, () => ({
openPlayersModal: () => setIsPlayersModalOpen(true),
openQuestionsModal: () => setIsQuestionsModalOpen(true),
2025-12-31 17:57:11 +00:00
newGame: () => {
setPlayers([])
setCurrentPlayerId(null)
setPlayerScores({})
setGameOver(false)
setRevealedAnswers({})
},
showAllAnswers: handleShowAllAnswers,
2025-12-31 17:40:34 +00:00
}))
2025-12-31 17:05:48 +00:00
const handleAddPlayer = (name) => {
const newPlayer = {
id: Date.now(),
name: name,
}
const updatedPlayers = [...players, newPlayer]
setPlayers(updatedPlayers)
// Если это первый участник, делаем его текущим
if (updatedPlayers.length === 1) {
setCurrentPlayerId(newPlayer.id)
setPlayerScores({ [newPlayer.id]: 0 })
} else {
setPlayerScores({ ...playerScores, [newPlayer.id]: 0 })
}
}
const handleSelectPlayer = (playerId) => {
setCurrentPlayerId(playerId)
}
const handleRemovePlayer = (playerId) => {
const updatedPlayers = players.filter(p => p.id !== playerId)
setPlayers(updatedPlayers)
const updatedScores = { ...playerScores }
delete updatedScores[playerId]
setPlayerScores(updatedScores)
// Если удалили текущего участника, выбираем другого
if (currentPlayerId === playerId) {
if (updatedPlayers.length > 0) {
setCurrentPlayerId(updatedPlayers[0].id)
} else {
setCurrentPlayerId(null)
}
}
}
const getNextPlayerId = () => {
if (players.length === 0) return null
if (players.length === 1) return currentPlayerId
const currentIndex = players.findIndex(p => p.id === currentPlayerId)
const nextIndex = (currentIndex + 1) % players.length
return players[nextIndex].id
}
2025-12-31 16:53:26 +00:00
const handleAnswerClick = (answerIndex, points) => {
2025-12-31 17:57:11 +00:00
const currentRevealed = getCurrentRevealedAnswers()
if (currentRevealed.includes(answerIndex)) return
2025-12-31 17:05:48 +00:00
if (!currentPlayerId) return
2025-12-31 17:13:24 +00:00
if (!currentQuestion) return
2025-12-31 16:53:26 +00:00
2025-12-31 17:57:11 +00:00
const isLastAnswer = currentRevealed.length === currentQuestion.answers.length - 1
2025-12-31 17:05:48 +00:00
2025-12-31 17:57:11 +00:00
updateRevealedAnswers([...currentRevealed, answerIndex])
2025-12-31 17:05:48 +00:00
// Добавляем очки текущему участнику
setPlayerScores({
...playerScores,
[currentPlayerId]: (playerScores[currentPlayerId] || 0) + points,
})
2025-12-31 16:53:26 +00:00
2025-12-31 17:05:48 +00:00
// Переходим к следующему участнику только если это не последний ответ
if (!isLastAnswer) {
const nextPlayerId = getNextPlayerId()
if (nextPlayerId) {
setTimeout(() => {
setCurrentPlayerId(nextPlayerId)
}, 500)
}
} else {
// Если это последний ответ, переходим к следующему участнику перед следующим вопросом
2025-12-31 16:53:26 +00:00
setTimeout(() => {
2025-12-31 17:05:48 +00:00
const nextPlayerId = getNextPlayerId()
if (nextPlayerId) {
setCurrentPlayerId(nextPlayerId)
}
2025-12-31 16:53:26 +00:00
if (isLastQuestion) {
setGameOver(true)
} else {
2025-12-31 17:05:48 +00:00
setTimeout(() => {
nextQuestion()
}, 500)
2025-12-31 16:53:26 +00:00
}
}, 2000)
}
}
const nextQuestion = () => {
2025-12-31 17:40:34 +00:00
if (onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex + 1)
}
2025-12-31 17:57:11 +00:00
// Не сбрасываем открытые ответы - они сохраняются для каждого вопроса отдельно
2025-12-31 16:53:26 +00:00
}
const restartGame = () => {
2025-12-31 17:40:34 +00:00
if (onQuestionIndexChange) {
onQuestionIndexChange(0)
}
2025-12-31 16:53:26 +00:00
setGameOver(false)
2025-12-31 17:57:11 +00:00
setRevealedAnswers({})
2025-12-31 17:05:48 +00:00
const initialScores = {}
players.forEach(player => {
initialScores[player.id] = 0
})
setPlayerScores(initialScores)
if (players.length > 0) {
setCurrentPlayerId(players[0].id)
}
2025-12-31 16:53:26 +00:00
}
2025-12-31 17:57:11 +00:00
const newGame = () => {
setPlayers([])
setCurrentPlayerId(null)
setPlayerScores({})
setGameOver(false)
setRevealedAnswers({})
if (onQuestionIndexChange) {
onQuestionIndexChange(0)
}
}
const handlePreviousQuestion = () => {
if (currentQuestionIndex > 0 && onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex - 1)
// Открытые ответы сохраняются для каждого вопроса отдельно
}
}
const handleNextQuestion = () => {
if (currentQuestionIndex < questions.length - 1 && onQuestionIndexChange) {
onQuestionIndexChange(currentQuestionIndex + 1)
// Открытые ответы сохраняются для каждого вопроса отдельно
}
}
2025-12-31 16:53:26 +00:00
if (gameOver) {
2025-12-31 17:05:48 +00:00
// Находим победителя(ей)
const scores = Object.values(playerScores)
const maxScore = scores.length > 0 ? Math.max(...scores) : 0
const winners = players.filter(p => playerScores[p.id] === maxScore)
2025-12-31 16:53:26 +00:00
return (
<div className="game-over">
<div className="game-over-content">
<h2 className="game-over-title">🎉 Игра окончена! 🎉</h2>
2025-12-31 17:05:48 +00:00
<div className="final-scores">
<h3 className="final-scores-title">Итоговые результаты:</h3>
{players
.sort((a, b) => (playerScores[b.id] || 0) - (playerScores[a.id] || 0))
.map((player) => (
<div
key={player.id}
className={`final-score-item ${
winners.includes(player) ? 'final-score-winner' : ''
}`}
>
<span className="final-score-name">{player.name}</span>
<span className="final-score-value">
{playerScores[player.id] || 0} очков
</span>
</div>
))}
</div>
2025-12-31 16:53:26 +00:00
<button className="restart-button" onClick={restartGame}>
Играть снова
</button>
</div>
</div>
)
}
return (
<div className="game">
2025-12-31 17:05:48 +00:00
<PlayersModal
isOpen={isPlayersModalOpen}
onClose={() => setIsPlayersModalOpen(false)}
players={players}
onAddPlayer={handleAddPlayer}
onRemovePlayer={handleRemovePlayer}
2025-12-31 16:53:26 +00:00
/>
2025-12-31 17:05:48 +00:00
2025-12-31 17:13:24 +00:00
<QuestionsModal
isOpen={isQuestionsModalOpen}
onClose={() => setIsQuestionsModalOpen(false)}
questions={questions}
2025-12-31 17:40:34 +00:00
onUpdateQuestions={onQuestionsChange}
2025-12-31 17:13:24 +00:00
/>
2025-12-31 17:05:48 +00:00
<div className="game-header">
{players.length > 0 && (
<Players
players={players}
currentPlayerId={currentPlayerId}
playerScores={playerScores}
onSelectPlayer={handleSelectPlayer}
/>
)}
</div>
{players.length > 0 && currentPlayerId ? (
2025-12-31 17:40:34 +00:00
<div className="game-content">
2025-12-31 17:13:24 +00:00
{questions.length === 0 ? (
<div className="no-players-message">
<p>Добавьте вопросы, чтобы начать игру</p>
</div>
) : currentQuestion ? (
2025-12-31 17:40:34 +00:00
<Question
question={currentQuestion}
questionNumber={currentQuestionIndex + 1}
onAnswerClick={handleAnswerClick}
2025-12-31 17:57:11 +00:00
revealedAnswers={getCurrentRevealedAnswers()}
onPreviousQuestion={handlePreviousQuestion}
onNextQuestion={handleNextQuestion}
canGoPrevious={currentQuestionIndex > 0}
canGoNext={currentQuestionIndex < questions.length - 1}
2025-12-31 17:40:34 +00:00
/>
2025-12-31 17:13:24 +00:00
) : (
<div className="no-players-message">
<p>Ошибка: вопрос не найден</p>
</div>
)}
2025-12-31 17:40:34 +00:00
</div>
2025-12-31 17:05:48 +00:00
) : (
<div className="no-players-message">
<p>Добавьте участников, чтобы начать игру</p>
</div>
)}
2025-12-31 16:53:26 +00:00
</div>
)
2025-12-31 17:40:34 +00:00
})
Game.displayName = 'Game'
2025-12-31 16:53:26 +00:00
export default Game