Almost done
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
html {
|
||||||
|
background-color: rgb(230, 230, 230);
|
||||||
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|||||||
58
src/lib/DisplayState.svelte.ts
Normal file
58
src/lib/DisplayState.svelte.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import type { Game, Wall } from "./games/games";
|
||||||
|
|
||||||
|
interface Player {
|
||||||
|
name: string;
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseRoute = "/connected/display/running";
|
||||||
|
let players: Player[] = $state([]);
|
||||||
|
let currentPlayer: string = $state("");
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let game: Game | undefined = undefined;
|
||||||
|
let gameIndex: number = -1;
|
||||||
|
let wall: Wall | undefined = undefined;
|
||||||
|
let wallIndex: number = -1;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
get players(): Player[] {
|
||||||
|
return players;
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
set players(newPlayers: any) {
|
||||||
|
players = newPlayers;
|
||||||
|
},
|
||||||
|
get currentPlayer(): string {
|
||||||
|
return currentPlayer;
|
||||||
|
},
|
||||||
|
set currentPlayer(str: string) {
|
||||||
|
currentPlayer = str;
|
||||||
|
},
|
||||||
|
baseRoute,
|
||||||
|
get game(): Game | undefined {
|
||||||
|
return game;
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
set game(newGame: any) {
|
||||||
|
game = newGame;
|
||||||
|
},
|
||||||
|
get gameIndex() {
|
||||||
|
return gameIndex;
|
||||||
|
},
|
||||||
|
set gameIndex(i: number) {
|
||||||
|
gameIndex = i;
|
||||||
|
},
|
||||||
|
get wall(): Wall | undefined {
|
||||||
|
return wall;
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
set wall(newWall: any) {
|
||||||
|
wall = newWall;
|
||||||
|
},
|
||||||
|
get wallIndex() {
|
||||||
|
return wallIndex;
|
||||||
|
},
|
||||||
|
set wallIndex(i: number) {
|
||||||
|
wallIndex = i;
|
||||||
|
}
|
||||||
|
};
|
||||||
10
src/lib/GameState.svelte.ts
Normal file
10
src/lib/GameState.svelte.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
let displayConnected = $state(false);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
get displayConnected() {
|
||||||
|
return displayConnected;
|
||||||
|
},
|
||||||
|
set displayConnected(c: boolean) {
|
||||||
|
displayConnected = c;
|
||||||
|
}
|
||||||
|
};
|
||||||
6
src/lib/GameState.ts
Normal file
6
src/lib/GameState.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export enum GameState {
|
||||||
|
INIT,
|
||||||
|
CHOOSING_QUESTION,
|
||||||
|
SHOW_QUESTION,
|
||||||
|
END
|
||||||
|
}
|
||||||
8
src/lib/MessageType.ts
Normal file
8
src/lib/MessageType.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export enum MessageType {
|
||||||
|
START = "START",
|
||||||
|
PLAYERS = "PLAYERS",
|
||||||
|
GOTO = "GOTO",
|
||||||
|
SHOW_ANSWER = "SHOW_ANSWER",
|
||||||
|
HIDE_ANSWER = "HIDE_ANSWER",
|
||||||
|
VISITED_QUESTIONS = "VISITED_QUESTIONS"
|
||||||
|
}
|
||||||
37
src/lib/PlusMinusButton.svelte
Normal file
37
src/lib/PlusMinusButton.svelte
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
plus: (label: string) => void;
|
||||||
|
minus: (label: string) => void;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { label, plus, minus }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="specialBtn flex w-min gap-2 text-2xl">
|
||||||
|
<button class="innerBtn" onclick={() => minus(label)}>-</button>
|
||||||
|
<div class="whitespace-nowrap">
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
<button class="innerBtn" onclick={() => plus(label)}>+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.specialBtn {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 4px;
|
||||||
|
height: fit-content;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.innerBtn {
|
||||||
|
width: 30px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.innerBtn:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
42
src/lib/Scoreboard.svelte
Normal file
42
src/lib/Scoreboard.svelte
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Player } from "./Player";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
players: Player[];
|
||||||
|
currentPlayer: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { players, currentPlayer }: Props = $props();
|
||||||
|
|
||||||
|
let _players = $derived(() => {
|
||||||
|
let p = [...players];
|
||||||
|
return p.sort((a: Player, b: Player) => b.points - a.points);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="h-full w-fit max-w-[400px] overflow-hidden border-r-1 border-solid border-gray-300 pr-4 pl-4"
|
||||||
|
>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<h3 class="text-5xl">Scoreboard</h3>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 text-3xl">
|
||||||
|
<table class="w-1/1">
|
||||||
|
<tbody>
|
||||||
|
{#each _players() as player}
|
||||||
|
<tr class="{currentPlayer === player.name ? 'current' : ''} h-12">
|
||||||
|
<td class="pr-4 pl-2">{player.points}</td>
|
||||||
|
<td>{player.name}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.current {
|
||||||
|
background-color: rgb(166, 255, 165);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
22
src/lib/SimpleQuestionComponent.svelte
Normal file
22
src/lib/SimpleQuestionComponent.svelte
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { SimpleQuestion } from "./games/games";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
question: SimpleQuestion;
|
||||||
|
showAnswer: boolean;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { question, showAnswer }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mb-4 flex grow flex-col items-center text-6xl">
|
||||||
|
<div class="flex grow-1 items-center">
|
||||||
|
<div>{question.data.question}</div>
|
||||||
|
</div>
|
||||||
|
{#if showAnswer}
|
||||||
|
<div class="flex grow-1 items-center">
|
||||||
|
{question.data.answer}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
1
src/lib/Types.ts
Normal file
1
src/lib/Types.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type VisitedQuestions = number[][];
|
||||||
49
src/lib/Wall.svelte
Normal file
49
src/lib/Wall.svelte
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Wall } from "$lib/games/games";
|
||||||
|
import type { VisitedQuestions } from "./Types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
wall: Wall | undefined;
|
||||||
|
onclick?: (catIndex: number, questionIndex: number) => unknown;
|
||||||
|
visited: VisitedQuestions;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVisited(catIndex: number, queIndex: number): boolean {
|
||||||
|
return visited[catIndex] && visited[catIndex].includes(queIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
let { wall, onclick, visited }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if wall != undefined}
|
||||||
|
<div class="grid h-full grow grid-flow-col grid-cols-5 grid-rows-6 gap-4 pb-4">
|
||||||
|
{#each wall.categories as category, catIndex}
|
||||||
|
<div class="flex items-center justify-center text-3xl font-semibold">
|
||||||
|
<div>{category.name}</div>
|
||||||
|
</div>
|
||||||
|
{#each category.questions as question, queIndex}
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<div
|
||||||
|
class="card {isVisited(catIndex, queIndex) ? 'visited' : ''}"
|
||||||
|
role="button"
|
||||||
|
aria-pressed="false"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => {
|
||||||
|
if (onclick) onclick(catIndex, queIndex);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{question.points}</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>Wall is undefined</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.visited {
|
||||||
|
background-color: gray;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -931,9 +931,11 @@ const games: Games = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export type QuestionType = "SIMPLE" | "MULTIPLE_CHOICE";
|
||||||
|
|
||||||
export type Question = {
|
export type Question = {
|
||||||
points: number;
|
points: number;
|
||||||
type: "SIMPLE" | "MULTIPLE_CHOICE";
|
type: QuestionType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SimpleQuestion = Question & {
|
export type SimpleQuestion = Question & {
|
||||||
@@ -953,6 +955,13 @@ export type MultipleChoiceQuestion = Question & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isSimpleQuestion(question: Question): question is SimpleQuestion {
|
||||||
|
return (question as SimpleQuestion).type === "SIMPLE";
|
||||||
|
}
|
||||||
|
export function isMultipleChoiceQuestion(question: Question): question is MultipleChoiceQuestion {
|
||||||
|
return (question as MultipleChoiceQuestion).type === "MULTIPLE_CHOICE";
|
||||||
|
}
|
||||||
|
|
||||||
export type Category = {
|
export type Category = {
|
||||||
name: string;
|
name: string;
|
||||||
questions: (SimpleQuestion | MultipleChoiceQuestion)[];
|
questions: (SimpleQuestion | MultipleChoiceQuestion)[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export enum SocketConnectionType {
|
export enum SocketConnectionType {
|
||||||
NONE,
|
NONE = "NONE",
|
||||||
HOST,
|
HOST = "HOST",
|
||||||
DISPLAY
|
DISPLAY = "DISPLAY"
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages: string[] = $state([]);
|
const messages: string[] = $state([]);
|
||||||
@@ -41,7 +41,7 @@ function onOpen(type: SocketConnectionType) {
|
|||||||
console.log("Connection established");
|
console.log("Connection established");
|
||||||
console.log(event);
|
console.log(event);
|
||||||
if (socket === undefined) return;
|
if (socket === undefined) return;
|
||||||
socket.send(type == SocketConnectionType.HOST ? "HOST" : "DISPLAY");
|
socket.send(type.toString());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +49,11 @@ function onOpen(type: SocketConnectionType) {
|
|||||||
function onFirstMessage(event: MessageEvent<any>) {
|
function onFirstMessage(event: MessageEvent<any>) {
|
||||||
if (socket === undefined) return;
|
if (socket === undefined) return;
|
||||||
console.log(event.data);
|
console.log(event.data);
|
||||||
if (event.data === "HOST") {
|
if (event.data === SocketConnectionType.HOST) {
|
||||||
connectionType = SocketConnectionType.HOST;
|
connectionType = SocketConnectionType.HOST;
|
||||||
socket.removeEventListener("message", onFirstMessage);
|
socket.removeEventListener("message", onFirstMessage);
|
||||||
socket.addEventListener("message", onMessage);
|
socket.addEventListener("message", onMessage);
|
||||||
} else if (event.data === "DISPLAY") {
|
} else if (event.data === SocketConnectionType.DISPLAY) {
|
||||||
connectionType = SocketConnectionType.DISPLAY;
|
connectionType = SocketConnectionType.DISPLAY;
|
||||||
socket.removeEventListener("message", onFirstMessage);
|
socket.removeEventListener("message", onFirstMessage);
|
||||||
socket.addEventListener("message", onMessage);
|
socket.addEventListener("message", onMessage);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
console.log("ConnectionType:", ws.connectionType);
|
||||||
if (ws.connectionType === SocketConnectionType.NONE) {
|
if (ws.connectionType === SocketConnectionType.NONE) {
|
||||||
goto("/");
|
goto("/");
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/routes/connected/display/+layout.svelte
Normal file
30
src/routes/connected/display/+layout.svelte
Normal 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?.()}
|
||||||
@@ -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">
|
<div class="flex h-full w-full items-center justify-center">
|
||||||
<p class="text-9xl">Waiting for game to start</p>
|
<p class="text-9xl">Waiting for game to start</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
32
src/routes/connected/display/running/+layout.svelte
Normal file
32
src/routes/connected/display/running/+layout.svelte
Normal 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>
|
||||||
0
src/routes/connected/display/running/+page.svelte
Normal file
0
src/routes/connected/display/running/+page.svelte
Normal file
39
src/routes/connected/display/running/[game]/+layout.svelte
Normal file
39
src/routes/connected/display/running/[game]/+layout.svelte
Normal 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>
|
||||||
@@ -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} />
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
19
src/routes/connected/games/+layout.svelte
Normal file
19
src/routes/connected/games/+layout.svelte
Normal 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?.()}
|
||||||
@@ -1,40 +1,72 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Wall from "./Wall.svelte";
|
import Wall from "$lib/Wall.svelte";
|
||||||
import type { Player } from "./Player";
|
import type { Player } from "$lib/Player";
|
||||||
import { GameState } from "./GameState";
|
import { GameState } from "$lib/GameState";
|
||||||
import type { Game } from "$lib/games/games";
|
import { isSimpleQuestion, type Game, type Question } from "$lib/games/games";
|
||||||
import ws from "$lib/websocket.svelte";
|
import ws from "$lib/websocket.svelte";
|
||||||
import { page } from "$app/state";
|
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 = $state(true);
|
||||||
let _startDisabled = true;
|
|
||||||
|
function handleDisplayConnected() {
|
||||||
|
if (!startDisabled) {
|
||||||
|
gameManager?.sendStart();
|
||||||
|
gameManager?.sendCurrentState();
|
||||||
|
startDisabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDisplayDisconnected() {
|
||||||
|
startDisabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!ws.message) return;
|
console.log("Display connected:", GameStateSvelte.displayConnected);
|
||||||
console.log(ws.message);
|
if (GameStateSvelte.displayConnected) {
|
||||||
if (ws.nextMessage() == "DISPLAY-CONNECTED") {
|
handleDisplayConnected();
|
||||||
if (!_startDisabled) {
|
} else {
|
||||||
gameManager?.sendStart();
|
handleDisplayDisconnected();
|
||||||
gameManager?.sendCurrentState();
|
|
||||||
}
|
|
||||||
_startDisabled = false;
|
|
||||||
startDisabled = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class GameManager {
|
class GameManager {
|
||||||
public state: GameState = $state(GameState.INIT);
|
public state: GameState = $state(GameState.INIT);
|
||||||
public game: Game;
|
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 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.game = game;
|
||||||
this.players = players;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame(): void {
|
startGame(): void {
|
||||||
|
this.currentPlayer = Math.floor(Math.random() * this.players.length);
|
||||||
this.state = GameState.CHOOSING_QUESTION;
|
this.state = GameState.CHOOSING_QUESTION;
|
||||||
this.sendStart();
|
this.sendStart();
|
||||||
this.sendCurrentState();
|
this.sendCurrentState();
|
||||||
@@ -42,49 +74,182 @@
|
|||||||
|
|
||||||
sendStart(): void {
|
sendStart(): void {
|
||||||
ws.sendMessage({
|
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 {
|
sendCurrentState(): void {
|
||||||
|
this.sendPlayers();
|
||||||
|
this.sendWall();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCurrentQuestion(): void {
|
||||||
ws.sendMessage({
|
ws.sendMessage({
|
||||||
type: "players",
|
type: MessageType.GOTO,
|
||||||
players
|
route: `/${page.params.game}/${this.currentWall}/${this.currentCategory}/${this.currentQuestion}`
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEnd(): void {
|
||||||
ws.sendMessage({
|
ws.sendMessage({
|
||||||
type: "goto",
|
type: MessageType.GOTO,
|
||||||
route: `/${page.params.game}/${this.currentWall}`
|
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 { 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() {
|
let gameManager = new GameManager(data);
|
||||||
players.push({
|
|
||||||
name: "Player " + (players.length + 1),
|
|
||||||
points: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePlayer(index: number) {
|
|
||||||
players.splice(index, 1);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex h-full flex-col">
|
<div class="flex h-full flex-col">
|
||||||
@@ -93,21 +258,78 @@
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<h2 class="grow pb-4 text-5xl">Spieler</h2>
|
<h2 class="grow pb-4 text-5xl">Spieler</h2>
|
||||||
<button class="btn" disabled={startDisabled} onclick={() => gameManager.startGame()}
|
<button
|
||||||
>Start</button
|
class="btn"
|
||||||
|
disabled={!startDisabled}
|
||||||
|
onclick={() => gameManager.startGame()}>Start</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col space-y-2 pb-4">
|
<div class="flex flex-col space-y-2 pb-4">
|
||||||
{#each gameManager.players as player, i}
|
{#each gameManager.players as player, i}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input class="inputField grow" type="text" bind:value={player.name} />
|
<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>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" onclick={addPlayer}>Spieler hinzufügen</button>
|
<button class="btn" onclick={() => gameManager.addPlayer()}>Spieler hinzufügen</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export enum GameState {
|
|
||||||
INIT,
|
|
||||||
CHOOSING_QUESTION
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
Reference in New Issue
Block a user