GP di Fortezza: guida semplice alla bona e Seria (si tiene premuto per il gas, lasciando si frena, lo sterzo va da solo e troppo forte in curva si scivola nella paglia), per il telefono; Ginocchio in terra resta con lo sterzo vero; prova nel browser del GP (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
+36
-7
@@ -50,12 +50,17 @@ export const RIDERS = [
|
||||
{ id: 'andrea', name: 'Andrea', bike: 'il Fifty', motore: 3, testa: 3, color: '#d8402f', trim: '#1c1c1c' },
|
||||
{ id: 'arcu', name: 'L\'Arcu', bike: 'il Betino', motore: 1, testa: 5, color: '#2f7a3a', trim: '#e8d9a0' },
|
||||
];
|
||||
// I livelli: quanto vanno gli altri, quanto si resta fermi nella paglia, e la guida disegnata in pista.
|
||||
// I livelli: quanto vanno gli altri, quanto si resta fermi nella paglia, e la guida disegnata in pista. `simple`: la
|
||||
// guida semplice (Andrea, 29 ottobre: dal telefono lo sterzo era ingiocabile): si tiene premuto per il gas e lasciando
|
||||
// si frena, lo sterzo segue la strada da solo, e chi entra in curva troppo forte scivola fuori. Ginocchio in terra
|
||||
// tiene lo sterzo vero.
|
||||
export const LEVELS = {
|
||||
bona: { rivals: 0.74, stun: 0.4, guide: 'zone' },
|
||||
seria: { rivals: 0.84, stun: 0.9, guide: 'coni' },
|
||||
bona: { rivals: 0.74, stun: 0.4, guide: 'zone', simple: true },
|
||||
seria: { rivals: 0.84, stun: 0.9, guide: 'coni', simple: true },
|
||||
male: { rivals: 0.93, stun: 1.4, guide: null },
|
||||
};
|
||||
// `def.simple` la forza (i test della guida vera la spengono anche alla bona).
|
||||
export const simple = c => !!c.simple;
|
||||
|
||||
function catmull(p0, p1, p2, p3, t) {
|
||||
const t2 = t * t, t3 = t2 * t;
|
||||
@@ -154,7 +159,7 @@ export function newGara(seed = Date.now() >>> 0, def = {}) {
|
||||
});
|
||||
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; }
|
||||
return { step: 'intro', t: 0, age: 0, seed, level, skill, 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, 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,
|
||||
power: SWIPE.sens, accumulator: 0, events: [], result: null, order: riders.map(r => r.id), taken: {} };
|
||||
}
|
||||
@@ -258,12 +263,33 @@ export function steering(c) {
|
||||
// La distanza davanti, sul giro, da s a un punto della pista (sempre fra 0 e la lunghezza del giro).
|
||||
export const ahead = (from, to) => ((((to - from) % TRACK.length) + TRACK.length) % TRACK.length);
|
||||
|
||||
// Lo sterzo che segue la strada: si guarda un punto più avanti sulla propria corsia e ci si gira (gli avversari, e
|
||||
// Andrea nella guida semplice). Andando troppo forte in curva lo sterzo non basta e si scivola fuori.
|
||||
const follow = r => { const aim = pointAt(r.s + 14 + r.v * 0.18, r.lane * 0.6); return clamp(wrapAngle(Math.atan2(aim.y - r.y, aim.x - r.x) - r.a) * 2.6, -1, 1); };
|
||||
// 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ù
|
||||
// quanto si esagera (SIMPLE.slide): a tutto gas, nelle curve vere si finisce nella paglia.
|
||||
export const SIMPLE = { grip: 1, slide: 8, window: 50 };
|
||||
export function simpleLimit(r, s = r.s) {
|
||||
let low = Infinity;
|
||||
for (let d = -10; d <= SIMPLE.window; d += STEP) low = Math.min(low, limitAt(s + d));
|
||||
return low * r.grip * SIMPLE.grip;
|
||||
}
|
||||
function autopilot(c, r) {
|
||||
const next = PICKUPS.find(p => p.kind !== 'boost' && ahead(r.s, p.s) < 30 + r.v * 0.6);
|
||||
// Chi sta davanti più lento si passa dall'altra parte; le buche si scansano, la miscela si prende.
|
||||
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 && o.v < r.v + 8);
|
||||
if (next?.kind === 'buca') r.lane = next.lane > 0 ? -5 : 5;
|
||||
else if (slow) r.lane = slow.lat > 0 ? -6 : 6;
|
||||
else if (next?.kind === 'miscela') r.lane = next.lane;
|
||||
r.excess = Math.max(0, r.v - simpleLimit(r));
|
||||
return follow(r);
|
||||
}
|
||||
// Gli avversari: guardano un punto più avanti sulla loro corsia e ci sterzano; danno gas finché, frenando da qui,
|
||||
// arriverebbero a ogni curva davanti sotto il limite (un po' meno, quanto si fidano). Chi ha poca testa ogni tanto
|
||||
// si fida troppo.
|
||||
function drive(c, r) {
|
||||
const aim = pointAt(r.s + 14 + r.v * 0.18, r.lane * 0.6);
|
||||
r.steer = clamp(wrapAngle(Math.atan2(aim.y - r.y, aim.x - r.x) - r.a) * 2.6, -1, 1);
|
||||
r.steer = follow(r);
|
||||
const curve = CURVES.find(k => { const s = ((r.s % TRACK.length) + TRACK.length) % TRACK.length; return s >= k.start - 60 && s < k.start - 57; });
|
||||
if (curve && r.lastCurve !== curve) { r.lastCurve = curve; r.risk = random(c) < r.slip ? 1.15 : 1; }
|
||||
const horizon = (r.v * r.v) / (2 * BRAKE) + 20;
|
||||
@@ -284,6 +310,7 @@ function drive(c, r) {
|
||||
function stepRider(c, r, dt) {
|
||||
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.id === 'andrea' && simple(c)) { r.gas = c.holding; r.brake = !c.holding; r.steer = autopilot(c, r); }
|
||||
else if (r.id === 'andrea') { r.gas = c.holding; r.brake = c.braking; r.steer = steering(c); }
|
||||
else drive(c, r);
|
||||
if (r.finished !== null) r.gas = false;
|
||||
@@ -299,6 +326,7 @@ function stepRider(c, r, dt) {
|
||||
const turn = r.steer * TURN * Math.min(1, r.v / 22) * dt;
|
||||
r.a = wrapAngle(r.a + turn);
|
||||
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
|
||||
r.u *= Math.exp(-GRIP * r.grip * 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;
|
||||
@@ -406,7 +434,7 @@ export function status(c) {
|
||||
const r = presenting(c);
|
||||
return r ? `${r.name} con ${r.bike}. (Un tocco: il prossimo.)` : 'Tutti in griglia!';
|
||||
}
|
||||
if (c.step === 'start') return c.t < COUNTDOWN - 0.01 ? `GP di Fortezza: si parte fra ${Math.ceil(COUNTDOWN - c.t)}… Tieni premuto per il gas, sterza con ← →.` : 'Via!';
|
||||
if (c.step === 'start') return c.t < COUNTDOWN - 0.01 ? `GP di Fortezza: si parte fra ${Math.ceil(COUNTDOWN - c.t)}… Tieni premuto per il gas, ${simple(c) ? 'lascia per frenare' : 'sterza con ← →'}.` : 'Via!';
|
||||
if (c.step === 'result') {
|
||||
const who = c.riders.find(r => r.id === c.result.order[0]).name;
|
||||
return c.result.place === 1 ? `Primo al GP di Fortezza! ${LAPS} giri in ${c.result.times.andrea.toFixed(1)} secondi.` : `${c.result.place}° posto. Ha vinto ${who}.`;
|
||||
@@ -414,6 +442,7 @@ export function status(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.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.'}`;
|
||||
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';
|
||||
|
||||
+8
-5
@@ -606,7 +606,7 @@ const MINI = {
|
||||
micro: { ...micro, back: 'Torna dal Chiaru', label: 'La pista col gesso: muovi il mouse o tocca il parcheggio per mirare, tieni premuto per caricare la spinta e lascia; oppure regola la forza e premi Spingi. Frecce o pulsanti Mira per girare.' },
|
||||
basket: { ...basket, back: 'Torna in strada', label: 'Fai due tiri: muovi il mouse o tocca per mirare, tieni premuto per caricare la forza, lascia per tirare.' },
|
||||
// La gara: niente mira né forza, il pulsante alterna gas e freno (chi non può tenere premuto).
|
||||
gara: { ...gara, toggle: true, back: 'Torna dal Trappola', label: 'GP di Fortezza: tieni premuto sulla scena per il gas e trascina verso la direzione in cui vuoi andare sullo schermo; alza il dito per mollare. Anche col mouse. Oppure Spazio, Invio o ↑ per il gas, ← → per sterzare, ↓ per frenare; disponibili anche i pulsanti Gas, Sterza e Frena.' },
|
||||
gara: { ...gara, toggle: true, back: 'Torna dal Trappola', label: 'GP di Fortezza. Alla bona e Seria: tieni premuto sulla scena (o Spazio) per il gas e lascia per frenare, lo sterzo va da solo; in curva troppo forte si esce. Ginocchio in terra: tieni premuto sulla scena per il gas e trascina verso la direzione in cui vuoi andare sullo schermo; alza il dito per mollare. Anche col mouse. Oppure Spazio, Invio o ↑ per il gas, ← → per sterzare, ↓ per frenare; disponibili anche i pulsanti Gas, Sterza e Frena.' },
|
||||
pesca: { ...pesca, back: 'Torna in Fortezza', label: 'La pesca: tieni premuto per lanciare, premi quando il galleggiante va sotto, tieni premuto per recuperare.' },
|
||||
};
|
||||
const miniState = () => running && game?.closeup && MINI[story.closeups[game.closeup.id].type] ? game.closeup : null;
|
||||
@@ -1011,19 +1011,22 @@ function updateUi() {
|
||||
// Il GP di Fortezza: niente forza, e i due pulsanti di mira diventano lo sterzo (si tengono premuti).
|
||||
// Nel GP di Fortezza il cursore della forza diventa la sensibilità dello sterzo col dito (ricordata).
|
||||
const powerLabel = $('bocce-power').closest('label');
|
||||
// Ai rinquarti la forza è sempre piena: il cursore non serve.
|
||||
setProp(powerLabel, 'hidden', def.type === 'rinquarto');
|
||||
// Ai rinquarti la forza è sempre piena: il cursore non serve. Nella guida semplice del GP (alla bona e seria) si
|
||||
// usa solo il gas: niente sensibilità, sterzo, freno né riquadro dello swipe.
|
||||
const easy = def.type === 'gara' && gara.simple(c);
|
||||
setProp(powerLabel, 'hidden', def.type === 'rinquarto' || easy);
|
||||
for (const id of ['bocce-left', 'bocce-right']) setProp($(id), 'hidden', easy);
|
||||
const powerName = def.type === 'gara' ? 'Sensibilità ' : 'Forza ';
|
||||
if (powerLabel.firstChild.nodeValue !== powerName) powerLabel.firstChild.nodeValue = powerName;
|
||||
setAttr($('bocce-power'), 'aria-label', def.type === 'gara' ? 'Sensibilità dello sterzo col dito' : 'Forza del lancio');
|
||||
setProp($('gara-pad'), 'hidden', def.type !== 'gara');
|
||||
setProp($('gara-pad'), 'hidden', def.type !== 'gara' || easy);
|
||||
if (def.type === 'gara' && c.sensLoaded === undefined) {
|
||||
c.sensLoaded = true;
|
||||
try { const v = localStorage.getItem(GARA_SENS); if (v !== null) gara.setPower(c, Number(v) / 100); } catch { /* si resta al valore di partenza */ }
|
||||
}
|
||||
setText($('bocce-left'), def.type === 'gara' ? '← Sterza' : '← Mira');
|
||||
setText($('bocce-right'), def.type === 'gara' ? 'Sterza →' : 'Mira →');
|
||||
setProp($('gara-brake'), 'hidden', def.type !== 'gara');
|
||||
setProp($('gara-brake'), 'hidden', def.type !== 'gara' || easy);
|
||||
setProp($('gara-brake'), 'disabled', !(mg.canAdjust?.(c) ?? false) || !!game.speech);
|
||||
setText($('bocce-throw'), mg.button?.(c) ?? (c.step === 'result' ? 'Finita' : c.step === 'rolling' ? 'In movimento…' : c.current === 'cpu' ? `Tira ${def.names.cpu}…` : def.type === 'bocce' ? 'Lancia' : 'Tira'));
|
||||
setProp($('bocce-help'), 'hidden', def.type !== 'bocce');
|
||||
|
||||
Reference in New Issue
Block a user