Механика: escape — рыба сбегает, если долго не тянуть (красная полоса дистанции, тост, +10% цены) + 3 автотеста (61/61)
This commit is contained in:
@@ -10,3 +10,7 @@ tests/shot-*.png
|
||||
# Локальные worktree спринтов (Orca)
|
||||
sprint3-*/
|
||||
sprint2-*/
|
||||
sprint4-*/
|
||||
|
||||
# Секреты (никогда не коммитить)
|
||||
.gitea_token
|
||||
|
||||
+28
-2
@@ -187,7 +187,8 @@ button{font-family:inherit;cursor:pointer}
|
||||
Дальше бросок = реже и жирнее рыба!</p>
|
||||
<p style="margin-top:8px">Видишь <b style="color:#ff6b5e">!</b> — быстро жми, чтобы зацепить.<br>
|
||||
При вываживании: тяни, когда рыба отдыхает,<br>
|
||||
отпусти, когда она <b>тянет</b> — иначе лопнет!</p>
|
||||
отпусти, когда она <b>тянет</b> — иначе лопнет!<br>
|
||||
Не тяни слишком долго — рыба <b>сбегает</b> (красная полоса)!</p>
|
||||
<p style="margin-top:8px">У каждой рыбы свой <b>вес</b>, продажа — по цене за кг.<br>
|
||||
У удочек есть <b>предел веса</b>: перевесишь — шнур лопнет! <br>
|
||||
Лучшие снасти = больше шансов на трофей 🏆</p>
|
||||
@@ -726,7 +727,7 @@ function hook(){
|
||||
const aggr=Math.min(1, 0.35*str + 0.75*Math.min(1.25,ov)/1.25); // «злость» рыбы
|
||||
const d0=40+3.5*w; // чем крупнее, тем дальше от лодки
|
||||
const line=rod().line+achBonus().line; // прочность с бонусом достижений
|
||||
reel={d:d0, d0:d0, tension:0, holding:false, phase:'calm', phaseT:rand(0.8,1.5), sp:sp, w:w, ov:ov, aggr:aggr, tick:0, line:line, boss:!!sp.boss};
|
||||
reel={d:d0, d0:d0, tension:0, escape:0, holding:false, phase:'calm', phaseT:rand(0.8,1.5), sp:sp, w:w, ov:ov, aggr:aggr, tick:0, line:line, boss:!!sp.boss};
|
||||
}
|
||||
function doCast(){
|
||||
const p=Math.max(0.12,power);
|
||||
@@ -769,6 +770,25 @@ function updateReel(dt){
|
||||
if(pulling) reel.d+=(3+4*ag)*(0.5+0.4*ov)*dt; // крупная/тяжело несёт шнур
|
||||
}
|
||||
reel.tension=Math.max(0,reel.tension);
|
||||
// escape: рыба ускользает если долго не тянешь
|
||||
if(reel.holding){
|
||||
reel.escape=Math.max(0,reel.escape-0.5*dt);
|
||||
}else{
|
||||
reel.escape=Math.min(1,reel.escape+(0.2+ag*0.4)*dt);
|
||||
}
|
||||
if(reel.escape>=1){
|
||||
stateBoss=false; bossPhase=0;
|
||||
const nm=Math.floor(reel.w*sp.price*0.1);
|
||||
if(nm>0){
|
||||
save.coins+=nm; persist(); updateHUD();
|
||||
toast('Сбежала! '+sp.emoji+' '+sp.name+' '+reel.w.toFixed(1)+' кг · +'+nm+' 🪙');
|
||||
}else{
|
||||
toast('Сбежала! '+sp.emoji+' '+sp.name+' '+reel.w.toFixed(1)+' кг');
|
||||
}
|
||||
state='WAIT'; biteTimer=rand(5,10)/lure().bite;
|
||||
sfx('escape');
|
||||
return;
|
||||
}
|
||||
if(reel.tension>=reel.line){
|
||||
state='SNAP'; snapT=1.0; shake=0.5;
|
||||
save.snaps++; persist(); // счётчик обрывов (достижения)
|
||||
@@ -1326,6 +1346,12 @@ function drawMeters(){
|
||||
// --- дистанция (снизу) ---
|
||||
const dw=W-96, dx=22, dy=H-52, dh=13;
|
||||
ctx.fillStyle='rgba(0,0,0,0.35)'; rr(dx,dy,dw,dh,7); ctx.fill();
|
||||
// escape: красная заливка справа налево (рыба ускользает)
|
||||
if(reel.escape>0.01){
|
||||
const ew=(dw-4)*reel.escape;
|
||||
ctx.fillStyle='rgba(231,76,60,0.7)';
|
||||
rr(dx+dw-2-ew,dy+2,ew,dh-4,5); ctx.fill();
|
||||
}
|
||||
// rem = доля оставшейся дистанции: полоска «сгорает» к лодке, рыбка на её конце
|
||||
const rem=Math.max(0,Math.min(1,reel.d/reel.d0));
|
||||
if(rem>0.01){
|
||||
|
||||
@@ -735,6 +735,49 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
state = 'IDLE'; reel = null; stateBoss = false; bossPhase = 0; biteFish = null;
|
||||
});
|
||||
|
||||
// --- 9f. escape: рыба ускользает если долго не тянешь ---
|
||||
// полный цикл: IDLE → CHARGE → WAIT → BITE → REEL
|
||||
await page.mouse.move(195, 600);
|
||||
await page.mouse.down();
|
||||
await sleep(500);
|
||||
await page.mouse.up(); // бросок
|
||||
await waitState(page, 'WAIT');
|
||||
await page.evaluate(() => { biteTimer = 0.05; }); // ускоряем клёв
|
||||
await waitState(page, 'BITE', 4000);
|
||||
await page.mouse.down(); await page.mouse.up(); // подсечка
|
||||
await waitState(page, 'REEL');
|
||||
// ставим рыбу в покой, чтобы escape не мешал, и фиксируем aggr
|
||||
await page.evaluate(() => { reel.phase = 'calm'; reel.phaseT = 9999; reel.holding = false; });
|
||||
await sleep(100);
|
||||
const escBefore = await page.evaluate(() => reel.escape);
|
||||
await sleep(2000); // 2 с без удержания — escape растёт
|
||||
const escAfter = await page.evaluate(() => ({ escape: reel.escape, state }));
|
||||
ok('escape: растёт когда палец отпущен',
|
||||
escAfter.escape > escBefore && escAfter.state === 'REEL',
|
||||
JSON.stringify({ before: escBefore, after: escAfter.escape }));
|
||||
|
||||
// теперь жмём палец — escape должен уменьшаться
|
||||
await page.mouse.down();
|
||||
await sleep(800);
|
||||
const escHeld = await page.evaluate(() => reel.escape);
|
||||
await page.mouse.up();
|
||||
ok('escape: уменьшается когда палец нажат',
|
||||
escHeld < escAfter.escape,
|
||||
JSON.stringify({ afterHold: escHeld, was: escAfter.escape }));
|
||||
|
||||
// рыба сбегает при escape >= 1
|
||||
await page.evaluate(() => { reel.escape = 0.95; reel.holding = false; reel.phase = 'calm'; reel.phaseT = 9999; });
|
||||
await sleep(600);
|
||||
const escGone = await page.evaluate(() => ({ escape: reel.escape, state, toast: document.getElementById('toast').textContent }));
|
||||
ok('escape: рыба сбегает при escape >= 1 (state=WAIT, тост «Сбежала!»)',
|
||||
escGone.state === 'WAIT' && escGone.toast.includes('Сбежала!'),
|
||||
JSON.stringify(escGone));
|
||||
|
||||
// сброс для следующих тестов
|
||||
await page.evaluate(() => {
|
||||
state = 'IDLE'; reel = null; biteFish = null;
|
||||
});
|
||||
|
||||
// --- 10. Ошибки консоли ---
|
||||
ok('нет ошибок JS (pageerror)', pageErrors.length === 0, pageErrors.join(' | '));
|
||||
ok('нет ошибок консоли', consoleErrors.length === 0, consoleErrors.join(' | '));
|
||||
|
||||
Reference in New Issue
Block a user