feat : lobby

This commit is contained in:
2024-12-29 23:39:15 +01:00
parent e0ece631d3
commit d92739bbd6
14 changed files with 313 additions and 203 deletions
+12 -6
View File
@@ -1,12 +1,18 @@
<script setup lang="ts">
import { useUserStore } from '~/store/user.store';
import { useUserStore } from "~/store/user.store";
import { useLobbyStore } from "./store/lobby.store";
const userStore = useUserStore();
const lobbyStore = useLobbyStore();
userStore.whoami();
lobbyStore.fetchGames();
onBeforeMount(() => {
lobbyStore.subscribe();
});
const req = await userStore.whoami()
onBeforeUnmount(() => {
lobbyStore.close();
});
</script>
<template>
@@ -14,7 +20,7 @@ const req = await userStore.whoami()
<NuxtRouteAnnouncer />
<NuxtLayout>
<NuxtLoadingIndicator> Loading... </NuxtLoadingIndicator>
<Header />
<ConceptHeader />
<NuxtPage />
</NuxtLayout>
</div>
+71
View File
@@ -0,0 +1,71 @@
<script setup lang="ts">
import type { GameResponseDTO } from "~/netcode/events/dto/gameresponse.dto";
import { useLobbyStore } from "~/store/lobby.store";
import { useUserStore } from "~/store/user.store";
const userStore = useUserStore();
const lobbyStore = useLobbyStore();
const currentGames = computed(() => {
const games = Object.values(lobbyStore.games).filter((g) =>
g.players.some((p) => p.user.id == userStore.self?.id)
);
console.log(games);
return games;
});
</script>
<template>
<header>
<div id="self">
<template v-if="userStore.has_contacted">
<ClientOnly>
<template v-if="userStore.self && userStore.self.id">
<User :id="userStore.self.id"></User>
<button>Logout</button>
</template>
<button v-else>Login</button>
</ClientOnly>
</template>
<template v-else> Contacting server... </template>
</div>
<div id="links">
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/words">Words</NuxtLink>
</div>
<div id="games">
<NuxtLink v-for="game of currentGames" :to="'/game/' + game.id"
><div>Back to Current Game</div></NuxtLink
>
</div>
</header>
</template>
<style lang="css" scoped>
header,
header * {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
}
header > * {
flex: 1 1 0px;
}
div#self {
gap: 10px;
height: 32px;
}
div#links {
flex-grow: 4;
gap: 1%;
}
div#links {
}
</style>
-52
View File
@@ -1,52 +0,0 @@
<script setup lang="ts">
import { useUserStore } from '~/store/user.store';
const userStore = useUserStore();
</script>
<template>
<header>
<div id="self">
<template v-if="userStore.has_contacted">
<ClientOnly>
<template v-if="userStore.self && userStore.self.id">
<User :id="userStore.self.id"></User>
<button>Logout</button>
</template>
<button v-else>Login</button>
</ClientOnly>
</template>
<template v-else>
Contacting server...
</template>
</div>
<div id="links">
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/words">Words</NuxtLink>
</div>
</header>
</template>
<style lang="css" scoped>
header,
header * {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
}
div#self {
height: 32px
}
div#links {
width: 100%;
gap: 1%;
}
</style>
+29
View File
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { useLobbyStore } from "~/store/lobby.store";
import { useUserStore } from "~/store/user.store";
export interface Props {
id: number;
}
const loading = ref(false);
const lobbyStore = useLobbyStore();
const userStore = useUserStore();
const props = defineProps<Props>();
const game = lobbyStore.games[props.id];
</script>
<template>
{{ game }}
<NuxtLink :to="'/game/' + id"
><input type="button" value="open" :disabled="!userStore.self || loading"
/></NuxtLink>
</template>
<style lang="css" scoped>
input:disabled {
cursor: wait;
}
</style>
-21
View File
@@ -1,21 +0,0 @@
import { GameResponseDTO } from './dto/gameresponse.dto';
import { PlayerResponseDTO } from './dto/playerresponse.dto';
export class GameCreateEvent extends GameResponseDTO {
type: 'create';
}
export class GameDeleteEvent {
type: 'delete';
constructor(public id?: number) {}
}
export class GameJoinEvent {
type: 'joingame';
gameId: number;
constructor(
public player: PlayerResponseDTO,
game?: Partial<GameResponseDTO>,
) {
this.gameId = game.id;
}
}
+4 -3
View File
@@ -2,18 +2,19 @@ const validPaths = ["/", "/rules", "/test", "/words"];
export default defineNuxtRouteMiddleware(async (to, from) => {
const config = useRuntimeConfig();
if (to.path.startsWith("/game/")) {
return;
}
if (to.path == "/login") {
console.log("hey")
const code = to.query.code;
if (!code) {
navigateTo(config.public.discordLoginEndpoint, { external: true });
} else {
navigateTo("/login");
}
return
return;
}
if (validPaths.includes(to.path)) {
return;
}
+28
View File
@@ -0,0 +1,28 @@
import { useLobbyStore } from "~/store/lobby.store";
import type { LobbyCreateEvent, LobbyDeleteEvent, LobbyJoinEvent, LobbyLeaveEvent, LobbyStartEvent } from "../events/lobby.events";
export const handlers = {
create: (event: LobbyCreateEvent) => {
const lobbyStore = useLobbyStore();
lobbyStore.games[event.game.id] = event.game
},
start: (event:LobbyStartEvent)=>{
const lobbyStore = useLobbyStore()
lobbyStore.games[event.game.id].started = true
},
delete:(event: LobbyDeleteEvent)=>{
const lobbyStore = useLobbyStore()
delete lobbyStore.games[event.id]
},
join:(event: LobbyJoinEvent)=>{
const lobbyStore = useLobbyStore()
lobbyStore.games[event.game.id].players.push(event.player)
},
leave:(event: LobbyLeaveEvent)=>{
const lobbyStore = useLobbyStore()
const playerIndex = lobbyStore.games[event.game.id].players.findIndex(p=>p.user.id == event.player.user.id)
lobbyStore.games[event.game.id].players.splice(playerIndex,1)
}
};
+1
View File
@@ -0,0 +1 @@
<template>Hello</template>
+29 -17
View File
@@ -1,26 +1,38 @@
<script setup lang="ts">
import { subscribe } from "~/netcode";
import { getGames } from "~/service/game.service";
import { useLobbyStore } from "~/store/lobby.store";
import { useUserStore } from "~/store/user.store";
let eventSource: EventSource;
const loading = ref(false);
const lobbyStore = useLobbyStore();
const userStore = useUserStore();
const games = await getGames();
onBeforeMount(() => {
eventSource = subscribe(`/games/subscribe`);
eventSource.addEventListener("message", async (message) => {
console.log(message);
});
});
onUnmounted(() => {
if (eventSource) {
eventSource.close();
async function createGame() {
if (loading.value) {
return;
}
loading.value = true;
await lobbyStore.createGame().catch((e) => {
if (e.data.statusCode == 400) {
console.error("Owned game limit reached");
} else {
console.log(e);
}
});
}
</script>
<template>
<div v-for="game in games" :key="game.id"></div>
<input type="button" value="Create Game" />
<LobbyGame v-for="game in lobbyStore.games" :key="game.id" :id="game.id" />
<input
type="button"
value="Create Game"
@click="createGame"
:disabled="!userStore.self"
/>
</template>
<style lang="css" scoped>
input:disabled {
cursor: wait;
}
</style>
+3 -15
View File
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { subscribe } from "~/netcode";
import { handlers } from "~/netcode/handlers/word.handler";
import { useWordStore } from "~/store/word.store";
const newWord = ref("");
@@ -11,7 +9,7 @@ await wordStore.fetchWordList();
const loading = ref(false);
const wordInput = ref<HTMLInputElement | null>(null);
async function createWord(e: Event) {
async function createWord() {
if (loading.value) {
return;
}
@@ -29,22 +27,12 @@ async function createWord(e: Event) {
}
}
let eventSource: EventSource;
onBeforeMount(() => {
eventSource = subscribe(`/words/subscribe`);
eventSource.addEventListener("message", async (message) => {
const data = JSON.parse(message.data);
const type = data.type as string;
if (type in handlers) {
handlers[type as keyof typeof handlers](data);
}
});
wordStore.subscribe();
});
onUnmounted(() => {
if (eventSource) {
eventSource.close();
}
wordStore.close();
});
</script>
-13
View File
@@ -1,13 +0,0 @@
import type { GameResponseDTO } from "~/netcode/events/dto/gameresponse.dto";
export async function getGames() {
const config = useRuntimeConfig();
const data = await useFetch<GameResponseDTO[]>(
config.public.endpoint + `/games`,
{
credentials: "include",
immediate: true,
}
);
return data.data
}
+37 -7
View File
@@ -1,25 +1,55 @@
import { defineStore } from "pinia";
import { subscribe } from "~/netcode";
import type { GameResponseDTO } from "~/netcode/events/dto/gameresponse.dto";
import { UserResponseDTO } from "~/netcode/events/dto/userresponse.dto";
import { handlers } from "~/netcode/handlers/lobby.handler";
export const useLobbyStore = defineStore("lobby", {
state: () => ({
games: {} as Record<string, GameResponseDTO,
games: {} as Record<number, GameResponseDTO>,
eventSource: null as null | EventSource,
}),
actions: {
async subscribe() {
this.eventSource = subscribe(`/games/subscribe`);
this.eventSource.addEventListener("message", async (message) => {
const data = JSON.parse(message.data);
const type = data.type as string;
if (type in handlers) {
handlers[type as keyof typeof handlers](data);
}
});
},
async close() {
this.eventSource?.close();
},
async fetchGames() {
const config = useRuntimeConfig();
const data = await useFetch<GameResponseDTO[]>(config.public.endpoint + `/users/` + id, {
const data = await useFetch<GameResponseDTO[]>(
config.public.endpoint + `/games/`,
{
credentials: "include",
immediate: true
})
immediate: true,
}
);
if (data.data.value) {
for (const game of data.data.value) {
this.games[game.id] = game
this.games[game.id] = game;
}
}
return data.data
return data.data;
},
async createGame() {
const config = useRuntimeConfig();
const data = await $fetch<GameResponseDTO>(
config.public.endpoint + "/games",
{
method: "POST",
credentials: "include",
}
);
this.games[data.id] = data;
return data;
},
},
});
+54 -24
View File
@@ -1,60 +1,90 @@
import { defineStore } from "pinia";
import { subscribe } from "~/netcode";
import { WordResponseDTO } from "~/netcode/events/dto/wordresponse.dto";
import { handlers } from "~/netcode/handlers/word.handler";
export const useWordStore = defineStore("word", {
state: () => ({
words: {} as Record<string, WordResponseDTO>,
eventSource: null as null | EventSource,
}),
actions: {
async subscribe() {
this.eventSource = subscribe(`/words/subscribe`);
this.eventSource.addEventListener("message", async (message) => {
const data = JSON.parse(message.data);
const type = data.type as string;
if (type in handlers) {
handlers[type as keyof typeof handlers](data);
}
});
},
async close() {
this.eventSource?.close();
},
async fetchWordList() {
const config = useRuntimeConfig();
const data = await useFetch<WordResponseDTO[]>(config.public.endpoint + `/words/all`, {
const data = await useFetch<WordResponseDTO[]>(
config.public.endpoint + `/words/all`,
{
credentials: "include",
})
}
);
if (data.data.value && !data.error.value) {
for (const word of data.data.value) {
this.words[word.id] = word
this.words[word.id] = word;
}
}
return data
return data;
},
async enableWord(word: WordResponseDTO) {
const config = useRuntimeConfig();
const data = await $fetch<WordResponseDTO>(config.public.endpoint + `/words/` + word.id + "/enable", {
const data = await $fetch<WordResponseDTO>(
config.public.endpoint + `/words/` + word.id + "/enable",
{
credentials: "include",
method: "PUT"
})
word.enabled = true
return data
method: "PUT",
}
);
word.enabled = true;
return data;
},
async disableWord(word: WordResponseDTO) {
const config = useRuntimeConfig();
const data = await $fetch<WordResponseDTO>(config.public.endpoint + `/words/` + word.id + "/disable", {
const data = await $fetch<WordResponseDTO>(
config.public.endpoint + `/words/` + word.id + "/disable",
{
credentials: "include",
method: "PUT"
})
word.enabled = false
return data
method: "PUT",
}
);
word.enabled = false;
return data;
},
async createWord(value: string) {
const config = useRuntimeConfig();
const data = await $fetch<WordResponseDTO>(config.public.endpoint + `/words/`, {
const data = await $fetch<WordResponseDTO>(
config.public.endpoint + `/words/`,
{
credentials: "include",
body: { value },
method: "POST"
})
this.words[data.id] = data
return data
method: "POST",
}
);
this.words[data.id] = data;
return data;
},
async deleteWord(word: WordResponseDTO) {
const config = useRuntimeConfig();
const data = await $fetch<WordResponseDTO>(config.public.endpoint + `/words/`+word.id, {
const data = await $fetch<WordResponseDTO>(
config.public.endpoint + `/words/` + word.id,
{
credentials: "include",
method: "DELETE"
})
delete this.words[word.id]
method: "DELETE",
}
);
delete this.words[word.id];
},
},
});