mirror of
https://github.com/randyjc/Rahoot.git
synced 2026-03-13 20:15:35 +01:00
adding pause and resume in game state
This commit is contained in:
@@ -35,6 +35,7 @@ export interface ServerToClientEvents {
|
|||||||
"game:reset": (_message: string) => void
|
"game:reset": (_message: string) => void
|
||||||
"game:updateQuestion": (_data: { current: number; total: number }) => void
|
"game:updateQuestion": (_data: { current: number; total: number }) => void
|
||||||
"game:playerAnswer": (_count: number) => void
|
"game:playerAnswer": (_count: number) => void
|
||||||
|
"game:break": (_active: boolean) => void
|
||||||
|
|
||||||
// Player events
|
// Player events
|
||||||
"player:successReconnect": (_data: {
|
"player:successReconnect": (_data: {
|
||||||
@@ -66,6 +67,7 @@ export interface ServerToClientEvents {
|
|||||||
"manager:quizzLoaded": (_quizz: QuizzWithId) => void
|
"manager:quizzLoaded": (_quizz: QuizzWithId) => void
|
||||||
"manager:quizzSaved": (_quizz: QuizzWithId) => void
|
"manager:quizzSaved": (_quizz: QuizzWithId) => void
|
||||||
"manager:quizzDeleted": (_id: string) => void
|
"manager:quizzDeleted": (_id: string) => void
|
||||||
|
"manager:break": (_active: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientToServerEvents {
|
export interface ClientToServerEvents {
|
||||||
@@ -78,6 +80,7 @@ export interface ClientToServerEvents {
|
|||||||
"manager:abortQuiz": (_message: MessageGameId) => void
|
"manager:abortQuiz": (_message: MessageGameId) => void
|
||||||
"manager:pauseCooldown": (_message: MessageGameId) => void
|
"manager:pauseCooldown": (_message: MessageGameId) => void
|
||||||
"manager:resumeCooldown": (_message: MessageGameId) => void
|
"manager:resumeCooldown": (_message: MessageGameId) => void
|
||||||
|
"manager:setBreak": (_message: { gameId?: string; active: boolean }) => void
|
||||||
"manager:endGame": (_message: MessageGameId) => void
|
"manager:endGame": (_message: MessageGameId) => void
|
||||||
"manager:skipQuestionIntro": (_message: MessageGameId) => void
|
"manager:skipQuestionIntro": (_message: MessageGameId) => void
|
||||||
"manager:nextQuestion": (_message: MessageGameId) => void
|
"manager:nextQuestion": (_message: MessageGameId) => void
|
||||||
|
|||||||
@@ -234,6 +234,10 @@ io.on("connection", (socket) => {
|
|||||||
withGame(gameId, socket, (game) => game.resumeCooldown(socket))
|
withGame(gameId, socket, (game) => game.resumeCooldown(socket))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
socket.on("manager:setBreak", ({ gameId, active }) =>
|
||||||
|
withGame(gameId, socket, (game) => game.setBreak(socket, active))
|
||||||
|
)
|
||||||
|
|
||||||
socket.on("manager:endGame", ({ gameId }) =>
|
socket.on("manager:endGame", ({ gameId }) =>
|
||||||
withGame(gameId, socket, (game) => game.endGame(socket, registry))
|
withGame(gameId, socket, (game) => game.endGame(socket, registry))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class Game {
|
|||||||
timer: NodeJS.Timeout | null
|
timer: NodeJS.Timeout | null
|
||||||
resolve: (() => void) | null
|
resolve: (() => void) | null
|
||||||
}
|
}
|
||||||
|
breakActive: boolean
|
||||||
|
|
||||||
constructor(io: Server, socket: Socket, quizz: Quizz) {
|
constructor(io: Server, socket: Socket, quizz: Quizz) {
|
||||||
if (!io) {
|
if (!io) {
|
||||||
@@ -84,6 +85,7 @@ class Game {
|
|||||||
timer: null,
|
timer: null,
|
||||||
resolve: null,
|
resolve: null,
|
||||||
}
|
}
|
||||||
|
this.breakActive = false
|
||||||
|
|
||||||
const roomInvite = createInviteCode()
|
const roomInvite = createInviteCode()
|
||||||
this.inviteCode = roomInvite
|
this.inviteCode = roomInvite
|
||||||
@@ -137,6 +139,7 @@ class Game {
|
|||||||
timer: null,
|
timer: null,
|
||||||
resolve: null,
|
resolve: null,
|
||||||
}
|
}
|
||||||
|
game.breakActive = snapshot.breakActive || false
|
||||||
|
|
||||||
if (game.cooldown.active && game.cooldown.remaining > 0 && !game.cooldown.paused) {
|
if (game.cooldown.active && game.cooldown.remaining > 0 && !game.cooldown.paused) {
|
||||||
game.startCooldown(game.cooldown.remaining)
|
game.startCooldown(game.cooldown.remaining)
|
||||||
@@ -193,6 +196,7 @@ class Game {
|
|||||||
paused: this.cooldown.paused,
|
paused: this.cooldown.paused,
|
||||||
remaining: this.cooldown.remaining,
|
remaining: this.cooldown.remaining,
|
||||||
},
|
},
|
||||||
|
breakActive: this.breakActive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +457,27 @@ class Game {
|
|||||||
this.persist()
|
this.persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBreak(socket: Socket, active: boolean) {
|
||||||
|
if (this.manager.id !== socket.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.breakActive = active
|
||||||
|
|
||||||
|
if (this.cooldown.active) {
|
||||||
|
if (active) {
|
||||||
|
this.cooldown.paused = true
|
||||||
|
} else {
|
||||||
|
this.cooldown.paused = false
|
||||||
|
}
|
||||||
|
this.io.to(this.gameId).emit("game:cooldownPause", this.cooldown.paused)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.io.to(this.gameId).emit("game:break", active)
|
||||||
|
this.io.to(this.manager.id).emit("manager:break", active)
|
||||||
|
this.persist()
|
||||||
|
}
|
||||||
|
|
||||||
skipQuestionIntro(socket: Socket) {
|
skipQuestionIntro(socket: Socket) {
|
||||||
if (this.manager.id !== socket.id) {
|
if (this.manager.id !== socket.id) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ const Manager = () => {
|
|||||||
const handleCreate = (quizzId: string) => {
|
const handleCreate = (quizzId: string) => {
|
||||||
socket?.emit("game:create", quizzId)
|
socket?.emit("game:create", quizzId)
|
||||||
}
|
}
|
||||||
|
const handleBreakToggle = (active: boolean) => {
|
||||||
|
socket?.emit("manager:setBreak", { gameId: null, active })
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
return <ManagerPassword onSubmit={handleAuth} />
|
return <ManagerPassword onSubmit={handleAuth} />
|
||||||
@@ -52,6 +55,7 @@ const Manager = () => {
|
|||||||
quizzList={quizzList}
|
quizzList={quizzList}
|
||||||
onBack={() => setShowEditor(false)}
|
onBack={() => setShowEditor(false)}
|
||||||
onListUpdate={setQuizzList}
|
onListUpdate={setQuizzList}
|
||||||
|
onBreakToggle={handleBreakToggle}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const GameWrapper = ({
|
|||||||
const { questionStates, setQuestionStates } = useQuestionStore()
|
const { questionStates, setQuestionStates } = useQuestionStore()
|
||||||
const { backgroundUrl, setBackground, setBrandName } = useThemeStore()
|
const { backgroundUrl, setBackground, setBrandName } = useThemeStore()
|
||||||
const [isDisabled, setIsDisabled] = useState(false)
|
const [isDisabled, setIsDisabled] = useState(false)
|
||||||
|
const [onBreak, setOnBreak] = useState(false)
|
||||||
const next = statusName ? MANAGER_SKIP_BTN[statusName] : null
|
const next = statusName ? MANAGER_SKIP_BTN[statusName] : null
|
||||||
|
|
||||||
useEvent("game:updateQuestion", ({ current, total }) => {
|
useEvent("game:updateQuestion", ({ current, total }) => {
|
||||||
@@ -52,6 +53,9 @@ const GameWrapper = ({
|
|||||||
setIsDisabled(false)
|
setIsDisabled(false)
|
||||||
}, [statusName])
|
}, [statusName])
|
||||||
|
|
||||||
|
useEvent("game:break", (active) => setOnBreak(active))
|
||||||
|
useEvent("manager:break", (active) => setOnBreak(active))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadTheme = async () => {
|
const loadTheme = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -171,6 +175,15 @@ const GameWrapper = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{onBreak && (
|
||||||
|
<div className="pointer-events-none fixed inset-0 z-40 flex items-center justify-center bg-black/60">
|
||||||
|
<div className="rounded-md bg-white/90 px-6 py-4 text-center shadow-lg">
|
||||||
|
<p className="text-lg font-semibold text-gray-800">Game paused for a break</p>
|
||||||
|
<p className="text-sm text-gray-600">We'll resume from the same spot.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type Props = {
|
|||||||
quizzList: QuizzWithId[]
|
quizzList: QuizzWithId[]
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
onListUpdate: (_quizz: QuizzWithId[]) => void
|
onListUpdate: (_quizz: QuizzWithId[]) => void
|
||||||
|
onBreakToggle?: (_active: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditableQuestion = QuizzWithId["questions"][number]
|
type EditableQuestion = QuizzWithId["questions"][number]
|
||||||
@@ -55,7 +56,7 @@ const formatBytes = (bytes: number) => {
|
|||||||
return `${value.toFixed(value >= 10 || value % 1 === 0 ? 0 : 1)} ${units[i]}`
|
return `${value.toFixed(value >= 10 || value % 1 === 0 ? 0 : 1)} ${units[i]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuizEditor = ({ quizzList, onBack, onListUpdate }: Props) => {
|
const QuizEditor = ({ quizzList, onBack, onListUpdate, onBreakToggle }: Props) => {
|
||||||
const { socket } = useSocket()
|
const { socket } = useSocket()
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
const [draft, setDraft] = useState<QuizzWithId | null>(null)
|
const [draft, setDraft] = useState<QuizzWithId | null>(null)
|
||||||
@@ -462,19 +463,29 @@ const QuizEditor = ({ quizzList, onBack, onListUpdate }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="mx-auto flex w-full max-w-[1280px] flex-col gap-7 rounded-md bg-white p-6 shadow-sm md:p-8">
|
<div className="mx-auto flex w-full max-w-[1280px] flex-col gap-7 rounded-md bg-white p-6 shadow-sm md:p-8">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button onClick={onBack} className="bg-gray-700">
|
<Button onClick={onBack} className="bg-gray-700">
|
||||||
Back
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleNew} className="bg-blue-600">
|
||||||
|
New quiz
|
||||||
|
</Button>
|
||||||
|
{selectedId && (
|
||||||
|
<Button className="bg-red-600" onClick={handleDeleteQuizz} disabled={saving}>
|
||||||
|
Delete quiz
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleNew} className="bg-blue-600">
|
)}
|
||||||
New quiz
|
{onBreakToggle && (
|
||||||
</Button>
|
<>
|
||||||
{selectedId && (
|
<Button className="bg-amber-500" onClick={() => onBreakToggle(true)}>
|
||||||
<Button className="bg-red-600" onClick={handleDeleteQuizz} disabled={saving}>
|
Break
|
||||||
Delete quiz
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
<Button className="bg-green-600" onClick={() => onBreakToggle(false)}>
|
||||||
</div>
|
Resume
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button onClick={handleSave} disabled={saving || loading}>
|
<Button onClick={handleSave} disabled={saving || loading}>
|
||||||
{saving ? "Saving..." : "Save quiz"}
|
{saving ? "Saving..." : "Save quiz"}
|
||||||
|
|||||||
Reference in New Issue
Block a user