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 currentGames = computed(() => {
const self = userStore.self;
if (!self) {
return [];
}
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;
});
+6 -1
View File
@@ -17,7 +17,12 @@ const game = lobbyStore.games[props.id];
<template>
{{ 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 }}
<NuxtLink :to="'/game/' + id"
+9 -2
View File
@@ -14,6 +14,8 @@ const userStore = useUserStore();
const word = wordStore.words[props.id];
const disabled = computed(() => word.loading || !userStore.self);
async function changeWordState() {
if (word.loading) {
return;
@@ -38,6 +40,11 @@ async function deleteWord() {
word.loading = false;
}
}
onMounted(() => {
setInterval(() => {
console.log(disabled.value);
}, 1000);
});
</script>
<template>
@@ -46,11 +53,11 @@ async function deleteWord() {
<input
type="checkbox"
:checked="word.enabled"
:disabled="word.loading || !userStore.self"
:disabled="disabled"
@click.prevent="changeWordState"
/>
<input
:disabled="word.loading || !userStore.self"
:disabled="disabled"
type="button"
@click="deleteWord"
value="Delete"
+7
View File
@@ -1,10 +1,17 @@
import { useGameStore } from "~/store/game.store";
const validPaths = ["/", "/rules", "/test", "/words"];
export default defineNuxtRouteMiddleware(async (to, from) => {
const config = useRuntimeConfig();
if (to.path.startsWith("/game/")) {
const gameId = Number(to.params.id);
const gameStore = useGameStore();
return;
}
if (to.path == "/login") {
const code = to.query.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 = {
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) => {
const lobbyStore = useLobbyStore();
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);
},
+115 -2
View File
@@ -1,10 +1,18 @@
<script setup lang="ts">
import type { GameResponseDTO } from "~/netcode/events/dto/gameresponse.dto";
import { useGameStore } from "~/store/game.store";
import { useUserStore } from "~/store/user.store";
const route = useRoute();
const gameId = Number(route.params.id);
const userStore = useUserStore();
const gameStore = useGameStore();
const game = await gameStore.fetchGame(gameId);
let game = (await gameStore.fetchGame(
gameId
)) as globalThis.Ref<GameResponseDTO>;
if (!game.value) {
navigateTo("/");
}
onBeforeMount(() => {
gameStore.subscribe(gameId);
@@ -13,6 +21,111 @@ onBeforeMount(() => {
onUnmounted(() => {
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>
<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", {
state: () => ({
game: null as null | GameResponseDTO,
game: {} as GameResponseDTO,
currentWord: null as null | string,
eventSource: null as null | EventSource,
}),
actions: {
@@ -14,6 +15,7 @@ export const useGameStore = defineStore("game", {
this.eventSource.addEventListener("message", async (message) => {
const data = JSON.parse(message.data);
const type = data.type as string;
console.log(type);
if (type in handlers) {
handlers[type as keyof typeof handlers](data);
}
@@ -34,8 +36,54 @@ export const useGameStore = defineStore("game", {
if (data.data.value) {
this.game = data.data.value;
}
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",
}
);
this.games[data.id] = data;
return data;
// this.games[data.id] = data;
// return data;
},
},
});
+42 -33
View File
@@ -2,39 +2,48 @@ import { defineStore } from "pinia";
import { UserResponseDTO } from "~/netcode/events/dto/userresponse.dto";
export const useUserStore = defineStore("user", {
state: () => ({
users: {} as Record<string, UserResponseDTO>,
self: null as UserResponseDTO | null,
has_contacted: false
}),
actions: {
async fetchUser(id: string) {
if (id in this.users) {
return this.users[id]
}
const config = useRuntimeConfig();
const data = await useFetch<UserResponseDTO>(config.public.endpoint + `/users/` + id, {
credentials: "include",
immediate: true
})
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) => {
this.self = data.response._data
this.has_contacted = true
},
lazy: true
})
return req;
state: () => ({
users: {} as Record<string, UserResponseDTO>,
self: null as UserResponseDTO | null,
has_contacted: false,
}),
actions: {
async fetchUser(id: string) {
if (id in this.users) {
return this.users[id];
}
const config = useRuntimeConfig();
const data = await useFetch<UserResponseDTO>(
config.public.endpoint + `/users/` + id,
{
credentials: "include",
immediate: true,
}
);
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",
}
);
word.enabled = true;
return data;
// word.enabled = true;
// return data;
},
async disableWord(word: WordResponseDTO) {
const config = useRuntimeConfig();
@@ -58,8 +58,8 @@ export const useWordStore = defineStore("word", {
method: "PUT",
}
);
word.enabled = false;
return data;
// word.enabled = false;
// return data;
},
async createWord(value: string) {
@@ -72,8 +72,8 @@ export const useWordStore = defineStore("word", {
method: "POST",
}
);
this.words[data.id] = data;
return data;
// this.words[data.id] = data;
// return data;
},
async deleteWord(word: WordResponseDTO) {
const config = useRuntimeConfig();
@@ -84,7 +84,7 @@ export const useWordStore = defineStore("word", {
method: "DELETE",
}
);
delete this.words[word.id];
// delete this.words[word.id];
},
},
});