feat : guess word

This commit is contained in:
2024-12-30 20:15:02 +01:00
parent 08daf733d1
commit 9eaf7009c6
12 changed files with 304 additions and 54 deletions
+5 -1
View File
@@ -7,8 +7,12 @@ const userStore = useUserStore();
const lobbyStore = useLobbyStore(); const lobbyStore = useLobbyStore();
const currentGames = computed(() => { const currentGames = computed(() => {
const self = userStore.self;
if (!self) {
return [];
}
const games = Object.values(lobbyStore.games).filter((g) => const games = Object.values(lobbyStore.games).filter((g) =>
g.players.some((p) => p.user.id == userStore.self?.id) g.players.some((p) => p.userId == self.id)
); );
return games; return games;
}); });
+6 -1
View File
@@ -17,7 +17,12 @@ const game = lobbyStore.games[props.id];
<template> <template>
{{ game.id }} {{ game.id }}
{{ game.players.map((p) => p.user.username) }} <User
v-for="player in game.players"
:key="player.id"
:id="player.userId"
></User>
{{ game.started }}
{{ game }} {{ game }}
<NuxtLink :to="'/game/' + id" <NuxtLink :to="'/game/' + id"
+9 -2
View File
@@ -14,6 +14,8 @@ const userStore = useUserStore();
const word = wordStore.words[props.id]; const word = wordStore.words[props.id];
const disabled = computed(() => word.loading || !userStore.self);
async function changeWordState() { async function changeWordState() {
if (word.loading) { if (word.loading) {
return; return;
@@ -38,6 +40,11 @@ async function deleteWord() {
word.loading = false; word.loading = false;
} }
} }
onMounted(() => {
setInterval(() => {
console.log(disabled.value);
}, 1000);
});
</script> </script>
<template> <template>
@@ -46,11 +53,11 @@ async function deleteWord() {
<input <input
type="checkbox" type="checkbox"
:checked="word.enabled" :checked="word.enabled"
:disabled="word.loading || !userStore.self" :disabled="disabled"
@click.prevent="changeWordState" @click.prevent="changeWordState"
/> />
<input <input
:disabled="word.loading || !userStore.self" :disabled="disabled"
type="button" type="button"
@click="deleteWord" @click="deleteWord"
value="Delete" value="Delete"
+7
View File
@@ -1,10 +1,17 @@
import { useGameStore } from "~/store/game.store";
const validPaths = ["/", "/rules", "/test", "/words"]; const validPaths = ["/", "/rules", "/test", "/words"];
export default defineNuxtRouteMiddleware(async (to, from) => { export default defineNuxtRouteMiddleware(async (to, from) => {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
if (to.path.startsWith("/game/")) { if (to.path.startsWith("/game/")) {
const gameId = Number(to.params.id);
const gameStore = useGameStore();
return; return;
} }
if (to.path == "/login") { if (to.path == "/login") {
const code = to.query.code; const code = to.query.code;
if (!code) { if (!code) {
+58 -1
View File
@@ -1,3 +1,60 @@
import { useGameStore } from "~/store/game.store";
import type {
GameDeleteEvent,
GameEndTurnEvent,
GameJoinEvent,
GameMessageDeleteEvent,
GameMessageEvent,
GameStartEvent,
} from "../events/game.events";
import type { GameResponseDTO } from "../events/dto/gameresponse.dto";
import { useUserStore } from "~/store/user.store";
export const handlers = { export const handlers = {
join: (event: any) => {}, joingame: (event: GameJoinEvent) => {
const gameStore = useGameStore();
gameStore.game.players.push(event.player);
},
start: (event: GameStartEvent) => {
const gameStore = useGameStore();
gameStore.game.started = true;
gameStore.game.currentPlayer = event.currentPlayer;
gameStore.currentWord = null;
},
delete: (event: GameDeleteEvent) => {
const gameStore = useGameStore();
gameStore.game = {} as GameResponseDTO;
alert("Game deleted");
navigateTo("/");
},
message: (event: GameMessageEvent) => {
const gameStore = useGameStore();
gameStore.game.messages.push(event.message);
if (!event.message.authorId) {
const player = gameStore.game.players.find(
(p) => p.userId == event.message.value
);
if (player) {
player.score++;
}
}
},
deleteMessage: (event: GameMessageDeleteEvent) => {
const gameStore = useGameStore();
const index = gameStore.game.messages.findIndex(
(m) => m.id == event.messageId
);
gameStore.game.messages.splice(index, 1);
},
endTurn: (event: GameEndTurnEvent) => {
const gameStore = useGameStore();
const userStore = useUserStore();
gameStore.game.players.forEach((p) => (p.hasGuessed = false));
gameStore.game.currentConcepts = [];
gameStore.game.messages = [];
gameStore.game.currentPlayer = event.currentPlayer;
if (userStore.self && userStore.self.id == event.currentPlayer.userId) {
void gameStore.getCurrentWord(gameStore.game.id);
}
},
}; };
+1 -1
View File
@@ -27,7 +27,7 @@ export const handlers = {
leavegame: (event: LobbyLeaveEvent) => { leavegame: (event: LobbyLeaveEvent) => {
const lobbyStore = useLobbyStore(); const lobbyStore = useLobbyStore();
const playerIndex = lobbyStore.games[event.game.id].players.findIndex( const playerIndex = lobbyStore.games[event.game.id].players.findIndex(
(p) => p.user.id == event.player.user.id (p) => p.userId == event.player.userId
); );
lobbyStore.games[event.game.id].players.splice(playerIndex, 1); lobbyStore.games[event.game.id].players.splice(playerIndex, 1);
}, },
+115 -2
View File
@@ -1,10 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import type { GameResponseDTO } from "~/netcode/events/dto/gameresponse.dto";
import { useGameStore } from "~/store/game.store"; import { useGameStore } from "~/store/game.store";
import { useUserStore } from "~/store/user.store";
const route = useRoute(); const route = useRoute();
const gameId = Number(route.params.id); const gameId = Number(route.params.id);
const userStore = useUserStore();
const gameStore = useGameStore(); const gameStore = useGameStore();
const game = await gameStore.fetchGame(gameId); let game = (await gameStore.fetchGame(
gameId
)) as globalThis.Ref<GameResponseDTO>;
if (!game.value) {
navigateTo("/");
}
onBeforeMount(() => { onBeforeMount(() => {
gameStore.subscribe(gameId); gameStore.subscribe(gameId);
@@ -13,6 +21,111 @@ onBeforeMount(() => {
onUnmounted(() => { onUnmounted(() => {
gameStore.close(); gameStore.close();
}); });
function startGame() {
gameStore.startGame(gameId);
}
function joinGame() {
gameStore.joinGame(gameId);
}
const isCurrentTurn = computed(
() =>
userStore.self && userStore.self?.id == game.value?.currentPlayer?.userId
);
watch(
() => userStore.self,
() => {
if (isCurrentTurn) {
if (!userStore.self) {
return;
}
if (!game.value?.currentPlayer) {
return;
}
if (game.value.currentPlayer.userId == userStore.self.id) {
void gameStore.getCurrentWord(gameId);
}
}
},
{ immediate: true }
);
const wordInput = ref<HTMLInputElement>();
const loading = ref(false);
const messageText = ref("");
async function createWord() {
if (messageText.value == "") {
return;
}
if (loading.value) {
return;
}
loading.value = true;
try {
await gameStore.sendMessage(gameId, messageText.value);
messageText.value = "";
} finally {
nextTick(() => {
loading.value = false;
nextTick(() => {
wordInput.value?.focus();
});
});
}
}
const selfPlayer = computed(() =>
game.value.players.find((p) => p.userId == userStore.self?.id)
);
</script> </script>
<template>{{ game }}</template> <template>
<div v-if="isCurrentTurn">Current word : {{ gameStore.currentWord }}</div>
Players in game :
<div v-for="player in game.players" :key="player.id">
<User :id="player.userId"></User> {{ player.score }}
</div>
<template v-if="game.currentPlayer">
Current player :
<User :id="game.currentPlayer?.userId"></User>
</template>
{{ game }}
<input
type="button"
v-if="userStore.self && !game.started && game.ownerId == userStore.self.id"
value="start"
@click="startGame"
/>
<input
type="button"
v-if="userStore.self && !game.started && !selfPlayer"
value="join"
@click="joinGame"
/>
<div>
Messages
<span v-for="message in game.messages" :key="message.id">
<template v-if="message.authorId">
{{ message.value }}<User :id="message.authorId"></User>
</template>
<template v-else>
<User :id="message.value"></User> Guessed the word !
</template>
</span>
<input
ref="wordInput"
type="text"
:disabled="loading || selfPlayer?.hasGuessed"
@keypress.enter="createWord"
v-model="messageText"
/>
<button :disabled="loading || selfPlayer?.hasGuessed" @click="createWord">
Submit
</button>
</div>
</template>
+50 -2
View File
@@ -5,7 +5,8 @@ import { handlers } from "~/netcode/handlers/game.handler";
export const useGameStore = defineStore("game", { export const useGameStore = defineStore("game", {
state: () => ({ state: () => ({
game: null as null | GameResponseDTO, game: {} as GameResponseDTO,
currentWord: null as null | string,
eventSource: null as null | EventSource, eventSource: null as null | EventSource,
}), }),
actions: { actions: {
@@ -14,6 +15,7 @@ export const useGameStore = defineStore("game", {
this.eventSource.addEventListener("message", async (message) => { this.eventSource.addEventListener("message", async (message) => {
const data = JSON.parse(message.data); const data = JSON.parse(message.data);
const type = data.type as string; const type = data.type as string;
console.log(type);
if (type in handlers) { if (type in handlers) {
handlers[type as keyof typeof handlers](data); handlers[type as keyof typeof handlers](data);
} }
@@ -34,8 +36,54 @@ export const useGameStore = defineStore("game", {
if (data.data.value) { if (data.data.value) {
this.game = data.data.value; this.game = data.data.value;
} }
return data.data; return data.data;
}, },
async joinGame(id: number) {
const config = useRuntimeConfig();
const data = await $fetch<GameResponseDTO>(
config.public.endpoint + `/games/` + id + "/join",
{
method: "POST",
credentials: "include",
}
);
},
async startGame(id: number) {
const config = useRuntimeConfig();
const data = await $fetch<GameResponseDTO>(
config.public.endpoint + `/games/` + id + "/start",
{
method: "POST",
credentials: "include",
}
);
},
async getCurrentWord(id: number) {
const config = useRuntimeConfig();
const data = await $fetch<string>(
config.public.endpoint + `/games/` + id + "/currentWord",
{
credentials: "include",
}
);
if (data) {
this.currentWord = data;
}
return data;
},
async sendMessage(id: number, message: string) {
const config = useRuntimeConfig();
const data = await $fetch<string>(
config.public.endpoint + `/games/` + id + "/message",
{
method: "POST",
body: {
value: message,
},
credentials: "include",
}
);
},
}, },
}); });
+2 -2
View File
@@ -48,8 +48,8 @@ export const useLobbyStore = defineStore("lobby", {
credentials: "include", credentials: "include",
} }
); );
this.games[data.id] = data; // this.games[data.id] = data;
return data; // return data;
}, },
}, },
}); });
+42 -33
View File
@@ -2,39 +2,48 @@ import { defineStore } from "pinia";
import { UserResponseDTO } from "~/netcode/events/dto/userresponse.dto"; import { UserResponseDTO } from "~/netcode/events/dto/userresponse.dto";
export const useUserStore = defineStore("user", { export const useUserStore = defineStore("user", {
state: () => ({ state: () => ({
users: {} as Record<string, UserResponseDTO>, users: {} as Record<string, UserResponseDTO>,
self: null as UserResponseDTO | null, self: null as UserResponseDTO | null,
has_contacted: false has_contacted: false,
}), }),
actions: { actions: {
async fetchUser(id: string) { async fetchUser(id: string) {
if (id in this.users) { if (id in this.users) {
return this.users[id] return this.users[id];
} }
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const data = await useFetch<UserResponseDTO>(config.public.endpoint + `/users/` + id, { const data = await useFetch<UserResponseDTO>(
credentials: "include", config.public.endpoint + `/users/` + id,
immediate: true {
}) credentials: "include",
if (data.data.value) { immediate: true,
this.users[data.data.value.id] = data.data.value
}
return data.data.value
},
async whoami() {
const config = useRuntimeConfig();
const req = await useFetch<UserResponseDTO>(config.public.endpoint + "/whoami", {
credentials: "include",
server: false,
immediate: true,
onResponse: (data) => {
this.self = data.response._data
this.has_contacted = true
},
lazy: true
})
return req;
} }
);
if (data.data.value) {
this.users[data.data.value.id] = data.data.value;
}
return data.data.value;
}, },
async whoami() {
const config = useRuntimeConfig();
const req = await useFetch<UserResponseDTO>(
config.public.endpoint + "/whoami",
{
credentials: "include",
server: false,
immediate: true,
onResponse: (data) => {
if (data.response.ok) {
this.self = data.response._data;
}
this.has_contacted = true;
},
lazy: true,
}
);
return req;
},
},
}); });
+7 -7
View File
@@ -46,8 +46,8 @@ export const useWordStore = defineStore("word", {
method: "PUT", method: "PUT",
} }
); );
word.enabled = true; // word.enabled = true;
return data; // return data;
}, },
async disableWord(word: WordResponseDTO) { async disableWord(word: WordResponseDTO) {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
@@ -58,8 +58,8 @@ export const useWordStore = defineStore("word", {
method: "PUT", method: "PUT",
} }
); );
word.enabled = false; // word.enabled = false;
return data; // return data;
}, },
async createWord(value: string) { async createWord(value: string) {
@@ -72,8 +72,8 @@ export const useWordStore = defineStore("word", {
method: "POST", method: "POST",
} }
); );
this.words[data.id] = data; // this.words[data.id] = data;
return data; // return data;
}, },
async deleteWord(word: WordResponseDTO) { async deleteWord(word: WordResponseDTO) {
const config = useRuntimeConfig(); const config = useRuntimeConfig();
@@ -84,7 +84,7 @@ export const useWordStore = defineStore("word", {
method: "DELETE", method: "DELETE",
} }
); );
delete this.words[word.id]; // delete this.words[word.id];
}, },
}, },
}); });