From 980ccdd075127dcfe727615a7b56c6b01aec94fb Mon Sep 17 00:00:00 2001 From: "smx.pusha" <139338836+smxpusha@users.noreply.github.com> Date: Thu, 6 Aug 2026 09:45:10 +0200 Subject: [PATCH] ENH - scale sky flappy difficulty --- .../features/games/sky-flappy/engine.test.ts | 18 +++++++++++-- .../src/features/games/sky-flappy/engine.ts | 26 ++++++++++++++++--- .../src/features/games/sky-flappy/types.ts | 1 + frontend/src/views/apps/SkyFlappyApp.vue | 2 +- sky_phone/source/html/index.html | 2 +- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/frontend/src/features/games/sky-flappy/engine.test.ts b/frontend/src/features/games/sky-flappy/engine.test.ts index 3df016c..5c88578 100644 --- a/frontend/src/features/games/sky-flappy/engine.test.ts +++ b/frontend/src/features/games/sky-flappy/engine.test.ts @@ -3,9 +3,12 @@ import { describe, expect, it } from 'vitest' import { createSkyFlappyGame, FLAPPY_GAP_HEIGHT, + FLAPPY_MAX_SPEED, FLAPPY_MAX_GAP_TOP, + FLAPPY_MIN_GAP_HEIGHT, FLAPPY_MIN_GAP_TOP, flapSkyGlider, + getSkyFlappyDifficulty, stepSkyFlappy, } from './engine' @@ -36,10 +39,21 @@ describe('sky flappy engine', () => { expect(high.obstacles[0].gapTop + FLAPPY_GAP_HEIGHT).toBeLessThan(100) }) + it('increases speed and narrows the gap as the score rises', () => { + const start = getSkyFlappyDifficulty(0) + const advanced = getSkyFlappyDifficulty(20) + const maximum = getSkyFlappyDifficulty(100) + + expect(advanced.speed).toBeGreaterThan(start.speed) + expect(advanced.gapHeight).toBeLessThan(start.gapHeight) + expect(maximum.speed).toBe(FLAPPY_MAX_SPEED) + expect(maximum.gapHeight).toBe(FLAPPY_MIN_GAP_HEIGHT) + }) + it('scores an obstacle only once', () => { const state = { ...flapSkyGlider(createSkyFlappyGame()), - obstacles: [{ gapTop: 30, id: 1, scored: false, x: 7 }], + obstacles: [{ gapHeight: FLAPPY_GAP_HEIGHT, gapTop: 30, id: 1, scored: false, x: 7 }], } const scored = stepSkyFlappy(state, 0.01, () => 0.5) expect(scored.score).toBe(1) @@ -49,7 +63,7 @@ describe('sky flappy engine', () => { it('detects collision using the player edges', () => { const state = { ...flapSkyGlider(createSkyFlappyGame()), - obstacles: [{ gapTop: 40, id: 1, scored: false, x: 22 }], + obstacles: [{ gapHeight: FLAPPY_GAP_HEIGHT, gapTop: 40, id: 1, scored: false, x: 22 }], playerY: 41, playerVelocity: 0, } diff --git a/frontend/src/features/games/sky-flappy/engine.ts b/frontend/src/features/games/sky-flappy/engine.ts index 1413e0d..4dae434 100644 --- a/frontend/src/features/games/sky-flappy/engine.ts +++ b/frontend/src/features/games/sky-flappy/engine.ts @@ -6,8 +6,21 @@ export const FLAPPY_GRAVITY = 82 export const FLAPPY_IMPULSE = -34 export const FLAPPY_OBSTACLE_WIDTH = 14 export const FLAPPY_GAP_HEIGHT = 29 +export const FLAPPY_MIN_GAP_HEIGHT = 22 export const FLAPPY_MIN_GAP_TOP = 15 export const FLAPPY_MAX_GAP_TOP = 56 +export const FLAPPY_BASE_SPEED = 20.5 +export const FLAPPY_MAX_SPEED = 40 + +export function getSkyFlappyDifficulty(score: number): { + gapHeight: number + speed: number +} { + return { + gapHeight: Math.max(FLAPPY_MIN_GAP_HEIGHT, FLAPPY_GAP_HEIGHT - score * 0.35), + speed: Math.min(FLAPPY_MAX_SPEED, FLAPPY_BASE_SPEED + score * 0.65), + } +} export function createSkyFlappyGame(): SkyFlappyGameState { return { @@ -27,9 +40,11 @@ export function flapSkyGlider(state: SkyFlappyGameState): SkyFlappyGameState { function createObstacle( id: number, + gapHeight: number, random: () => number, ): SkyFlappyObstacle { return { + gapHeight, gapTop: FLAPPY_MIN_GAP_TOP + Math.max(0, Math.min(1, random())) * @@ -52,7 +67,7 @@ function collidesWithObstacle( return ( playerY - FLAPPY_PLAYER_RADIUS < obstacle.gapTop || - playerY + FLAPPY_PLAYER_RADIUS > obstacle.gapTop + FLAPPY_GAP_HEIGHT + playerY + FLAPPY_PLAYER_RADIUS > obstacle.gapTop + obstacle.gapHeight ) } @@ -63,19 +78,22 @@ export function stepSkyFlappy( ): SkyFlappyGameState { if (state.status !== 'playing' || elapsedSeconds <= 0) return state - const speed = Math.min(33, 20.5 + state.score * 0.38) + const difficulty = getSkyFlappyDifficulty(state.score) const playerVelocity = state.playerVelocity + FLAPPY_GRAVITY * elapsedSeconds const playerY = state.playerY + playerVelocity * elapsedSeconds let nextObstacleId = state.nextObstacleId let obstacles = state.obstacles .map((obstacle) => ({ ...obstacle, - x: obstacle.x - speed * elapsedSeconds, + x: obstacle.x - difficulty.speed * elapsedSeconds, })) .filter((obstacle) => obstacle.x + FLAPPY_OBSTACLE_WIDTH > -2) if (obstacles.length === 0 || obstacles[obstacles.length - 1].x < 61) { - obstacles = [...obstacles, createObstacle(nextObstacleId, random)] + obstacles = [ + ...obstacles, + createObstacle(nextObstacleId, difficulty.gapHeight, random), + ] nextObstacleId += 1 } diff --git a/frontend/src/features/games/sky-flappy/types.ts b/frontend/src/features/games/sky-flappy/types.ts index fa42cb4..f4fd71d 100644 --- a/frontend/src/features/games/sky-flappy/types.ts +++ b/frontend/src/features/games/sky-flappy/types.ts @@ -2,6 +2,7 @@ export type SkyFlappyStatus = 'over' | 'paused' | 'playing' | 'ready' export type SkyFlappyDesign = 'dawn' | 'neon' | 'storm' export type SkyFlappyObstacle = { + gapHeight: number gapTop: number id: number scored: boolean diff --git a/frontend/src/views/apps/SkyFlappyApp.vue b/frontend/src/views/apps/SkyFlappyApp.vue index c727e9d..befce7d 100644 --- a/frontend/src/views/apps/SkyFlappyApp.vue +++ b/frontend/src/views/apps/SkyFlappyApp.vue @@ -141,7 +141,7 @@ onBeforeUnmount(() => { {{ phone.t('Apps.skyFlappy.firstTap') }} diff --git a/sky_phone/source/html/index.html b/sky_phone/source/html/index.html index be1dc48..ba95d5e 100644 --- a/sky_phone/source/html/index.html +++ b/sky_phone/source/html/index.html @@ -4,7 +4,7 @@ Sky Phone - +