From a9c7826ce451e151b091cbe7c0e9af07deea6e1c Mon Sep 17 00:00:00 2001
From: steel <mael.acier@ensiie.fr>
Date: Sun, 1 Sep 2024 21:53:52 +0200
Subject: [PATCH] wip: level selector

---
 src/lib/data.ts                        |  31 +++----
 src/lib/game.ts                        |  12 ++-
 src/lib/graphql/queries.ts             |   4 +-
 src/lib/utils.ts                       |   2 +
 src/routes/quiz/+page.server.ts        |  46 +++++++----
 src/routes/quiz/+page.svelte           |  31 +++----
 src/routes/quiz/new/+page.server.ts    | 109 +++++++++++++++++++++++++
 src/routes/quiz/new/+page.svelte       |  48 +++++++++++
 src/routes/quiz/new/images/baby.png    | Bin 0 -> 7183 bytes
 src/routes/quiz/new/images/index.ts    |   7 ++
 src/routes/quiz/new/images/student.png | Bin 0 -> 6793 bytes
 src/routes/quiz/new/schema.ts          |   7 ++
 12 files changed, 247 insertions(+), 50 deletions(-)
 create mode 100644 src/routes/quiz/new/+page.server.ts
 create mode 100644 src/routes/quiz/new/+page.svelte
 create mode 100644 src/routes/quiz/new/images/baby.png
 create mode 100644 src/routes/quiz/new/images/index.ts
 create mode 100644 src/routes/quiz/new/images/student.png
 create mode 100644 src/routes/quiz/new/schema.ts

diff --git a/src/lib/data.ts b/src/lib/data.ts
index cf26ee64..bf92eed3 100644
--- a/src/lib/data.ts
+++ b/src/lib/data.ts
@@ -5,7 +5,7 @@ import { PROMOTION_QUERY, USER_DETAILS_QUERY } from './graphql/queries';
 type UserId = string;
 type PromoCache = {
 	lastUpdateMs: number;
-	promo: Set<UserId>;
+	promotion: Set<UserId>;
 };
 
 const cache = new Map<number, PromoCache>();
@@ -26,8 +26,8 @@ async function cacheImages(users: UserId[]): Promise<void> {
 	}
 }
 
-async function fetchPromotion(promotion: number): Promise<PromoCache> {
-	const array = await Array.fromAsync(pageIterator(PROMOTION_QUERY, { promotion }));
+async function fetchPromotion(year: number): Promise<PromoCache> {
+	const array = await Array.fromAsync(pageIterator(PROMOTION_QUERY, { year }));
 	const users = array.map((node) => node.id);
 
 	// background task
@@ -35,30 +35,31 @@ async function fetchPromotion(promotion: number): Promise<PromoCache> {
 
 	return {
 		lastUpdateMs: new Date().getTime(),
-		promo: new Set(users)
+		promotion: new Set(users)
 	};
 }
 
-export async function getPromotion(promo: number): Promise<Set<UserId>> {
-	if (cache.has(promo)) {
-		const data = cache.get(promo)!;
-		if (new Date().getTime() - data.lastUpdateMs < CACHE_DURATION_MS) return data.promo;
+export async function getPromotion(year: number): Promise<Set<UserId>> {
+	if (cache.has(year)) {
+		const data = cache.get(year)!;
+		if (new Date().getTime() - data.lastUpdateMs < CACHE_DURATION_MS) return data.promotion;
 	}
-	const freshData = await fetchPromotion(promo);
-	cache.set(promo, freshData);
-	return freshData.promo;
+	console.log('year', year, 'not in cache, fetching');
+	const freshData = await fetchPromotion(year);
+	cache.set(year, freshData);
+	return freshData.promotion;
 }
 
 export async function* promotionIterator(
 	min: number,
 	max: number
 ): AsyncGenerator<UserId, void, undefined> {
-	for (let i = min; i <= max; i++) {
-		const promo = await getPromotion(i);
-		yield* promo;
+	for (let i = min; i < max; i++) {
+		const promotion = await getPromotion(i);
+		yield* promotion;
 	}
 }
 
-export function getPromotionRange(min: number, max: number): Promise<Set<UserId>> {
+export function getPromotionRange(min: number, max = min): Promise<Set<UserId>> {
 	return Array.fromAsync(promotionIterator(min, max)).then((array) => new Set(array));
 }
diff --git a/src/lib/game.ts b/src/lib/game.ts
index 5ad81ad8..dcd2697a 100644
--- a/src/lib/game.ts
+++ b/src/lib/game.ts
@@ -3,6 +3,7 @@ import { SecureCookie } from './cookie';
 import { z } from 'zod';
 
 export enum GameStage {
+	NEW,
 	PLAYING,
 	SOLUTION,
 	NEXT,
@@ -15,6 +16,9 @@ export const gameStateSchema = z.object({
 	score: z.number(),
 	options: z.array(z.string()),
 	stage: z.nativeEnum(GameStage),
+	year: z.number(),
+	maxYear: z.number().optional(),
+	label: z.string(),
 	solution: z.string()
 });
 
@@ -22,7 +26,7 @@ export type GameState = z.infer<typeof gameStateSchema>;
 
 export class Game {
 	state: GameState;
-	protected cookie_name = 'qui-est-ce';
+	protected cookie_name = 'quiestce-quiz';
 	protected cookie: SecureCookie<GameState>;
 
 	constructor(protected cookies: Cookies) {
@@ -32,12 +36,14 @@ export class Game {
 
 	protected defaultState(): GameState {
 		return {
-			stage: GameStage.NEXT,
+			stage: GameStage.NEW,
 			history: [],
 			step: 0,
 			score: 0,
+			year: 0,
 			options: [],
-			solution: ''
+			solution: '',
+			label: '?'
 		};
 	}
 
diff --git a/src/lib/graphql/queries.ts b/src/lib/graphql/queries.ts
index c9bb02d9..f3aa344b 100644
--- a/src/lib/graphql/queries.ts
+++ b/src/lib/graphql/queries.ts
@@ -1,11 +1,11 @@
 import { graphql } from '.';
 
 export const PROMOTION_QUERY = graphql(`
-	query GetPromotion($first: Int!, $after: String, $promotion: Int!) {
+	query GetYear($first: Int!, $after: String, $year: Int!) {
 		page: users(
 			first: $first
 			after: $after
-			filter: { promotion: { eq: [$promotion] }, nickname: { null: false }, photo: { null: false } }
+			filter: { year: { eq: [$year] }, nickname: { null: false }, photo: { null: false } }
 		) {
 			pageInfo {
 				endCursor
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 705385da..c92a0335 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -11,3 +11,5 @@ export function getRandomItems<T>(array: T[], count: number): T[] {
 	items.push(item);
 	return items;
 }
+
+export const sum = (a: number, b: number) => a + b;
diff --git a/src/routes/quiz/+page.server.ts b/src/routes/quiz/+page.server.ts
index ec9a25ef..943b4f0f 100644
--- a/src/routes/quiz/+page.server.ts
+++ b/src/routes/quiz/+page.server.ts
@@ -3,8 +3,8 @@ import { zod } from 'sveltekit-superforms/adapters';
 import { schema } from './schema';
 import { fail, redirect } from '@sveltejs/kit';
 import { getRandomItems } from '$lib/utils';
-import { GameStage, Game } from '$lib/game';
-import { getPromotion } from '$lib/data';
+import { GameStage, Game, type GameState } from '$lib/game';
+import { getPromotionRange } from '$lib/data';
 import { client } from '$lib/graphql';
 import { USER_DETAILS_QUERY } from '$lib/graphql/queries';
 import { handleGqlError } from '$lib/graphql/error';
@@ -14,21 +14,30 @@ type Option = {
 	label: string;
 };
 
-export async function load(event) {
-	const game = new Game(event.cookies);
-
-	if (game.state.stage === GameStage.GAME_OVER) redirect(303, '/quiz/game-over');
+async function next(state: GameState) {
+	const all = await getPromotionRange(state.year, state.maxYear);
+	const previous = new Set(state.history);
+	const available = all.difference(previous);
 
-	if (game.state.stage === GameStage.NEXT) {
-		const all = await getPromotion(2023);
+	state.options = getRandomItems(Array.from(available), 4);
+	state.solution = getRandomItems([...state.options], 1)[0];
+	state.stage = GameStage.PLAYING;
+	state.step++;
+}
 
-		const previous = new Set(game.state.history);
-		const available = all.difference(previous);
+export async function load(event) {
+	const game = new Game(event.cookies);
 
-		game.state.options = getRandomItems(Array.from(available), 4);
-		game.state.solution = getRandomItems([...game.state.options], 1)[0];
-		game.state.stage = GameStage.PLAYING;
-		game.state.step++;
+	switch (game.state.stage) {
+		case GameStage.NEW:
+			redirect(303, '/quiz/new');
+			break;
+		case GameStage.GAME_OVER:
+			redirect(303, '/quiz/game-over');
+			break;
+		case GameStage.NEXT:
+			await next(game.state);
+			break;
 	}
 
 	const details = await client
@@ -46,7 +55,14 @@ export async function load(event) {
 	const photo = users.find((node) => node.id === game.state.solution)?.photo;
 
 	const form = await superValidate(zod(schema));
-	return { form, options, score: game.state.score, step: game.state.step, photo };
+	return {
+		form,
+		options,
+		score: game.state.score,
+		step: game.state.step,
+		photo,
+		label: game.state.label
+	};
 }
 
 export const actions = {
diff --git a/src/routes/quiz/+page.svelte b/src/routes/quiz/+page.svelte
index 5ef86b11..4533b4c7 100644
--- a/src/routes/quiz/+page.svelte
+++ b/src/routes/quiz/+page.svelte
@@ -5,29 +5,31 @@
 	export let data;
 	export let form;
 
-	const form2 = superForm(data.form, {
+	const sForm = superForm(data.form, {
 		// On récupère les valeurs après affichage des résultats
 		resetForm: false
 	});
 
-	const { form: formData, enhance } = form2;
+	const { form: formData, enhance } = sForm;
 
 	const questionAmount = 10;
 </script>
 
 <div class="relative mx-auto my-12 w-full grow">
 	<section class="relative m-auto flex flex-col items-center">
-		<h1 class="absolute -top-8 text-xl">Qui est-ce ?</h1>
+		<h1 class="absolute -top-8 text-xl">Qui est-ce ? {data.label}</h1>
 		<div class="relative">
-			<span class="absolute -left-14 flex flex-col items-end">
-				<span class="text-9xl leading-[0.4] text-random-300"> {data.step} </span>
-				<span> / {questionAmount} </span>
-			</span>
-			<span
-				class="absolute right-[calc(100%_+_0.5rem)] top-24 whitespace-nowrap pt-4 text-xl text-zinc-800 before:absolute before:right-0 before:top-0 before:h-1.5 before:w-10 before:bg-zinc-800"
-			>
-				{data.score}
-				<small>pts</small>
+			<span class="absolute right-[calc(100%_+_0.5rem)] text-right">
+				<span class="flex flex-col items-end">
+					<span class="text-9xl leading-[0.4] text-random-300"> {data.step} </span>
+					<span> / {questionAmount} </span>
+				</span>
+				<span class="inline-block h-1.5 w-10 bg-zinc-800"></span>
+				<br />
+				<span class="top-24 pt-4 text-xl text-zinc-800">
+					{data.score}
+					<small>pts</small>
+				</span>
 			</span>
 			<div
 				class="relative z-10 flex h-64 w-64 items-center justify-center overflow-hidden rounded-2xl border-6 border-solid border-zinc-800 bg-white before:absolute before:-z-10 before:h-32 before:w-32 before:rounded-full before:bg-slate-300 before:opacity-100 before:transition-[0.65s] before:duration-[ease-in-out] after:absolute after:-z-10 after:h-32 after:w-32 after:scale-0 after:rounded-full after:border-solid after:border-slate-300 after:transition-[0.4s] after:duration-[ease-in-out]"
@@ -35,7 +37,7 @@
 				<img src={data.photo?.url} alt="Chargement..." class="w-auto" />
 			</div>
 			<form method="post" use:enhance>
-				<Fieldset form={form2} name="choice">
+				<Fieldset form={sForm} name="choice">
 					<div
 						class="relative -top-6 z-20 mx-auto my-0 flex w-44 flex-col items-center space-y-0.5 rounded-2xl bg-zinc-800 px-5 py-0"
 					>
@@ -53,8 +55,7 @@
 									/>
 									<Label
 										tabindex={0}
-										data-solution={form?.solution || null}
-										class="relative block cursor-pointer select-none overflow-hidden rounded-2xl border-6 border-solid border-zinc-800 bg-slate-300 p-2 text-center text-lg transition-[0.45s] before:absolute before:left-2/4 before:top-2/4 before:-z-10 before:h-[200px] before:w-[200px] before:-translate-x-2/4 before:-translate-y-2/4 before:scale-0 before:rounded-full before:bg-indigo-300 before:transition-[0.2s] before:duration-[ease-in-out] focus:[outline:none] active:before:-translate-x-2/4 active:before:-translate-y-2/4 active:before:scale-100 peer-enabled:hover:translate-y-[-3px] peer-enabled:hover:bg-slate-400 peer-enabled:focus:border-indigo-500 peer-checked:peer-enabled:bg-indigo-300 data-[solution]:cursor-default data-[solution]:text-[#94acbd] peer-checked:data-[solution]:bg-red-400 peer-checked:data-[solution]:text-inherit peer-data-[valid]:!bg-lime-400 peer-data-[valid]:text-inherit"
+										class="relative block cursor-pointer select-none overflow-hidden rounded-2xl border-6 border-solid border-zinc-800 bg-slate-300 p-2 text-center text-lg transition-[0.45s] before:absolute before:left-2/4 before:top-2/4 before:-z-10 before:h-[200px] before:w-[200px] before:-translate-x-2/4 before:-translate-y-2/4 before:scale-0 before:rounded-full before:bg-indigo-300 before:transition-[0.2s] before:duration-[ease-in-out] focus:[outline:none] active:before:-translate-x-2/4 active:before:-translate-y-2/4 active:before:scale-100 peer-enabled:hover:translate-y-[-3px] peer-enabled:hover:bg-slate-400 peer-enabled:focus:border-indigo-500 peer-checked:peer-enabled:bg-indigo-300 peer-disabled:cursor-default peer-disabled:text-[#94acbd] peer-checked:peer-disabled:bg-red-400 peer-checked:peer-disabled:text-inherit peer-data-[valid]:!bg-lime-400 peer-data-[valid]:text-inherit"
 									>
 										{option.label}
 									</Label>
diff --git a/src/routes/quiz/new/+page.server.ts b/src/routes/quiz/new/+page.server.ts
new file mode 100644
index 00000000..f839073e
--- /dev/null
+++ b/src/routes/quiz/new/+page.server.ts
@@ -0,0 +1,109 @@
+import { superValidate } from 'sveltekit-superforms';
+import { zod } from 'sveltekit-superforms/adapters';
+import { schema } from './schema';
+import { fail, redirect } from '@sveltejs/kit';
+import { Game, GameStage } from '$lib/game';
+import { getPromotion } from '$lib/data';
+import images from './images';
+import { sum } from '$lib/utils';
+
+type BaseLevel = {
+	year: number;
+	maxYear?: number;
+	name: string;
+	image: string;
+};
+
+type Level = BaseLevel & {
+	size: number;
+};
+
+const VIIEUX_YEAR = 20;
+
+export async function load(event) {
+	const game = new Game(event.cookies);
+
+	if (game.state.stage !== GameStage.NEW) redirect(303, '/quiz');
+
+	const baseLevels: BaseLevel[] = [
+		{
+			year: 1,
+			name: '1A',
+			image: images.baby
+		},
+		{
+			year: 2,
+			name: '2A',
+			image: images.baby
+		},
+		{
+			year: 3,
+			name: '3A',
+			image: images.student
+		},
+		{
+			year: 1,
+			maxYear: 3,
+			name: '1-3A',
+			image: images.student
+		},
+		{
+			year: 4,
+			name: '4A',
+			image: images.student
+		},
+		{
+			year: 5,
+			name: '5A',
+			image: images.student
+		},
+		{
+			year: 4,
+			maxYear: VIIEUX_YEAR,
+			name: 'Viieux',
+			image: images.student
+		},
+		{
+			year: 1,
+			maxYear: VIIEUX_YEAR,
+			name: 'IIEns',
+			image: images.student
+		}
+	];
+
+	const promoSizes = await Promise.all(
+		Array.from(new Array(VIIEUX_YEAR), (_, i) => getPromotion(i + 1).then((promo) => promo.size))
+	);
+
+	const levels: Level[] = baseLevels.map((level) => {
+		const end = (level.maxYear ?? level.year) + 1;
+		return {
+			size: promoSizes.slice(level.year, end).reduce(sum, 0),
+			...level
+		};
+	});
+
+	const form = await superValidate(zod(schema));
+	return { form, levels };
+}
+
+export const actions = {
+	async default(event) {
+		const form = await superValidate(event, zod(schema));
+
+		if (!form.valid) {
+			return fail(400, { form });
+		}
+
+		const state = new Game(event.cookies);
+
+		state.state.year = form.data.year;
+		state.state.label = form.data.label;
+		state.state.maxYear = form.data.maxYear;
+		state.state.stage = GameStage.NEXT;
+
+		state.save();
+
+		redirect(303, '/quiz');
+	}
+};
diff --git a/src/routes/quiz/new/+page.svelte b/src/routes/quiz/new/+page.svelte
new file mode 100644
index 00000000..d7bc2c15
--- /dev/null
+++ b/src/routes/quiz/new/+page.svelte
@@ -0,0 +1,48 @@
+<script lang="ts">
+	import { superForm } from 'sveltekit-superforms';
+	import { Control, Field } from 'formsnap';
+
+	export let data;
+
+	const form = superForm(data.form, {
+		// On récupère les valeurs après affichage des résultats
+		resetForm: false
+	});
+	const { enhance } = form;
+</script>
+
+<div class="m-16">
+	<div class="flex flex-wrap items-center justify-center gap-8 text-gray-700">
+		{#each data.levels as level}
+			<form method="post" use:enhance>
+				<Field {form} name="year">
+					<Control let:attrs>
+						<input type="hidden" {...attrs} value={level.year} />
+					</Control>
+				</Field>
+				<Field {form} name="maxYear">
+					<Control let:attrs>
+						<input type="hidden" {...attrs} value={level.maxYear} />
+					</Control>
+				</Field>
+				<Field {form} name="label">
+					<Control let:attrs>
+						<input type="hidden" {...attrs} value={level.name} />
+					</Control>
+				</Field>
+				<button
+					type="submit"
+					class="col-span-1 flex h-72 w-72 flex-col divide-y divide-gray-200 rounded-2xl border-6 border-solid border-zinc-800 bg-slate-100 text-center shadow transition-[0.45s] hover:translate-y-[-3px] hover:bg-slate-300 focus:border-indigo-500"
+				>
+					<div class="flex flex-1 flex-col p-8">
+						<img class="mx-auto h-32 w-32 flex-shrink-0" src={level.image} alt="" />
+						<h1 class="mt-4 text-6xl font-medium">{level.name}</h1>
+						<dl class="mt-1 flex flex-grow flex-col justify-between">
+							<dd class="text-sm font-light text-gray-500">{level.size} personnes</dd>
+						</dl>
+					</div>
+				</button>
+			</form>
+		{/each}
+	</div>
+</div>
diff --git a/src/routes/quiz/new/images/baby.png b/src/routes/quiz/new/images/baby.png
new file mode 100644
index 0000000000000000000000000000000000000000..60d09632825cea2366b7f0f19f7f2802ec29042c
GIT binary patch
literal 7183
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
I=akR{08NdI=Kufz

literal 0
HcmV?d00001

diff --git a/src/routes/quiz/new/images/index.ts b/src/routes/quiz/new/images/index.ts
new file mode 100644
index 00000000..a947a639
--- /dev/null
+++ b/src/routes/quiz/new/images/index.ts
@@ -0,0 +1,7 @@
+import baby from './baby.png';
+import student from './student.png';
+
+export default {
+	baby,
+	student
+};
diff --git a/src/routes/quiz/new/images/student.png b/src/routes/quiz/new/images/student.png
new file mode 100644
index 0000000000000000000000000000000000000000..4df8222c20902f02cdb712ab556b047e1fa2ad68
GIT binary patch
literal 6793
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
AX#fBK

literal 0
HcmV?d00001

diff --git a/src/routes/quiz/new/schema.ts b/src/routes/quiz/new/schema.ts
new file mode 100644
index 00000000..d561d4e5
--- /dev/null
+++ b/src/routes/quiz/new/schema.ts
@@ -0,0 +1,7 @@
+import { z } from 'zod';
+
+export const schema = z.object({
+	year: z.number(),
+	maxYear: z.number().optional(),
+	label: z.string()
+});
-- 
GitLab