From de65fddba2fe9007f815b08fe2e478c062c8ccd6 Mon Sep 17 00:00:00 2001 From: Dane Johnson Date: Sun, 19 Feb 2023 11:29:28 -0600 Subject: [PATCH] Event driven, add clue clock --- src/Contestant.tsx | 7 +++--- src/Timer.tsx | 48 ++++++++++++++++++++++++++++++++++++ src/display/ClueDisplay.tsx | 15 ++++++++++- src/socket.ts | 8 +----- src/store/commonSlice.ts | 9 +------ src/store/contestantSlice.ts | 8 +----- 6 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 src/Timer.tsx diff --git a/src/Contestant.tsx b/src/Contestant.tsx index 7fdb421..108319a 100644 --- a/src/Contestant.tsx +++ b/src/Contestant.tsx @@ -1,19 +1,20 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { debounce } from "./utils"; import { socket } from "./socket"; import { useAppSelector } from "./hooks"; -import { selectCanBuzz, selectSignature } from "./store/contestantSlice"; +import { selectSignature } from "./store/contestantSlice"; const Contestant = () => { const { room } = useParams(); const signature = useAppSelector(selectSignature); - const canBuzz = useAppSelector(selectCanBuzz); + const [canBuzz, setCanBuzz] = useState(false); useEffect(() => { socket.emit("contestant-join", { room, signature }); + socket.on("clue-clock-on", () => setCanBuzz(true)); }, []); const handleBuzz = debounce(() => { diff --git a/src/Timer.tsx b/src/Timer.tsx new file mode 100644 index 0000000..8355408 --- /dev/null +++ b/src/Timer.tsx @@ -0,0 +1,48 @@ +import { useState, useEffect } from "react"; +import { clamp } from "lodash"; + +interface Props { + startTime: number | null; + length: number; + onTimeout?: () => unknown; +} + +const invLerp = (a: number, b: number, x: number): number => + clamp((x - b) / (a - b), 0, 1); + +const Timer = ({ startTime, length, onTimeout }: Props) => { + const [segs, setSegs] = useState(0); + const updateTimer = () => { + if (!startTime) return; + const endTime = startTime + length; + const currentTime = Date.now(); + const segs = invLerp(startTime, endTime, currentTime) * 5; + setSegs(segs); + if (segs > 0) { + setTimeout(updateTimer, 100); + } else if (onTimeout) { + onTimeout(); + } + }; + useEffect(() => { + setTimeout(updateTimer, 100); + }, [startTime, length]); + + return ( + + {[5, 4, 3, 2, 1, 2, 3, 4, 5].map((seg, i) => ( + + ))} + + ); +}; + +export default Timer; diff --git a/src/display/ClueDisplay.tsx b/src/display/ClueDisplay.tsx index 169614d..289bc1e 100644 --- a/src/display/ClueDisplay.tsx +++ b/src/display/ClueDisplay.tsx @@ -1,11 +1,24 @@ +import { useState, useEffect } from "react"; + +import Timer from "../Timer"; import type { Clue } from "../store/cluesSlice"; +import { socket } from "../socket"; interface Props { activeClue: Clue; } const ClueDisplay = ({ activeClue }: Props) => { - return
{activeClue.question}
; + const [startTime, setStartTime] = useState(null); + useEffect(() => { + socket.on("clue-clock-on", () => setStartTime(Date.now())); + }, []); + return ( + <> +
{activeClue.question}
+ + + ); }; export default ClueDisplay; diff --git a/src/socket.ts b/src/socket.ts index 9a479c1..5cd05af 100644 --- a/src/socket.ts +++ b/src/socket.ts @@ -1,8 +1,7 @@ import { io } from "socket.io-client"; import store from "./store"; -import { setRoomCode, setShouldStartClueClock } from "./store/commonSlice"; +import { setRoomCode } from "./store/commonSlice"; import { addContestant } from "./store/displaySlice"; -import { setCanBuzz } from "./store/contestantSlice"; import { setCategories, setActiveClue } from "./store/cluesSlice"; import type { Clue } from "./store/cluesSlice"; @@ -31,9 +30,4 @@ export const setup = () => { socket.on("active-clue", (data: Clue) => { store.dispatch(setActiveClue(data)); }); - - socket.on("clue-clock-on", () => { - store.dispatch(setCanBuzz(true)); - store.dispatch(setShouldStartClueClock(true)); - }); }; diff --git a/src/store/commonSlice.ts b/src/store/commonSlice.ts index 4c09eed..91e8bbb 100644 --- a/src/store/commonSlice.ts +++ b/src/store/commonSlice.ts @@ -3,12 +3,10 @@ import type { RootState } from "./"; interface CommonState { roomCode: string; - shouldStartClueClock: boolean; } const initialState: CommonState = { roomCode: "", - shouldStartClueClock: false, }; export const commonSlice = createSlice({ @@ -18,15 +16,10 @@ export const commonSlice = createSlice({ setRoomCode: (state, { payload }: PayloadAction) => { state.roomCode = payload; }, - setShouldStartClueClock: (state, { payload }: PayloadAction) => { - state.shouldStartClueClock = payload; - }, }, }); -export const { setRoomCode, setShouldStartClueClock } = commonSlice.actions; +export const { setRoomCode } = commonSlice.actions; export const selectRoomCode = (state: RootState) => state.common.roomCode; -export const selectShouldStartClueClock = (state: RootState) => - state.common.shouldStartClueClock; export default commonSlice.reducer; diff --git a/src/store/contestantSlice.ts b/src/store/contestantSlice.ts index 609b64b..fe4209e 100644 --- a/src/store/contestantSlice.ts +++ b/src/store/contestantSlice.ts @@ -3,12 +3,10 @@ import type { RootState } from "./"; interface ContestantState { signature: number[][]; - canBuzz: boolean; } const initialState: ContestantState = { signature: [], - canBuzz: false, }; export const contestantSlice = createSlice({ @@ -18,14 +16,10 @@ export const contestantSlice = createSlice({ addPathToSignature: (state, { payload }: PayloadAction) => { state.signature = [...state.signature, payload]; }, - setCanBuzz: (state, { payload }: PayloadAction) => { - state.canBuzz = payload; - }, }, }); -export const { addPathToSignature, setCanBuzz } = contestantSlice.actions; +export const { addPathToSignature } = contestantSlice.actions; export const selectSignature = (state: RootState) => state.contestant.signature; -export const selectCanBuzz = (state: RootState) => state.contestant.canBuzz; export default contestantSlice.reducer;