kill counter fixed. 8x8 anf 8x16 fonts added. terrain slides are aminated.

This commit is contained in:
catarrh committed 2026-07-25 19:24:12 +03:00
1 parent 43ee0d9150
commit e8a79c2a81
9 files changed
+79 -19

No files matched your search

Binary file not shown.
Binary file not shown.
+2
View File
@@ -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) }
</style>
</head>
<body>
+1 -1
View File
@@ -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';
+5 -1
View File
@@ -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);
})();
}
+19 -5
View File
@@ -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,9 +485,12 @@ function update() {
}
else if (state === 'land-collapse') {
collapseTerrain(terrain);
if (!collapseState) collapseState = startCollapseTerrain(terrain);
if (collapseTerrainStep(terrain, collapseState)) {
collapseState = null;
state = 'land-players';
}
}
else if (state === 'land-players') {
let stable = true;
@@ -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();
});
+2 -2
View File
@@ -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';
+13 -10
View File
@@ -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<PROJECTILE_ITERATIONS_PER_FRAME; i++) {
for (let i=0; i<iterations; i++) {
const {ox, oy, a, p, t} = projectile;
const [x, y] = parable(
@@ -69,7 +70,7 @@ export const PROJECTILE_TYPES = {
) {
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);
exploded = true;
@@ -108,7 +109,7 @@ 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 {state, weapon, player, wind} = projectile;
const weaponType = WEAPON_TYPES[weapon.type];
@@ -116,7 +117,8 @@ export const PROJECTILE_TYPES = {
if (state === 'flying') {
const {ox, oy, a, p, t} = projectile;
for (let i=0; i<PROJECTILE_ITERATIONS_PER_FRAME; i++) {
const iterations = Math.max(1, Math.round(PROJECTILE_ITERATIONS_PER_FRAME * dt * 60));
for (let i=0; i<iterations; i++) {
let [x, y] = parable(
t, ox, oy, deg2rad(180+a),
p / PROJECTILE_POWER_REDUCTION_FACTOR,
@@ -183,12 +185,13 @@ export const PROJECTILE_TYPES = {
else if (state === 'rolling') {
const {x, y, d} = projectile;
const nextY = landHeight(terrain, x+d);
const rollSpeed = Math.max(1, Math.round(dt * 60));
const nextY = landHeight(terrain, x + d * rollSpeed);
if (nextY < y || y > 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) {
+37
View File
@@ -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;