459 lines
17 KiB
Svelte
459 lines
17 KiB
Svelte
<script lang="ts">
|
|
import Wall from "$lib/Wall.svelte";
|
|
import type { Player } from "$lib/Player";
|
|
import { GameState } from "$lib/GameState";
|
|
import {
|
|
isMultipleChoiceQuestion,
|
|
isSimpleQuestion,
|
|
isImageQuestion,
|
|
type Game,
|
|
isAudioQuestion,
|
|
isAudioMultipleChoiceQuestion,
|
|
isImageMultipleChoiceQuestion
|
|
} from "$lib/games/games";
|
|
import ws from "$lib/websocket.svelte";
|
|
import { page } from "$app/state";
|
|
import { MessageType } from "$lib/MessageType";
|
|
import GameStateSvelte from "$lib/GameState.svelte.js";
|
|
import Scoreboard from "$lib/Scoreboard.svelte";
|
|
import SimpleQuestionComponent from "$lib/SimpleQuestionComponent.svelte";
|
|
import PlusMinusButton from "$lib/PlusMinusButton.svelte";
|
|
import type { VisitedQuestions } from "$lib/Types.js";
|
|
import MultipleChoiceQuestionComponent from "$lib/MultipleChoiceQuestionComponent.svelte";
|
|
import ImageQuestionComponent from "$lib/ImageQuestionComponent.svelte";
|
|
import AudioQuestionComponent from "$lib/AudioQuestionComponent.svelte";
|
|
import AudioMultipleChoiceQuestionComponent from "$lib/AudioMultipleChoiceQuestionComponent.svelte";
|
|
import ImageMultipleChoiceQuestionComponent from "$lib/ImageMultipleChoiceQuestionComponent.svelte";
|
|
|
|
let startDisabled = $state(true);
|
|
|
|
function handleDisplayConnected() {
|
|
if (!startDisabled) {
|
|
gameManager?.sendStart();
|
|
gameManager?.sendCurrentState();
|
|
startDisabled = false;
|
|
}
|
|
}
|
|
|
|
function handleDisplayDisconnected() {
|
|
startDisabled = true;
|
|
}
|
|
|
|
$effect(() => {
|
|
console.log("Display connected:", GameStateSvelte.displayConnected);
|
|
if (GameStateSvelte.displayConnected) {
|
|
handleDisplayConnected();
|
|
} else {
|
|
handleDisplayDisconnected();
|
|
}
|
|
});
|
|
|
|
class GameManager {
|
|
public state: GameState = $state(GameState.INIT);
|
|
public game: Game;
|
|
public players: Player[] = $state([
|
|
{
|
|
name: "Player 1",
|
|
points: 0
|
|
},
|
|
{
|
|
name: "Player 2",
|
|
points: 0
|
|
},
|
|
{
|
|
name: "Player 3",
|
|
points: 0
|
|
}
|
|
]);
|
|
|
|
public currentPlayer = $state(0);
|
|
public currentWall = $state(0);
|
|
public visitedQuestions: VisitedQuestions = $state([]);
|
|
public currentCategory = $state(0);
|
|
public currentQuestion = $state(0);
|
|
|
|
public answerIsShowing = $state(false);
|
|
public questionIsShowing = $state(false);
|
|
public isBuzzed = $state(false);
|
|
|
|
constructor(game: Game) {
|
|
this.game = game;
|
|
}
|
|
|
|
startGame(): void {
|
|
this.currentPlayer = Math.floor(Math.random() * this.players.length);
|
|
this.state = GameState.CHOOSING_QUESTION;
|
|
this.sendStart();
|
|
this.sendCurrentState();
|
|
}
|
|
|
|
sendStart(): void {
|
|
ws.sendMessage({
|
|
type: MessageType.START
|
|
});
|
|
}
|
|
|
|
sendPlayers(): void {
|
|
ws.sendMessage({
|
|
type: MessageType.PLAYERS,
|
|
players: this.players,
|
|
current: this.currentPlayerToName()
|
|
});
|
|
}
|
|
|
|
sendWall(): void {
|
|
ws.sendMessage({
|
|
type: MessageType.GOTO,
|
|
route: `/${page.params.game}/${this.currentWall}`
|
|
});
|
|
this.sendVisited();
|
|
}
|
|
|
|
sendVisited(): void {
|
|
ws.sendMessage({
|
|
type: MessageType.VISITED_QUESTIONS,
|
|
visitedQuestions: this.visitedQuestions
|
|
});
|
|
}
|
|
|
|
sendCurrentState(): void {
|
|
this.sendPlayers();
|
|
this.sendWall();
|
|
}
|
|
|
|
sendCurrentQuestion(): void {
|
|
ws.sendMessage({
|
|
type: MessageType.GOTO,
|
|
route: `/${page.params.game}/${this.currentWall}/${this.currentCategory}/${this.currentQuestion}`
|
|
});
|
|
}
|
|
|
|
sendEnd(): void {
|
|
ws.sendMessage({
|
|
type: MessageType.GOTO,
|
|
route: `/${page.params.game}/${this.currentWall}/end`
|
|
});
|
|
}
|
|
|
|
tileClicked(categoryIndex: number, questionIndex: number) {
|
|
console.log("Cat", categoryIndex, "Que", questionIndex);
|
|
console.log(gameManager.wall.categories[categoryIndex]?.questions[questionIndex]);
|
|
|
|
this.currentCategory = categoryIndex;
|
|
this.currentQuestion = questionIndex;
|
|
this.answerIsShowing = false;
|
|
|
|
this.sendCurrentQuestion();
|
|
|
|
this.state = GameState.SHOW_QUESTION;
|
|
}
|
|
|
|
addPlayer() {
|
|
this.players.push({
|
|
name: "Player " + (this.players.length + 1),
|
|
points: 0
|
|
});
|
|
}
|
|
|
|
removePlayer(index: number) {
|
|
this.players.splice(index, 1);
|
|
}
|
|
|
|
showAnswer() {
|
|
this.answerIsShowing = true;
|
|
this.questionIsShowing = true;
|
|
ws.sendMessage({
|
|
type: MessageType.SHOW_ANSWER
|
|
});
|
|
}
|
|
|
|
hideAnswer() {
|
|
this.answerIsShowing = false;
|
|
ws.sendMessage({
|
|
type: MessageType.HIDE_ANSWER
|
|
});
|
|
}
|
|
|
|
buzzerPressed() {
|
|
this.isBuzzed = true;
|
|
ws.sendMessage({
|
|
type: MessageType.BUZZER_PRESSED
|
|
});
|
|
}
|
|
|
|
buzzerReleased() {
|
|
this.isBuzzed = false;
|
|
ws.sendMessage({
|
|
type: MessageType.BUZZER_RELEASED
|
|
});
|
|
}
|
|
|
|
showQuestion() {
|
|
this.questionIsShowing = true;
|
|
ws.sendMessage({
|
|
type: MessageType.SHOW_QUESTION
|
|
});
|
|
}
|
|
|
|
hideQuestion() {
|
|
this.questionIsShowing = false;
|
|
this.answerIsShowing = false;
|
|
ws.sendMessage({
|
|
type: MessageType.HIDE_QUESTION
|
|
});
|
|
}
|
|
|
|
plus(player: Player) {
|
|
if (!this.answerIsShowing) return;
|
|
if (player.name === this.currentPlayerToName()) {
|
|
player.points += this.question.points * 2;
|
|
} else {
|
|
player.points += this.question.points;
|
|
}
|
|
this.sendPlayers();
|
|
}
|
|
|
|
minus(player: Player) {
|
|
if (player.name === this.currentPlayerToName()) {
|
|
player.points -= this.question.points * 2;
|
|
} else {
|
|
player.points -= this.question.points;
|
|
}
|
|
this.sendPlayers();
|
|
}
|
|
|
|
goBack(): void {
|
|
this.sendWall();
|
|
this.state = GameState.CHOOSING_QUESTION;
|
|
this.setupGoingBack();
|
|
}
|
|
|
|
setupGoingBack(): void {
|
|
this.answerIsShowing = false;
|
|
this.questionIsShowing = false;
|
|
this.isBuzzed = false;
|
|
}
|
|
|
|
finishQuestion(): void {
|
|
if (this.visitedQuestions[this.currentCategory] === undefined) {
|
|
this.visitedQuestions[this.currentCategory] = [this.currentQuestion];
|
|
} else if (
|
|
!this.visitedQuestions[this.currentCategory].includes(this.currentQuestion)
|
|
) {
|
|
this.visitedQuestions[this.currentCategory].push(this.currentQuestion);
|
|
}
|
|
this.setupGoingBack();
|
|
this.nextPlayer();
|
|
if (this.wallIsDone()) {
|
|
this.goToNextWall();
|
|
} else {
|
|
this.sendWall();
|
|
this.state = GameState.CHOOSING_QUESTION;
|
|
}
|
|
}
|
|
|
|
wallIsDone(): boolean {
|
|
let visitedNum = 0;
|
|
for (const questions of this.visitedQuestions) {
|
|
if (questions !== undefined) {
|
|
visitedNum += questions.length;
|
|
}
|
|
}
|
|
let totalNum = 0;
|
|
for (const category of this.wall.categories) {
|
|
totalNum += category.questions.length;
|
|
}
|
|
console.log(`${visitedNum} >= ${totalNum}`);
|
|
return visitedNum >= totalNum;
|
|
}
|
|
|
|
goToNextWall(): void {
|
|
if (this.currentWall + 1 >= this.game.walls.length) {
|
|
this.goToEndScreen();
|
|
} else {
|
|
this.currentWall += 1;
|
|
this.visitedQuestions = [];
|
|
this.sendWall();
|
|
this.state = GameState.CHOOSING_QUESTION;
|
|
}
|
|
}
|
|
|
|
goToEndScreen(): void {
|
|
this.sendEnd();
|
|
this.state = GameState.END;
|
|
}
|
|
|
|
nextPlayer(): void {
|
|
this.currentPlayer = (this.currentPlayer + 1) % this.players.length;
|
|
this.sendPlayers();
|
|
}
|
|
|
|
currentPlayerToName(): string {
|
|
return this.players[this.currentPlayer].name;
|
|
}
|
|
|
|
get wall() {
|
|
return this.game.walls[this.currentWall];
|
|
}
|
|
|
|
get category() {
|
|
return this.wall.categories[this.currentCategory];
|
|
}
|
|
|
|
get question() {
|
|
return this.category.questions[this.currentQuestion];
|
|
}
|
|
}
|
|
|
|
let { data } = $props();
|
|
|
|
let gameManager = new GameManager(data);
|
|
</script>
|
|
|
|
<div class="flex h-full flex-col">
|
|
<h1 class="ms-4 text-7xl font-bold">{gameManager.game.name}</h1>
|
|
{#if gameManager.state === GameState.INIT}
|
|
<div class="p-4">
|
|
<div class="flex items-center">
|
|
<h2 class="grow pb-4 text-5xl">Spieler</h2>
|
|
<button
|
|
class="btn"
|
|
disabled={!startDisabled}
|
|
onclick={() => gameManager.startGame()}>Start</button
|
|
>
|
|
</div>
|
|
<div class="flex flex-col space-y-2 pb-4">
|
|
{#each gameManager.players as player, i}
|
|
<div class="flex items-center">
|
|
<input class="inputField grow" type="text" bind:value={player.name} />
|
|
<button class="btn" onclick={() => gameManager.removePlayer(i)}
|
|
>Löschen</button
|
|
>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
<button class="btn" onclick={() => gameManager.addPlayer()}>Spieler hinzufügen</button>
|
|
</div>
|
|
{:else}
|
|
<div class="flex grow">
|
|
<Scoreboard
|
|
players={gameManager.players}
|
|
currentPlayer={gameManager.currentPlayerToName()}
|
|
editable={true}
|
|
onReload={() => gameManager.sendPlayers()}
|
|
/>
|
|
{#if gameManager.state === GameState.SHOW_QUESTION}
|
|
<div class="flex grow flex-col">
|
|
<div class="m-4 flex justify-between text-4xl">
|
|
<div>{gameManager.category.name}</div>
|
|
<div>
|
|
{gameManager.question.points} Punkte
|
|
</div>
|
|
</div>
|
|
<div class="flex grow ps-4 pe-4">
|
|
{#if gameManager.question === undefined}
|
|
<p>Question is undefined</p>
|
|
{:else if isSimpleQuestion(gameManager.question)}
|
|
<SimpleQuestionComponent
|
|
question={gameManager.question}
|
|
showAnswer={true}
|
|
showQuestion={true}
|
|
/>
|
|
{:else if isMultipleChoiceQuestion(gameManager.question)}
|
|
<MultipleChoiceQuestionComponent
|
|
question={gameManager.question}
|
|
showAnswer={true}
|
|
showQuestion={true}
|
|
/>
|
|
{:else if isImageQuestion(gameManager.question)}
|
|
<ImageQuestionComponent
|
|
question={gameManager.question}
|
|
showAnswer={true}
|
|
showQuestion={true}
|
|
isBuzzed={false}
|
|
/>
|
|
{:else if isImageMultipleChoiceQuestion(gameManager.question)}
|
|
<ImageMultipleChoiceQuestionComponent
|
|
question={gameManager.question}
|
|
showAnswer={true}
|
|
showQuestion={true}
|
|
isBuzzed={false}
|
|
/>
|
|
{:else if isAudioQuestion(gameManager.question)}
|
|
<AudioQuestionComponent
|
|
question={gameManager.question}
|
|
showAnswer={true}
|
|
showPlayer={true}
|
|
showQuestion={true}
|
|
/>
|
|
{:else if isAudioMultipleChoiceQuestion(gameManager.question)}
|
|
<AudioMultipleChoiceQuestionComponent
|
|
question={gameManager.question}
|
|
showAnswer={true}
|
|
showPlayer={true}
|
|
showQuestion={true}
|
|
/>
|
|
{:else}
|
|
<p>Type of question unknown</p>
|
|
{/if}
|
|
</div>
|
|
<div class="m-4 flex flex-wrap items-center gap-4">
|
|
<button class="btn" onclick={() => gameManager.goBack()}>Zurück</button>
|
|
{#if gameManager.questionIsShowing}
|
|
<button class="btn" onclick={() => gameManager.hideQuestion()}
|
|
>Frage verstecken</button
|
|
>
|
|
{:else}
|
|
<button class="btn" onclick={() => gameManager.showQuestion()}
|
|
>Frage aufdecken</button
|
|
>
|
|
{/if}
|
|
{#if gameManager.answerIsShowing}
|
|
<button class="btn" onclick={() => gameManager.hideAnswer()}
|
|
>Antwort verstecken</button
|
|
>
|
|
{:else}
|
|
<button class="btn" onclick={() => gameManager.showAnswer()}
|
|
>Antwort aufdecken</button
|
|
>
|
|
{/if}
|
|
{#if gameManager.isBuzzed}
|
|
<button class="btn" onclick={() => gameManager.buzzerReleased()}
|
|
>Entbuzzern</button
|
|
>
|
|
{:else}
|
|
<button class="btn" onclick={() => gameManager.buzzerPressed()}
|
|
>Buzzern</button
|
|
>
|
|
{/if}
|
|
{#each gameManager.players as player}
|
|
<PlusMinusButton
|
|
label={player.name}
|
|
plus={() => gameManager.plus(player)}
|
|
minus={() => gameManager.minus(player)}
|
|
showPlus={gameManager.answerIsShowing}
|
|
/>
|
|
{/each}
|
|
<div class="grow"></div>
|
|
{#if gameManager.answerIsShowing}
|
|
<button class="btn" onclick={() => gameManager.finishQuestion()}
|
|
>Abschließen</button
|
|
>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else if gameManager.state === GameState.END}
|
|
<div class="flex grow items-center justify-center text-7xl"><div>ENDE</div></div>
|
|
{:else}
|
|
<div class="grow ps-4 pe-4">
|
|
<Wall
|
|
wall={gameManager.game.walls[gameManager.currentWall]}
|
|
onclick={(cat, que) => gameManager.tileClicked(cat, que)}
|
|
visited={gameManager.visitedQuestions}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|