GP di Fortezza: guida a un dito col telefono (premuto è gas, lo swipe indica dove andare sullo schermo; Codex) e il casco di Tozzino alla Damon Hill (Andrea)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECdCPptNRwbVzjzHbohi
This commit is contained in:
2026-10-28 21:26:53 +01:00
co-authored by Claude Opus 5.5
parent e1b92278a5
commit 06f7cfbfad
8 changed files with 131 additions and 28 deletions
+73 -8
View File
@@ -1,6 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { newGara, update, press, presenting, INTRO, release, shoot, key, steerButton, aimScreen, steering, status, button, standings, player, rider, limitAt, pointAt, TRACK, CURVES, PICKUPS, BRAKE, VERGE, LAPS, COUNTDOWN, RIDERS } from '../src/engine/gara.js';
import { newGara, update, press, presenting, INTRO, release, cancel, shoot, key, steerButton, brakeButton, aimScreen, steering, status, button, standings, player, rider, limitAt, pointAt, TRACK, CURVES, PICKUPS, BRAKE, VERGE, LAPS, COUNTDOWN, RIDERS } from '../src/engine/gara.js';
import { piloti } from '../src/story/piloti.js';
import { story } from '../src/story/story.js';
import { Game } from '../src/engine/game.js';
@@ -18,6 +18,12 @@ const run = (c, seconds, drive, dt = 1 / 60) => {
};
const skip = c => { while (c.step === 'intro') press(c); return c; };
const race = c => { skip(c); assert.equal(c.step, 'start'); run(c, COUNTDOWN + 0.01); assert.equal(c.step, 'race'); return c; };
// Un dito al centro della scena indica la direzione del punto davanti al motorino.
const swipeTo = (c, target) => {
const r = player(c), dx = target.x - r.x, dy = target.y - r.y, length = Math.hypot(dx, dy);
if (c.pointer === null) { aimScreen(c, [320, 180], 'bot'); press(c, 'bot'); }
aimScreen(c, [c.anchor[0] + dx / length * 50, c.anchor[1] + dy / length * 50], 'bot');
};
// Il pilota di giudizio: guarda avanti e ci sterza (scansando buche e chi gli sta davanti), e dà gas finché,
// frenando, arriverebbe a ogni curva un po' sotto il limite. Usa i comandi del giocatore (il trascinamento).
const careful = c => {
@@ -25,10 +31,7 @@ const careful = c => {
const next = PICKUPS.find(p => p.kind !== 'boost' && p.s - here > -8 && p.s - here < 60);
const slow = c.riders.find(o => o !== r && o.s - r.s > 0 && o.s - r.s < 26 && Math.abs(o.lat - r.lat) < 7);
const lane = next?.kind === 'buca' ? (next.lane > 0 ? -6 : 6) : slow ? (slow.lat > 0 ? -6 : 6) : next ? next.lane : 0;
const ahead = pointAt(r.s + 14 + r.v * 0.18, lane), want = Math.atan2(ahead.y - r.y, ahead.x - r.x) - r.a;
// Come un dito premuto che trascina: lo zero dove si è premuto, di lato quanto serve.
if (c.pointer === null) { aimScreen(c, [320, 180], 'bot'); press(c, 'bot'); }
aimScreen(c, [320 + 28 * Math.max(-1, Math.min(1, Math.atan2(Math.sin(want), Math.cos(want)) * 2.6)), 180], 'bot');
swipeTo(c, pointAt(r.s + 14 + r.v * 0.18, lane));
const horizon = r.v * r.v / (2 * BRAKE) + 20;
let brake = false;
for (let d = 0; d <= horizon; d += 4) { const a = limitAt(r.s + d) * 0.85; if (r.v * r.v > a * a + 2 * BRAKE * d) { brake = true; break; } }
@@ -98,15 +101,77 @@ test('lo sterzo: frecce, pulsanti e il dito che trascina; il mouse che passa sen
aimScreen(c, [300, 100], 5); aimScreen(c, [360, 100], 5);
assert.equal(steering(c), -1);
key(c, 'ArrowUp', false); key(c, 'ArrowLeft', false);
// Il dito premuto: il punto dove preme è lo zero, di lato si sterza; un altro puntatore non sposta niente.
// Il dito indica una direzione; un altro puntatore non sposta niente.
player(c).a = -0.5 / 2.6;
aimScreen(c, [300, 100], 9); press(c, 9);
aimScreen(c, [314, 100], 9); assert.equal(steering(c), 0.5);
aimScreen(c, [250, 100], 4); assert.equal(steering(c), 0.5);
aimScreen(c, [314, 100], 9); assert.ok(Math.abs(steering(c) - 0.5) < 1e-10);
aimScreen(c, [250, 100], 4); assert.ok(Math.abs(steering(c) - 0.5) < 1e-10);
release(c, 9); assert.equal(steering(c), 0, 'lasciando il dito lo sterzo torna dritto');
const a = player(c).a; key(c, 'ArrowUp', true); key(c, 'ArrowRight', true); run(c, 0.4); assert.notEqual(player(c).a, a, 'sterzando si gira');
assert.equal(key(c, 'x', true), false, 'gli altri tasti non sono suoi');
});
test('lo swipe segue lo schermo: scendendo, destra sterza negativo; avanti e piccoli movimenti vanno dritto', () => {
const c = race(newGara(3)), r = player(c);
r.a = Math.PI / 2;
aimScreen(c, [320, 180], 9); press(c, 9);
aimScreen(c, [370, 180], 9);
assert.equal(steering(c), -1, 'scendendo, la destra dello schermo è a sinistra del motorino');
r.a = 0;
assert.ok(Math.abs(steering(c)) < 1e-10, 'col dito fermo lo sterzo si annulla quando il motorino è allineato');
r.a = Math.PI / 2;
aimScreen(c, [320, 230], 9); assert.ok(Math.abs(steering(c)) < 1e-10, 'swipe in avanti');
aimScreen(c, [323, 184], 9); assert.equal(steering(c), 0, '5 px non fanno tremare lo sterzo');
aimScreen(c, [330, 230], 9);
assert.ok(Math.abs(steering(c) + Math.atan2(10, 50) * 2.6) < 1e-10, 'sterzo proporzionale all’angolo');
r.a = -Math.PI + 0.1;
aimScreen(c, [270, 180], 9);
assert.ok(Math.abs(steering(c) + 0.26) < 1e-10, 'si gira dalla parte più breve anche passando da π a -π');
release(c, 9);
assert.equal(c.holding, false); assert.equal(steering(c), 0);
assert.equal(c.anchor, null); assert.equal(c.finger, null);
});
test('l’origine segue gli swipe lunghi e si può correggere; rilascio e annullamento puliscono il dito', () => {
const c = race(newGara(3));
player(c).a = 0;
aimScreen(c, [100, 100], 9); press(c, 9);
for (const x of [200, 300, 400]) {
aimScreen(c, [x, 100], 9);
assert.deepEqual(c.anchor, [x - 60, 100]);
assert.ok(Math.abs(steering(c)) < 1e-10);
}
aimScreen(c, [400, 160], 9);
assert.ok(Math.abs(Math.hypot(c.finger[0] - c.anchor[0], c.finger[1] - c.anchor[1]) - 60) < 1e-10);
assert.equal(steering(c), 1, 'si corregge verso il basso senza arrivare al bordo');
key(c, 'ArrowLeft', true); shoot(c); brakeButton(c, true);
release(c, 9);
assert.equal(steering(c), -1, 'lasciare il dito conserva lo sterzo della tastiera');
assert.ok(c.holding && c.braking, 'Gas e Frena del pannello restano attivi');
key(c, 'ArrowDown', true); brakeButton(c, false);
assert.ok(c.braking, 'il freno della tastiera resta attivo');
aimScreen(c, [200, 200], 10); press(c, 10);
assert.equal(c.drag, 0, 'il nuovo tocco non eredita lo swipe');
cancel(c);
assert.equal(c.holding, false); assert.equal(c.braking, false); assert.equal(steering(c), 0);
assert.equal(c.anchor, null); assert.equal(c.finger, null);
});
test('alla bona si completano tre giri usando solo un dito, senza tasti o pulsanti', () => {
const c = race(newGara(5, { level: 'bona', riders: piloti }));
aimScreen(c, [320, 180], 'bot'); press(c, 'bot');
for (let t = 0; t < 180 && c.step === 'race'; t += 1 / 60) {
swipeTo(c, pointAt(player(c).s + 50));
assert.ok(c.holding && c.pointer === 'bot');
assert.equal(c.keys.gas.size, 0); assert.equal(c.toggle, false);
assert.equal(c.keys.left || c.keys.right || c.braking || !!c.buttons, false);
update(c, 1 / 60);
}
assert.equal(c.step, 'result');
assert.equal(player(c).laps, LAPS);
assert.ok(player(c).finished > 0);
});
test('la presentazione: uno alla volta arrivano in griglia col motorino, poi il semaforo', () => {
const c = newGara(2, { riders: piloti });
assert.equal(c.step, 'intro'); assert.equal(presenting(c).id, piloti[0].id);