From e8a79c2a8184563172e43eb73f87226c74d5b39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 25 Jul 2026 19:24:12 +0300 Subject: [PATCH] kill counter fixed. 8x8 anf 8x16 fonts added. terrain slides are aminated. --- assets/Web437_IBM_BIOS.woff | Bin 0 -> 8832 bytes assets/Web437_IBM_VGA_8x16.woff | Bin 0 -> 9788 bytes index.html | 2 ++ src/constants.js | 2 +- src/gfx.js | 6 +++++- src/main.js | 26 ++++++++++++++++------ src/panel.js | 4 ++-- src/projectiles.js | 23 +++++++++++--------- src/terrain.js | 37 ++++++++++++++++++++++++++++++++ 9 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 assets/Web437_IBM_BIOS.woff create mode 100644 assets/Web437_IBM_VGA_8x16.woff diff --git a/assets/Web437_IBM_BIOS.woff b/assets/Web437_IBM_BIOS.woff new file mode 100644 index 0000000000000000000000000000000000000000..9daecb32fd2a72690825701ec583edaf73f3a246 GIT binary patch literal 8832 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 B1Cjs$ literal 0 HcmV?d00001 diff --git a/assets/Web437_IBM_VGA_8x16.woff b/assets/Web437_IBM_VGA_8x16.woff new file mode 100644 index 0000000000000000000000000000000000000000..1f1508d1d8276d85aeb98cd2753e79dd46584204 GIT binary patch literal 9788 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 98bd407..60f3573 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,8 @@ canvas {image-rendering: pixelated; margin: auto; position:absolute; left:0; right:0; top:0; bottom:0} @font-face { font-family: 'text'; src:url(assets/font.ttf) } + @font-face { font-family: 'ibm-vga'; src:url(assets/Web437_IBM_VGA_8x16.woff) } + @font-face { font-family: 'ibm-bios'; src:url(assets/Web437_IBM_BIOS.woff) } diff --git a/src/constants.js b/src/constants.js index b6d3672..5fe5015 100644 --- a/src/constants.js +++ b/src/constants.js @@ -121,7 +121,7 @@ export const PLAYER_STARTING_TOOLS = [ export const PANEL_WIDTH = 400; export const PANEL_HEIGHT = 300; -export const PANEL_FONT_SIZE = 10; +export const PANEL_FONT_SIZE = 8; export const PANEL_TITLE_FONT_SIZE = 16; export const PANEL_BG_COLOR = '#666'; export const PANEL_BORDER_COLOR = '#333'; diff --git a/src/gfx.js b/src/gfx.js index 806a208..271d14d 100644 --- a/src/gfx.js +++ b/src/gfx.js @@ -163,8 +163,12 @@ export function drawParagraph(ctx, text, x, y, w, h, color) { } export function loop(fn) { + let lastTime = performance.now(); (function looped() { - fn(); + const now = performance.now(); + const dt = Math.min((now - lastTime) / 1000, 0.1); + lastTime = now; + fn(dt); requestAnimationFrame(looped); })(); } diff --git a/src/main.js b/src/main.js index 9aa1fc8..5132ff9 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,7 @@ import {clamp, deg2rad, distance, parable, random, randomInt, vec, wrap} from '. import {PROJECTILE_TYPES} from './projectiles.js?v=5'; import {generateSky} from './sky.js?v=5'; import {playTickSound} from './sound.js?v=5'; -import {clipTerrain, closestLand, collapseTerrain, generateTerrain, isTerrain, landHeight} from './terrain.js?v=5'; +import {clipTerrain, closestLand, collapseTerrain, generateTerrain, isTerrain, landHeight, startCollapseTerrain, collapseTerrainStep} from './terrain.js?v=5'; import {sample, shuffle} from './utils.js?v=5'; import {EXPLOSION_TYPES} from './weapons.js?v=5'; import {drawPanelBg, drawPanelTitle, drawPanelText, drawPanelDivider, drawPanelMenu, getPanelBounds} from './panel.js?v=5'; @@ -22,6 +22,8 @@ let particles = []; let screenShake = 0; let trajectories = []; let idle = false; +let dt = 0; +let collapseState = null; let winner; let score = 0; @@ -162,6 +164,7 @@ function initAllPlayerStats() { deaths: 0, shotsFired: 0, wins: 0, + lastDamageSource: null, }); } } @@ -200,7 +203,7 @@ function updateMarket() { const item = marketItems[menuState.selected]; const itemData = MARKET_ITEMS[item]; const humanPlayer = players.find(p => !p.ai); - if (humanPlayer && score >= itemData.price && item !== 'babyMissile') { + if (humanPlayer && score >= itemData.price && item !== 'babyMissile' && WEAPON_TYPES[item]) { score -= itemData.price; let existingWeapon = humanPlayer.weapons.find(w => w.type === item); if (existingWeapon) { @@ -285,6 +288,7 @@ function initPlayers() { player.kills = 0; player.deaths = 0; player.shotsFired = 0; + player.lastDamageSource = null; } players = existingPlayers; } else { @@ -310,6 +314,7 @@ function initPlayers() { deaths: 0, shotsFired: 0, wins: 0, + lastDamageSource: null, }); } } @@ -435,7 +440,7 @@ function update() { for (let i=projectiles.length-1; i>=0; i--) { const projectile = projectiles[i]; const projectileType = PROJECTILE_TYPES[projectile.type]; - if (projectileType.update(projectile, terrain, projectiles, trajectories, explosions)) continue; + if (projectileType.update(projectile, terrain, projectiles, trajectories, explosions, dt)) continue; projectileType.stop(projectile); projectiles.splice(i, 1); } @@ -463,6 +468,8 @@ function update() { let remainingDamage = damage; if (!damage) continue; + if (explosion.source) player.lastDamageSource = explosion.source; + if (player.shield) { remainingDamage = clamp(0, damage-player.shield.energy, Infinity); player.shield.energy = clamp(0, player.shield.energy-damage, Infinity); @@ -478,8 +485,11 @@ function update() { } else if (state === 'land-collapse') { - collapseTerrain(terrain); - state = 'land-players'; + if (!collapseState) collapseState = startCollapseTerrain(terrain); + if (collapseTerrainStep(terrain, collapseState)) { + collapseState = null; + state = 'land-players'; + } } else if (state === 'land-players') { @@ -518,6 +528,9 @@ function update() { createParticles(x, y, PLAYER_EXPLOSION_PARTICLE_POWER, c); dyingPlayer.dead = true; dyingPlayer.deaths++; + if (dyingPlayer.lastDamageSource && dyingPlayer.lastDamageSource !== dyingPlayer) { + dyingPlayer.lastDamageSource.kills++; + } state = 'explosions'; } @@ -912,7 +925,8 @@ function drawStatus() { drawText(foreground, `Round: ${round}/${totalRounds} Score: ${score}`, W-8, 18, 'white', 'right'); } -loop(() => { +loop((deltaTime) => { + dt = deltaTime; update(); draw(); }); diff --git a/src/panel.js b/src/panel.js index cbca199..4d0aef6 100644 --- a/src/panel.js +++ b/src/panel.js @@ -20,7 +20,7 @@ export function drawPanelBg(ctx) { export function drawPanelTitle(ctx, text) { const x = PANEL_X + PANEL_WIDTH / 2; const y = PANEL_Y + 20; - ctx.font = `${PANEL_TITLE_FONT_SIZE}px text`; + ctx.font = `${PANEL_TITLE_FONT_SIZE}px ibm-vga`; ctx.textBaseline = 'top'; ctx.textAlign = 'center'; ctx.fillStyle = 'black'; @@ -30,7 +30,7 @@ export function drawPanelTitle(ctx, text) { } export function drawPanelText(ctx, text, x, y, color = 'white', align = 'left') { - ctx.font = `${PANEL_FONT_SIZE}px text`; + ctx.font = `${PANEL_FONT_SIZE}px ibm-bios`; ctx.textBaseline = 'top'; ctx.textAlign = align; ctx.fillStyle = 'black'; diff --git a/src/projectiles.js b/src/projectiles.js index 85bea0f..5ee9a15 100644 --- a/src/projectiles.js +++ b/src/projectiles.js @@ -23,13 +23,14 @@ export const PROJECTILE_TYPES = { stop(projectile) { projectile.osc.stop(0); }, - update(projectile, terrain, projectiles, trajectories, explosions) { + update(projectile, terrain, projectiles, trajectories, explosions, dt) { const prevProjectile = {...projectile}; const {weapon, player, wind} = projectile; const weaponType = WEAPON_TYPES[weapon.type]; let exploded = false; + const iterations = Math.max(1, Math.round(PROJECTILE_ITERATIONS_PER_FRAME * dt * 60)); - for (let i=0; i H || isTank(x, y)) { projectile.state = 'explode'; } else { - projectile.x += d; + projectile.x += d * rollSpeed; projectile.y = nextY; } } @@ -197,7 +200,7 @@ export const PROJECTILE_TYPES = { const {x, y, p} = projectile; const explosionSpec = weaponType.explosion; const explosionType = EXPLOSION_TYPES[explosionSpec.type]; - explosions.push(explosionType.create(explosionSpec, x, y)); + explosions.push({...explosionType.create(explosionSpec, x, y), source: player}); // @ts-ignore: canvas color hack createParticles(x, y, p, terrain.color); finished = true; @@ -255,7 +258,7 @@ export const PROJECTILE_TYPES = { }]; }, stop() {}, - update(projectile, terrain, projectiles, trajectories, explosions) { + update(projectile, terrain, projectiles, trajectories, explosions, dt) { const {player, weapon, ox, oy, a, p, wind, n, s} = projectile; const projectileType = PROJECTILE_TYPES.normal; @@ -268,7 +271,7 @@ export const PROJECTILE_TYPES = { } const alive = projectileType.update( - projectile.payload, terrain, projectiles, trajectories, explosions + projectile.payload, terrain, projectiles, trajectories, explosions, dt ); if (!alive) { diff --git a/src/terrain.js b/src/terrain.js index d036816..c1de650 100644 --- a/src/terrain.js +++ b/src/terrain.js @@ -72,6 +72,43 @@ export function collapseTerrain(ctx, ) { cacheImageData(ctx); } +export function startCollapseTerrain(terrain) { + return {}; +} + +export function collapseTerrainStep(terrain, state) { + const {width, height} = terrain.canvas; + const imageData = terrain.getImageData(0, 0, width, height); + const data = imageData.data; + let anyMoved = false; + + for (let y = height - 2; y >= 0; y--) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const belowIdx = ((y + 1) * width + x) * 4; + if (data[idx + 3] > 0 && data[belowIdx + 3] === 0) { + data[belowIdx] = data[idx]; + data[belowIdx + 1] = data[idx + 1]; + data[belowIdx + 2] = data[idx + 2]; + data[belowIdx + 3] = data[idx + 3]; + data[idx] = 0; + data[idx + 1] = 0; + data[idx + 2] = 0; + data[idx + 3] = 0; + anyMoved = true; + } + } + } + + if (!anyMoved) { + cacheImageData(terrain); + return true; + } + + terrain.putImageData(imageData, 0, 0); + return false; +} + export function isTerrain(ctx, x, y) { const index = coords2index(ctx.canvas.width, x, y) * 4; return cachedImageData.data[index+3] > 0;