diff --git a/src/engine/biliardo.js b/src/engine/biliardo.js index f793c10..e0d5079 100644 --- a/src/engine/biliardo.js +++ b/src/engine/biliardo.js @@ -30,7 +30,10 @@ export function castle() { } export const powerAt = time => clamp(time / SWING, 0, 1); -export const speedFor = power => MIN_SPEED + (MAX_SPEED - MIN_SPEED) * power; +// Il tiro più forte: al biliardo dell'Ing. Tozzi si arriva a STRONG (Andrea, 29 ottobre: «più potenza»); i rinquarti +// restano a MAX_SPEED, perché le loro posizioni sono misurate a quella forza. +export const STRONG = 720; +export const speedFor = (power, max = MAX_SPEED) => MIN_SPEED + (max - MIN_SPEED) * power; // Quanta strada fa una biglia lanciata a velocità v, senza urti (attrito costante più un po' di resistenza). export function reach(v) { const k = FRICTION / DRAG, t = Math.log((v + k) / k) / DRAG; @@ -48,7 +51,7 @@ export const LEVELS = { }; export function newBiliardo(seed = Date.now() >>> 0, def = {}) { 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, + const c = { step: 'aim', t: 0, seed, level, skill: LEVELS[level], current: 'player', target: def.target ?? TARGET, maxSpeed: def.maxSpeed ?? STRONG, 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(); @@ -171,7 +174,7 @@ function step(w, dt) { export function strike(c, who, angle, power) { c.turn = null; - const b = c.balls[who], v = speedFor(power); + const b = c.balls[who], v = speedFor(power, c.maxSpeed); c.visual.shot = { at: c.visual.time, angle, power, x: b.x, y: b.y }; c.pending = { ball: b, vx: Math.cos(angle) * v, vy: Math.sin(angle) * v, power }; c.windup = WINDUP; @@ -183,7 +186,7 @@ export function strike(c, who, angle, power) { export function simulate(c, who, angle, power, { onContact, stop } = {}) { const balls = list(c).map(b => ({ ...b })), pins = c.pins.map(p => ({ ...p })); const w = { balls, pins, onContact, shot: { shooter: who, first: null, pallino: false, pins: [] } }; - const b = balls.find(x => x.role === who), v = speedFor(power); + const b = balls.find(x => x.role === who), v = speedFor(power, c.maxSpeed); b.vx = Math.cos(angle) * v; b.vy = Math.sin(angle) * v; for (let i = 0; i < 15 / FIXED_STEP && !stop?.() && balls.some(x => x.vx || x.vy); i++) step(w, FIXED_STEP); return judge(w.shot); @@ -343,7 +346,7 @@ export function predict(c) { const cached = predictions.get(c); if (cached?.key === key) return cached.result; const balls = list(c).map(b => ({ ...b })), me = balls.find(b => b.role === c.current); - const v = speedFor(c.power); + const v = speedFor(c.power, c.maxSpeed); me.vx = Math.cos(c.angle) * v; me.vy = Math.sin(c.angle) * v; const result = { points: [[me.x, me.y]], ghost: null, hit: null, dir: null, outgoing: [] }; const tracks = []; diff --git a/src/engine/rinquarto.js b/src/engine/rinquarto.js index 7df6395..0eb2401 100644 --- a/src/engine/rinquarto.js +++ b/src/engine/rinquarto.js @@ -152,7 +152,7 @@ function nextPosition(c) { export function newRinquarto(seed = Date.now() >>> 0, def = {}) { const level = LEVELS[def.level] ? def.level : 'seria'; - const c = { ...billiard.newBiliardo(seed), level, skill: LEVELS[level], position: 0, score: { andrea: 0, bertovo: 0 }, history: [], + const c = { ...billiard.newBiliardo(seed, { maxSpeed: billiard.MAX_SPEED }), level, skill: LEVELS[level], position: 0, score: { andrea: 0, bertovo: 0 }, history: [], order: LEVELS[level].training ? ['bertovo', 'andrea'] : ['andrea', 'bertovo'], ready: [], gen: null, fixedPower: 1, trail: [], last: false }; nextPosition(c); return c; diff --git a/tests/biliardo.browser.mjs b/tests/biliardo.browser.mjs index 435b1ea..4f415d5 100644 --- a/tests/biliardo.browser.mjs +++ b/tests/biliardo.browser.mjs @@ -128,11 +128,13 @@ try { const fixed = (await state()).angle; await page.mouse.move(target.r.x + target.r.width * 0.55, target.r.y + target.r.height * 0.8, { steps: 8 }); assert.equal((await state()).angle, fixed, 'spostarsi verso Tira non deve cambiare la mira'); + // Le frecce girano finché sono giù (sempre più svelte); un colpo secco è un passo, con Maiusc un passo fine. await page.keyboard.press('ArrowRight'); const nudged = (await state()).angle; - assert.ok(Math.abs(nudged - fixed - 0.002) < 1e-10); + assert.ok(nudged - fixed >= 0.03 - 1e-9 && nudged - fixed < 0.1, `passo: ${nudged - fixed}`); await page.keyboard.press('Shift+ArrowLeft'); - assert.ok(Math.abs((await state()).angle - nudged + 0.0005) < 1e-10); + const fine = nudged - (await state()).angle; + assert.ok(fine >= 0.0005 - 1e-9 && fine < 0.01, `passo fine: ${fine}`); await page.locator('#bocce-power').fill('60'); const prepared = await state(); await page.screenshot({ path: `${out}/${name}-mira.png` }); @@ -158,7 +160,8 @@ try { const cdp = mobile ? await page.context().newCDPSession(page) : null; if (mobile) await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x: hold.x, y: hold.y }] }); else { await page.mouse.move(hold.x, hold.y); await page.mouse.down(); } - await page.waitForFunction(() => game.closeup.holding && game.closeup.power > 0.25); + // Si tiene premuto: dopo LONG_PRESS la stecca comincia a caricare. + await page.waitForFunction(() => game.closeup.holding && game.closeup.charging); const heldAngle = (await state()).angle; if (mobile) await cdp.send('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints: [{ x: hold.x - 30, y: hold.y + 20 }] }); else await page.mouse.move(hold.x - 30, hold.y + 20, { steps: 4 }); diff --git a/tests/biliardo.test.mjs b/tests/biliardo.test.mjs index d6689d4..0e37028 100644 --- a/tests/biliardo.test.mjs +++ b/tests/biliardo.test.mjs @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { newBiliardo, update, press, release, cancel, setPower, shoot, judge, value, reach, speedFor, predict, castle, aimAt, aimScreen, nudge, canThrow, collide, simulate, LEN, WID, R, VIEW, SWING, powerAt, FIXED_STEP, CUSHION, WINDUP } from '../src/engine/biliardo.js'; +import { newBiliardo, update, press, release, cancel, setPower, shoot, judge, value, reach, speedFor, predict, castle, aimAt, aimScreen, nudge, canThrow, collide, simulate, LEN, WID, R, VIEW, SWING, powerAt, FIXED_STEP, CUSHION, WINDUP, STRONG } from '../src/engine/biliardo.js'; import { story } from '../src/story/story.js'; import { Game } from '../src/engine/game.js'; import { settle } from './helpers.mjs'; @@ -34,6 +34,9 @@ test('la forza: sale finché si tiene premuto; a metà la biglia fa circa un tav assert.ok(reach(speedFor(0.5)) > LEN * 0.9 && reach(speedFor(0.5)) < LEN * 1.3, `a metà forza: ${reach(speedFor(0.5)).toFixed(0)} cm`); assert.ok(reach(speedFor(1)) > LEN * 2); assert.ok(reach(speedFor(0)) < 30, 'piano piano si sposta appena'); + // All'Ing. Tozzi il tiro pieno va più forte (Andrea, 29 ottobre); i rinquarti restano alla forza di prima. + assert.ok(reach(speedFor(1, STRONG)) > reach(speedFor(1)) * 1.3); + assert.equal(newBiliardo(1).maxSpeed, STRONG); }); test('un colpo dritto sulla biglia avversaria la manda avanti, poi tocca all\'altro', () => { @@ -126,14 +129,14 @@ test('la guida e il tiro finiscono nello stesso punto, anche dopo sponde oblique const end = p.points.at(-1); shoot(c); roll(c); near(c.balls.player.x, end[0]); near(c.balls.player.y, end[1]); - if (angle === 0) near(c.balls.player.x - x, reach(speedFor(power)), 0.01); + if (angle === 0) near(c.balls.player.x - x, reach(speedFor(power, c.maxSpeed)), 0.01); } }); test('la sponda smorza la componente normale: anche la guida cambia angolo al rimbalzo', () => { const c = emptyTable(); Object.assign(c.balls.player, { x: 270, y: 30 }); - c.angle = Math.PI / 4; c.power = 0.3; + c.angle = Math.PI / 4; c.power = 0.2; // la stessa velocità di 0,3 prima del tiro più forte const p = predict(c), [start, rail, end] = p.points; assert.equal(p.points.length, 3); near(rail[0], LEN - R);