diff --git a/src/app/icare/icarus-game.tsx b/src/app/icare/icarus-game.tsx index bfcea5d..6e3b388 100644 --- a/src/app/icare/icarus-game.tsx +++ b/src/app/icare/icarus-game.tsx @@ -6,13 +6,17 @@ import { COLUMN_GAP_MARGIN, COLUMN_SPACING, COLUMN_WIDTH, + DIFFICULTY_RAMP_SPAN, + DIFFICULTY_START_SCORE, FIXED_DT_MS, FLAP_IMPULSE, FORWARD_SPEED, + GAP_MIN_MULTIPLIER, GROUND_Y, ICARUS_RADIUS, ICARUS_X, MAX_FRAME_DT_MS, + SPEED_MAX_MULTIPLIER, TILT_MAX_RADIANS, VIEWPORT_HEIGHT, VIEWPORT_WIDTH, @@ -29,9 +33,16 @@ type Status = "idle" | "flying" | "dead"; const CONFETTI_COLORS = ["#E7C560", "#C9A227", "#F4ECD8", "#A5342A", "#5E6B3B", "#2E6E7E", "#B08D57"]; -function randomGapCenter(): number { - const minCenter = COLUMN_GAP / 2 + COLUMN_GAP_MARGIN; - const maxCenter = GROUND_Y - COLUMN_GAP / 2 - COLUMN_GAP_MARGIN; +// 0 avant DIFFICULTY_START_SCORE, monte jusqu'à 1 sur DIFFICULTY_RAMP_SPAN +// points, puis reste à 1 (palier, pour ne pas devenir injouable). +function difficultyProgress(score: number): number { + if (score <= DIFFICULTY_START_SCORE) return 0; + return Math.min(1, (score - DIFFICULTY_START_SCORE) / DIFFICULTY_RAMP_SPAN); +} + +function randomGapCenter(gap: number): number { + const minCenter = gap / 2 + COLUMN_GAP_MARGIN; + const maxCenter = GROUND_Y - gap / 2 - COLUMN_GAP_MARGIN; return minCenter + Math.random() * Math.max(0, maxCenter - minCenter); } @@ -112,8 +123,8 @@ function drawColumnShaft( } function drawColumn(ctx: CanvasRenderingContext2D, column: Column) { - const gapTop = column.gapCenterY - COLUMN_GAP / 2; - const gapBottom = column.gapCenterY + COLUMN_GAP / 2; + const gapTop = column.gapCenterY - column.gap / 2; + const gapBottom = column.gapCenterY + column.gap / 2; drawColumnShaft(ctx, column.x, 0, gapTop, true); drawColumnShaft(ctx, column.x, gapBottom, GROUND_Y - gapBottom, false); } @@ -203,8 +214,14 @@ export function IcarusGame({ onFinish }: { onFinish: (score: number) => void }) } icarusRef.current = stepIcarus(icarusRef.current, FIXED_DT_MS); + // Difficulté progressive : vitesse en hausse et écart resserré au + // fil du score, jusqu'à un palier (voir difficultyProgress). + const progress = difficultyProgress(scoreRef.current); + const currentSpeed = FORWARD_SPEED * (1 + (SPEED_MAX_MULTIPLIER - 1) * progress); + const currentGap = COLUMN_GAP * (1 - (1 - GAP_MIN_MULTIPLIER) * progress); + for (const column of columnsRef.current) { - column.x -= FORWARD_SPEED * (FIXED_DT_MS / 1000); + column.x -= currentSpeed * (FIXED_DT_MS / 1000); if (!column.scored && column.x + COLUMN_WIDTH < ICARUS_X - ICARUS_RADIUS) { column.scored = true; scoreRef.current += 1; @@ -217,7 +234,8 @@ export function IcarusGame({ onFinish }: { onFinish: (score: number) => void }) if (!last || last.x < VIEWPORT_WIDTH - COLUMN_SPACING) { columnsRef.current.push({ x: last ? last.x + COLUMN_SPACING : VIEWPORT_WIDTH, - gapCenterY: randomGapCenter(), + gapCenterY: randomGapCenter(currentGap), + gap: currentGap, scored: false, }); } diff --git a/src/lib/icarus/constants.ts b/src/lib/icarus/constants.ts index e1a105a..e7f52a3 100644 --- a/src/lib/icarus/constants.ts +++ b/src/lib/icarus/constants.ts @@ -27,12 +27,22 @@ export const WING_IDLE_PERIOD_MS = 480; export const WING_FLAP_MAX_RADIANS = (75 * Math.PI) / 180; export const WING_FLAP_ANIM_MS = 260; -export const FORWARD_SPEED = 130; // unités/seconde, vitesse de défilement des colonnes +export const FORWARD_SPEED = 130; // unités/seconde, vitesse de défilement des colonnes de base export const COLUMN_WIDTH = 46; -export const COLUMN_GAP = 128; +export const COLUMN_GAP = 128; // écart de base entre les colonnes d'une paire export const COLUMN_SPACING = 210; export const COLUMN_GAP_MARGIN = 50; // distance mini entre le centre du trou et le sol/plafond +// Difficulté progressive : au-delà de DIFFICULTY_START_SCORE, la vitesse +// augmente et l'écart entre les colonnes se resserre progressivement, +// jusqu'à un palier atteint à DIFFICULTY_START_SCORE + DIFFICULTY_RAMP_SPAN +// (au-delà, la difficulté n'augmente plus — évite un jeu qui devient +// littéralement impossible sur une très longue partie). +export const DIFFICULTY_START_SCORE = 10; +export const DIFFICULTY_RAMP_SPAN = 40; +export const SPEED_MAX_MULTIPLIER = 1.6; +export const GAP_MIN_MULTIPLIER = 0.75; + export const FIXED_DT_MS = 1000 / 60; export const MAX_FRAME_DT_MS = 250; // évite la "spirale de la mort" après un onglet en arrière-plan diff --git a/src/lib/icarus/physics.ts b/src/lib/icarus/physics.ts index 72a3c41..5bf7d92 100644 --- a/src/lib/icarus/physics.ts +++ b/src/lib/icarus/physics.ts @@ -1,5 +1,4 @@ import { - COLUMN_GAP, COLUMN_WIDTH, GRAVITY, GROUND_Y, @@ -31,7 +30,7 @@ export function collidesWithColumn(icarusY: number, column: Column): boolean { const overlapsX = columnRight > ICARUS_X - ICARUS_RADIUS && columnLeft < ICARUS_X + ICARUS_RADIUS; if (!overlapsX) return false; - const gapTop = column.gapCenterY - COLUMN_GAP / 2; - const gapBottom = column.gapCenterY + COLUMN_GAP / 2; + const gapTop = column.gapCenterY - column.gap / 2; + const gapBottom = column.gapCenterY + column.gap / 2; return icarusY - ICARUS_RADIUS < gapTop || icarusY + ICARUS_RADIUS > gapBottom; } diff --git a/src/lib/icarus/types.ts b/src/lib/icarus/types.ts index 54abdc2..7253eec 100644 --- a/src/lib/icarus/types.ts +++ b/src/lib/icarus/types.ts @@ -1,3 +1,6 @@ -export type Column = { x: number; gapCenterY: number; scored: boolean }; +// gap est stocké par colonne (pas une constante globale) : la difficulté +// progressive resserre l'écart des colonnes créées après un certain score, +// sans changer la forme des colonnes déjà en place. +export type Column = { x: number; gapCenterY: number; gap: number; scored: boolean }; export type IcarusState = { y: number; velocityY: number };