Almost done

This commit is contained in:
2025-09-06 01:50:13 +02:00
parent 362cd7019b
commit 985f6d9bf9
26 changed files with 832 additions and 98 deletions

View File

@@ -5,6 +5,7 @@
let { children } = $props();
$effect(() => {
console.log("ConnectionType:", ws.connectionType);
if (ws.connectionType === SocketConnectionType.NONE) {
goto("/");
}

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import { goto } from "$app/navigation";
import DisplayStateSvelte from "$lib/DisplayState.svelte";
import { MessageType } from "$lib/MessageType";
import ws from "$lib/websocket.svelte";
let { children } = $props();
$effect(() => {
if (ws.messageNum <= 0) return;
console.log(ws.message);
if (ws.message == "HOST-DISCONNECTED") {
ws.nextMessage();
goto("/connected/display");
}
if (ws.message == "HOST-CONNECTED") {
ws.nextMessage();
}
try {
let json = JSON.parse(ws.message);
if (json.type == MessageType.START) {
ws.nextMessage();
goto(DisplayStateSvelte.baseRoute);
return;
}
} catch (e) {}
});
</script>
{@render children?.()}

View File

@@ -1,15 +1,3 @@
<script lang="ts">
import { goto } from "$app/navigation";
import ws from "$lib/websocket.svelte";
$effect(() => {
if (!ws.message) return;
console.log(ws.message);
if (ws.nextMessage() == "HOST-DISCONNECTED") {
goto("/connected/display");
}
});
</script>
<div class="flex h-full w-full items-center justify-center">
<p class="text-9xl">Waiting for game to start</p>
</div>

View File

@@ -0,0 +1,32 @@
<script lang="ts">
let { children } = $props();
import { MessageType } from "$lib/MessageType";
import ws from "$lib/websocket.svelte";
import DisplayState from "$lib/DisplayState.svelte";
import type { Player } from "$lib/Player";
import { goto } from "$app/navigation";
import Scoreboard from "$lib/Scoreboard.svelte";
$effect(() => {
if (ws.messageNum <= 0) return;
console.log(ws.message);
try {
let json = JSON.parse(ws.message);
if (json.type == MessageType.PLAYERS) {
ws.nextMessage();
DisplayState.players = json.players;
DisplayState.currentPlayer = json.current;
}
if (json.type == MessageType.GOTO) {
ws.nextMessage();
goto(DisplayState.baseRoute + json.route);
}
} catch (e) {}
});
</script>
<div class="flex h-full">
<Scoreboard players={DisplayState.players} currentPlayer={DisplayState.currentPlayer} />
<div class="flex grow flex-col">
{@render children?.()}
</div>
</div>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
let { children } = $props();
import { error } from "@sveltejs/kit";
import games from "$lib/games/games";
import { page } from "$app/state";
import DisplayStateSvelte from "$lib/DisplayState.svelte";
console.log("game:", page.params.game);
let paramGame = page.params.game;
if (paramGame === undefined) {
error(404);
}
const gameIndex = parseInt(paramGame);
if (isNaN(gameIndex)) {
error(404);
}
if (DisplayStateSvelte.gameIndex !== gameIndex) {
const game = games[gameIndex];
if (game) {
DisplayStateSvelte.game = game;
DisplayStateSvelte.gameIndex = gameIndex;
} else {
error(404);
}
}
</script>
<div class="flex grow flex-col pr-4 pl-4">
<div>
<h2 class="text-5xl">
{DisplayStateSvelte.game?.name}
</h2>
</div>
<div class="flex grow">
{@render children?.()}
</div>
</div>

View File

@@ -0,0 +1,45 @@
<script lang="ts">
import Wall from "$lib/Wall.svelte";
import DisplayStateSvelte from "$lib/DisplayState.svelte";
import { page } from "$app/state";
import { error } from "@sveltejs/kit";
import ws from "$lib/websocket.svelte";
import { MessageType } from "$lib/MessageType";
import type { VisitedQuestions } from "$lib/Types";
console.log("wall:", page.params.wall);
let paramWall = page.params.wall;
if (paramWall === undefined) {
error(404);
}
const wallIndex = parseInt(paramWall);
if (isNaN(wallIndex)) {
error(404);
}
if (DisplayStateSvelte.wallIndex !== wallIndex) {
const wall = DisplayStateSvelte.game?.walls[wallIndex];
if (wall) {
DisplayStateSvelte.wall = wall;
DisplayStateSvelte.gameIndex = wallIndex;
} else {
error(404);
}
}
let visited: VisitedQuestions = $state([]);
$effect(() => {
if (ws.messageNum <= 0) return;
console.log(ws.message);
try {
let json = JSON.parse(ws.message);
if (json.type == MessageType.VISITED_QUESTIONS) {
ws.nextMessage();
visited = json.visitedQuestions;
}
} catch (e) {}
});
</script>
<Wall wall={DisplayStateSvelte.wall} {visited} />

View File

@@ -0,0 +1,99 @@
<script lang="ts">
import DisplayStateSvelte from "$lib/DisplayState.svelte";
import { page } from "$app/state";
import { error } from "@sveltejs/kit";
import SimpleQuestionComponent from "$lib/SimpleQuestionComponent.svelte";
import { isSimpleQuestion } from "$lib/games/games";
import ws from "$lib/websocket.svelte";
import { MessageType } from "$lib/MessageType";
import { untrack } from "svelte";
console.log("wall:", page.params.wall);
let paramWall = page.params.wall;
if (paramWall === undefined) {
error(404);
}
const wallIndex = parseInt(paramWall);
if (isNaN(wallIndex)) {
error(404);
}
if (DisplayStateSvelte.wallIndex !== wallIndex) {
const wall = DisplayStateSvelte.game?.walls[wallIndex];
if (wall) {
DisplayStateSvelte.wall = wall;
DisplayStateSvelte.gameIndex = wallIndex;
} else {
error(404);
}
}
if (page.params.category === undefined) {
error(404);
}
const categoryIndex = parseInt(page.params.category);
if (isNaN(categoryIndex)) {
error(404);
}
const category = DisplayStateSvelte.wall?.categories[categoryIndex];
if (category === undefined) {
error(404);
}
if (page.params.question === undefined) {
error(404);
}
const questionIndex = parseInt(page.params.question);
if (isNaN(questionIndex)) {
error(404);
}
const question = category.questions[questionIndex];
console.log(question);
if (question === undefined) {
error(404);
}
let showAnswer = $state(false);
$effect(() => {
if (ws.messageNum <= 0) return;
console.log(ws.message);
try {
let json = JSON.parse(ws.message);
if (json.type == MessageType.SHOW_ANSWER) {
ws.nextMessage();
untrack(() => {
showAnswer = true;
});
}
if (json.type == MessageType.HIDE_ANSWER) {
ws.nextMessage();
untrack(() => {
showAnswer = false;
});
}
} catch (e) {}
});
</script>
<div class="mt-4 flex grow flex-col">
<div class="mb-4 flex justify-between text-4xl">
<div>{category.name}</div>
<div>
{question.points} Punkte
</div>
</div>
<div class="flex grow flex-col">
{#if question === undefined}
<p>Question is undefined</p>
{:else if isSimpleQuestion(question)}
<SimpleQuestionComponent {question} {showAnswer} />
{:else}
<p>Type of question unknown: {question.type}</p>
{/if}
</div>
</div>

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import DisplayStateSvelte from "$lib/DisplayState.svelte";
import type { Player } from "$lib/Player";
let winners = $derived(() => {
let highscore = 0;
for (const player of DisplayStateSvelte.players) {
if (player.points > highscore) highscore = player.points;
}
let _winners: Player[] = [];
for (const player of DisplayStateSvelte.players) {
if (player.points >= highscore) _winners.push(player);
}
return _winners;
});
</script>
<div class="flex grow flex-col items-center justify-center gap-4 text-5xl">
<div class="m-32">Herzlichen Glückwunsch</div>
{#each winners() as player}
<div class="text-7xl">
{player.name}
</div>
{/each}
<div class="m-32">
{#if winners().length > 1}
haben
{:else}
hat
{/if}
gewonnen!
</div>
</div>
<style>
.m-32 {
margin: 32px;
}
</style>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import GameStateSvelte from "$lib/GameState.svelte";
import ws from "$lib/websocket.svelte";
let { children } = $props();
$effect(() => {
if (ws.messageNum <= 0) return;
console.log(ws.message);
if (ws.message == "DISPLAY-CONNECTED") {
GameStateSvelte.displayConnected = true;
ws.nextMessage();
} else if (ws.message == "DISPLAY-DISCONNECTED") {
GameStateSvelte.displayConnected = false;
ws.nextMessage();
}
});
</script>
{@render children?.()}

View File

@@ -1,40 +1,72 @@
<script lang="ts">
import Wall from "./Wall.svelte";
import type { Player } from "./Player";
import { GameState } from "./GameState";
import type { Game } from "$lib/games/games";
import Wall from "$lib/Wall.svelte";
import type { Player } from "$lib/Player";
import { GameState } from "$lib/GameState";
import { isSimpleQuestion, type Game, type Question } 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";
let startDisabled = $state(true);
let _startDisabled = true;
function handleDisplayConnected() {
if (!startDisabled) {
gameManager?.sendStart();
gameManager?.sendCurrentState();
startDisabled = false;
}
}
function handleDisplayDisconnected() {
startDisabled = true;
}
$effect(() => {
if (!ws.message) return;
console.log(ws.message);
if (ws.nextMessage() == "DISPLAY-CONNECTED") {
if (!_startDisabled) {
gameManager?.sendStart();
gameManager?.sendCurrentState();
}
_startDisabled = false;
startDisabled = false;
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[];
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);
constructor(game: Game, players: Player[]) {
public answerIsShowing = $state(false);
constructor(game: Game) {
this.game = game;
this.players = players;
}
startGame(): void {
this.currentPlayer = Math.floor(Math.random() * this.players.length);
this.state = GameState.CHOOSING_QUESTION;
this.sendStart();
this.sendCurrentState();
@@ -42,49 +74,182 @@
sendStart(): void {
ws.sendMessage({
type: "start"
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: "players",
players
type: MessageType.GOTO,
route: `/${page.params.game}/${this.currentWall}/${this.currentCategory}/${this.currentQuestion}`
});
}
sendEnd(): void {
ws.sendMessage({
type: "goto",
route: `/${page.params.game}/${this.currentWall}`
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;
ws.sendMessage({
type: MessageType.SHOW_ANSWER
});
}
hideAnswer() {
this.answerIsShowing = false;
ws.sendMessage({
type: MessageType.HIDE_ANSWER
});
}
plus(player: Player) {
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;
}
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.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 question() {
return this.wall.categories[this.currentCategory].questions[this.currentQuestion];
}
}
let { data } = $props();
let players: Player[] = $state([
{
name: "Player 1",
points: 0
},
{
name: "Player 2",
points: 0
},
{
name: "Player 3",
points: 0
}
]);
let gameManager = new GameManager(data, players);
function addPlayer() {
players.push({
name: "Player " + (players.length + 1),
points: 0
});
}
function removePlayer(index: number) {
players.splice(index, 1);
}
let gameManager = new GameManager(data);
</script>
<div class="flex h-full flex-col">
@@ -93,21 +258,78 @@
<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
<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={() => removePlayer(i)}>Löschen</button>
<button class="btn" onclick={() => gameManager.removePlayer(i)}
>Löschen</button
>
</div>
{/each}
</div>
<button class="btn" onclick={addPlayer}>Spieler hinzufügen</button>
<button class="btn" onclick={() => gameManager.addPlayer()}>Spieler hinzufügen</button>
</div>
{:else}
<Wall wall={gameManager.game.walls[gameManager.currentWall]} />
<div class="flex grow">
<Scoreboard
players={gameManager.players}
currentPlayer={gameManager.currentPlayerToName()}
/>
{#if gameManager.state === GameState.SHOW_QUESTION}
<div class="flex grow flex-col">
<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}
/>
{:else}
<p>Type of question unknown: {gameManager.question.type}</p>
{/if}
</div>
<div class="m-4 flex items-center gap-4">
<button class="btn" onclick={() => gameManager.goBack()}>Zurück</button>
{#if gameManager.answerIsShowing}
<button class="btn" onclick={() => gameManager.hideAnswer()}
>Antwort verstecken</button
>
{:else}
<button class="btn" onclick={() => gameManager.showAnswer()}
>Antwort aufdecken</button
>
{/if}
{#each gameManager.players as player}
<PlusMinusButton
label={player.name}
plus={() => gameManager.plus(player)}
minus={() => gameManager.minus(player)}
/>
{/each}
<button class="btn" onclick={() => gameManager.finishQuestion()}
>Abschließen</button
>
</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>

View File

@@ -1,4 +0,0 @@
export enum GameState {
INIT,
CHOOSING_QUESTION
}

View File

@@ -1,4 +0,0 @@
export interface Player {
name: string;
points: number;
}

View File

@@ -1,23 +0,0 @@
<script lang="ts">
import type { Wall } from "$lib/games/games";
interface Props {
wall: Wall;
[key: string]: unknown;
}
let { wall }: Props = $props();
</script>
<div class="grid h-full grid-flow-col grid-cols-5 grid-rows-6 gap-4 ps-4 pe-4 pb-4">
{#each wall.categories as category}
<div class="flex items-center justify-center text-3xl font-semibold">
<div>{category.name}</div>
</div>
{#each category.questions as question}
<div class="card">
<div>{question.points}</div>
</div>
{/each}
{/each}
</div>