ENH - scale sky flappy difficulty

This commit is contained in:
smx.pusha
2026-08-06 09:45:10 +02:00
parent b273c6af55
commit 980ccdd075
5 changed files with 41 additions and 8 deletions
@@ -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,
}
@@ -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
}
@@ -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
+1 -1
View File
@@ -141,7 +141,7 @@ onBeforeUnmount(() => {
<div class="flappy-clouds" aria-hidden="true"><i v-for="cloud in 7" :key="cloud"></i></div>
<div v-for="obstacle in game.obstacles" :key="obstacle.id" class="flappy-obstacle" :style="obstacleStyle(obstacle)" aria-hidden="true">
<span class="flappy-obstacle__top" :style="{ height: `${obstacle.gapTop}%` }"></span>
<span class="flappy-obstacle__bottom" :style="{ top: `${obstacle.gapTop + 29}%` }"></span>
<span class="flappy-obstacle__bottom" :style="{ top: `${obstacle.gapTop + obstacle.gapHeight}%` }"></span>
</div>
<div :key="flapEffect" class="sky-glider sky-glider--player" :style="playerStyle" aria-hidden="true"><i></i><span></span><b></b></div>
<strong v-if="game.status === 'ready'" class="flappy-ready">{{ phone.t('Apps.skyFlappy.firstTap') }}</strong>
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sky Phone</title>
<script type="module" crossorigin src="./assets/sky-index-DNP-Eo5h.js"></script>
<script type="module" crossorigin src="./assets/sky-index-49wlApfN.js"></script>
<link rel="stylesheet" crossorigin href="./assets/sky-index-D9eWeoNZ.css">
</head>
<body>