«Fai due tiri» al campo da basket del Circolo 4 Maggio
Minigioco nuovo (idea di Andrea, 27 ottobre): vista di lato, Andrea da solo con dieci palloni; il mouse o il dito danno l'angolo, tenendo premuto sale la forza, lasciando la palla vola in parabola, rimbalza sul ferro (davanti e dietro dell'anello), sul tabellone e per terra, entra anche sporca; da sotto non vale. A ogni canestro si arretra di una postazione fino a dietro la linea da tre (tre punti). Tre livelli: quanti canestri su dieci per la gloria (3, 5, 7) e quanta parabola mostra la guida (quasi tutta, l'inizio, niente). Sfondo del campo e foglio di Andrea col pallone di Codex; suoni di palla, ferro, tabellone e retina; stessi comandi delle bocce (pannello, tastiera, pressione); dialogo di scelta dal campo di Via Cipriani, battute provvisorie e voci; test del motore (da ogni postazione esiste un tiro che entra) e prova nel browser. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECdBYE8csKd4tcFvwntT
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { newBasket, press, release, cancel, update, shoot, setAngle, setPower, aimScreen, nudge, arc, status, canThrow, releasePoint,
|
||||
powerAt, SWING, BALLS, SPOTS, LEVELS, RIM, BOARD, FLOOR, BALL, ANGLE } from '../src/engine/basket.js';
|
||||
import { story } from '../src/story/story.js';
|
||||
import { Game } from '../src/engine/game.js';
|
||||
import { settle } from './helpers.mjs';
|
||||
|
||||
const fly = c => { for (let i = 0; i < 2000 && c.step === 'flight'; i++) update(c, 1 / 120); return c.ball; };
|
||||
const rest = c => { for (let i = 0; i < 200 && c.step === 'settle'; i++) update(c, 1 / 60); };
|
||||
// Cerca un tiro che entra dalla postazione di adesso: la mano ferma dei test.
|
||||
function goodShot(c) {
|
||||
for (let angle = 40; angle <= 70; angle += 1) for (let power = 0.2; power <= 1; power += 0.02) {
|
||||
const probe = structuredClone(c);
|
||||
setAngle(probe, angle); setPower(probe, power); shoot(probe);
|
||||
if (fly(probe).scored) return { angle, power };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
test('la forza sale finché si tiene premuto; un tocco breve non spreca il pallone', () => {
|
||||
const c = newBasket(1);
|
||||
assert.equal(powerAt(SWING / 2), 0.5); assert.equal(powerAt(SWING * 2), 1);
|
||||
assert.ok(press(c)); update(c, 0.05); assert.ok(release(c)); // troppo breve: annullato
|
||||
assert.equal(c.thrown, 0); assert.equal(c.power, 0.5, 'la forza torna com\'era');
|
||||
assert.ok(press(c)); update(c, 0.7); assert.ok(release(c));
|
||||
assert.equal(c.thrown, 1); assert.equal(c.step, 'flight');
|
||||
assert.ok(Math.abs(c.ball.vx) > 0 && c.ball.vy < 0, 'la palla parte in alto verso il canestro');
|
||||
assert.ok(!press(c), 'in volo non si carica');
|
||||
assert.ok(!cancel(c));
|
||||
});
|
||||
|
||||
test('la mira: l\'angolo segue il punto toccato, resta fra i limiti, non si tira all\'indietro', () => {
|
||||
const c = newBasket(2), r = releasePoint(c);
|
||||
assert.ok(aimScreen(c, [r.x + 100, r.y - 100])); assert.equal(Math.round(c.angle), 45);
|
||||
assert.ok(aimScreen(c, [r.x + 10, r.y - 400])); assert.equal(c.angle, ANGLE.max, 'non oltre il massimo');
|
||||
assert.ok(aimScreen(c, [r.x + 300, r.y])); assert.equal(c.angle, ANGLE.min, 'non sotto il minimo');
|
||||
assert.ok(!aimScreen(c, [r.x - 50, r.y - 50]), 'dietro di lui niente');
|
||||
assert.ok(nudge(c, 1)); assert.equal(c.angle, ANGLE.min + 2);
|
||||
assert.ok(nudge(c, 1, true)); assert.equal(c.angle, ANGLE.min + 2.5);
|
||||
press(c); assert.ok(!aimScreen(c, [r.x + 100, r.y - 100]), 'mentre si carica la mira è ferma');
|
||||
assert.ok(!setPower(c, 0.3));
|
||||
});
|
||||
|
||||
test('la parabola della guida comincia dalle mani e scende per la gravità', () => {
|
||||
const c = newBasket(3), r = releasePoint(c), pts = arc(c, 1);
|
||||
assert.deepEqual(pts[0], r);
|
||||
assert.ok(pts[5].y < r.y, 'prima sale');
|
||||
assert.ok(pts.at(-1).y > pts[5].y, 'poi scende');
|
||||
});
|
||||
|
||||
test('da ogni postazione esiste un tiro che entra, e un tiro entrato conta canestro e punti', () => {
|
||||
for (const [i, s] of SPOTS.entries()) {
|
||||
const c = newBasket(10 + i); c.spot = i;
|
||||
const shot = goodShot(c);
|
||||
assert.ok(shot, `nessun tiro entra ${s.name}`);
|
||||
setAngle(c, shot.angle); setPower(c, shot.power); shoot(c);
|
||||
const b = fly(c);
|
||||
assert.ok(b.scored && b.dead);
|
||||
assert.ok(c.events.some(e => e.kind === 'basket-swish' || e.kind === 'basket-in'), 'si sente la retina');
|
||||
rest(c);
|
||||
assert.equal(c.baskets, 1); assert.equal(c.points, s.points);
|
||||
assert.equal(c.spot, Math.min(i + 1, SPOTS.length - 1), 'dopo un canestro si arretra');
|
||||
assert.equal(c.step, 'aim');
|
||||
}
|
||||
});
|
||||
|
||||
test('un tiro corto batte sul campo e rimbalza; uno lungo prende il tabellone e torna indietro', () => {
|
||||
const c = newBasket(4); c.spot = 3; // da tre, piano: non arriva nemmeno al palo
|
||||
setAngle(c, 45); setPower(c, 0); shoot(c);
|
||||
let b = fly(c);
|
||||
assert.ok(!b.scored && b.bounces > 0, 'rimbalza per terra');
|
||||
assert.ok(c.events.some(e => e.kind === 'basket-bounce'));
|
||||
assert.ok(b.y + BALL <= FLOOR + 0.01, 'non sprofonda');
|
||||
const d = newBasket(5); d.spot = 1;
|
||||
setAngle(d, 30); setPower(d, 1); shoot(d);
|
||||
b = fly(d);
|
||||
assert.ok(b.board, 'ha preso il tabellone');
|
||||
assert.ok(c.events.every(e => e.kind !== 'basket-board') && d.events.some(e => e.kind === 'basket-board'));
|
||||
assert.ok(b.x < BOARD.x, 'non lo attraversa');
|
||||
});
|
||||
|
||||
test('la palla rimbalza sul ferro, e da sotto non è canestro', () => {
|
||||
const c = newBasket(6);
|
||||
c.ball = { x: RIM.x1 - 2, y: RIM.y - 60, vx: 0, vy: 150, time: 0, bounces: 0, rim: false, board: false, under: false, scored: false, dead: false, spin: 0 };
|
||||
c.step = 'flight';
|
||||
for (let i = 0; i < 60; i++) update(c, 1 / 120);
|
||||
assert.ok(c.ball.rim, 'il davanti del ferro l\'ha toccata');
|
||||
assert.ok(c.events.some(e => e.kind === 'basket-rim'));
|
||||
const d = newBasket(7);
|
||||
d.ball = { x: (RIM.x1 + RIM.x2) / 2, y: RIM.y + 20, vx: 0, vy: -200, time: 0, bounces: 0, rim: false, board: false, under: false, scored: false, dead: false, spin: 0 };
|
||||
d.step = 'flight';
|
||||
fly(d);
|
||||
assert.ok(d.ball.under && !d.ball.scored, 'sale attraverso l\'anello e ricade: non vale');
|
||||
});
|
||||
|
||||
test('dieci palloni, poi il risultato: la gloria dipende dal livello', () => {
|
||||
for (const [level, { goal }] of Object.entries(LEVELS)) {
|
||||
const c = newBasket(8, { level });
|
||||
let made = 0;
|
||||
for (let i = 0; i < BALLS; i++) {
|
||||
assert.ok(canThrow(c));
|
||||
const shot = made < goal ? goodShot(c) : { angle: 45, power: 0 }; // quanto basta per la gloria, poi a vuoto
|
||||
setAngle(c, shot.angle); setPower(c, shot.power); shoot(c);
|
||||
if (fly(c).scored) made++;
|
||||
rest(c);
|
||||
}
|
||||
assert.equal(c.step, 'result'); assert.equal(c.thrown, BALLS); assert.ok(!canThrow(c));
|
||||
assert.equal(c.result.baskets, goal); assert.equal(c.result.winner, 'player', level);
|
||||
assert.match(status(c), /canestri su 10/);
|
||||
assert.ok(!shoot(c), 'finita');
|
||||
}
|
||||
const miss = newBasket(9, { level: 'bona' });
|
||||
for (let i = 0; i < BALLS; i++) { setAngle(miss, 45); setPower(miss, 0); shoot(miss); fly(miss); rest(miss); }
|
||||
assert.equal(miss.result.winner, 'cpu'); assert.equal(miss.spot, 0, 'senza canestri non ci si muove');
|
||||
});
|
||||
|
||||
test('nella storia: dal campo di Via Cipriani si sceglie il livello e parte il gioco; la gloria lascia il flag', () => {
|
||||
const g = new Game(story);
|
||||
g.go('via_cipriani', [340, 316]); settle(g);
|
||||
g.act('use', 'campo_basket'); settle(g);
|
||||
assert.ok(g.dialogue, 'si decide come tirare');
|
||||
g.choose(g.options().findIndex(o => o.text === 'Due tiri seri.')); settle(g);
|
||||
assert.equal(g.closeup?.id, 'basket'); assert.equal(g.closeup.level, 'seria');
|
||||
g.closeup.result = { winner: 'player', baskets: 6, points: 13, goal: 5 }; g.closeup.step = 'result';
|
||||
g.closeupClose(); settle(g);
|
||||
assert.ok(g.state.flags.basket_vinto, 'la gloria resta');
|
||||
assert.ok(!g.closeup);
|
||||
});
|
||||
Reference in New Issue
Block a user