Tapage/app/[id]/page.tsx

392 lines
15 KiB
TypeScript

'use client'
import { useState, useEffect, useRef } from 'react'
import { Socket, io } from "socket.io-client"
import getSocketUrl from './socket'
import { getRandomQuestion, playModes } from "./questions"
import { IconCrown, IconPhotoUp, IconCamera } from "@tabler/icons-react"
import { defaultAvatarImage } from '../avatarImage'
import { resizeBase64Image, getBase64OfImage } from '../utils'
import ModeCard from '@/components/modeCard'
import EditCustomQuestions from '@/components/editCustomQuestions'
import Webcam from 'react-webcam'
import { PlayerChoice } from '@/utils/types'
interface roomProps {
params: {
id: string;
};
}
export default function GameRoom({ params }: roomProps) {
const [isConnected, setIsConnected] = useState<boolean>(false)
const [forceUpdate, setForceUpdate] = useState<number>(0)
const [socketURL, setSocketURL] = useState<string>("")
const [role, setRole] = useState<string|null>("")
const [name, setName] = useState<string|null>("")
const [avatar, setAvatar] = useState<string|null>(defaultAvatarImage)
const [browserId, setBrowserId] = useState<string|null>("")
const [gameStarted, setGameStarted] = useState<boolean|null>(false)
const [gameEnded, setGameEnded] = useState<boolean|null>(false)
const [questionReply, setQuestionReply] = useState<any>({})
const [questionDisplayed, setQuestionDisplayed] = useState<any|null>({})
const [possibleChoice, setPossibleChoice] = useState<PlayerChoice[]>([])
const [totalVotes, setTotalVotes] = useState<number>(0)
const [choice, setChoice] = useState<string|null>("")
const [countdown, setCountdown] = useState<number>(0)
const [players, setPlayers] = useState<any[]>([])
const [questionNbr, setQuestionNbr] = useState<number>(0)
const [questionAlreadyDone, setQuestionAlreadyDone] = useState<any[]>([])
const [selectedMode, setSelectedMode] = useState<number>(0)
const [customQuestions, setCustomQuestions] = useState<any[]|null>([])
const socketRef = useRef<Socket>() // eslint-disable-line
const inputPhotoRef = useRef<any>()
const editCustomQuestionsRef = useRef<any>()
const duration = 15
const questionLimit = 10
getSocketUrl().then((url) => {setSocketURL(url)})
const { id } = params
const roomNameDisplay = id.substring(0,3) + " " + id.substring(3,6)
useEffect(() => {
const localName = localStorage.getItem('name')
setName(localName)
const localAvatar = localStorage.getItem('avatar')
setAvatar(localAvatar)
const localBrowserId = localStorage.getItem("browserId")
setBrowserId(localBrowserId)
const localCustomQuestionsString = localStorage.getItem("customQuestions")
const localCustomQuestions = JSON.parse(localCustomQuestionsString != null ? localCustomQuestionsString : "[]")
setCustomQuestions(localCustomQuestions == null ? [] : localCustomQuestions)
// Listen for incoming setMessages
}, []);
useEffect(() => {
if(socketURL != ""){
socketRef.current = io(socketURL);
socketRef.current!.on("connect", () => {
setIsConnected(true)
socketRef.current!.emit('room_connect', {id: id, name: name, avatar: avatar, browserId: browserId})
});
socketRef.current!.on("new_player", (params) => {
setPlayers(oldPlayers => [...oldPlayers, params])
})
socketRef.current!.on("start_game", (params) => {
setQuestionNbr(params.questionNbr)
setGameStarted(true)
setQuestionDisplayed(params.question)
setPossibleChoice(params.possibleChoice)
setCountdown(params.duration)
})
socketRef.current!.on("next_question", (params) => {
setQuestionNbr(oldQ => oldQ + 1)
setChoice("")
setQuestionDisplayed(params.question)
setPossibleChoice(params.possibleChoice)
setCountdown(params.duration)
setTotalVotes(0)
})
socketRef.current!.on("question_reply", (params) => {
console.log(params)
setQuestionReply(params)
})
socketRef.current!.on("reset_game", (params) => {
setGameStarted(false)
setCountdown(0)
setPossibleChoice([])
setQuestionDisplayed("")
setChoice("")
setQuestionNbr(0)
setTotalVotes(0)
})
socketRef.current!.on("room_joined", (params) => {
setPlayers(params.room_users)
setRole(params.role)
})
// Clean up the socket connection on unmount
return () => {
socketRef.current!.disconnect();
};
}
}, [socketURL])
function forceUpdateFunc(){
setForceUpdate(fu => fu + 1)
}
useEffect(() => {
const eventListener = (params: any) => {
setTotalVotes(oldTotal => oldTotal += 1)
console.log(params)
let temp_possibleChoice = possibleChoice
temp_possibleChoice!.forEach((playerChoice) => {
if(playerChoice.name == params.choice) {
playerChoice.nbrVotes += 1
}
})
forceUpdateFunc()
};
if(socketRef.current != undefined){
socketRef.current!.on("player_choice", eventListener)
return () => {socketRef.current!.off("player_choice", eventListener)}
}
}, [possibleChoice])
useEffect(() => {
countdown > 0 && setTimeout(() => setCountdown(countdown - 1), 1000);
}, [countdown]);
function getPossibleChoice() {
let choice1 = Math.floor(Math.random() * players.length)
console.log(players.length)
let choice2 = Math.floor(Math.random() * players.length)
console.log(choice2)
while(choice2 == choice1){
choice2 = Math.floor(Math.random() * players.length)
}
let playerChoice1 = players[choice1]
playerChoice1.nbrVotes = 0
let playerChoice2 = players[choice2]
playerChoice2.nbrVotes = 0
return [playerChoice1, playerChoice2]
}
function startGame() {
let possibleChoice = getPossibleChoice()
let questionObj = getRandomQuestion(playModes[selectedMode], [], customQuestions)
let question = questionObj.question
setQuestionAlreadyDone(questionObj.alreadyDone)
socketRef.current!.emit("start_game", {roomId: id, question: question, possibleChoice: possibleChoice, duration: duration})
setCountdown(duration)
setPossibleChoice(possibleChoice)
setQuestionDisplayed(question)
setGameStarted(true)
setQuestionNbr(oldQ => oldQ + 1)
}
function nextQuestion() {
let possibleChoice = getPossibleChoice()
let questionObj = getRandomQuestion(playModes[selectedMode],questionAlreadyDone, customQuestions)
let question = questionObj.question
setQuestionAlreadyDone(questionObj.alreadyDone)
socketRef.current!.emit("next_question", {roomId: id, question: question, possibleChoice: possibleChoice, duration: duration})
setCountdown(duration)
setPossibleChoice(possibleChoice)
setQuestionDisplayed(question)
setChoice("")
setQuestionNbr(oldQ => oldQ + 1)
setTotalVotes(0)
}
function resetGame() {
socketRef.current!.emit("reset_game", {roomId: id})
setGameStarted(false)
setCountdown(0)
setPossibleChoice([])
setQuestionDisplayed("")
setChoice("")
setQuestionNbr(0)
setTotalVotes(0)
}
function setAndSendChoice(playerName: any) {
setChoice(playerName)
setTotalVotes(oldTotal => oldTotal += 1)
let temp_possibleChoice = possibleChoice
temp_possibleChoice.forEach((playerChoice) => {
if(playerChoice.name == playerName) {
playerChoice.nbrVotes += 1
}
})
setPossibleChoice(temp_possibleChoice)
socketRef.current!.emit("player_choice", {roomId: id, choice: playerName, player: name})
}
function setAndSendPhoto(){
getBase64OfImage(inputPhotoRef.current!.files[0], (data: any) => resizeBase64Image(data).then((data: any) =>{
setQuestionReply({photo: data})
socketRef.current!.emit("question_reply", { roomId: id, data:{photo: data}})
}))
}
function setAndSaveCustomQuestions(data: any){
setCustomQuestions(data)
localStorage.setItem("customQuestions", JSON.stringify(data))
forceUpdateFunc()
}
return (
<main data-theme="cupcake" className="flex min-h-screen flex-col items-center space-y-16 p-4">
{ !gameStarted &&
<>
<div className="flex flex-col">
<h1 className="text-6xl">{roomNameDisplay}</h1>
</div>
<div className="flex flex-col space-y-12">
{ !isConnected &&
<p>Connecting to room...</p>
}
{ isConnected &&
<>
{ role == "owner" &&
<>
<div className="flex flex-col space-y-4">
<h2 className="text-2xl font-bold">Choose game mode</h2>
<div className="grid grid-cols-3 place-items-center">
{ playModes.map((mode, index) => {
return(
<ModeCard key={index} mode={mode} onChange={() => setSelectedMode(index)} selected={selectedMode === index} />
)
})}
</div>
</div>
{ playModes[selectedMode].name == "Custom" &&
<>
<EditCustomQuestions dataRef={editCustomQuestionsRef} questionList={customQuestions} setQuestions={(data: any) => setAndSaveCustomQuestions(data)} />
<div className="flex flex-col items-center w-full space-y-4">
<p>You have {customQuestions!.length} custom questions</p>
<button className="btn btn-primary w-full" onClick={() => editCustomQuestionsRef.current!.showModal()}>Edit questions...</button>
</div>
</>
}
</>
}
<div className="flex flex-col space-y-4">
<h2 className="text-2xl font-bold">Players</h2>
{ players.length == 0 &&
<p>Waiting for players to join...</p>
}
{ players.length > 0 &&
<div className="flex flex-col space-y-16">
<div className="grid grid-cols-3 gap-4 place-items-center">
{ players.map((player) => {
return (
<>
<div className="flex flex-col items-center">
<div className="avatar indicator">
{player.role == "owner" && <IconCrown className="indicator-item" />}
<div className="w-16 rounded">
<img src={player.avatar} />
</div>
</div>
<p className="flex flex-row">{player.name}</p>
</div>
</>
)
})}
</div>
{ role == "owner" &&
<button className="btn btn-primary" onClick={startGame} disabled={players!.length <= 1 || (customQuestions!.length == 0 && playModes[selectedMode].name == "Custom")}>Start game</button>
}
</div>
}
</div>
</>
}
</div>
</>
}
{ gameStarted &&
<>
<div className="flex flex-col space-y-4 items-center">
<h1 className="text-3xl">{questionDisplayed!.text}</h1>
<span className="indicator-item badge indicator-bottom indicator-center opacity-30">{questionNbr}/{questionLimit}</span>
</div>
{ countdown > 0 &&
<>
<span className="countdown">
<span style={{"--value":countdown.toString()} as React.CSSProperties }></span>
</span>
<div className="flex flex-row space-x-4">
{ choice == "" &&
<>
{ possibleChoice.map((playerChoice, index) => (
<button key={index} className="btn btn-primary" disabled={choice != ""} onClick={() => {setAndSendChoice(playerChoice.name)}}>{playerChoice.name}</button>
))}
</>
}
{ choice != "" &&
<span>Waiting <span className="loading loading-dots loading-xs"></span></span>
}
</div>
</>
}
{ countdown == 0 &&
<>
<div className="flex flex-row space-x-4">
{ possibleChoice.map((playerChoice, index) => {
console.log(totalVotes)
console.log(playerChoice.nbrVotes / totalVotes)
let percent = Math.floor((playerChoice.nbrVotes / totalVotes) * 100)
return (
<div key={index} className="flex items-center space-y-4 flex-col">
<div className="radial-progress text-primary" style={{ "--value": percent } as React.CSSProperties} role="progressbar">
{percent}%
</div>
<h1>{playerChoice.name}</h1>
</div>
)})}
</div>
{ (questionDisplayed!.type == "photo" && possibleChoice.sort((a,b) => b.nbrVotes - a.nbrVotes)[0].browserId == browserId) &&
<>
{ questionReply.photo == undefined &&
<div className="flex flex-col items-center space-y-4">
<span className="text-xl">À toi d&apos;envoyer une photo !</span>
<input type="file" ref={inputPhotoRef} onChange={setAndSendPhoto} className="hidden" />
<div className="flex flex-row space-x-4">
<button className="btn btn-primary" onClick={() => inputPhotoRef.current!.click()}><IconPhotoUp /></button>
</div>
</div>
}
{ questionReply.photo != undefined &&
<div>
<img src={questionReply.photo} />
<span className="text-zinc-500">Photo received from {possibleChoice.sort((a,b) => b.nbrVotes - a.nbrVotes)[0].name}...</span>
</div>
}
</>
}
{ (questionDisplayed!.type == "photo" && possibleChoice.sort((a,b) => b.nbrVotes - a.nbrVotes)[0].browserId != browserId) &&
<>
{ questionReply.photo == undefined &&
<div>
<span className="text-zinc-500">Waiting for {possibleChoice.sort((a,b) => b.nbrVotes - a.nbrVotes)[0].name}...</span>
</div>
}
{ questionReply.photo != undefined &&
<div>
<img src={questionReply.photo} />
<span className="text-zinc-500">Photo received from {possibleChoice.sort((a,b) => b.nbrVotes - a.nbrVotes)[0].name}...</span>
</div>
}
</>
}
{ (role == "owner" && questionNbr < questionLimit) &&
<button className="btn btn-primary" onClick={nextQuestion}>Next question</button>
}
{ (role == "owner" && questionNbr == questionLimit) &&
<button className="btn btn-primary" onClick={resetGame}>Replay</button>
}
</>
}
</>
}
</main>
);
}