La Condor al campone delle poste: la gara col Taglia con la pistola ad aria compressa coi pallini gialli (fiato trattenuto, il pallino che cala con la distanza, cartone, lattine, bottigliette, palloncino e la lattina in aria), sfondo di Codex (Andrea)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
2026-10-29 23:57:08 +01:00
co-authored by Claude Opus 5.5
parent 00f9f15370
commit 9d823375aa
20 changed files with 517 additions and 9 deletions
+90
View File
@@ -0,0 +1,90 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import * as m from '../src/engine/condor.js';
import { drawCondor } from '../src/engine/condor-render.js';
import { Game, MINIGAMES } from '../src/engine/game.js';
import { story } from '../src/story/story.js';
import { settle } from './helpers.mjs';
// Ferma il respiro (il mirino va dove si punta), spara e aspetta che il pallino arrivi.
function shot(c, x, y) {
c.skill = { ...c.skill, sway: 0 }; c.aim = { x, y };
assert.ok(m.shoot(c));
for (let i = 0; i < 200 && c.step === 'flying'; i++) m.update(c, 1 / 60);
return c.line;
}
// Aspetta la ricarica.
const reload = c => { for (let i = 0; i < 120 && !m.canThrow(c) && c.step !== 'result'; i++) m.update(c, 1 / 60); };
// Mira sopra quanto cala il pallino a quella distanza.
const over = (x, y) => [x, y - m.DROP * m.far(x) ** 2];
test('il pallino cala con la distanza: al cartone poco, alla porta di più; si mira sopra', () => {
const c = m.newCondor(1, { level: 'seria' });
assert.ok(m.far(m.CARTONE.x) < m.far(560));
assert.match(shot(c, ...over(m.CARTONE.x, m.CARTONE.y)), /10 punti/);
assert.equal(c.holes.length, 1); reload(c);
const b = c.bottles[1];
assert.match(shot(c, b.x, m.BAR - 6), /prato/, 'mirando dritto alla bottiglietta in fondo si va sotto');
reload(c);
assert.match(shot(c, ...over(b.x, m.BAR - 6)), /Bottiglietta giù/);
assert.ok(b.down); reload(c);
const k = c.cans[0];
assert.match(shot(c, ...over(k.x, m.BENCH - 6)), /Lattina giù/);
assert.equal(c.points, 10 + m.BOTTLE.points + m.CAN.points);
});
test('il palloncino scoppia; la lattina in aria del Taglia c\'è solo a seria e a fassi male', () => {
const c = m.newCondor(2, { level: 'bona' });
const at = m.balloon({ visual: { time: c.visual.time + m.flight(m.BALLOON.x) } });
assert.match(shot(c, ...over(at.x, at.y)), /palloncino/);
assert.ok(c.balloon.popped);
for (let i = 0; i < 60 * 20; i++) m.update(c, 1 / 60);
assert.equal(c.volo, null, 'alla bona il Taglia non tira niente in aria');
const s = m.newCondor(3, { level: 'seria' });
while (!s.volo) m.update(s, 1 / 60);
// Si mira dove sarà la lattina quando arriva il pallino (lì, vicina, non cala).
while (s.volo.t < 0.6) m.update(s, 1 / 60);
s.skill = { ...s.skill, sway: 0 };
let v = m.voloAt(s, s.volo.t + 0.3);
for (let i = 0; i < 3; i++) v = m.voloAt(s, s.volo.t + m.flight(v.x));
s.aim = { x: v.x, y: v.y }; m.shoot(s);
for (let i = 0; i < 200 && s.step === 'flying'; i++) m.update(s, 1 / 60);
assert.match(s.line, /al volo/); assert.equal(s.points, m.VOLO.points);
});
test('trattenere il fiato ferma il mirino; troppo e il colpo parte da solo; il tocco secco mira soltanto', () => {
const c = m.newCondor(4, { level: 'male' });
assert.ok(m.aimScreen(c, [300, 150])); assert.deepEqual(c.aim, { x: 300, y: 150 });
assert.ok(m.press(c, 1)); m.update(c, 0.05); assert.equal(m.release(c, 1), true); assert.equal(c.step, 'aim');
assert.equal(m.steady(c), 1);
m.press(c, 1);
for (let i = 0; i < 60 * m.STILL; i++) m.update(c, 1 / 60);
assert.ok(m.steady(c) < 0.35, 'fermo');
assert.ok(m.release(c, 1)); assert.equal(c.step, 'flying'); assert.equal(c.left, m.PELLETS - 1);
reload(c);
m.press(c, 2);
for (let i = 0; i < 60 * (m.BREATH + m.GASP + 0.2); i++) m.update(c, 1 / 60);
assert.equal(c.holding, false); assert.equal(c.left, m.PELLETS - 2, 'senza fiato il colpo è partito');
assert.ok(m.key(c, 'ArrowUp', true));
});
test('finiti i pallini si confronta col Taglia: vince chi ha di più, pari se uguali', () => {
for (const [points, winner] of [[50, 'player'], [null, 'draw'], [0, 'cpu']]) {
const c = m.newCondor(5, { level: 'seria' });
c.left = 1; c.points = points ?? c.taglia;
shot(c, 600, 340); reload(c);
assert.equal(c.step, 'result'); assert.equal(c.result.winner, winner);
}
});
test('il disegno della Condor non cambia lo stato', () => {
const calls = [], ctx = new Proxy({}, { get: (o, k) => k in o ? o[k] : (...args) => calls.push([k, ...args]) });
const c = m.newCondor(6, { level: 'male' }), bg = {};
while (!c.volo) m.update(c, 1 / 60);
const draw = () => { const before = structuredClone(c); drawCondor(ctx, c, story.closeups.condor, bg); assert.deepEqual(c, before); };
draw(); assert.ok(calls.some(([op, img]) => op === 'drawImage' && img === bg));
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'LA CONDOR'));
c.step = 'result'; c.result = { winner: 'player', points: 50, taglia: 44 }; calls.length = 0; draw();
assert.ok(calls.some(([op, t]) => op === 'fillText' && t === 'BATTUTO IL TAGLIA!'));
assert.ok(MINIGAMES.condor);
});