Implemented style, better game flow, learning mode

This commit is contained in:
Florian Sylvain
2023-03-08 17:43:19 +01:00
parent 3a4f723c85
commit b597acbc35
11 changed files with 401 additions and 119 deletions
+1 -1
View File
@@ -175,7 +175,7 @@ async function getPainting(id?: number, name?: string): Promise<Painting | null>
return await prisma.painting.findUnique({ return await prisma.painting.findUnique({
where: id ? { id } : { name }, where: id ? { id } : { name },
include: { PaintingAuthor: { select: { author: true } } }, include: { PaintingAuthor: { select: { author: true } }, Musuem: true },
}) })
} }
+12
View File
@@ -0,0 +1,12 @@
<svg viewBox="0 0 137 287" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_2)">
<rect width="137" height="287" fill="white"/>
<path d="M-85.7607 146.769L90.991 -51.6342L166.405 15.55L-10.3467 213.953L-85.7607 146.769Z" fill="#4B68D7"/>
<path d="M-115.288 123.92L47.3721 -80.1942L152.099 3.26319L-10.5611 207.378L-115.288 123.92Z" fill="#15245A"/>
</g>
<defs>
<clipPath id="clip0_1_2">
<rect width="137" height="287" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 485 B

+7 -1
View File
@@ -3,7 +3,13 @@ import { RouterView } from "vue-router"
</script> </script>
<template> <template>
<div class="container">
<RouterView /> <RouterView />
</div>
</template> </template>
<style scoped></style> <style scoped>
.container {
padding: 16px;
}
</style>
+46
View File
@@ -1,3 +1,49 @@
:root {
--color-primary: #15245A;
--color-secondary: #4B68D7;
--color-tertiary: #E6E6E6;
--color-success: #0AA607;
--color-fail: #A52853;
--color-text: #1e1e1e;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
}
body {
margin: 0;
background-image: url(/background.svg);
background-size: cover;
background-repeat: no-repeat;
}
button {
border: none;
border-radius: 0;
outline: none;
padding: 6px 12px;
background-color: var(--color-tertiary);
cursor: pointer;
}
.button-neutral {
color: var(--color-text);
border: solid 2px var(--color-text);
}
.button-success {
color: var(--color-success);
border: solid 2px var(--color-success);
}
.button-fail {
color: var(--color-fail);
border: solid 2px var(--color-fail);
} }
View File
+16 -31
View File
@@ -1,23 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, Ref, ref } from "vue" import { onMounted, Ref, ref } from "vue"
import { AuthorData, fetchRandomPainting, Painting } from "../utils/apiFetch"
interface AuthorData {
author: {
id: number
fullname: string
created_at: string
updated_at: string
}
}
interface Painting {
id: number
name: string
image_url: string
link: string
musuemId: number
PaintingAuthor: AuthorData[]
}
const emits = defineEmits(["win", "lose"]) const emits = defineEmits(["win", "lose"])
const props = defineProps<{ disabled: boolean }>() const props = defineProps<{ disabled: boolean }>()
@@ -25,17 +8,8 @@ const props = defineProps<{ disabled: boolean }>()
const paintings: Ref<Painting[]> = ref([]) const paintings: Ref<Painting[]> = ref([])
const winningPainting: Ref<Painting> = ref({} as Painting) const winningPainting: Ref<Painting> = ref({} as Painting)
function fetchRandomPainting(): Promise<{ paintings: Painting[] }> {
return new Promise((resolve, reject) => {
fetch(`${__APP_ENV__.API_ADDRESS}/paintings/random/4`)
.then((response) => response.json())
.then((data) => resolve(data))
.catch((error) => reject(error))
})
}
async function initPaintings(): Promise<void> { async function initPaintings(): Promise<void> {
const fetchedData = await fetchRandomPainting() const fetchedData = await fetchRandomPainting(4)
paintings.value = fetchedData.paintings paintings.value = fetchedData.paintings
} }
@@ -44,10 +18,20 @@ function setWinningPainting(): void {
winningPainting.value = paintings.value[randomIndex] winningPainting.value = paintings.value[randomIndex]
} }
function authorClickHandler(auhtorId: number): void { function setElementClassById(id: string, cssClass: string): void {
if (auhtorId === winningPainting.value.PaintingAuthor[0].author.id) { document.getElementById(id)?.classList.add(cssClass)
}
function authorClickHandler(authorData: AuthorData): void {
if (authorData.author.fullname === winningPainting.value.PaintingAuthor[0].author.fullname) {
setElementClassById(authorData.author.fullname, "button-success")
emits("win") emits("win")
} else { } else {
setElementClassById(authorData.author.fullname, "button-fail")
setElementClassById(
winningPainting.value.PaintingAuthor[0].author.fullname,
"button-neutral"
)
emits("lose") emits("lose")
} }
} }
@@ -68,7 +52,8 @@ onMounted(async () => {
<div class="choices"> <div class="choices">
<button <button
v-for="painting of paintings" v-for="painting of paintings"
@click="() => authorClickHandler(painting.PaintingAuthor[0].author.id)" @click="() => authorClickHandler({ author: painting.PaintingAuthor[0].author })"
:id="painting.PaintingAuthor[0].author.fullname"
type="button" type="button"
:disabled="disabled" :disabled="disabled"
> >
+11 -1
View File
@@ -1,4 +1,4 @@
import { createRouter, createWebHistory } from "vue-router" import { createRouter, createWebHistory, RouteComponent } from "vue-router"
import HomeView from "../views/HomeView.vue" import HomeView from "../views/HomeView.vue"
const router = createRouter({ const router = createRouter({
@@ -9,6 +9,16 @@ const router = createRouter({
name: "home", name: "home",
component: HomeView, component: HomeView,
}, },
{
path: "/game/normal",
name: "gameNormal",
component: () => import("../views/NormalGame.vue"),
},
{
path: "/game/learn",
name: "gameLearn",
component: () => import("../views/LearnGame.vue"),
},
], ],
}) })
+32
View File
@@ -0,0 +1,32 @@
export interface AuthorData {
author: {
id: number
fullname: string
created_at: string
updated_at: string
}
}
export interface Painting {
id: number
name: string
image_url: string
link: string
musuemId: number
PaintingAuthor: AuthorData[]
Musuem: {
id: number
name: string
created_at: string
updated_at: string
}
}
export function fetchRandomPainting(limit: number): Promise<{ paintings: Painting[] }> {
return new Promise((resolve, reject) => {
fetch(`${__APP_ENV__.API_ADDRESS}/paintings/random/${limit}`)
.then((response) => response.json())
.then((data) => resolve(data))
.catch((error) => reject(error))
})
}
+15 -84
View File
@@ -1,68 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, Ref, ref } from "vue" import { useRouter } from "vue-router"
import Quizz from "../components/Quizz.vue"
const gameMessage = ref("choisissez une réponse") const router = useRouter()
const gameProgression = ref(1)
const gameProgressionMax = 10
const gameScore = ref(0)
const gameShouldGoNext = ref(false)
function gameHandler(callback: () => void): void {
if (gameShouldGoNext.value) return
gameShouldGoNext.value = true
callback()
}
function winHandler(): void {
gameMessage.value = "Bonne réponse !"
gameScore.value += 1
}
function loseHandler(): void {
gameMessage.value = "Mauvaise réponse"
}
function nextGame(): void {
if (!gameShouldGoNext.value) return
gameShouldGoNext.value = false
gameMessage.value = "choisissez une réponse"
gameProgression.value += 1
}
function resetGame(): void {
gameMessage.value = "choisissez une réponse"
gameProgression.value = 1
gameScore.value = 0
}
</script> </script>
<template> <template>
<div class="container"> <div class="container">
<template v-if="gameProgression < gameProgressionMax"> <h1>Quizz peintres / peintures Paris Musées</h1>
<p>Quiz ({{ gameProgression }}/{{ gameProgressionMax }})</p> <button @click="() => router.push('/game/learn')">Mode apprentissage</button>
<h2>Qui a peint cette oeuvre ?</h2> <button @click="() => router.push('/game/normal')">Mode normal</button>
<Quizz
@win="() => gameHandler(winHandler)"
@lose="() => gameHandler(loseHandler)"
:key="gameProgression"
:disabled="gameShouldGoNext"
></Quizz>
<div class="quizz-footer">
<p>{{ gameMessage }}</p>
<button type="button" @click="nextGame" :disabled="!gameShouldGoNext">
suivant
</button>
</div>
</template>
<div class="game-over" v-else>
<p>Vous avez {{ gameScore }}/{{ gameProgressionMax }} bonnes réponses !</p>
<button type="button" @click="resetGame">rejouer</button>
</div>
</div> </div>
</template> </template>
@@ -70,32 +16,17 @@ function resetGame(): void {
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px;
width: 100%;
max-width: 350px;
}
.container > p,
.container > h2 {
margin: 0;
}
.container > p {
text-align: center;
}
.quizz-footer {
display: flex;
justify-content: space-between;
align-items: center; align-items: center;
}
.game-over {
display: flex;
flex-direction: column;
gap: 16px;
justify-content: center; justify-content: center;
align-items: center; gap: 16px;
}
.container h1 {
color: #fff;
}
.container button {
width: 100%;
max-width: 200px;
} }
</style> </style>
+126
View File
@@ -0,0 +1,126 @@
<script setup lang="ts">
import { onMounted, Ref, ref } from "vue"
import { useRouter } from "vue-router"
import { fetchRandomPainting, Painting } from "../utils/apiFetch"
const router = useRouter()
const gameProgression = ref(1)
const gameProgressionMax = 9
const paintings: Ref<Painting[]> = ref([])
async function initGame(): Promise<void> {
paintings.value = (await fetchRandomPainting(gameProgressionMax)).paintings
console.log(paintings.value)
}
function nextGame(): void {
gameProgression.value += 1
}
async function resetGame(): Promise<void> {
await initGame()
gameProgression.value = 1
}
onMounted(async () => {
await initGame()
})
</script>
<template>
<div class="container">
<template v-if="gameProgression <= gameProgressionMax">
<p>Apprentissage ({{ gameProgression }}/{{ gameProgressionMax }})</p>
<h2>Qui a peint cette oeuvre ?</h2>
<img :src="paintings[gameProgression - 1]?.image_url" alt="" />
<div class="description">
<p>Cette oeuvre a été peinte par</p>
<p class="author">
{{ paintings[gameProgression - 1]?.PaintingAuthor[0].author.fullname }}
</p>
<p>{{ paintings[gameProgression - 1]?.name }}</p>
<p class="museum">{{ paintings[gameProgression - 1]?.Musuem.name }}</p>
</div>
<div class="quizz-footer">
<button type="button" @click="nextGame">suivant</button>
</div>
</template>
<div class="game-over" v-else>
<p>Vous avez terminé une série de {{ gameProgressionMax }} peintres / peintures !</p>
<button type="button" @click="resetGame">recommencer</button>
<button type="button" @click="() => router.push('/')">accueil</button>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
gap: 16px;
margin: auto;
width: 100%;
max-width: 350px;
}
.container > p,
.container > h2 {
margin: 0;
color: #fff;
}
.container > p {
align-self: center;
padding: 8px;
border-bottom: solid 1px var(--color-secondary);
}
.quizz-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.game-over {
display: flex;
flex-direction: column;
gap: 16px;
justify-content: center;
align-items: center;
color: #fff;
}
.description {
display: flex;
flex-direction: column;
gap: 8px;
justify-content: center;
align-items: center;
padding: 16px;
backdrop-filter: blur(10px);
text-align: center;
}
.description p {
margin: 0;
}
.museum {
color: var(--color-secondary);
}
.author {
padding: 6px 8px;
background-color: var(--color-tertiary);
}
</style>
+134
View File
@@ -0,0 +1,134 @@
<script setup lang="ts">
import { ref } from "vue"
import { useRouter } from "vue-router"
import Quizz from "../components/Quizz.vue"
const router = useRouter()
const gameMessageContainer = ref<HTMLElement>()
const gameMessage = ref("choisissez une réponse")
const gameProgression = ref(1)
const gameProgressionMax = 10
const gameScore = ref(0)
const gameShouldGoNext = ref(false)
function gameHandler(callback: () => void): void {
if (gameShouldGoNext.value) return
gameShouldGoNext.value = true
callback()
}
function winHandler(): void {
gameMessage.value = "Bonne réponse !"
gameScore.value += 1
gameMessageContainer.value?.classList.add("game-message-win")
}
function loseHandler(): void {
gameMessage.value = "Mauvaise réponse"
gameMessageContainer.value?.classList.add("game-message-lose")
}
function resetGameMessageClasses(): void {
gameMessageContainer.value?.classList.remove("game-message-win")
gameMessageContainer.value?.classList.remove("game-message-lose")
}
function nextGame(): void {
if (!gameShouldGoNext.value) return
gameShouldGoNext.value = false
resetGameMessageClasses()
gameMessage.value = "choisissez une réponse"
gameProgression.value += 1
}
function resetGame(): void {
resetGameMessageClasses()
gameMessage.value = "choisissez une réponse"
gameProgression.value = 1
gameScore.value = 0
}
</script>
<template>
<div class="container">
<template v-if="gameProgression <= gameProgressionMax">
<p>Quiz ({{ gameProgression }}/{{ gameProgressionMax }})</p>
<h2>Qui a peint cette oeuvre ?</h2>
<Quizz
@win="() => gameHandler(winHandler)"
@lose="() => gameHandler(loseHandler)"
:key="gameProgression"
:disabled="gameShouldGoNext"
></Quizz>
<div class="quizz-footer">
<p ref="gameMessageContainer" class="game-message">{{ gameMessage }}</p>
<button type="button" @click="nextGame" :disabled="!gameShouldGoNext">
suivant
</button>
</div>
</template>
<div class="game-over" v-else>
<p>Vous avez {{ gameScore }}/{{ gameProgressionMax }} bonnes réponses !</p>
<button type="button" @click="resetGame">rejouer</button>
<button type="button" @click="() => router.push('/')">accueil</button>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
flex-direction: column;
gap: 16px;
margin: auto;
width: 100%;
max-width: 350px;
}
.container > p,
.container > h2 {
margin: 0;
color: #fff;
}
.container > p {
align-self: center;
padding: 8px;
border-bottom: solid 1px var(--color-secondary);
}
.quizz-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.game-message {
color: #848484;
}
.game-over {
display: flex;
flex-direction: column;
gap: 16px;
justify-content: center;
align-items: center;
color: #fff;
}
.game-message-win {
color: var(--color-success);
}
.game-message-lose {
color: var(--color-fail);
}
</style>