mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-30 17:59:02 +00:00
140 lines
3.6 KiB
TypeScript
140 lines
3.6 KiB
TypeScript
import type {
|
|
TowerActiveBlock,
|
|
TowerBlock,
|
|
TowerGameState,
|
|
TowerPlacement,
|
|
} from './types'
|
|
|
|
export const TOWER_FIELD_WIDTH = 100
|
|
export const TOWER_BASE_WIDTH = 68
|
|
export const TOWER_PERFECT_TOLERANCE = 1.35
|
|
export const TOWER_START_SPEED = 23
|
|
export const TOWER_MAX_SPEED = 48
|
|
|
|
function speedForLevel(level: number): number {
|
|
return Math.min(
|
|
TOWER_MAX_SPEED,
|
|
TOWER_START_SPEED + Math.max(0, level - 1) * 1.65,
|
|
)
|
|
}
|
|
|
|
function createActiveBlock(
|
|
level: number,
|
|
width: number,
|
|
direction: -1 | 1,
|
|
): TowerActiveBlock {
|
|
return {
|
|
colorIndex: level % 6,
|
|
direction,
|
|
id: level,
|
|
level,
|
|
speed: speedForLevel(level),
|
|
width,
|
|
x: direction === 1 ? 0 : TOWER_FIELD_WIDTH - width,
|
|
}
|
|
}
|
|
|
|
export function createTowerGame(): TowerGameState {
|
|
const base: TowerBlock = {
|
|
colorIndex: 0,
|
|
id: 0,
|
|
level: 0,
|
|
width: TOWER_BASE_WIDTH,
|
|
x: (TOWER_FIELD_WIDTH - TOWER_BASE_WIDTH) / 2,
|
|
}
|
|
|
|
return {
|
|
active: createActiveBlock(1, TOWER_BASE_WIDTH, 1),
|
|
blocks: [base],
|
|
perfects: 0,
|
|
score: 0,
|
|
status: 'playing',
|
|
}
|
|
}
|
|
|
|
export function advanceTowerBlock(
|
|
state: TowerGameState,
|
|
elapsedSeconds: number,
|
|
): TowerGameState {
|
|
if (state.status !== 'playing' || !state.active || elapsedSeconds <= 0) {
|
|
return state
|
|
}
|
|
|
|
const active = { ...state.active }
|
|
let nextX = active.x + active.direction * active.speed * elapsedSeconds
|
|
const maximumX = TOWER_FIELD_WIDTH - active.width
|
|
|
|
while (nextX < 0 || nextX > maximumX) {
|
|
if (nextX > maximumX) {
|
|
nextX = maximumX - (nextX - maximumX)
|
|
active.direction = -1
|
|
} else {
|
|
nextX = -nextX
|
|
active.direction = 1
|
|
}
|
|
}
|
|
|
|
active.x = nextX
|
|
return { ...state, active }
|
|
}
|
|
|
|
export function placeTowerBlock(state: TowerGameState): TowerPlacement {
|
|
if (state.status !== 'playing' || !state.active) {
|
|
return { cutSide: null, cutWidth: 0, outcome: 'missed', state }
|
|
}
|
|
|
|
const active = state.active
|
|
const support = state.blocks[state.blocks.length - 1]
|
|
const overlapStart = Math.max(active.x, support.x)
|
|
const overlapEnd = Math.min(active.x + active.width, support.x + support.width)
|
|
const overlapWidth = overlapEnd - overlapStart
|
|
|
|
if (overlapWidth <= 0) {
|
|
return {
|
|
cutSide: active.x < support.x ? 'left' : 'right',
|
|
cutWidth: active.width,
|
|
outcome: 'missed',
|
|
state: { ...state, active: null, status: 'over' },
|
|
}
|
|
}
|
|
|
|
const offset = active.x - support.x
|
|
const isPerfect = Math.abs(offset) <= TOWER_PERFECT_TOLERANCE
|
|
const placedWidth = isPerfect
|
|
? Math.min(TOWER_BASE_WIDTH, support.width + 0.85)
|
|
: overlapWidth
|
|
const placedX = isPerfect
|
|
? support.x - (placedWidth - support.width) / 2
|
|
: overlapStart
|
|
const block: TowerBlock = {
|
|
colorIndex: active.colorIndex,
|
|
id: active.id,
|
|
level: active.level,
|
|
width: placedWidth,
|
|
x: placedX,
|
|
}
|
|
const nextLevel = active.level + 1
|
|
const direction: -1 | 1 = nextLevel % 2 === 0 ? -1 : 1
|
|
|
|
return {
|
|
cutSide: isPerfect ? null : offset < 0 ? 'left' : 'right',
|
|
cutWidth: isPerfect ? 0 : active.width - overlapWidth,
|
|
outcome: isPerfect ? 'perfect' : 'placed',
|
|
state: {
|
|
active: createActiveBlock(nextLevel, placedWidth, direction),
|
|
blocks: [...state.blocks, block],
|
|
perfects: state.perfects + (isPerfect ? 1 : 0),
|
|
score: state.score + 1 + (isPerfect ? 3 : 0),
|
|
status: 'playing',
|
|
},
|
|
}
|
|
}
|
|
|
|
export function pauseTowerGame(state: TowerGameState): TowerGameState {
|
|
return state.status === 'playing' ? { ...state, status: 'paused' } : state
|
|
}
|
|
|
|
export function resumeTowerGame(state: TowerGameState): TowerGameState {
|
|
return state.status === 'paused' ? { ...state, status: 'playing' } : state
|
|
}
|