GP di Fortezza, Seria semplice più difficile (parere di Codex, simulato): sopra il limite in curva si esce di sicuro, la curva pulita dà una spinta, rivali a 0,81; CURVA! e LASCIA! sopra il motorino (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
+3
-1
@@ -22,7 +22,9 @@ Online su 50037.manzolo.it tutto fino agli effetti ambientali di Codex. Aperto,
|
|||||||
frena; lo sterzo segue la strada da solo (scansa le buche, sorpassa, prende la miscela), ma oltre la velocità che la
|
frena; lo sterzo segue la strada da solo (scansa le buche, sorpassa, prende la miscela), ma oltre la velocità che la
|
||||||
curva regge il motorino scivola fuori verso la paglia (`SIMPLE` in `gara.js`). Ginocchio in terra resta con lo
|
curva regge il motorino scivola fuori verso la paglia (`SIMPLE` in `gara.js`). Ginocchio in terra resta con lo
|
||||||
sterzo vero. Il pannello nasconde Sterza, Frena, swipe e sensibilità. Test in `gara.test.mjs` e
|
sterzo vero. Il pannello nasconde Sterza, Frena, swipe e sensibilità. Test in `gara.test.mjs` e
|
||||||
`npm run browser:gara`.
|
`npm run browser:gara`. Seria più difficile (parere di Codex, simulato): sopra il 110% del limite in curva si esce
|
||||||
|
di sicuro (stun 1,2 s), la curva pulita dà una spinta, rivali a 0,81; CURVA! (preavviso) e LASCIA! sul motorino.
|
||||||
|
In simulazione: chi frena giusto 1° in 46 s, tutto gas 4°-5° in 55-59 s.
|
||||||
- **La pista col gesso (29 ottobre, modifiche locali)**: le Micro Machines sul parcheggio di Cardetole contro il
|
- **La pista col gesso (29 ottobre, modifiche locali)**: le Micro Machines sul parcheggio di Cardetole contro il
|
||||||
Chiaru (Matteo Chiarusi, sprite di Codex dalla foto di Andrea, accanto alla pista). Motore in `micro.js`, disegno in
|
Chiaru (Matteo Chiarusi, sprite di Codex dalla foto di Andrea, accanto alla pista). Motore in `micro.js`, disegno in
|
||||||
`micro-render.js`: pista disegnata a gesso dal codice sopra l'asfalto di Codex (`micro-parcheggio.png`), un giro,
|
`micro-render.js`: pista disegnata a gesso dal codice sopra l'asfalto di Codex (`micro-parcheggio.png`), un giro,
|
||||||
|
|||||||
+3
-1
@@ -228,7 +228,9 @@
|
|||||||
<p>Il GP di Fortezza: tre giri attorno alle mura coi cinquantini. <b>Alla bona e Seria</b> la guida è semplice:
|
<p>Il GP di Fortezza: tre giri attorno alle mura coi cinquantini. <b>Alla bona e Seria</b> la guida è semplice:
|
||||||
tieni premuto sulla scena (o Spazio, o il pulsante Gas) per accelerare e lascia per frenare; lo sterzo segue la
|
tieni premuto sulla scena (o Spazio, o il pulsante Gas) per accelerare e lascia per frenare; lo sterzo segue la
|
||||||
strada da solo, scansa le buche e sorpassa. Ma se entri in curva troppo forte scivoli fuori, e nella paglia ti
|
strada da solo, scansa le buche e sorpassa. Ma se entri in curva troppo forte scivoli fuori, e nella paglia ti
|
||||||
fermi: molla prima delle curve.</p>
|
fermi: molla prima delle curve. Sopra il motorino «CURVA!» avvisa che ne arriva una, «LASCIA!» dice quando
|
||||||
|
mollare. A Seria chi entra in curva troppo forte va fuori di sicuro, e chi la prende giusta fa la «curva pulita»
|
||||||
|
ed esce con una spinta in più.</p>
|
||||||
<p><b>Ginocchio in terra</b> si guida davvero. Con un solo dito: tieni premuto sulla scena per
|
<p><b>Ginocchio in terra</b> si guida davvero. Con un solo dito: tieni premuto sulla scena per
|
||||||
il gas e trascina verso la direzione in cui vuoi andare sullo schermo. A destra vai verso destra, anche quando
|
il gas e trascina verso la direzione in cui vuoi andare sullo schermo. A destra vai verso destra, anche quando
|
||||||
il motorino scende. I piccoli movimenti non sterzano; il cerchietto segue gli swipe lunghi. Alza il dito per
|
il motorino scende. I piccoli movimenti non sterzano; il cerchietto segue gli swipe lunghi. Alza il dito per
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TRACK, CURVES, PICKUPS, BRAKE, LAPS, COUNTDOWN, ROAD, VERGE, CAN, SWIPE, pointAt, standings, player, lapOf, presenting, placeOf, INTRO } from './gara.js';
|
import { TRACK, CURVES, PICKUPS, BRAKE, LAPS, COUNTDOWN, ROAD, VERGE, CAN, SWIPE, pointAt, standings, player, lapOf, presenting, placeOf, INTRO, mustLift, PREAVVISO } from './gara.js';
|
||||||
import { text, panel } from './bocce-render.js';
|
import { text, panel } from './bocce-render.js';
|
||||||
import { ambientHash as hash } from './ambient.js';
|
import { ambientHash as hash } from './ambient.js';
|
||||||
|
|
||||||
@@ -216,6 +216,12 @@ function result(ctx, c) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Una scritta sopra il motorino di Andrea, su un fondo scuro perché si legga anche sullo sterrato e sul telefono.
|
||||||
|
function tag(ctx, line, r, color) {
|
||||||
|
const x = Math.round(r.x), y = Math.round(r.y) - 20, w = line.length * 9 + 10;
|
||||||
|
ctx.fillStyle = 'rgba(23,22,15,0.85)'; ctx.fillRect(x - w / 2, y - 7, w, 14);
|
||||||
|
text(ctx, line, x, y, 10, color, 'center');
|
||||||
|
}
|
||||||
export function drawGara(ctx, c, def, bg, look = () => null, sheet = () => null) {
|
export function drawGara(ctx, c, def, bg, look = () => null, sheet = () => null) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
if (bg) ctx.drawImage(bg, 0, 0, 640, 360);
|
if (bg) ctx.drawImage(bg, 0, 0, 640, 360);
|
||||||
@@ -232,6 +238,10 @@ export function drawGara(ctx, c, def, bg, look = () => null, sheet = () => null)
|
|||||||
const me = player(c);
|
const me = player(c);
|
||||||
if (c.step === 'race' && me.out > 0) text(ctx, me.lastHole !== undefined ? 'NELLA BUCA!' : 'NELLA PAGLIA!', 320, 180, 10, '#ffe28a', 'center');
|
if (c.step === 'race' && me.out > 0) text(ctx, me.lastHole !== undefined ? 'NELLA BUCA!' : 'NELLA PAGLIA!', 320, 180, 10, '#ffe28a', 'center');
|
||||||
if (c.step === 'race' && me.boost > 0 && me.out <= 0) text(ctx, 'MISCELA!', 320, 180, 8, '#ffb030', 'center');
|
if (c.step === 'race' && me.boost > 0 && me.out <= 0) text(ctx, 'MISCELA!', 320, 180, 8, '#ffb030', 'center');
|
||||||
|
// La guida semplice: LASCIA! sopra il motorino quando serve frenare, e la curva pulita.
|
||||||
|
if (c.step === 'race' && c.holding && mustLift(c)) { if (Math.floor(c.age * 8) % 2) tag(ctx, 'LASCIA!', me, '#ff6a4a'); }
|
||||||
|
else if (c.step === 'race' && c.holding && mustLift(c, PREAVVISO)) tag(ctx, 'CURVA!', me, '#ffe28a');
|
||||||
|
if (c.step === 'race' && c.clean !== undefined && c.t - c.clean < 1.2) text(ctx, 'CURVA PULITA!', 320, 180, 9, '#b9e5b3', 'center');
|
||||||
if (c.step === 'result') result(ctx, c);
|
if (c.step === 'result') result(ctx, c);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-14
@@ -56,7 +56,10 @@ export const RIDERS = [
|
|||||||
// tiene lo sterzo vero.
|
// tiene lo sterzo vero.
|
||||||
export const LEVELS = {
|
export const LEVELS = {
|
||||||
bona: { rivals: 0.74, stun: 0.4, guide: 'zone', simple: true },
|
bona: { rivals: 0.74, stun: 0.4, guide: 'zone', simple: true },
|
||||||
seria: { rivals: 0.84, stun: 0.9, guide: 'coni', simple: true },
|
// Seria (Andrea, 29 ottobre: «chi non rallenta in curva deve andare fuori»; valori proposti da Codex e simulati):
|
||||||
|
// si scivola di più, sopra il limite in curva si esce per forza, e la curva pulita dà una spinta.
|
||||||
|
seria: { rivals: 0.81, stun: 1.2, guide: 'coni', simple: true,
|
||||||
|
tune: { slide: 12, clean: { min: 0.85, slack: 2, time: 0.6, accel: 1.2 }, force: { over: 1.1, hold: 0.18, apex: 1.15, time: 0.3, guard: 0.6 } } },
|
||||||
male: { rivals: 0.93, stun: 1.4, guide: null },
|
male: { rivals: 0.93, stun: 1.4, guide: null },
|
||||||
};
|
};
|
||||||
// `def.simple` la forza (i test della guida vera la spengono anche alla bona).
|
// `def.simple` la forza (i test della guida vera la spengono anche alla bona).
|
||||||
@@ -159,7 +162,7 @@ export function newGara(seed = Date.now() >>> 0, def = {}) {
|
|||||||
});
|
});
|
||||||
if (!riders.some(r => r.id === 'andrea')) throw new Error('Nella gara manca Andrea');
|
if (!riders.some(r => r.id === 'andrea')) throw new Error('Nella gara manca Andrea');
|
||||||
for (const r of riders) { r.grid = r.s; r.shown = false; }
|
for (const r of riders) { r.grid = r.s; r.shown = false; }
|
||||||
return { step: 'intro', t: 0, age: 0, seed, level, skill, simple: def.simple ?? !!skill.simple, riders, holding: false, braking: false, keys: { left: false, right: false, gas: new Set(), brake: false },
|
return { step: 'intro', t: 0, age: 0, seed, level, skill, simple: def.simple ?? !!skill.simple, tune: { ...SIMPLE, ...skill.tune }, riders, holding: false, braking: false, keys: { left: false, right: false, gas: new Set(), brake: false },
|
||||||
pointer: null, toggle: false, brakeButton: false, drag: 0, buttons: 0, anchor: null, finger: null, sens: SWIPE.sens,
|
pointer: null, toggle: false, brakeButton: false, drag: 0, buttons: 0, anchor: null, finger: null, sens: SWIPE.sens,
|
||||||
power: SWIPE.sens, accumulator: 0, events: [], result: null, order: riders.map(r => r.id), taken: {} };
|
power: SWIPE.sens, accumulator: 0, events: [], result: null, order: riders.map(r => r.id), taken: {} };
|
||||||
}
|
}
|
||||||
@@ -269,11 +272,53 @@ const follow = r => { const aim = pointAt(r.s + 14 + r.v * 0.18, r.lane * 0.6);
|
|||||||
// La guida semplice di Andrea: lo sterzo scansa le buche, sorpassa e va sulle lattine di miscela; ma oltre la velocità
|
// La guida semplice di Andrea: lo sterzo scansa le buche, sorpassa e va sulle lattine di miscela; ma oltre la velocità
|
||||||
// che la curva regge (la più stretta nei SIMPLE.window pixel davanti) il motorino scivola verso l'esterno, tanto più
|
// che la curva regge (la più stretta nei SIMPLE.window pixel davanti) il motorino scivola verso l'esterno, tanto più
|
||||||
// quanto si esagera (SIMPLE.slide): a tutto gas, nelle curve vere si finisce nella paglia.
|
// quanto si esagera (SIMPLE.slide): a tutto gas, nelle curve vere si finisce nella paglia.
|
||||||
export const SIMPLE = { grip: 1, slide: 8, window: 50 };
|
// Ogni livello può stringere (LEVELS.*.tune). `clean`: il premio della curva pulita, presa senza mai passare il
|
||||||
export function simpleLimit(r, s = r.s) {
|
// limite (`slack` px/s di tolleranza) e senza frenare troppo (almeno `min` del limite): per `time` secondi in uscita
|
||||||
|
// si accelera di più (`accel`), senza superare la velocità massima. `force`: l'uscita sicura, sopra `over` volte il
|
||||||
|
// limite per `hold` secondi (o `apex` volte nel punto più stretto) si va nella paglia in `time` secondi, una volta per
|
||||||
|
// curva; rientrati, `guard` secondi di tregua.
|
||||||
|
export const SIMPLE = { grip: 1, slide: 8, window: 50, clean: null, force: null };
|
||||||
|
export function simpleLimit(r, s = r.s, tune = SIMPLE) {
|
||||||
let low = Infinity;
|
let low = Infinity;
|
||||||
for (let d = -10; d <= SIMPLE.window; d += STEP) low = Math.min(low, limitAt(s + d));
|
for (let d = -10; d <= tune.window; d += STEP) low = Math.min(low, limitAt(s + d));
|
||||||
return low * r.grip * SIMPLE.grip;
|
return low * r.grip * tune.grip;
|
||||||
|
}
|
||||||
|
// La curva in corso: dentro si tiene il peggio dell'eccesso e la velocità più bassa (per il premio), e si guarda se
|
||||||
|
// si è andati troppo forte abbastanza a lungo (per l'uscita sicura).
|
||||||
|
function judgeCurve(c, r, dt) {
|
||||||
|
const tune = c.tune, s = ((r.s % TRACK.length) + TRACK.length) % TRACK.length;
|
||||||
|
const k = CURVES.find(k => s >= k.start - 20 && s <= k.end + 10);
|
||||||
|
if (k) {
|
||||||
|
const w = r.curve?.k === k ? r.curve : (r.curve = { k, excess: 0, slow: Infinity, over: 0, fault: false });
|
||||||
|
w.excess = Math.max(w.excess, r.excess); w.slow = Math.min(w.slow, r.v);
|
||||||
|
const f = tune.force, limit = limitAt(k.apex) * r.grip * tune.grip;
|
||||||
|
if (f && !w.fault && !(r.guard > 0) && r.out === 0) {
|
||||||
|
w.over = r.v > limit * f.over ? w.over + dt : 0;
|
||||||
|
if (w.over >= f.hold || (Math.abs(s - k.apex) < STEP * 2 && r.v > limit * f.apex)) { w.fault = true; r.forced = f.time; }
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const w = r.curve; r.curve = null;
|
||||||
|
if (!w || !tune.clean || w.fault) return;
|
||||||
|
const limit = limitAt(w.k.apex) * r.grip * tune.grip;
|
||||||
|
if (w.excess <= tune.clean.slack && w.slow >= limit * tune.clean.min && r.out === 0) {
|
||||||
|
r.push = tune.clean.time; r.cleans = (r.cleans ?? 0) + 1;
|
||||||
|
c.events.push({ kind: 'gara-giro', who: r.id, power: 1 }); c.clean = c.t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Andrea, nella guida semplice, deve lasciare entro `lead` secondi per arrivare alle curve davanti sotto il limite.
|
||||||
|
// Sopra il motorino: CURVA! in giallo con PREAVVISO secondi d'anticipo, poi LASCIA! in rosso quando è il momento (sul
|
||||||
|
// telefono non serve l'istante preciso, e lasciare troppo presto, che qui vuol dire frenare, non conviene).
|
||||||
|
export const PREAVVISO = 0.7, LIFT = 0;
|
||||||
|
export function mustLift(c, lead = LIFT) {
|
||||||
|
const r = player(c);
|
||||||
|
if (!simple(c) || c.step !== 'race' || r.out > 0) return false;
|
||||||
|
const horizon = r.v * r.v / (2 * BRAKE) + r.v * lead + 10;
|
||||||
|
for (let d = 0; d <= horizon; d += 4) {
|
||||||
|
const a = simpleLimit(r, r.s + d, c.tune), room = Math.max(0, d - r.v * lead);
|
||||||
|
if (r.v > a && r.v * r.v > a * a + 2 * BRAKE * room) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
function autopilot(c, r) {
|
function autopilot(c, r) {
|
||||||
const next = PICKUPS.find(p => p.kind !== 'boost' && ahead(r.s, p.s) < 30 + r.v * 0.6);
|
const next = PICKUPS.find(p => p.kind !== 'boost' && ahead(r.s, p.s) < 30 + r.v * 0.6);
|
||||||
@@ -282,7 +327,7 @@ function autopilot(c, r) {
|
|||||||
if (next?.kind === 'buca') r.lane = next.lane > 0 ? -5 : 5;
|
if (next?.kind === 'buca') r.lane = next.lane > 0 ? -5 : 5;
|
||||||
else if (slow) r.lane = slow.lat > 0 ? -6 : 6;
|
else if (slow) r.lane = slow.lat > 0 ? -6 : 6;
|
||||||
else if (next?.kind === 'miscela') r.lane = next.lane;
|
else if (next?.kind === 'miscela') r.lane = next.lane;
|
||||||
r.excess = Math.max(0, r.v - simpleLimit(r));
|
r.excess = Math.max(0, r.v - simpleLimit(r, r.s, c.tune));
|
||||||
return follow(r);
|
return follow(r);
|
||||||
}
|
}
|
||||||
// Gli avversari: guardano un punto più avanti sulla loro corsia e ci sterzano; danno gas finché, frenando da qui,
|
// Gli avversari: guardano un punto più avanti sulla loro corsia e ci sterzano; danno gas finché, frenando da qui,
|
||||||
@@ -309,24 +354,28 @@ function drive(c, r) {
|
|||||||
}
|
}
|
||||||
function stepRider(c, r, dt) {
|
function stepRider(c, r, dt) {
|
||||||
if (r.finished !== null) { r.gas = false; r.brake = false; drive(c, r); r.gas = false; }
|
if (r.finished !== null) { r.gas = false; r.brake = false; drive(c, r); r.gas = false; }
|
||||||
else if (r.out > 0) { r.out = Math.max(0, r.out - dt); r.v = 0; r.u = 0; return; }
|
else if (r.out > 0) { r.out = Math.max(0, r.out - dt); r.v = 0; r.u = 0; if (!r.out && c.tune.force) r.guard = c.tune.force.guard; return; }
|
||||||
else if (r.id === 'andrea' && simple(c)) { r.gas = c.holding; r.brake = !c.holding; r.steer = autopilot(c, r); }
|
else if (r.id === 'andrea' && simple(c)) { r.gas = c.holding; r.brake = !c.holding; r.steer = autopilot(c, r); judgeCurve(c, r, dt); }
|
||||||
else if (r.id === 'andrea') { r.gas = c.holding; r.brake = c.braking; r.steer = steering(c); }
|
else if (r.id === 'andrea') { r.gas = c.holding; r.brake = c.braking; r.steer = steering(c); }
|
||||||
else drive(c, r);
|
else drive(c, r);
|
||||||
if (r.finished !== null) r.gas = false;
|
if (r.finished !== null) r.gas = false;
|
||||||
const grass = Math.abs(r.lat) > ROAD, top = r.vmax * (r.boost > 0 ? BOOST.speed : 1) * (grass ? 0.5 : 1);
|
const grass = Math.abs(r.lat) > ROAD, top = r.vmax * (r.boost > 0 ? BOOST.speed : 1) * (grass ? 0.5 : 1);
|
||||||
// Il freno vince sul gas: chi li tiene tutti e due frena.
|
// Il freno vince sul gas: chi li tiene tutti e due frena.
|
||||||
if (r.brake) r.v -= BRAKE * dt;
|
if (r.brake) { r.v -= BRAKE * dt; r.push = 0; }
|
||||||
else if (r.gas) r.v += r.accel * dt * (1 - 0.4 * r.v / top) * (r.boost > 0 ? 1.6 : 1);
|
else if (r.gas) r.v += r.accel * dt * (1 - 0.4 * r.v / top) * (r.boost > 0 ? 1.6 : 1) * (r.push > 0 ? c.tune.clean.accel : 1);
|
||||||
else r.v -= COAST * dt;
|
else r.v -= COAST * dt;
|
||||||
if (r.v > top) r.v = Math.max(top, r.v - 120 * dt);
|
if (r.v > top) r.v = Math.max(top, r.v - 120 * dt);
|
||||||
r.v = Math.max(0, r.v);
|
r.v = Math.max(0, r.v);
|
||||||
r.boost = Math.max(0, r.boost - dt);
|
r.boost = Math.max(0, r.boost - dt);
|
||||||
|
if (r.push) r.push = Math.max(0, r.push - dt);
|
||||||
|
if (r.guard) r.guard = Math.max(0, r.guard - dt);
|
||||||
// Lo sterzo: da fermi non si gira; in curva una parte della velocità resta per traverso e si scivola fuori.
|
// Lo sterzo: da fermi non si gira; in curva una parte della velocità resta per traverso e si scivola fuori.
|
||||||
const turn = r.steer * TURN * Math.min(1, r.v / 22) * dt;
|
const turn = r.steer * TURN * Math.min(1, r.v / 22) * dt;
|
||||||
r.a = wrapAngle(r.a + turn);
|
r.a = wrapAngle(r.a + turn);
|
||||||
r.u -= r.v * Math.sin(turn) * SLIDE * (r.v / r.vmax) / r.grip;
|
r.u -= r.v * Math.sin(turn) * SLIDE * (r.v / r.vmax) / r.grip;
|
||||||
if (r.excess) r.u -= TRACK.side[r.idx] * r.excess * SIMPLE.slide * dt; // la guida semplice: troppo forte, si allarga
|
if (r.excess) r.u -= TRACK.side[r.idx] * r.excess * c.tune.slide * dt; // la guida semplice: troppo forte, si allarga
|
||||||
|
// L'uscita sicura: verso l'esterno della curva, e lo sterzo non la salva.
|
||||||
|
if (r.forced > 0) { r.forced = Math.max(0, r.forced - dt); r.u = -(r.curve?.k.side ?? TRACK.side[r.idx]) * 90; r.v = Math.max(r.v, 30); }
|
||||||
r.u *= Math.exp(-GRIP * r.grip * dt);
|
r.u *= Math.exp(-GRIP * r.grip * dt);
|
||||||
r.x += (Math.cos(r.a) * r.v - Math.sin(r.a) * r.u) * dt;
|
r.x += (Math.cos(r.a) * r.v - Math.sin(r.a) * r.u) * dt;
|
||||||
r.y += (Math.sin(r.a) * r.v + Math.cos(r.a) * r.u) * dt;
|
r.y += (Math.sin(r.a) * r.v + Math.cos(r.a) * r.u) * dt;
|
||||||
@@ -335,7 +384,8 @@ function stepRider(c, r, dt) {
|
|||||||
if (Math.abs(r.lat) > VERGE) {
|
if (Math.abs(r.lat) > VERGE) {
|
||||||
const h = TRACK.heading[r.idx], p = TRACK.pts[r.idx], side = Math.sign(r.lat), back = side * (VERGE - 1);
|
const h = TRACK.heading[r.idx], p = TRACK.pts[r.idx], side = Math.sign(r.lat), back = side * (VERGE - 1);
|
||||||
r.x = p[0] - Math.sin(h) * back; r.y = p[1] + Math.cos(h) * back; r.lat = back; r.u = 0;
|
r.x = p[0] - Math.sin(h) * back; r.y = p[1] + Math.cos(h) * back; r.lat = back; r.u = 0;
|
||||||
if (r.v > 40 && r.finished === null) {
|
if ((r.v > 40 || r.forced > 0) && r.finished === null) {
|
||||||
|
r.forced = 0;
|
||||||
r.out = c.skill.stun * (r.id === 'andrea' ? 1 : 0.7); r.offs++; r.risk = 1;
|
r.out = c.skill.stun * (r.id === 'andrea' ? 1 : 0.7); r.offs++; r.risk = 1;
|
||||||
c.events.push({ kind: 'gara-fuori', who: r.id, power: r.id === 'andrea' ? 1 : 0.4 });
|
c.events.push({ kind: 'gara-fuori', who: r.id, power: r.id === 'andrea' ? 1 : 0.4 });
|
||||||
}
|
}
|
||||||
@@ -442,7 +492,7 @@ export function status(c) {
|
|||||||
const me = player(c), place = placeOf(c);
|
const me = player(c), place = placeOf(c);
|
||||||
if (me.out > 0) return me.lastHole !== undefined ? 'Nella buca! Ci si rialza…' : 'Nella paglia! Si riparte…';
|
if (me.out > 0) return me.lastHole !== undefined ? 'Nella buca! Ci si rialza…' : 'Nella paglia! Si riparte…';
|
||||||
if (me.boost > 0) return `Giro ${lapOf(me)} di ${LAPS}, ${place}° posto. Miscela!`;
|
if (me.boost > 0) return `Giro ${lapOf(me)} di ${LAPS}, ${place}° posto. Miscela!`;
|
||||||
if (simple(c)) return `Giro ${lapOf(me)} di ${LAPS}, ${place}° posto. ${c.holding ? 'Gas!' : 'Freno.'}`;
|
if (simple(c)) return `Giro ${lapOf(me)} di ${LAPS}, ${place}° posto. ${c.clean !== undefined && c.t - c.clean < 1.5 ? 'Curva pulita!' : mustLift(c) && c.holding ? 'Lascia!' : mustLift(c, PREAVVISO) && c.holding ? 'Curva!' : c.holding ? 'Gas!' : 'Freno.'}`;
|
||||||
return `Giro ${lapOf(me)} di ${LAPS}, ${place}° posto. ${c.braking ? 'Freno!' : c.holding ? 'Gas!' : 'Senza gas.'}`;
|
return `Giro ${lapOf(me)} di ${LAPS}, ${place}° posto. ${c.braking ? 'Freno!' : c.holding ? 'Gas!' : 'Senza gas.'}`;
|
||||||
}
|
}
|
||||||
export const button = c => c.step === 'intro' ? 'Avanti' : c.step === 'result' ? 'Finita' : c.holding ? 'Molla' : 'Gas';
|
export const button = c => c.step === 'intro' ? 'Avanti' : c.step === 'result' ? 'Finita' : c.holding ? 'Molla' : 'Gas';
|
||||||
|
|||||||
+28
-1
@@ -1,6 +1,6 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { SIMPLE, simple, simpleLimit, SWIPE, setPower, 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 { SIMPLE, simple, simpleLimit, mustLift, PREAVVISO, SWIPE, setPower, 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 { piloti } from '../src/story/piloti.js';
|
||||||
import { story } from '../src/story/story.js';
|
import { story } from '../src/story/story.js';
|
||||||
import { Game } from '../src/engine/game.js';
|
import { Game } from '../src/engine/game.js';
|
||||||
@@ -304,3 +304,30 @@ test('guida semplice: sempre a tutto gas si finisce nella paglia; mollando prima
|
|||||||
}
|
}
|
||||||
assert.ok(SIMPLE.slide > 0);
|
assert.ok(SIMPLE.slide > 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Seria semplice: chi non rallenta in curva va fuori e perde, chi frena giusto fa le curve pulite e vince; LASCIA! dice quando', () => {
|
||||||
|
const riders = piloti.filter(p => p.consenso !== null);
|
||||||
|
const wise = c => { const r = player(c), h = r.v * r.v / (2 * BRAKE) + 10; for (let d = 0; d <= h; d += 4) { const a = simpleLimit(r, r.s + d, c.tune); if (r.v * r.v > a * a + 2 * BRAKE * d) return false; } return true; };
|
||||||
|
for (const seed of [1, 2]) {
|
||||||
|
const wild = race(newGara(seed, { level: 'seria', riders }));
|
||||||
|
run(wild, 120, () => true);
|
||||||
|
const good = race(newGara(seed, { level: 'seria', riders }));
|
||||||
|
run(good, 120, wise);
|
||||||
|
assert.ok(wild.result.offs >= 4, `a tutto gas fuori in quasi ogni curva (${wild.result.offs})`);
|
||||||
|
assert.ok(wild.result.place >= 3);
|
||||||
|
assert.equal(good.result.offs, 0); assert.equal(good.result.winner, 'player', `seme ${seed}`);
|
||||||
|
assert.ok(player(good).cleans >= 5, 'le curve pulite');
|
||||||
|
assert.ok(wild.result.times.andrea - good.result.times.andrea > 6, 'tutto gas costa più di 6 secondi');
|
||||||
|
}
|
||||||
|
// Chi segue LASCIA! guida come chi frena giusto; CURVA! arriva prima.
|
||||||
|
const c = race(newGara(1, { level: 'seria', riders }));
|
||||||
|
let early = 0, said = false;
|
||||||
|
for (let t = 0; t < 20; t += 1 / 60) {
|
||||||
|
c.holding = !mustLift(c);
|
||||||
|
if (mustLift(c, PREAVVISO) && !mustLift(c)) early++;
|
||||||
|
update(c, 1 / 60);
|
||||||
|
if (c.holding && mustLift(c)) said ||= /Lascia!/.test(status(c));
|
||||||
|
}
|
||||||
|
assert.ok(early > 20, `il preavviso CURVA! viene prima di LASCIA! (${early} fotogrammi)`);
|
||||||
|
assert.ok(said, 'il pannello dice Lascia!');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user