344 lines
11 KiB
JavaScript
344 lines
11 KiB
JavaScript
import { useState, useImperativeHandle, forwardRef, useEffect } from 'react'
|
||
import Question from './Question'
|
||
import Players from './Players'
|
||
import PlayersModal from './PlayersModal'
|
||
import QuestionsModal from './QuestionsModal'
|
||
import { getCookie, setCookie, deleteCookie } from '../utils/cookies'
|
||
import './Game.css'
|
||
|
||
const Game = forwardRef(({
|
||
questions = [],
|
||
currentQuestionIndex = 0,
|
||
onQuestionIndexChange,
|
||
onQuestionsChange,
|
||
}, ref) => {
|
||
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,
|
||
})
|
||
}
|
||
const [isPlayersModalOpen, setIsPlayersModalOpen] = useState(false)
|
||
const [isQuestionsModalOpen, setIsQuestionsModalOpen] = useState(false)
|
||
|
||
// Сохраняем состояние в 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)
|
||
}
|
||
|
||
useImperativeHandle(ref, () => ({
|
||
openPlayersModal: () => setIsPlayersModalOpen(true),
|
||
openQuestionsModal: () => setIsQuestionsModalOpen(true),
|
||
newGame: () => {
|
||
setPlayers([])
|
||
setCurrentPlayerId(null)
|
||
setPlayerScores({})
|
||
setGameOver(false)
|
||
setRevealedAnswers({})
|
||
},
|
||
showAllAnswers: handleShowAllAnswers,
|
||
}))
|
||
|
||
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
|
||
}
|
||
|
||
const handleAnswerClick = (answerIndex, points) => {
|
||
const currentRevealed = getCurrentRevealedAnswers()
|
||
if (currentRevealed.includes(answerIndex)) return
|
||
if (!currentPlayerId) return
|
||
if (!currentQuestion) return
|
||
|
||
const isLastAnswer = currentRevealed.length === currentQuestion.answers.length - 1
|
||
|
||
updateRevealedAnswers([...currentRevealed, answerIndex])
|
||
|
||
// Добавляем очки текущему участнику
|
||
setPlayerScores({
|
||
...playerScores,
|
||
[currentPlayerId]: (playerScores[currentPlayerId] || 0) + points,
|
||
})
|
||
|
||
// Переходим к следующему участнику только если это не последний ответ
|
||
if (!isLastAnswer) {
|
||
const nextPlayerId = getNextPlayerId()
|
||
if (nextPlayerId) {
|
||
setTimeout(() => {
|
||
setCurrentPlayerId(nextPlayerId)
|
||
}, 500)
|
||
}
|
||
} else {
|
||
// Если это последний ответ, переходим к следующему участнику перед следующим вопросом
|
||
setTimeout(() => {
|
||
const nextPlayerId = getNextPlayerId()
|
||
if (nextPlayerId) {
|
||
setCurrentPlayerId(nextPlayerId)
|
||
}
|
||
|
||
if (isLastQuestion) {
|
||
setGameOver(true)
|
||
} else {
|
||
setTimeout(() => {
|
||
nextQuestion()
|
||
}, 500)
|
||
}
|
||
}, 2000)
|
||
}
|
||
}
|
||
|
||
const nextQuestion = () => {
|
||
if (onQuestionIndexChange) {
|
||
onQuestionIndexChange(currentQuestionIndex + 1)
|
||
}
|
||
// Не сбрасываем открытые ответы - они сохраняются для каждого вопроса отдельно
|
||
}
|
||
|
||
const restartGame = () => {
|
||
if (onQuestionIndexChange) {
|
||
onQuestionIndexChange(0)
|
||
}
|
||
setGameOver(false)
|
||
setRevealedAnswers({})
|
||
const initialScores = {}
|
||
players.forEach(player => {
|
||
initialScores[player.id] = 0
|
||
})
|
||
setPlayerScores(initialScores)
|
||
if (players.length > 0) {
|
||
setCurrentPlayerId(players[0].id)
|
||
}
|
||
}
|
||
|
||
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)
|
||
// Открытые ответы сохраняются для каждого вопроса отдельно
|
||
}
|
||
}
|
||
|
||
if (gameOver) {
|
||
// Находим победителя(ей)
|
||
const scores = Object.values(playerScores)
|
||
const maxScore = scores.length > 0 ? Math.max(...scores) : 0
|
||
const winners = players.filter(p => playerScores[p.id] === maxScore)
|
||
|
||
return (
|
||
<div className="game-over">
|
||
<div className="game-over-content">
|
||
<h2 className="game-over-title">🎉 Игра окончена! 🎉</h2>
|
||
<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>
|
||
<button className="restart-button" onClick={restartGame}>
|
||
Играть снова
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="game">
|
||
<PlayersModal
|
||
isOpen={isPlayersModalOpen}
|
||
onClose={() => setIsPlayersModalOpen(false)}
|
||
players={players}
|
||
onAddPlayer={handleAddPlayer}
|
||
onRemovePlayer={handleRemovePlayer}
|
||
/>
|
||
|
||
<QuestionsModal
|
||
isOpen={isQuestionsModalOpen}
|
||
onClose={() => setIsQuestionsModalOpen(false)}
|
||
questions={questions}
|
||
onUpdateQuestions={onQuestionsChange}
|
||
/>
|
||
|
||
<div className="game-header">
|
||
{players.length > 0 && (
|
||
<Players
|
||
players={players}
|
||
currentPlayerId={currentPlayerId}
|
||
playerScores={playerScores}
|
||
onSelectPlayer={handleSelectPlayer}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{players.length > 0 && currentPlayerId ? (
|
||
<div className="game-content">
|
||
{questions.length === 0 ? (
|
||
<div className="no-players-message">
|
||
<p>Добавьте вопросы, чтобы начать игру</p>
|
||
</div>
|
||
) : currentQuestion ? (
|
||
<Question
|
||
question={currentQuestion}
|
||
questionNumber={currentQuestionIndex + 1}
|
||
onAnswerClick={handleAnswerClick}
|
||
revealedAnswers={getCurrentRevealedAnswers()}
|
||
onPreviousQuestion={handlePreviousQuestion}
|
||
onNextQuestion={handleNextQuestion}
|
||
canGoPrevious={currentQuestionIndex > 0}
|
||
canGoNext={currentQuestionIndex < questions.length - 1}
|
||
/>
|
||
) : (
|
||
<div className="no-players-message">
|
||
<p>Ошибка: вопрос не найден</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<div className="no-players-message">
|
||
<p>Добавьте участников, чтобы начать игру</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
})
|
||
|
||
Game.displayName = 'Game'
|
||
|
||
export default Game
|
||
|