merge both repos! atempt 1 by making the file system the same!
This commit is contained in:
parent
badb303ea6
commit
2fe58ee6be
128 changed files with 2320 additions and 4285 deletions
18
src/routes/kahootclone/+page.svelte
Normal file
18
src/routes/kahootclone/+page.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="bg-grey-900 flex h-full items-center justify-center">
|
||||
<div class="flex flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-8 shadow-lg">
|
||||
<h1 class="m-[0] text-6xl">DaKahootClone</h1>
|
||||
<p class="m-[0] mb-2 text-lg text-gray-400">The best ever kahoot clone.</p>
|
||||
<a href="./kahootclone/join">
|
||||
<button
|
||||
class="cursor-pointer rounded-full bg-green-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
>Join a game</button
|
||||
></a
|
||||
>
|
||||
<a href="./kahootclone/create">
|
||||
<button
|
||||
class="cursor-pointer rounded-full bg-blue-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
>Create and Host a game</button
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
28
src/routes/kahootclone/create/+page.svelte
Normal file
28
src/routes/kahootclone/create/+page.svelte
Normal file
|
@ -0,0 +1,28 @@
|
|||
<script>
|
||||
import UseDemoQuestions from "./components/buttons/UseDemoQuestions.svelte";
|
||||
import NewQuestion from "./components/buttons/NewQuestion.svelte";
|
||||
import StartGame from "./components/buttons/StartGame.svelte";
|
||||
import Question from "./components/Questions/question.svelte";
|
||||
import { questions, Wait } from "./logic/GameCreateData.svelte.js";
|
||||
import WaitStartGame from "./components/buttons/WaitStartGame.svelte";
|
||||
import GenerateQuetionsUsingAi from "./components/buttons/GenerateQuetionsUsingAI.svelte";
|
||||
</script>
|
||||
|
||||
<div class="bg-grey-900 flex justify-center p-5">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-8 shadow-lg"
|
||||
>
|
||||
<div class="flex gap-3"><UseDemoQuestions /> <GenerateQuetionsUsingAi /></div>
|
||||
{#each questions.v as question, index}
|
||||
<Question {index} />
|
||||
{/each}
|
||||
<div class="flex gap-3">
|
||||
<NewQuestion />
|
||||
{#if Wait.v == false}
|
||||
<StartGame />
|
||||
{:else}
|
||||
<WaitStartGame />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,23 @@
|
|||
<script>
|
||||
import { questions } from '../../logic/GameCreateData.svelte.js';
|
||||
|
||||
let props = $props();
|
||||
let questionsIndex = props.questionsIndex;
|
||||
let index = props.answersIndex;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
value={index}
|
||||
name={index.toString()}
|
||||
bind:group={questions.v[questionsIndex].correctAnswer}
|
||||
class="input"
|
||||
/>
|
||||
|
||||
<input
|
||||
placeholder="Option {index + 1}"
|
||||
bind:value={questions.v[questionsIndex].answers[index]}
|
||||
class="w-[500px] rounded-lg bg-gray-800 p-1 text-center text-white"
|
||||
/>
|
||||
</div>
|
|
@ -0,0 +1,54 @@
|
|||
<script>
|
||||
import DeleteQuestion from "../buttons/DeleteQuestion.svelte";
|
||||
import GenerateOptionsUsingAI from "../buttons/GenerateOptionsUsingAI.svelte";
|
||||
import Answers from "./answers.svelte";
|
||||
import { questions } from "../../logic/GameCreateData.svelte.js";
|
||||
|
||||
let props = $props();
|
||||
let index = props.index;
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="mb-3 flex flex-col items-center justify-center gap-1 rounded-2xl bg-gray-600 p-2">
|
||||
<div class="flex h-fit items-center gap-3">
|
||||
<h1 class="mt-2 mb-3 text-2xl">Q{index + 1}.</h1>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={questions.v[index].name}
|
||||
placeholder="Question {index + 1}"
|
||||
class="h-fit w-[500px] rounded-xl bg-gray-800 p-1 text-center text-2xl text-white"
|
||||
/>
|
||||
<select
|
||||
bind:value={questions.v[index].answers.length}
|
||||
onchange={(e) => {
|
||||
const newLength = questions.v[index].answers.length;
|
||||
const currentAnswers = questions.v[index].answers;
|
||||
|
||||
if (newLength > currentAnswers.length) {
|
||||
// Add more answers
|
||||
while (questions.v[index].answers.length < newLength) {
|
||||
questions.v[index].answers.push("");
|
||||
}
|
||||
} else if (newLength < currentAnswers.length) {
|
||||
// Remove excess answers
|
||||
questions.v[index].answers = currentAnswers.slice(0, newLength);
|
||||
}
|
||||
}}
|
||||
class="h-fit rounded-xl bg-gray-800 p-1 text-center text-white"
|
||||
>
|
||||
<option disabled selected>Options</option>
|
||||
{#each Array(7) as _, i}
|
||||
<option value={i + 2}>{i + 2}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<DeleteQuestion {index} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each questions.v[index].answers as _, answersIndex}
|
||||
<Answers questionsIndex={index} {answersIndex} />
|
||||
{/each}
|
||||
</div>
|
||||
<GenerateOptionsUsingAI {index} />
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
import { DeleteQuestion } from '../../logic/GameCreateData.svelte.js';
|
||||
|
||||
let props = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={() => DeleteQuestion(props.index)}
|
||||
class="flex h-fit cursor-pointer items-center justify-center rounded-xl bg-red-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#FFFFFF"
|
||||
><path
|
||||
d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"
|
||||
/></svg
|
||||
>Delete question</button
|
||||
>
|
|
@ -0,0 +1,19 @@
|
|||
<script>
|
||||
import { GenerateOptionsUsingAI } from "../../logic/GenerateOptionsUsingAI.js";
|
||||
import { questions } from "../../logic/GameCreateData.svelte.js";
|
||||
|
||||
let props = $props();
|
||||
let index = props.index;
|
||||
</script>
|
||||
|
||||
{#if questions.v[index].answers.every((answer) => answer === "")}
|
||||
<button
|
||||
onclick={() => {
|
||||
GenerateOptionsUsingAI(index);
|
||||
}}
|
||||
class="mt-1 mb-1 flex h-fit cursor-pointer items-center justify-center gap-2 rounded-xl bg-blue-700 p-2 transition-all hover:scale-110 hover:-rotate-5"
|
||||
>
|
||||
<i class="nf nf-cod-sparkle"></i>
|
||||
Generate Options using AI
|
||||
</button>
|
||||
{/if}
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
import { GenerateQuestionsUsingAI } from "../../logic/GenerateQuestionsUsingAI.js";
|
||||
import { questions } from "../../logic/GameCreateData.svelte.js";
|
||||
</script>
|
||||
|
||||
{#if questions.v.length === 0 || (questions.v.length === 1 && questions.v[0].name === "" && questions.v[0].answers.every((answer) => answer === "") && questions.v[0].correctAnswer === undefined)}
|
||||
<button
|
||||
onclick={GenerateQuestionsUsingAI}
|
||||
class="-mt-5 mb-3 flex h-fit cursor-pointer items-center justify-center gap-2 rounded-xl bg-blue-700 p-2 transition-all hover:scale-110 hover:-rotate-5"
|
||||
>
|
||||
<i class="nf nf-cod-sparkle"></i>
|
||||
Generate questions using AI
|
||||
</button>
|
||||
{/if}
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
import { AddQuestion } from '../../logic/GameCreateData.svelte.js';
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={() => AddQuestion()}
|
||||
class="flex h-fit cursor-pointer items-center justify-center rounded-xl bg-green-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#FFFFFF"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z" /></svg
|
||||
>New question</button
|
||||
>
|
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
import { startGame } from '../../logic/StartGame.js';
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={startGame}
|
||||
class="flex h-fit cursor-pointer items-center justify-center gap-1 rounded-xl bg-green-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#FFFFFF"
|
||||
><path
|
||||
d="M560-360q17 0 29.5-12.5T602-402q0-17-12.5-29.5T560-444q-17 0-29.5 12.5T518-402q0 17 12.5 29.5T560-360Zm-30-128h60q0-29 6-42.5t28-35.5q30-30 40-48.5t10-43.5q0-45-31.5-73.5T560-760q-41 0-71.5 23T446-676l54 22q9-25 24.5-37.5T560-704q24 0 39 13.5t15 36.5q0 14-8 26.5T578-596q-33 29-40.5 45.5T530-488ZM320-240q-33 0-56.5-23.5T240-320v-480q0-33 23.5-56.5T320-880h480q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H320Zm0-80h480v-480H320v480ZM160-80q-33 0-56.5-23.5T80-160v-560h80v560h560v80H160Zm160-720v480-480Z"
|
||||
/></svg
|
||||
>Start Quiz</button
|
||||
>
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
import { SetQuestionsToDemoQuestions } from '../../logic/GameCreateData.svelte.js';
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={() => SetQuestionsToDemoQuestions()}
|
||||
class="-mt-5 mb-3 flex h-fit cursor-pointer items-center justify-center rounded-xl bg-green-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#FFFFFF"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z" /></svg
|
||||
>Use demo questions</button
|
||||
>
|
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
import { startGame } from "../../logic/StartGame.js";
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={startGame}
|
||||
class="flex h-fit cursor-pointer items-center justify-center gap-1 rounded-xl bg-gray-700 p-2 transition-all hover:scale-110"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#FFFFFF"
|
||||
><path
|
||||
d="M560-360q17 0 29.5-12.5T602-402q0-17-12.5-29.5T560-444q-17 0-29.5 12.5T518-402q0 17 12.5 29.5T560-360Zm-30-128h60q0-29 6-42.5t28-35.5q30-30 40-48.5t10-43.5q0-45-31.5-73.5T560-760q-41 0-71.5 23T446-676l54 22q9-25 24.5-37.5T560-704q24 0 39 13.5t15 36.5q0 14-8 26.5T578-596q-33 29-40.5 45.5T530-488ZM320-240q-33 0-56.5-23.5T240-320v-480q0-33 23.5-56.5T320-880h480q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H320Zm0-80h480v-480H320v480ZM160-80q-33 0-56.5-23.5T80-160v-560h80v560h560v80H160Zm160-720v480-480Z"
|
||||
/></svg
|
||||
>Wait for game to be created</button
|
||||
>
|
36
src/routes/kahootclone/create/logic/GameCreateData.svelte.js
Normal file
36
src/routes/kahootclone/create/logic/GameCreateData.svelte.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { DefaultQuestions } from "$lib/config.js";
|
||||
|
||||
export let Wait = $state({
|
||||
v: false,
|
||||
});
|
||||
export let questions = $state({
|
||||
v: [
|
||||
{
|
||||
name: "",
|
||||
answers: ["", "", "", ""],
|
||||
correctAnswer: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export function SetQuestionsToDemoQuestions() {
|
||||
questions.v = DefaultQuestions;
|
||||
}
|
||||
|
||||
export function AddQuestion() {
|
||||
questions.v.push({
|
||||
name: "",
|
||||
answers: ["", "", "", ""],
|
||||
correctAnswer: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function DeleteQuestion(index) {
|
||||
if (questions.v.length > 1) {
|
||||
if (confirm("Are you sure you want to delete this question? You cant undo this.")) {
|
||||
questions.v.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
alert("You need at least one question.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { questions } from "./GameCreateData.svelte.js";
|
||||
import { AiPrompts } from "$lib/config.js";
|
||||
|
||||
export function GenerateOptionsUsingAI(index) {
|
||||
fetch("https://ai.hackclub.com/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: AiPrompts.GenerateOptionsUsingAI.replace(
|
||||
"[question]",
|
||||
questions.v[index].name,
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
let question = questions.v[index].name;
|
||||
questions.v[index] = JSON.parse(data.choices[0].message.content);
|
||||
questions.v[index].name = question;
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Error:" + error);
|
||||
return;
|
||||
});
|
||||
|
||||
alert("added!");
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { questions } from "./GameCreateData.svelte.js";
|
||||
import { AiPrompts } from "$lib/config.js";
|
||||
|
||||
export function GenerateQuestionsUsingAI() {
|
||||
let topic = window.prompt(
|
||||
"What is the topic of the questions?\nand the number of questions in the topic?",
|
||||
);
|
||||
|
||||
if (!topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("https://ai.hackclub.com/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: AiPrompts.GenerateQuestionsUsingAI.replace("[topic]", topic),
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
questions.v = JSON.parse(data.choices[0].message.content);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Error:" + error);
|
||||
return;
|
||||
});
|
||||
|
||||
alert("added!");
|
||||
}
|
51
src/routes/kahootclone/create/logic/InsertGameInDB.js
Normal file
51
src/routes/kahootclone/create/logic/InsertGameInDB.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { supabase } from "$lib/supabase";
|
||||
|
||||
export async function createGame(questions, gamePin) {
|
||||
const { data: gameData, error: gameError } = await supabase.from("games").insert({
|
||||
creator: "anonymous",
|
||||
creationdate: new Date().toISOString(),
|
||||
status: "lobby",
|
||||
gamepin: gamePin,
|
||||
});
|
||||
|
||||
if (gameError) {
|
||||
alert("Failed to create game: " + gameError.message + "\n\nPlease try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare questions and answers for batch insertion
|
||||
const questionsData = questions.map((q, index) => ({
|
||||
gameid: gamePin,
|
||||
questionstext: q.name,
|
||||
correctanswer: q.correctAnswer,
|
||||
}));
|
||||
|
||||
const { data: questionsResult, error: questionsError } = await supabase
|
||||
.from("questions")
|
||||
.insert(questionsData)
|
||||
.select("id");
|
||||
|
||||
if (questionsError) {
|
||||
alert("Failed to insert questions: " + questionsError.message + "\n\nPlease try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const answersData = [];
|
||||
questionsResult.forEach((question, index) => {
|
||||
questions[index].answers.forEach((answer, answerIndex) => {
|
||||
answersData.push({
|
||||
questionid: question.id,
|
||||
content: answer,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const { error: answersError } = await supabase.from("answers").insert(answersData);
|
||||
|
||||
if (answersError) {
|
||||
alert("Failed to insert answers: " + answersError.message + "\n\nPlease try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = `/host?gamepin=${gamePin}` ;
|
||||
}
|
16
src/routes/kahootclone/create/logic/StartGame.js
Normal file
16
src/routes/kahootclone/create/logic/StartGame.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { createGame } from "./InsertGameInDB.js";
|
||||
import { questions,Wait } from "./GameCreateData.svelte.js";
|
||||
|
||||
export async function startGame() {
|
||||
if (questions.v.some((q) => q.name === "")) return alert("Please fill all questions");
|
||||
if (questions.v.some((q) => q.answers.some((a) => a === ""))) return alert("Fill all options");
|
||||
if (questions.v.some((q) => q.correctAnswer === undefined))
|
||||
return alert("Select correct answers");
|
||||
|
||||
const gamePin = Math.floor(Math.random() * 1000000)
|
||||
.toString()
|
||||
.padStart(6, "0");
|
||||
|
||||
Wait.v = true;
|
||||
|
||||
await createGame(questions.v, gamePin);}
|
30
src/routes/kahootclone/host/+page.svelte
Normal file
30
src/routes/kahootclone/host/+page.svelte
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script>
|
||||
import PlayingDisplay from "./components/DuringGame/display.svelte";
|
||||
|
||||
import LobbyDisplay from "./components/lobby/display.svelte";
|
||||
import { Status } from "./logic/HostsData.svelte.js";
|
||||
import { AutoUpdatePlayersList } from "./logic/UpdatePlayersList.js";
|
||||
import { GetCurrentPlayers } from "./logic/GetCurrentPlayers.js";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let gamePin;
|
||||
|
||||
onMount(() => {
|
||||
gamePin = new URLSearchParams(new URL(window.location.href).search).get("gamepin");
|
||||
|
||||
GetCurrentPlayers(gamePin);
|
||||
AutoUpdatePlayersList(gamePin);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-grey-900 flex h-full items-center justify-center">
|
||||
<div
|
||||
class="flex max-w-[700px] flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-8 shadow-lg"
|
||||
>
|
||||
{#if Status.v == "lobby"}
|
||||
<LobbyDisplay {gamePin} />
|
||||
{:else if Status.v == "started"}
|
||||
<PlayingDisplay />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import { PeopleAwnseredQ, Totalplayers } from "./../../logic/HostsData.svelte.js";
|
||||
</script>
|
||||
|
||||
<div class="mt-2 mb-3 flex w-full flex-col rounded-2xl border-2 border-green-400 p-2">
|
||||
<h3>{PeopleAwnseredQ.v} out of {Totalplayers.v} have answered the question</h3>
|
||||
<div class="flex-1 rounded-full border-2 border-gray-600">
|
||||
<div
|
||||
class="h-4 rounded-full bg-green-600 transition-all duration-500"
|
||||
style="width: {(PeopleAwnseredQ.v / Totalplayers.v) * 100}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import { CurrentQuestionDetails } from "../../../logic/HostsData.svelte.js";
|
||||
|
||||
import { AnswersSymbolAndColorScheme } from "$lib/config.js";
|
||||
</script>
|
||||
|
||||
<div class="mt-5 grid grid-cols-2 gap-5 gap-x-3">
|
||||
{#each CurrentQuestionDetails.v.answers as answer, index}
|
||||
<div class="flex">
|
||||
<input type="radio" name="question" class="sr-only" />
|
||||
<label
|
||||
for="O{index}"
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[index].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[index].Color};
|
||||
"
|
||||
class="w-full rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[index].Symbol}"></i>
|
||||
{answer}
|
||||
</label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import { currentQuestion, totalQuetions } from "./../../../logic/HostsData.svelte.js";
|
||||
</script>
|
||||
|
||||
<div class="mt-5 mb-2 flex w-full items-center justify-center gap-3">
|
||||
<h3>Question {currentQuestion.v + 1} of {totalQuetions.v}</h3>
|
||||
<div class="flex-1 rounded-full border-2 border-gray-600">
|
||||
<div
|
||||
class="h-4 rounded-full bg-green-600 transition-all duration-700"
|
||||
style="width: {(currentQuestion.v / totalQuetions.v) * 100}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
<script>
|
||||
import Question from "./text/Quetion.svelte";
|
||||
import Awnsers from "./Awnsers.svelte";
|
||||
import ProgressBar from "./ProgressBar.svelte";
|
||||
</script>
|
||||
|
||||
<ProgressBar />
|
||||
<Question />
|
||||
<Awnsers />
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import { currentQuestion, CurrentQuestionDetails } from "../../../../logic/HostsData.svelte.js";
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] text-center text-5xl">
|
||||
Q{currentQuestion.v + 1}. {CurrentQuestionDetails.v.question}
|
||||
</h1>
|
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
import PeopleAwnsered from "./PeopleAwnsered.svelte";
|
||||
import Display from "./awnseringQuetions/display.svelte";
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] text-7xl">HOSTING</h1>
|
||||
<Display />
|
||||
<PeopleAwnsered />
|
|
@ -0,0 +1,6 @@
|
|||
<script>
|
||||
let props = $props();
|
||||
let playerName = props.playerName;
|
||||
</script>
|
||||
|
||||
<span class="m-[0] rounded-xl bg-gray-700 pt-1 pr-2 pb-0 pl-2 font-mono text-3xl">{playerName}</span>
|
|
@ -0,0 +1,12 @@
|
|||
<script>
|
||||
import { players } from "../../../logic/HostsData.svelte.js";
|
||||
import PlayerBadge from "./playerBadge.svelte";
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] mt-10 text-6xl">Players Joined:</h1>
|
||||
<h1 class="m-[0] text-4xl text-gray-400">(Total Players: {players.v.length})</h1>
|
||||
<div class="mt-2 flex flex-wrap justify-center gap-2">
|
||||
{#each players.v as playerName}
|
||||
<PlayerBadge {playerName} />
|
||||
{/each}
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
<script>
|
||||
import { startGame } from "../../../logic/startGame.js";
|
||||
|
||||
let props = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="mt-5 cursor-pointer rounded-2xl bg-green-700 p-2 text-4xl transition-all hover:scale-110 hover:-rotate-10"
|
||||
onclick={() => {
|
||||
startGame(props.gamePin);
|
||||
}}>Start the game</button
|
||||
>
|
15
src/routes/kahootclone/host/components/lobby/display.svelte
Normal file
15
src/routes/kahootclone/host/components/lobby/display.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
import StartGame from "./buttons/startGame.svelte";
|
||||
import Players from "./PlayersGUI/players.svelte";
|
||||
|
||||
let props = $props();
|
||||
let gamePin = props.gamePin;
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] text-9xl">HOSTING</h1>
|
||||
<h1 class="m-[0] text-7xl">Game Pin:</h1>
|
||||
<h1 class="m-[0] rounded-2xl bg-gray-700 pt-1.5 pr-2 pb-0 pl-2 font-mono text-5xl">
|
||||
{gamePin}
|
||||
</h1>
|
||||
<Players />
|
||||
<StartGame {gamePin} />
|
7
src/routes/kahootclone/host/logic/GameOver.js
Normal file
7
src/routes/kahootclone/host/logic/GameOver.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
|
||||
export async function GameOver(GamePin) {
|
||||
await supabase.from("games").update({ status: `completed` }).eq("gamepin", GamePin);
|
||||
|
||||
window.location.replace("/results?gamepin=" + GamePin + "&playerID=host-null");
|
||||
}
|
18
src/routes/kahootclone/host/logic/GetCurrentPlayers.js
Normal file
18
src/routes/kahootclone/host/logic/GetCurrentPlayers.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { players } from "./HostsData.svelte.js";
|
||||
|
||||
export async function GetCurrentPlayers(gamePin) {
|
||||
const { data, error } = await supabase
|
||||
.from("players")
|
||||
.select("playername")
|
||||
.eq("gameid", Number(gamePin));
|
||||
|
||||
console.log("Current players data:", JSON.stringify(data));
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching players:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
players.v = data ? data.map(player => player.playername) : [];
|
||||
}
|
10
src/routes/kahootclone/host/logic/HostsData.svelte.js
Normal file
10
src/routes/kahootclone/host/logic/HostsData.svelte.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export let players = $state({ v: [] });
|
||||
export let Status = $state({ v: "lobby" });
|
||||
export let questions = { v: {} };
|
||||
|
||||
export let currentQuestion = $state({ v: 0 });
|
||||
export let totalQuetions = $state({ v: 3 });
|
||||
export let PeopleAwnseredQ = $state({ v: 0 });
|
||||
export let Totalplayers = $state({ v: 3 });
|
||||
|
||||
export let CurrentQuestionDetails = $state({ v: {} });
|
26
src/routes/kahootclone/host/logic/UpdatePlayersList.js
Normal file
26
src/routes/kahootclone/host/logic/UpdatePlayersList.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { players } from "./HostsData.svelte.js";
|
||||
|
||||
export let LobbyConnection;
|
||||
|
||||
function onNewPlayer(Newplayers) {
|
||||
players.v.push(Newplayers.playername);
|
||||
}
|
||||
|
||||
export async function AutoUpdatePlayersList(gamePin) {
|
||||
LobbyConnection = supabase
|
||||
.channel("players-realtime")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{
|
||||
event: "INSERT",
|
||||
schema: "public",
|
||||
table: "players",
|
||||
filter: `gameid=eq.${gamePin}`,
|
||||
},
|
||||
(payload) => {
|
||||
onNewPlayer(payload.new);
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
}
|
51
src/routes/kahootclone/host/logic/WaitForAwnser.js
Normal file
51
src/routes/kahootclone/host/logic/WaitForAwnser.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { onNewPlayerAwnsered } from "./onNewPlayerAwnsered.js";
|
||||
import { currentQuestion, questions, CurrentQuestionDetails } from "./HostsData.svelte.js";
|
||||
|
||||
let WaitingForAwnserConection;
|
||||
|
||||
export async function WaitForAwnser(questionid, gamePin) {
|
||||
if (questionid != 0) {
|
||||
await supabase.removeChannel(WaitingForAwnserConection);
|
||||
}
|
||||
|
||||
await supabase
|
||||
.from("games")
|
||||
.update({ status: `question-${currentQuestion.v}` })
|
||||
.eq("gamepin", gamePin);
|
||||
|
||||
WaitingForAwnserConection = supabase
|
||||
.channel("answeredby-realtime")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{
|
||||
event: "INSERT",
|
||||
schema: "public",
|
||||
table: "answeredby",
|
||||
filter: `questionid=eq.${questions.v[questionid].id}`,
|
||||
},
|
||||
(payload) => {
|
||||
onNewPlayerAwnsered(gamePin);
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
const { data: questionsData } = await supabase
|
||||
.from("questions")
|
||||
.select("id,questionstext,correctanswer")
|
||||
.eq("gameid", Number(gamePin))
|
||||
.order("id", { ascending: true });
|
||||
|
||||
const { data: answers } = await supabase
|
||||
.from("answers")
|
||||
.select("content")
|
||||
.eq("questionid", Number(questionsData[currentQuestion.v].id))
|
||||
.order("id", { ascending: true });
|
||||
|
||||
CurrentQuestionDetails.v = {
|
||||
question: questionsData[currentQuestion.v].questionstext,
|
||||
correctAnswer: questionsData[currentQuestion.v].correctanswer,
|
||||
answers: answers.map((answer) => answer.content),
|
||||
questionid: questionsData[currentQuestion.v].id,
|
||||
};
|
||||
}
|
23
src/routes/kahootclone/host/logic/onNewPlayerAwnsered.js
Normal file
23
src/routes/kahootclone/host/logic/onNewPlayerAwnsered.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
Totalplayers,
|
||||
PeopleAwnseredQ,
|
||||
currentQuestion,
|
||||
totalQuetions,
|
||||
} from "./HostsData.svelte.js";
|
||||
import { GameOver } from "./GameOver.js";
|
||||
import { WaitForAwnser } from "./WaitForAwnser.js";
|
||||
|
||||
export async function onNewPlayerAwnsered(GamePin) {
|
||||
PeopleAwnseredQ.v++;
|
||||
|
||||
if (PeopleAwnseredQ.v == Totalplayers.v) {
|
||||
currentQuestion.v++;
|
||||
if (currentQuestion.v == totalQuetions.v) {
|
||||
GameOver(GamePin);
|
||||
return;
|
||||
}
|
||||
PeopleAwnseredQ.v = 0;
|
||||
|
||||
WaitForAwnser(currentQuestion.v, GamePin);
|
||||
}
|
||||
}
|
41
src/routes/kahootclone/host/logic/startGame.js
Normal file
41
src/routes/kahootclone/host/logic/startGame.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { LobbyConnection } from "./UpdatePlayersList.js";
|
||||
import {
|
||||
questions,
|
||||
Status,
|
||||
Totalplayers,
|
||||
totalQuetions,
|
||||
players,
|
||||
} from "./HostsData.svelte.js";
|
||||
import { WaitForAwnser } from "./WaitForAwnser.js";
|
||||
|
||||
export async function startGame(gamePin) {
|
||||
if (players.v.length == 0) {
|
||||
alert("you need at least 1 person to start the game!");
|
||||
return;
|
||||
}
|
||||
|
||||
await supabase.removeChannel(LobbyConnection);
|
||||
|
||||
Status.v = "started";
|
||||
|
||||
const { data } = await supabase
|
||||
.from("questions")
|
||||
.select("*")
|
||||
.eq("gameid", Number(gamePin))
|
||||
.order("id", { ascending: true });
|
||||
|
||||
questions.v = data;
|
||||
|
||||
totalQuetions.v = data.length;
|
||||
|
||||
const { data: playersData } = await supabase
|
||||
.from("players")
|
||||
.select("id")
|
||||
.eq("gameid", Number(gamePin))
|
||||
.order("id", { ascending: true });
|
||||
|
||||
Totalplayers.v = playersData.length;
|
||||
|
||||
WaitForAwnser(0, gamePin);
|
||||
}
|
47
src/routes/kahootclone/join/+page.svelte
Normal file
47
src/routes/kahootclone/join/+page.svelte
Normal file
|
@ -0,0 +1,47 @@
|
|||
<script>
|
||||
import { joinGame } from "./logic/joinGame.js";
|
||||
import { Checking } from "./logic/JoinGameData.svelte.js";
|
||||
|
||||
let pin;
|
||||
let name;
|
||||
</script>
|
||||
|
||||
<div class="bg-grey-900 flex h-full items-center justify-center">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-7 shadow-lg"
|
||||
>
|
||||
<h1 class="m-[0] mb-3 text-5xl">Join a game here</h1>
|
||||
|
||||
<input
|
||||
placeholder="Enter game pin"
|
||||
class="rounded-lg bg-gray-800 p-2 text-center text-white"
|
||||
bind:value={pin}
|
||||
/>
|
||||
|
||||
<input
|
||||
placeholder="Enter your name"
|
||||
bind:value={name}
|
||||
class="rounded-lg bg-gray-800 p-2 text-center text-white"
|
||||
/>
|
||||
|
||||
{#if Checking.v}
|
||||
<button
|
||||
class="mt-2 cursor-pointer rounded-full bg-gray-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
>
|
||||
Checking if pin is valid...
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="mt-2 cursor-pointer rounded-full bg-green-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
on:click={() => {
|
||||
if (!pin || !name) {
|
||||
alert("Please fill in the game pin and your name.");
|
||||
} else {
|
||||
joinGame(pin, name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Join game
|
||||
</button>{/if}
|
||||
</div>
|
||||
</div>
|
19
src/routes/kahootclone/join/logic/InsertPlayerInDB.js
Normal file
19
src/routes/kahootclone/join/logic/InsertPlayerInDB.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { supabase } from "$lib/supabase";
|
||||
|
||||
export async function addPlayer(name, gamePin) {
|
||||
const { data, error } = await supabase
|
||||
.from("players")
|
||||
.insert({
|
||||
gameid: gamePin,
|
||||
score: 0,
|
||||
playername: name,
|
||||
})
|
||||
.select("id");
|
||||
|
||||
if (error) {
|
||||
alert("Failed to join game: " + error.message + "\n\nPlease try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
return data[0].id;
|
||||
}
|
3
src/routes/kahootclone/join/logic/JoinGameData.svelte.js
Normal file
3
src/routes/kahootclone/join/logic/JoinGameData.svelte.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export let Checking = $state({
|
||||
v: false,
|
||||
});
|
19
src/routes/kahootclone/join/logic/joinGame.js
Normal file
19
src/routes/kahootclone/join/logic/joinGame.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { addPlayer } from "./InsertPlayerInDB.js";
|
||||
import { validateGamePin } from "./validateGamePin.js";
|
||||
import { Checking } from "./JoinGameData.svelte.js";
|
||||
|
||||
export async function joinGame(pin, name) {
|
||||
Checking.v = true;
|
||||
|
||||
if (!(await validateGamePin(pin))) {
|
||||
alert("Invalid game pin. Please try again.");
|
||||
Checking.v = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let id = await addPlayer(name, pin);
|
||||
|
||||
Checking.v = false;
|
||||
|
||||
window.location.href = `./play?gamepin=${pin}&name=${name}&playerid=${id}`;
|
||||
}
|
11
src/routes/kahootclone/join/logic/validateGamePin.js
Normal file
11
src/routes/kahootclone/join/logic/validateGamePin.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { supabase } from '$lib/supabase';
|
||||
|
||||
export async function validateGamePin(pin) {
|
||||
const { data, error } = await supabase
|
||||
.from('games')
|
||||
.select('gamepin')
|
||||
.eq('gamepin', Number(pin))
|
||||
.maybeSingle();
|
||||
|
||||
return data !== null && !error;
|
||||
}
|
34
src/routes/kahootclone/play/+page.svelte
Normal file
34
src/routes/kahootclone/play/+page.svelte
Normal file
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
import AwnserQuetion from "./components/awnseringQuetions/display.svelte";
|
||||
import LobbyDisplay from "./components/lobby/display.svelte";
|
||||
import { Status } from "./logic/HostsData.svelte.js";
|
||||
import { AutoUpdatePlayersList } from "./logic/UpdatePlayersList.js";
|
||||
import { GetCurrentPlayers } from "./logic/GetCurrentPlayers.js";
|
||||
import { IntializeGameStart } from "./logic/IntializeGameStart.js";
|
||||
import { onMount } from "svelte";
|
||||
import { name,playerid } from "./logic/HostsData.svelte.js";
|
||||
|
||||
let gamePin;
|
||||
|
||||
onMount(() => {
|
||||
name.v = new URLSearchParams(new URL(window.location.href).search).get("name");
|
||||
playerid.v = new URLSearchParams(new URL(window.location.href).search).get("playerid");
|
||||
gamePin = new URLSearchParams(new URL(window.location.href).search).get("gamepin");
|
||||
|
||||
GetCurrentPlayers(gamePin);
|
||||
AutoUpdatePlayersList(gamePin);
|
||||
IntializeGameStart(gamePin);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-grey-900 flex h-full items-center justify-center">
|
||||
<div
|
||||
class="flex max-w-[700px] flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-8 shadow-lg"
|
||||
>
|
||||
{#if Status.v == "lobby"}
|
||||
<LobbyDisplay {gamePin} />
|
||||
{:else if Status.v == "started"}
|
||||
<AwnserQuetion />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,36 @@
|
|||
<script>
|
||||
import { questions, Selected } from "../../logic/HostsData.svelte.js";
|
||||
import { AnswersSymbolAndColorScheme } from "$lib/config.js";
|
||||
</script>
|
||||
|
||||
<div class="mt-5 grid grid-cols-2 gap-5 gap-x-3">
|
||||
{#each questions.v.answers as answer, index}
|
||||
<div class="flex">
|
||||
<input
|
||||
type="radio"
|
||||
id="O{index}"
|
||||
name="question"
|
||||
class="peer sr-only"
|
||||
value={index}
|
||||
bind:group={Selected.v}
|
||||
/>
|
||||
|
||||
<label
|
||||
for="O{index}"
|
||||
style="
|
||||
--border-color: {AnswersSymbolAndColorScheme[index].Color};
|
||||
--bg-color: {AnswersSymbolAndColorScheme[index].Color};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[index].SelectedColor};
|
||||
--bg-color-checked: {AnswersSymbolAndColorScheme[index].SelectedColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[index].HoverBorderColor};
|
||||
--border-color-checked: {AnswersSymbolAndColorScheme[index].SelectedBorderColor};
|
||||
--border-color-hover: {AnswersSymbolAndColorScheme[index].HoverBorderColor};
|
||||
"
|
||||
class="w-full cursor-pointer rounded-lg border-[5px] border-[var(--border-color)] bg-[var(--bg-color)] pt-1 pr-2 pb-1 pl-2 text-center text-3xl transition-all peer-checked:border-[var(--border-color-checked)] peer-checked:border-[var(--border-color-checked)] peer-checked:bg-[var(--bg-color-checked)] hover:border-[var(--border-color-hover)]"
|
||||
>
|
||||
<i class="nf {AnswersSymbolAndColorScheme[index].Symbol}"></i>
|
||||
{answer}
|
||||
</label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import { CurrentQuestion, TotalQuestions } from "../../logic/HostsData.svelte.js";
|
||||
</script>
|
||||
|
||||
<div class="mb-5 flex w-full items-center justify-center gap-3">
|
||||
<h3>Question {CurrentQuestion.v + 1} of {TotalQuestions.v}</h3>
|
||||
<div class="flex-1 rounded-full border-2 border-gray-600">
|
||||
<div
|
||||
class="h-4 rounded-full bg-green-600 transition-all duration-700"
|
||||
style="width: {(CurrentQuestion.v / TotalQuestions.v) * 100}%;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
<div class="group relative">
|
||||
<button
|
||||
class="mt-4 cursor-not-allowed gap-0 rounded-lg bg-gray-500 p-2 text-3xl text-gray-300 transition-all"
|
||||
disabled
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<div
|
||||
class="invisible absolute bottom-full left-1/2 mb-2 w-40 -translate-x-1/2 rounded-md bg-black px-3 py-1 text-center text-sm text-white opacity-0 transition-all group-hover:visible group-hover:opacity-100"
|
||||
>
|
||||
Select an option to submit
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,9 @@
|
|||
<script>
|
||||
import { SubmitAnswer } from "../../../logic/SubmitAnswer.js";
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="mt-4 cursor-pointer gap-0 rounded-lg bg-green-700 p-2 text-3xl transition-all hover:scale-110 hover:-rotate-10"
|
||||
onclick={SubmitAnswer}
|
||||
>Submit
|
||||
</button>
|
|
@ -0,0 +1,28 @@
|
|||
<script>
|
||||
import Question from "./text/Quetion.svelte";
|
||||
import Awnsers from "./Awnsers.svelte";
|
||||
import ProgressBar from "./ProgressBar.svelte";
|
||||
import SelectFirst from "./buttons/SelectFirst.svelte";
|
||||
import Wait from "./text/wait.svelte";
|
||||
import SubmitAwnser from "./buttons/submitAwnser.svelte";
|
||||
import { CurrentQuestion, Selected } from "../../logic/HostsData.svelte.js";
|
||||
</script>
|
||||
|
||||
<div class="bg-grey-900 flex h-full items-center justify-center">
|
||||
<div
|
||||
class="flex max-w-[700px] flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-8 shadow-lg"
|
||||
>
|
||||
<ProgressBar />
|
||||
{#if CurrentQuestion.v != null}
|
||||
<Question />
|
||||
<Awnsers />
|
||||
{#if Selected.v != null}
|
||||
<SubmitAwnser />
|
||||
{:else}
|
||||
<SelectFirst />
|
||||
{/if}
|
||||
{:else}
|
||||
<Wait />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
<script>
|
||||
import { CurrentQuestion, questions } from "../../../logic/HostsData.svelte.js";
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] text-center text-5xl">
|
||||
Q{CurrentQuestion.v + 1}. {questions.v.question}
|
||||
</h1>
|
|
@ -0,0 +1 @@
|
|||
<h1 class="m-[0] text-center text-5xl">Please wait for everyone else to answer the question.</h1>
|
|
@ -0,0 +1,6 @@
|
|||
<script>
|
||||
let props = $props();
|
||||
let playerName = props.playerName;
|
||||
</script>
|
||||
|
||||
<span class="m-[0] rounded-xl bg-gray-700 pt-1 pr-2 pb-0 pl-2 font-mono text-3xl">{playerName}</span>
|
|
@ -0,0 +1,12 @@
|
|||
<script>
|
||||
import { players } from "../../../logic/HostsData.svelte.js";
|
||||
import PlayerBadge from "./playerBadge.svelte";
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] mt-5 text-6xl">Players Joined:</h1>
|
||||
<h1 class="m-[0] text-4xl text-gray-400">(Total Players: {players.v.length})</h1>
|
||||
<div class="mt-2 flex flex-wrap justify-center gap-2">
|
||||
{#each players.v as playerName}
|
||||
<PlayerBadge {playerName} />
|
||||
{/each}
|
||||
</div>
|
18
src/routes/kahootclone/play/components/lobby/display.svelte
Normal file
18
src/routes/kahootclone/play/components/lobby/display.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
import Players from "./PlayersGUI/players.svelte";
|
||||
|
||||
let props = $props();
|
||||
let gamePin = props.gamePin;
|
||||
</script>
|
||||
|
||||
<h1 class="m-[0] text-9xl">PLAYING</h1>
|
||||
|
||||
<h1 class="m-[0] text-7xl text-center">Waiting for host to start the game</h1>
|
||||
|
||||
<!--
|
||||
<h1 class="m-[0] text-7xl">Game Pin:</h1>
|
||||
<h1 class="m-[0] rounded-2xl bg-gray-700 pt-1.5 pr-2 pb-0 pl-2 font-mono text-5xl">
|
||||
{gamePin}
|
||||
</h1>
|
||||
-->
|
||||
<Players />
|
18
src/routes/kahootclone/play/logic/GetCurrentPlayers.js
Normal file
18
src/routes/kahootclone/play/logic/GetCurrentPlayers.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { players } from "./HostsData.svelte.js";
|
||||
|
||||
export async function GetCurrentPlayers(gamePin) {
|
||||
const { data, error } = await supabase
|
||||
.from("players")
|
||||
.select("playername")
|
||||
.eq("gameid", Number(gamePin));
|
||||
|
||||
console.log("Current players data:", JSON.stringify(data));
|
||||
|
||||
if (error) {
|
||||
console.error("Error fetching players:", error);
|
||||
return;
|
||||
}
|
||||
|
||||
players.v = data ? data.map(player => player.playername) : [];
|
||||
}
|
9
src/routes/kahootclone/play/logic/HostsData.svelte.js
Normal file
9
src/routes/kahootclone/play/logic/HostsData.svelte.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
export let players = $state({ v: {} });
|
||||
export let Status = $state({ v: "lobby" });
|
||||
export let questions = $state({ v: {} });
|
||||
export let CurrentQuestion = $state({ v: null });
|
||||
export let TotalQuestions = $state({ v: 0 });
|
||||
export let Selected = $state({ v: null });
|
||||
export let isWait = $state({ v: true });
|
||||
export let name = $state({ v: "" });
|
||||
export let playerid = $state({ v: null });
|
22
src/routes/kahootclone/play/logic/IntializeGameStart.js
Normal file
22
src/routes/kahootclone/play/logic/IntializeGameStart.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { NewStatus } from "./NewStatus.js";
|
||||
|
||||
export async function IntializeGameStart(gamepin) {
|
||||
supabase
|
||||
.channel(`game_status_${gamepin}`)
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{
|
||||
event: "UPDATE",
|
||||
schema: "public",
|
||||
table: "games",
|
||||
filter: `gamepin=eq.${gamepin}`,
|
||||
},
|
||||
(payload) => {
|
||||
if (payload.new.status) {
|
||||
NewStatus(payload.new.status, gamepin);
|
||||
}
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
}
|
44
src/routes/kahootclone/play/logic/NewStatus.js
Normal file
44
src/routes/kahootclone/play/logic/NewStatus.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import {
|
||||
CurrentQuestion,
|
||||
Status,
|
||||
questions,
|
||||
isWait,
|
||||
Selected,
|
||||
playerid,
|
||||
TotalQuestions,
|
||||
} from "./HostsData.svelte.js";
|
||||
import { supabase } from "$lib/supabase.js";
|
||||
|
||||
export async function NewStatus(NewStatus, gamePin) {
|
||||
if (NewStatus == "completed") {
|
||||
window.location.replace("/results?gamepin" + gamePin + "&playerID=" + playerid.v);
|
||||
return;
|
||||
}
|
||||
|
||||
Status.v = "started";
|
||||
CurrentQuestion.v = Number(NewStatus.replace("question-", ""));
|
||||
|
||||
const { data: questionsData } = await supabase
|
||||
.from("questions")
|
||||
.select("id,questionstext,correctanswer")
|
||||
.eq("gameid", Number(gamePin))
|
||||
.order("id", { ascending: true });
|
||||
|
||||
TotalQuestions.v = questionsData.length;
|
||||
|
||||
const { data: answers } = await supabase
|
||||
.from("answers")
|
||||
.select("content")
|
||||
.eq("questionid", Number(questionsData[CurrentQuestion.v].id))
|
||||
.order("id", { ascending: true });
|
||||
|
||||
questions.v = {
|
||||
question: questionsData[CurrentQuestion.v].questionstext,
|
||||
correctAnswer: questionsData[CurrentQuestion.v].correctanswer,
|
||||
answers: answers.map((answer) => answer.content),
|
||||
questionid: questionsData[CurrentQuestion.v].id,
|
||||
};
|
||||
|
||||
isWait.v = false;
|
||||
Selected.v = null;
|
||||
}
|
31
src/routes/kahootclone/play/logic/SubmitAnswer.js
Normal file
31
src/routes/kahootclone/play/logic/SubmitAnswer.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { CurrentQuestion, Selected, questions, playerid } from "./HostsData.svelte.js";
|
||||
import { supabase } from "$lib/supabase.js";
|
||||
|
||||
export async function SubmitAnswer() {
|
||||
CurrentQuestion.v = null;
|
||||
|
||||
if (Selected.v == questions.v.correctAnswer) {
|
||||
await supabase
|
||||
.from("answeredby")
|
||||
.insert([
|
||||
{ questionid: questions.v.questionid, nameofanswerer: playerid.v, correct: true },
|
||||
])
|
||||
.select();
|
||||
|
||||
let { data: score } = await supabase.from("players").select("score").eq("id", playerid.v);
|
||||
|
||||
await supabase
|
||||
.from("players")
|
||||
.update({ score: score[0].score + 1 })
|
||||
.eq("id", playerid.v)
|
||||
.select();
|
||||
|
||||
} else {
|
||||
await supabase
|
||||
.from("answeredby")
|
||||
.insert([
|
||||
{ questionid: questions.v.questionid, nameofanswerer: playerid.v, correct: false },
|
||||
])
|
||||
.select();
|
||||
}
|
||||
}
|
26
src/routes/kahootclone/play/logic/UpdatePlayersList.js
Normal file
26
src/routes/kahootclone/play/logic/UpdatePlayersList.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { players } from "./HostsData.svelte.js";
|
||||
|
||||
export let LobbyConnection;
|
||||
|
||||
function onNewPlayer(Newplayers) {
|
||||
players.v.push(Newplayers.playername);
|
||||
}
|
||||
|
||||
export async function AutoUpdatePlayersList(gamePin) {
|
||||
LobbyConnection = supabase
|
||||
.channel("players-realtime")
|
||||
.on(
|
||||
"postgres_changes",
|
||||
{
|
||||
event: "INSERT",
|
||||
schema: "public",
|
||||
table: "players",
|
||||
filter: `gameid=eq.${gamePin}`,
|
||||
},
|
||||
(payload) => {
|
||||
onNewPlayer(payload.new);
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
}
|
23
src/routes/kahootclone/play/logic/startGame.js
Normal file
23
src/routes/kahootclone/play/logic/startGame.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { supabase } from "$lib/supabase.js";
|
||||
import { LobbyConnection } from "./UpdatePlayersList.js";
|
||||
import { questions, Status, CurrentQuestion, TotalQuestions } from "./HostsData.svelte.js";
|
||||
|
||||
export async function startGame(gamePin) {
|
||||
await supabase.removeChannel(LobbyConnection);
|
||||
|
||||
Status.v = "started";
|
||||
|
||||
const { data } = await supabase
|
||||
.from("questions")
|
||||
.select("*")
|
||||
.eq("gameid", Number(gamePin))
|
||||
.order("id", { ascending: true });
|
||||
TotalQuestions.v = data.length;
|
||||
|
||||
CurrentQuestion.v = 0;
|
||||
|
||||
await supabase
|
||||
.from("games")
|
||||
.update({ status: `question-${CurrentQuestion.v}` })
|
||||
.eq("gamepin", gamePin);
|
||||
}
|
82
src/routes/kahootclone/results/+page.svelte
Normal file
82
src/routes/kahootclone/results/+page.svelte
Normal file
|
@ -0,0 +1,82 @@
|
|||
<script>
|
||||
import { supabase } from "$lib/supabase";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let gamePin;
|
||||
|
||||
let playerID;
|
||||
let players = [];
|
||||
|
||||
onMount(async () => {
|
||||
playerID = new URLSearchParams(new URL(window.location.href).search).get("playerID");
|
||||
gamePin = new URLSearchParams(new URL(window.location.href).search).get("gamepin");
|
||||
|
||||
|
||||
let { data: fetchedPlayers } = await supabase
|
||||
.from("players")
|
||||
.select("*")
|
||||
.eq("gameid", gamePin);
|
||||
|
||||
players = fetchedPlayers.sort((a, b) => b.score - a.score);
|
||||
|
||||
console.log(players);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="bg-grey-900 flex h-full items-center justify-center">
|
||||
<div
|
||||
class="flex max-w-[700px] flex-col items-center justify-center gap-1 rounded-lg bg-gray-900 p-8 shadow-lg"
|
||||
>
|
||||
<h1 class="mb-3 text-7xl font-bold text-white">Leaderboard</h1>
|
||||
|
||||
{#each players as player, i}
|
||||
{#if player.id == playerID}
|
||||
<div class="flex w-full items-center justify-between rounded-lg bg-green-950 p-2">
|
||||
<div
|
||||
class="mr-2 flex h-6 w-6 items-center justify-center rounded-full bg-gray-500"
|
||||
>
|
||||
{i + 1}
|
||||
</div>
|
||||
|
||||
<div class="w-20 text-white">{player.playername}</div>
|
||||
|
||||
<div class="flex-1 rounded-full border-2 border-gray-600">
|
||||
<div
|
||||
class="flex h-6 items-center justify-center rounded-full bg-green-600 transition-all duration-700"
|
||||
style="width: {(player.score / players[0].score) * 100}%;"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-17 justify-end">{player.score} points</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex w-full items-center justify-between rounded-lg bg-gray-800 p-2">
|
||||
<div
|
||||
class="mr-2 flex h-6 w-6 items-center justify-center rounded-full bg-gray-500"
|
||||
>
|
||||
{i + 1}
|
||||
</div>
|
||||
|
||||
<div class="w-20 text-white">{player.playername}</div>
|
||||
|
||||
<div class="flex-1 rounded-full border-2 border-gray-600">
|
||||
<div
|
||||
class="flex h-6 items-center justify-center rounded-full bg-green-600 transition-all duration-700"
|
||||
style="width: {(player.score / players[0].score) * 100}%;"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-17 justify-end">{player.score} points</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<a href="/"
|
||||
><button
|
||||
class="mt-4 cursor-pointer rounded-full bg-green-700 p-2 transition-all hover:scale-110 hover:-rotate-10"
|
||||
>
|
||||
Go back to the home page!
|
||||
</button></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue