diff --git a/client/.env b/client/.env index e29fdba..74092bf 100644 --- a/client/.env +++ b/client/.env @@ -1 +1 @@ -VITE_API_URL=https://localhost:7144 +VITE_API_URL=https://localhost:7144/ diff --git a/client/src/App.tsx b/client/src/App.tsx index e22cfb7..389913b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,3 @@ -import React, { useState } from 'react'; import { HashRouter as Router, Routes, Route } from 'react-router-dom'; import GameField from './views/Game'; import Home from './views/Home'; @@ -9,25 +8,15 @@ import CreateGame from './views/CreateGame'; import JoinGame from './views/JoinGame'; const App = () => { - const [settings, setSettings] = useState({ - speedOption: 'fast', - pointOption: 10 - }); - - const handleCreateGameSubmit = (name: string, sessionId: string) => { - console.log("Game created with name:", name, "and sessionId:", sessionId); - // Weitere Logik hier - }; - return ( } /> - } /> + } /> } /> } /> } /> - } /> {/* Stelle sicher, dass onSubmit übergeben wird */} + } /> } /> diff --git a/client/src/components/QuestionDialog.tsx b/client/src/components/QuestionDialog.tsx index 754c168..e516c45 100644 --- a/client/src/components/QuestionDialog.tsx +++ b/client/src/components/QuestionDialog.tsx @@ -1,16 +1,10 @@ import React, { useEffect, useState, useCallback, useRef } from "react"; -import { Question, QuestionDialogProps, gamepad } from "../utils/types"; +import { QuestionDialogProps } from "../utils/types"; import { gameDefaults } from "../views/Game"; import correctSound from "../assets/correct.mp3"; import wrongSound from "../assets/wrong.mp3"; import { playSound } from "../utils/board"; -const state: any = { - question: undefined, - activeIndex: 0, - isDone: false, -}; - const QuestionDialogCmp: React.FC = ({ answeredQuestion, question, @@ -21,6 +15,7 @@ const QuestionDialogCmp: React.FC = ({ const [isCorrect, setIsCorrect] = useState(false); const [timer, setTimer] = useState(gameDefaults.questionSeconds); let timeoutRef: any = useRef(null); + let timerInterval: any; useEffect(() => { if (isAnsweredQuestionCorrect == undefined) return; @@ -39,18 +34,39 @@ const QuestionDialogCmp: React.FC = ({ answeredQuestion(activeIndex); }, [activeIndex, question]); + const handleClick = (index: number) => { + clearTimeout(timeoutRef.current); + setActiveIndex(index); + answeredQuestion(index); + }; + useEffect(() => { setIsWrong(false); setIsCorrect(false); setTimer(gameDefaults.questionSeconds); - // if times is up we push a wrong question index + // if time is up we push a wrong question index timeoutRef.current = setTimeout(() => { answeredQuestion(1000); }, gameDefaults.questionSeconds * 1000); + let timerState = timer; + timerInterval = setInterval(() => { + + if(timerState === 0){ + clearInterval(timerInterval); + return; + } + + timerState -= 1; + setTimer(timerState) + }, 1000); + return () => { clearTimeout(timeoutRef.current); + if (timerInterval) { + clearInterval(timerInterval); + } }; }, []); @@ -76,7 +92,6 @@ const QuestionDialogCmp: React.FC = ({ [handleSpace] ); - useEffect(() => { window.addEventListener("keydown", handleKeyPress); @@ -98,30 +113,30 @@ const QuestionDialogCmp: React.FC = ({
{question?.question}
handleClick(0)} > {question?.A}
handleClick(1)} > {question?.B}
handleClick(2)} > {question?.C}
handleClick(3)} > {question?.D}
diff --git a/client/src/views/CreateGame.tsx b/client/src/views/CreateGame.tsx index f5f0c6c..f71cd88 100644 --- a/client/src/views/CreateGame.tsx +++ b/client/src/views/CreateGame.tsx @@ -9,9 +9,7 @@ import { PlayerSessionData } from "../utils/types"; import { updateGameState } from "../utils/game-state"; let playerPollIntervall: any; -const CreateGame: React.FC<{ - onSubmit: (name: string, sessionId: string) => void; -}> = ({ onSubmit }) => { +const CreateGame: React.FC = () => { const [name, setName] = useState(""); const [error, setError] = useState(null); const [emptyError, setEmptyError] = useState(null); @@ -41,7 +39,6 @@ const CreateGame: React.FC<{ const sessionId = response.sessionId; if (sessionId) { setSessionId(sessionId); - onSubmit(name, sessionId); const joinLink = `${window.location.origin}/#/join/${sessionId}`; setLink(joinLink); setIsWaitingForPlayer(true); @@ -67,8 +64,6 @@ const CreateGame: React.FC<{ const me = players[0]; updateGameState({ name: me.name ?? "random1", id: me.id, sessionId, isHost: true }); - console.log("Number of players:", players.length); - if (players.length > 1) { setIsWaitingForPlayer(false); setPlayers(players); @@ -82,17 +77,36 @@ const CreateGame: React.FC<{ const copyToClipboard = async () => { await playSound(buttonClickSound); if (link) { - navigator.clipboard - .writeText(link) - .catch((err) => console.error("Failed to copy: ", err)); + if (navigator.clipboard && window.isSecureContext) { + // Use Clipboard API if available and secure context + try { + await navigator.clipboard.writeText(link); + } catch (err) { + console.error("Failed to copy: ", err); + } + } else { + // Fallback for insecure context or older browsers + const textArea = document.createElement("textarea"); + textArea.value = link; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + try { + document.execCommand('copy'); + console.log("Text copied successfully."); + } catch (err) { + console.error("Fallback: Failed to copy: ", err); + } + document.body.removeChild(textArea); + } setShowCopyMessage(true); - setTimeout(() => { setShowCopyMessage(false); }, 2000); } }; + const onStartGame = async () => { await playSound(buttonClickSound); if (sessionId) { @@ -189,46 +203,49 @@ const CreateGame: React.FC<{ } return ( -
- - + <> + + + + - {emptyError &&

{emptyError}

} - {error &&

{error}

} + {emptyError &&

{emptyError}

} + {error &&

{error}

} - {showInviteMessage && ( -
-

Leite den Einladungslink an deinen Mitspieler weiter 😊

-
- )} - {link && ( -
- {link} - - {showCopyMessage && ( -
-

Link in zwischenablage kopiert

-
- )} -
- )} + {showInviteMessage && ( +
+

Leite den Einladungslink an deinen Mitspieler weiter 😊

+
+ )} + {link && ( +
+ {link} + + {showCopyMessage && ( +
+

Link in zwischenablage kopiert

+
+ )} +
+ )} - {isWaitingForPlayer && ( -
-

Warte auf weiteren Spieler

- -
- )} - + {isWaitingForPlayer && ( +
+

Warte auf weiteren Spieler

+ +
+ )} + + ); }; diff --git a/client/src/views/Game.tsx b/client/src/views/Game.tsx index c4b2423..b0a3be7 100644 --- a/client/src/views/Game.tsx +++ b/client/src/views/Game.tsx @@ -32,14 +32,14 @@ import { } from "../utils/question"; const INITIAL_GAME_DEFAULTS: BaseSettings = { - velocityXIncrement: 1.2, - baseVelocityX: 2.5, - baseVelocityY: 1.7, + velocityXIncrement: 1.1, + baseVelocityX: -2.3, + baseVelocityY: 1.5, boardHeightDivisor: 1.7, maxBoardWidth: 700, maxLife: 2, - maxVelocityX: 7, - moveSpeed: 6, + maxVelocityX: 5, + moveSpeed: 9, playerHeight: 60, playerWidth: 10, player1KeyDown: "ArrowDown", @@ -67,7 +67,7 @@ export const assignGameDefaults = (settings: BaseSettings) => { let animationFrame: number | null = null; // simple state management -const GameField: React.FC = () => { +const GameField: React.FC = () => { const boardWidth = determineBoardWidth(); const boardHeight = boardWidth / gameDefaults.boardHeightDivisor; const contextRef = useRef(null); @@ -411,8 +411,9 @@ const GameField: React.FC = () => { }; const handleGoal = () => { - setLife((prevLife) => prevLife - 1); - if (life > 0) { + const newLife = gameState().life! - 1; + setLife(newLife); + if (newLife > 0) { playSound(goalSound); resetBall(); } else { @@ -537,7 +538,9 @@ const GameField: React.FC = () => { }; const answeredQuestion = (questionIndex: number) => { - gameHub.pushAnsweredQuestion(gameState().sessionId!, { questionIndex }); + if (gameState().isHost) { + gameHub.pushAnsweredQuestion(gameState().sessionId!, { questionIndex }); + } }; const handleAnswer = (isCorrect: boolean) => { @@ -547,7 +550,7 @@ const GameField: React.FC = () => { if (isCorrect) { setScore(gameState().score! + timer); } else { - setLife((prevLife) => prevLife - 1); + setLife(gameState().life! - 1); } } @@ -565,6 +568,10 @@ const GameField: React.FC = () => { try { await initializeHubSession(); + if (gameState().isHost) { // litle hacky delay to sync /TODO + await new Promise((resolve) => setTimeout(() => resolve(null), 500)) + } + setIsCountdownActive(false); resetGameState(); startTimer(); @@ -584,7 +591,6 @@ const GameField: React.FC = () => { try { const sessionData = await getSessionById(gameStateId()); const state = gameState(); - console.log("STATE", state) if (!sessionData || !sessionData.isSessionRunning) { alert("Spiel wurde beendet"); @@ -603,7 +609,6 @@ const GameField: React.FC = () => { await gameHub.start(); registerHubEvents(); await gameHub.joinLobby(gameStateId()); - console.log("STATE AFTRER", state) setIsHubActive(true); } catch (err) { diff --git a/client/src/views/Home.tsx b/client/src/views/Home.tsx index f934f3f..8cf2cf6 100644 --- a/client/src/views/Home.tsx +++ b/client/src/views/Home.tsx @@ -1,20 +1,14 @@ import buttonClickSound from "../assets/button-click-sound.mp3"; import { HomeProps, gamepad } from "../utils/types"; -import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import backgroundMusic from "../assets/game.mp3"; -import valuehero from "../assets/valuehero.png"; -import valuehero2 from "../assets/valuehero2.png"; +import valuehero2 from "../assets/valuehero.png"; import AudioComponent from "../components/Audio"; import { playSound } from "../utils/board"; +import QuestionDialogCmp from "../components/QuestionDialog"; +import { getRandomQuestion } from "../utils/question"; -const state: any = { - activeIndex: 0, - gameMode: 0, -}; const Home: React.FC = ({ }) => { - const [activeIndex, setActiveIndex] = useState(0); - const [gameMode, setGameMode] = useState(1); const navigate = useNavigate(); const goToGame = async () => { @@ -26,60 +20,6 @@ const Home: React.FC = ({ }) => { navigate("/scores"); }; - const handlePress = () => { - switch (state.activeIndex) { - case 0: - goToGame(); - break; - case 1: - goToHighscore(); - break; - default: - break; - } - }; - - useEffect(() => { - state.activeIndex = activeIndex; - state.gameMode = gameMode; - - localStorage.setItem("gameMode", gameMode.toString()); - }, [activeIndex, gameMode]); - - useEffect(() => { - const handleKeyPress = (event: KeyboardEvent) => { - event.preventDefault(); - event.stopPropagation(); - - switch (event.key) { - case "ArrowUp": - const newIndex = state.activeIndex > 0 ? state.activeIndex - 1 : 0; - setActiveIndex(newIndex); - break; - case "ArrowDown": - const newIndexD = state.activeIndex < 1 ? state.activeIndex + 1 : 1; - setActiveIndex(newIndexD); - break; - case "ArrowRight": - const newIndexR = state.gameMode == 0 ? state.gameMode + 1 : 0; - setGameMode(newIndexR); - break; - case " ": - handlePress(); - break; - default: - break; - } - }; - - window.addEventListener("keydown", handleKeyPress); - - - return () => { - window.removeEventListener("keydown", handleKeyPress); - }; - }, []); - return ( <>
@@ -89,29 +29,23 @@ const Home: React.FC = ({ }) => {
-

+

#ValueHero

-
+ {/* { }} + value={"Verbundenheit"} + /> */} { }} path={backgroundMusic} volume={0.005} /> - + ); };