Difficoltà alle bocce e al biliardo, scelta nel dialogo con l'avversario: «Alla bona», «Seria», «A fassi male». Tarata con partite simulate contro un giocatore attento: al biliardo vince 9 su 12, circa 4 su 10 e quasi mai; alle bocce 72%, 50% e 35% delle mani. Battute nuove per Mariano e l'Ingegnere, test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ks2K99uqCZjAxvy7Rdj1R
This commit is contained in:
+15
-6
@@ -36,8 +36,16 @@ export function reach(v) {
|
||||
export const canThrow = c => c.step === 'aim' && c.current === 'player';
|
||||
export const moving = c => list(c).some(b => b.vx || b.vy);
|
||||
|
||||
// Quanto è bravo l'Ingegnere: quanti colpi prova a vuoto (direzioni intorno al tavolo, ritocchi vicino alla biglia
|
||||
// avversaria, forze) e quanto gli trema la mano. «A fassi male» è il campione vero: vince quasi sempre.
|
||||
export const LEVELS = {
|
||||
bona: { angles: 0, fine: 0.15, powers: [0.6], wobble: 0.16, powerNoise: 0.2 },
|
||||
seria: { angles: 14, fine: 0.09, powers: [0.5, 0.8], wobble: 0.072, powerNoise: 0.1 },
|
||||
male: { angles: 64, fine: 0.025, powers: [0.3, 0.5, 0.7, 0.9], wobble: 0.03, powerNoise: 0.05 },
|
||||
};
|
||||
export function newBiliardo(seed = Date.now() >>> 0, def = {}) {
|
||||
const c = { step: 'aim', t: 0, seed, current: 'player', target: def.target ?? TARGET, power: 0.45, angle: 0,
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
const c = { step: 'aim', t: 0, seed, level, skill: LEVELS[level], current: 'player', target: def.target ?? TARGET, power: 0.45, angle: 0,
|
||||
holding: false, holdTime: 0, accumulator: 0, events: [], points: { player: 0, cpu: 0 }, last: null, shot: null, plan: null };
|
||||
c.balls = { player: ball('player', LEN * 0.18, WID * 0.5), cpu: ball('cpu', LEN * 0.81, WID * 0.25), pallino: ball('pallino', LEN * 0.88, WID * 0.7) };
|
||||
c.pins = castle();
|
||||
@@ -210,10 +218,10 @@ function finish(c) {
|
||||
// L'avversario prova tanti colpi a vuoto e sceglie quello che rende di più; poi la mano gli trema un poco.
|
||||
// Le prove si fanno a pezzi, un po' per fotogramma: tutte insieme fermerebbero il gioco per un decimo di secondo.
|
||||
function cpuCandidates(c) {
|
||||
const base = angleTo(c.balls.cpu, c.balls.player), angles = [];
|
||||
for (let i = 0; i < 64; i++) angles.push(i / 64 * Math.PI * 2);
|
||||
for (let d = -0.3; d <= 0.3; d += 0.025) angles.push(base + d);
|
||||
return angles.flatMap(angle => [0.3, 0.5, 0.7, 0.9].map(power => ({ angle, power })));
|
||||
const k = c.skill ?? LEVELS.male, base = angleTo(c.balls.cpu, c.balls.player), angles = [];
|
||||
for (let i = 0; i < k.angles; i++) angles.push(i / k.angles * Math.PI * 2);
|
||||
for (let d = -0.3; d <= 0.3; d += k.fine) angles.push(base + d);
|
||||
return angles.flatMap(angle => k.powers.map(power => ({ angle, power })));
|
||||
}
|
||||
function cpuThink(c, budget = 12) {
|
||||
const t = c.thinking ??= { todo: cpuCandidates(c), best: null };
|
||||
@@ -222,7 +230,8 @@ function cpuThink(c, budget = 12) {
|
||||
if (!t.best || gain > t.best.gain) t.best = { angle, power, gain };
|
||||
}
|
||||
if (t.todo.length) return;
|
||||
c.plan = { angle: t.best.angle + deviation(c) * (c.wobble ?? 0.03), power: clamp(t.best.power + deviation(c) * 0.05, 0.05, 1) };
|
||||
const k = c.skill ?? LEVELS.male;
|
||||
c.plan = { angle: t.best.angle + deviation(c) * (c.wobble ?? k.wobble), power: clamp(t.best.power + deviation(c) * k.powerNoise, 0.05, 1) };
|
||||
c.angle = c.plan.angle; c.power = c.plan.power; c.thinking = null;
|
||||
}
|
||||
|
||||
|
||||
+14
-5
@@ -19,8 +19,16 @@ export const powerFor = distance => clamp((distance - 8) / 96, 0, 1);
|
||||
export const moving = c => [c.jack, ...c.balls].some(b => Math.hypot(b.vu, b.vw) > EPS);
|
||||
export const closest = (c, who) => c.balls.filter(b => b.who === who).sort((a, b) => gap(a, c.jack) - gap(b, c.jack))[0];
|
||||
|
||||
export function newBocce(seed = Date.now() >>> 0) {
|
||||
const c = { step: 'aim', t: 0, age: 0, seed, balls: [], turn: 0, current: 'player', power: 0.5,
|
||||
// Quanto è bravo Mariano: quanto si allarga il suo tiro e quanto spesso prova a bocciare la boccia migliore.
|
||||
// «A fassi male» è il campione vero del pallaio.
|
||||
export const LEVELS = {
|
||||
bona: { spread: 3, attack: 0.1 },
|
||||
seria: { spread: 1.15, attack: 0.4 },
|
||||
male: { spread: 0.22, attack: 0.85 },
|
||||
};
|
||||
export function newBocce(seed = Date.now() >>> 0, def = {}) {
|
||||
const level = LEVELS[def.level] ? def.level : 'seria';
|
||||
const c = { step: 'aim', t: 0, age: 0, seed, level, skill: LEVELS[level], balls: [], turn: 0, current: 'player', power: 0.5,
|
||||
holding: false, holdTime: 0, accumulator: 0, events: [], trails: [], travel: 0, lastThrower: null };
|
||||
c.jack = { u: 45 + random(c) * 18, w: (random(c) - 0.5) * 5, vu: 0, vw: 0, r: JACK, mass: 0.1, jack: true, spin: 0 };
|
||||
c.aim = { u: c.jack.u, w: c.jack.w };
|
||||
@@ -134,10 +142,11 @@ function nextTurn(c) {
|
||||
function cpuThrow(c) {
|
||||
// Mariano cerca un accosto libero; con un avversario attaccato al pallino può
|
||||
// tentare di spostarlo. Usa gli stessi lanci e la stessa fisica del giocatore.
|
||||
const other = closest(c, 'player'), attack = other && gap(other, c.jack) < 0.6 && random(c) < 0.45;
|
||||
const k = c.skill ?? LEVELS.male;
|
||||
const other = closest(c, 'player'), attack = other && gap(other, c.jack) < 0.6 && random(c) < k.attack;
|
||||
const target = attack ? other : c.jack;
|
||||
const u = clamp(target.u + deviation(c) * (attack ? 0.5 : 2.2), 10, LANE - 2);
|
||||
const w = clamp(target.w + deviation(c) * (attack ? 0.18 : 0.8), -HALF + BALL, HALF - BALL);
|
||||
const u = clamp(target.u + deviation(c) * (attack ? 0.5 : 2.2) * k.spread, 10, LANE - 2);
|
||||
const w = clamp(target.w + deviation(c) * (attack ? 0.18 : 0.8) * k.spread, -HALF + BALL, HALF - BALL);
|
||||
launch(c, 'cpu', u, w, Math.hypot(u, w) + (attack ? 14 : -0.35));
|
||||
}
|
||||
export function update(c, dt) {
|
||||
|
||||
+4
-3
@@ -6,8 +6,8 @@ import * as biliardo from './biliardo.js';
|
||||
|
||||
// I minigiochi in primo piano: stessi comandi (mira, pressione, rilascio), motori diversi.
|
||||
export const MINIGAMES = {
|
||||
bocce: { create: seed => bocce.newBocce(seed), update: bocce.update, press: bocce.press, release: bocce.release, cancel: bocce.cancel, aim: bocce.aimScreen },
|
||||
biliardo: { create: seed => biliardo.newBiliardo(seed), update: biliardo.update, press: biliardo.press, release: biliardo.release, cancel: biliardo.cancel, aim: biliardo.aimScreen },
|
||||
bocce: { create: (seed, level) => bocce.newBocce(seed, { level }), update: bocce.update, press: bocce.press, release: bocce.release, cancel: bocce.cancel, aim: bocce.aimScreen },
|
||||
biliardo: { create: (seed, level) => biliardo.newBiliardo(seed, { level }), update: biliardo.update, press: biliardo.press, release: biliardo.release, cancel: biliardo.cancel, aim: biliardo.aimScreen },
|
||||
};
|
||||
|
||||
export const VERBS = ['walk', 'look', 'take', 'use', 'talk', 'open', 'give', 'drop'];
|
||||
@@ -439,7 +439,8 @@ export class Game {
|
||||
else if (e.closeup) {
|
||||
const type = this.story.closeups[e.closeup].type;
|
||||
this.closeup = type === 'torch' ? { id: e.closeup, step: 'search', t: 0, age: 0, found: 0, dwell: 0, beam: [320, 180] }
|
||||
: MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296)) }
|
||||
// `level`: «Alla bona», «Seria» o «A fassi male», scelto nel dialogo con l'avversario.
|
||||
: MINIGAMES[type] ? { id: e.closeup, ...MINIGAMES[type].create(Math.floor(Math.random() * 4294967296), e.level) }
|
||||
: { id: e.closeup, step: 'cover', t: 0, age: 0 };
|
||||
}
|
||||
else throw new Error('Effetto sconosciuto: ' + JSON.stringify(e));
|
||||
|
||||
+22
-10
@@ -2256,18 +2256,26 @@ export const story = {
|
||||
start: {
|
||||
options: [
|
||||
{
|
||||
text: 'Mariano! Facciamo una partita a bocce?', when: '!bocce_vinte', next: 'end',
|
||||
text: 'Mariano! Facciamo una partita a bocce?', when: '!bocce_vinte', next: 'livello',
|
||||
then: [
|
||||
{ say: 'Con me? Al pallaio sono il campione.', who: 'mariano', pose: 'explain' },
|
||||
{ say: 'Facciamo così: se mi batti, ti porto a Brento Sanico.', who: 'mariano' },
|
||||
{ closeup: 'bocce' },
|
||||
{ say: 'Come la vuoi?', who: 'mariano' },
|
||||
],
|
||||
},
|
||||
{ text: 'La rivincita?', when: 'bocce_vinte', next: 'end', then: [{ say: 'Il campione non scappa. Tira!', who: 'mariano', pose: 'throw' }, { closeup: 'bocce' }] },
|
||||
{ text: 'La rivincita?', when: 'bocce_vinte', next: 'livello', then: [{ say: 'Il campione non scappa. Come la vuoi?', who: 'mariano', pose: 'throw' }] },
|
||||
{ text: 'Allora, Brento Sanico?', when: 'bocce_vinte', then: [{ say: 'Promesso: ti ci porto. Ma prima ritrova il palio, che senza festa non si parte.', who: 'mariano', pose: 'explain' }] },
|
||||
{ text: 'Ciao Mariano.', next: 'end' },
|
||||
],
|
||||
},
|
||||
// La difficoltà (Andrea, 25 ottobre): quanto si impegna Mariano.
|
||||
livello: {
|
||||
options: [
|
||||
{ text: 'Alla bona.', next: 'end', then: [{ say: 'Alla bona: tiro con gli occhi chiusi.', who: 'mariano', pose: 'laugh' }, { closeup: 'bocce', level: 'bona' }] },
|
||||
{ text: 'Seria.', next: 'end', then: [{ say: 'Seria. Così mi piace.', who: 'mariano' }, { closeup: 'bocce', level: 'seria' }] },
|
||||
{ text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Poi non piangere, eh.', who: 'mariano', pose: 'cheer' }, { closeup: 'bocce', level: 'male' }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
ing_tozzi: {
|
||||
@@ -2275,17 +2283,21 @@ export const story = {
|
||||
start: {
|
||||
options: [
|
||||
{
|
||||
text: 'Ingegnere, una partita ai birilli?', when: '!biliardo_vinto', next: 'end',
|
||||
then: [
|
||||
{ say: 'Una partita ai birilli? Si va a venti.', who: 'ing_tozzi', pose: 'chalk' },
|
||||
{ say: 'Gnamo.' },
|
||||
{ closeup: 'biliardo' },
|
||||
],
|
||||
text: 'Ingegnere, una partita ai birilli?', when: '!biliardo_vinto', next: 'livello',
|
||||
then: [{ say: 'Una partita ai birilli? Si va a venti. Come la vuoi?', who: 'ing_tozzi', pose: 'chalk' }],
|
||||
},
|
||||
{ text: 'La rivincita?', when: 'biliardo_vinto', next: 'end', then: [{ say: 'Ci mancherebbe. Stavolta guardo bene.', who: 'ing_tozzi', pose: 'explain' }, { closeup: 'biliardo' }] },
|
||||
{ text: 'La rivincita?', when: 'biliardo_vinto', next: 'livello', then: [{ say: 'Ci mancherebbe. Stavolta guardo bene. Come la vuoi?', who: 'ing_tozzi', pose: 'explain' }] },
|
||||
{ text: 'Arrivederci, Ingegnere.', next: 'end' },
|
||||
],
|
||||
},
|
||||
// La difficoltà: quanti colpi prova a vuoto l'Ingegnere, e quanto gli trema la mano.
|
||||
livello: {
|
||||
options: [
|
||||
{ text: 'Alla bona.', next: 'end', then: [{ say: 'Alla bona: tiro senza occhiali.', who: 'ing_tozzi', pose: 'laugh' }, { say: 'Gnamo.' }, { closeup: 'biliardo', level: 'bona' }] },
|
||||
{ text: 'Seria.', next: 'end', then: [{ say: 'Seria. Dammi il gesso.', who: 'ing_tozzi', pose: 'chalk' }, { say: 'Gnamo.' }, { closeup: 'biliardo', level: 'seria' }] },
|
||||
{ text: 'A fassi male.', next: 'end', then: [{ say: 'A fassi male? Allora mi tolgo la giacca. Ah no, non ce l\'ho.', who: 'ing_tozzi', pose: 'explain' }, { say: 'Gnamo.' }, { closeup: 'biliardo', level: 'male' }] },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Lo "psicologo": il castello ascolta e risponde con un indizio su quello che manca.
|
||||
|
||||
Reference in New Issue
Block a user