← back to Games Agentabrams
Baguette Brawl: add Training mode (re-inlined build)
0b14433d245bd363f8603e29751134de416c72df · 2026-08-31 13:37:44 -0700 · Steve Abrams
Files touched
M games/baguette-brawl/BaguetteBrawl.aaM games/baguette-brawl/index.html
Diff
commit 0b14433d245bd363f8603e29751134de416c72df
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 31 13:37:44 2026 -0700
Baguette Brawl: add Training mode (re-inlined build)
---
games/baguette-brawl/BaguetteBrawl.aa | 88 +++++++++++++++++++++++++++--------
games/baguette-brawl/index.html | 88 +++++++++++++++++++++++++++--------
2 files changed, 138 insertions(+), 38 deletions(-)
diff --git a/games/baguette-brawl/BaguetteBrawl.aa b/games/baguette-brawl/BaguetteBrawl.aa
index 2ef86e1..e958118 100644
--- a/games/baguette-brawl/BaguetteBrawl.aa
+++ b/games/baguette-brawl/BaguetteBrawl.aa
@@ -62,8 +62,10 @@ button.play{
box-shadow:0 8px 24px rgba(198,95,56,.4);transition:transform .16s ease,box-shadow .16s ease;
}
button.play.alt{background:linear-gradient(135deg,#5c7d8f,#38505d);box-shadow:0 8px 24px rgba(75,107,122,.4)}
+button.play.train{background:linear-gradient(135deg,#6a8f5c,#3d5d38);box-shadow:0 8px 24px rgba(106,143,92,.4)}
button.play:hover{transform:translateY(-2px);box-shadow:0 16px 40px rgba(198,95,56,.5)}
button.play.alt:hover{box-shadow:0 16px 40px rgba(75,107,122,.5)}
+button.play.train:hover{box-shadow:0 16px 40px rgba(106,143,92,.5)}
.diffrow{display:flex;align-items:center;gap:10px;font-size:13px;color:#e0d7c9;
animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.22s;}
@@ -132,6 +134,7 @@ kbd{
<div class="btns">
<button class="play" data-mode="1">Solo vs. Chef IA</button>
<button class="play alt" data-mode="2">Deux Joueurs</button>
+ <button class="play train" data-mode="3">Entraînement</button>
</div>
<div class="controls">
<div class="ctrlcard">
@@ -147,7 +150,7 @@ kbd{
<div class="row"><span>Guard · Super</span><span><kbd>/</kbd><kbd>;</kbd></span></div>
</div>
</div>
- <div class="foot">Pause <kbd>P</kbd> · Mute <kbd>M</kbd> · Un jeu de pain · Paris</div>
+ <div class="foot">Pause <kbd>P</kbd> · Mute <kbd>M</kbd> · Entraînement: <kbd>1</kbd>/<kbd>2</kbd>/<kbd>3</kbd> dummy, <kbd>R</kbd> reset · Un jeu de pain · Paris</div>
</div>
<!-- PAUSE -->
@@ -272,6 +275,12 @@ function handleGlobalKeys(k){
if(k==='m'){ muted=!muted; }
if(state==='fight'){
if(k==='p'||k==='Escape'){ togglePause(); }
+ if(training){
+ if(k==='1') dummyMode='stand';
+ else if(k==='2') dummyMode='block';
+ else if(k==='3') dummyMode='cpu';
+ else if(k==='r') resetTraining();
+ }
} else if(state==='paused'){
if(k==='p'||k==='Escape'){ togglePause(); }
}
@@ -310,7 +319,7 @@ function makeFighter(x, facing, cfg){
swing:0, walkPhase:0,
color:cfg.color, beret:cfg.beret, name:cfg.name,
isAI:cfg.isAI||false, ctrl:cfg.ctrl,
- hitFlash:0, wins:0, _t:0, _think:0, combo:0, comboT:0
+ hitFlash:0, wins:0, _t:0, _think:0, combo:0, comboT:0, isDummy:false
};
}
@@ -319,6 +328,7 @@ let roundNum=1, roundTime=60, timeLeft=60, roundPhase='intro';
let freeze=0, shake=0, particles=[], clouds=[], stars=[], koTimer=null;
let flash=0, flashCol='#ffffff', zoom=0, comboShow={n:0,x:W/2,t:0};
let showAnnounce='', announceT=0, countdownT=0, lastCount=99;
+let training=false, dummyMode='stand', trainMax=0; // training-room state
const AI_TUNE = {
easy: {react:.55, aggr:.35, blockCh:.30, superCh:.15, whiff:.25, jumpCh:.006, speed:2.6},
@@ -333,10 +343,12 @@ function initClouds(){
initClouds();
function startGame(m){
- mode=m; roundNum=1;
+ mode=m; training=(m===3); roundNum=1;
f1=makeFighter(300, 1,{color:'#e0a458',beret:'#c62f2f',name:'Pierre',ctrl:P1CTRL});
- f2=makeFighter(660,-1,{color:'#d9974a',beret:'#2f4b6b',name:'Gaston',ctrl:P2CTRL,isAI:(m===1)});
+ f2=makeFighter(660,-1,{color:'#d9974a',beret:'#2f4b6b',name:training?'Sac':'Gaston',ctrl:P2CTRL,isAI:(m===1)});
f1.wins=0; f2.wins=0;
+ f2.isDummy=training;
+ if(training){ dummyMode='stand'; trainMax=0; }
actx();
startRound();
}
@@ -351,8 +363,8 @@ function startRound(){
resetFighter(f1,300,1);
resetFighter(f2,660,-1);
timeLeft=roundTime; particles=[]; freeze=0; shake=0; flash=0; zoom=0; comboShow={n:0,x:W/2,t:0};
- showAnnounce=`MANCHE ${roundNum}`; announceT=1.6; countdownT=3.2; lastCount=99;
- roundPhase='intro';
+ showAnnounce=training?'ENTRAÎNEMENT':`MANCHE ${roundNum}`; announceT=1.6; countdownT=3.2; lastCount=99;
+ roundPhase = training ? 'fight' : 'intro'; // training skips the countdown — jump straight in
state='fight';
}
@@ -385,7 +397,9 @@ function updateFighter(f, other, dt){
if(f.onGround && f.state!=='attack'){ f.facing = other.x < f.x ? -1 : 1; }
- if(!f.isAI){
+ if(f.isDummy){
+ dummyControl(f, other, dt);
+ } else if(!f.isAI){
let mv=0;
if(canAct && f.state!=='attack'){
if(keys.has(c.left)) mv-=1;
@@ -484,15 +498,25 @@ function landHit(attacker, target, a){
if(a.unblock){ flash=Math.max(flash,.55); flashCol='#ffe7a0'; zoom=Math.max(zoom,.06); }
else if(a.dmg>10){ zoom=Math.max(zoom,.035); }
}
- target.hp=Math.max(0,target.hp-dmg);
+ target.hp=Math.max(target.isDummy?1:0, target.hp-dmg); // training dummy never dies
target.vx = attacker.facing*kb;
if(!blocked && a.dmg>10){ target.vy=-4-(a.unblock?3:0); target.onGround=false; }
- if(target.hp<=0){
+ if(target.hp<=0 && !target.isDummy){
target.state='ko'; target.vx=attacker.facing*5; target.vy=-8; target.onGround=false;
flash=Math.max(flash,.7); flashCol='#ffffff'; zoom=Math.max(zoom,.08); sfx.ko();
}
}
+// ---------------------------------------------------------------------------
+// Training dummy — Immobile (stand) / Garde (auto-block) / CPU (spar)
+// ---------------------------------------------------------------------------
+function dummyControl(f, o, dt){
+ f.vx=0;
+ if(dummyMode==='block'){ f.blocking = (f.state!=='attack'); } // "Guard: All" — blocks even through a string
+ else if(dummyMode==='cpu'){ aiControl(f, o, dt); }
+ // 'stand' → just take it (physics/gravity still run in updateFighter)
+}
+
// ---------------------------------------------------------------------------
// AI
// ---------------------------------------------------------------------------
@@ -616,7 +640,18 @@ function addBtn(label, fn, alt){
const b=document.createElement('button'); b.className='play'+(alt?' alt':''); b.textContent=label;
b.onclick=fn; resBtns.appendChild(b);
}
-function toMenu(){ clearTimeout(koTimer); koTimer=null; state='menu'; resEl.classList.add('hide'); pauseEl.classList.add('hide'); menuEl.classList.remove('hide'); }
+function toMenu(){ clearTimeout(koTimer); koTimer=null; training=false; state='menu'; resEl.classList.add('hide'); pauseEl.classList.add('hide'); menuEl.classList.remove('hide'); }
+
+// training room: infinite meter, track best combo, regen the dummy between strings
+function trainingTick(dt){
+ f1.meter=f1.maxMeter;
+ if(f1.combo>trainMax) trainMax=f1.combo;
+ if(f1.combo===0 && f2.state!=='hurt' && f2.hp<100) f2.hp=Math.min(100, f2.hp+70*dt);
+}
+function resetTraining(){
+ resetFighter(f1,300,1); resetFighter(f2,660,-1);
+ f1.meter=f1.maxMeter; trainMax=0; particles=[]; flash=0; zoom=0; comboShow={n:0,x:W/2,t:0};
+}
function togglePause(){
if(state==='fight'){ state='paused'; pauseEl.classList.remove('hide'); }
else if(state==='paused'){ state='fight'; pauseEl.classList.add('hide'); }
@@ -832,20 +867,35 @@ function drawHUD(){
hpBar(W-30-360,26,360,f2,true,'Gaston',f2.wins);
meterBar(30,52,360,f1,false);
meterBar(W-30-360,52,360,f2,true);
- ctx.save();
- ctx.fillStyle='rgba(18,15,22,.7)'; roundedRect(W/2-40,20,80,54,10); ctx.fill();
- ctx.strokeStyle='rgba(224,164,88,.5)'; ctx.lineWidth=1.5; ctx.stroke();
- ctx.fillStyle='#f5f2ea'; ctx.font='800 30px Georgia,serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
- ctx.fillText(Math.ceil(Math.max(0,timeLeft)), W/2, 48);
- ctx.restore();
+ if(training){ drawTrainingHUD(); }
+ else {
+ ctx.save();
+ ctx.fillStyle='rgba(18,15,22,.7)'; roundedRect(W/2-40,20,80,54,10); ctx.fill();
+ ctx.strokeStyle='rgba(224,164,88,.5)'; ctx.lineWidth=1.5; ctx.stroke();
+ ctx.fillStyle='#f5f2ea'; ctx.font='800 30px Georgia,serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillText(Math.ceil(Math.max(0,timeLeft)), W/2, 48);
+ ctx.restore();
+ }
if(muted){ ctx.save(); ctx.fillStyle='rgba(245,242,234,.6)'; ctx.font='600 12px -apple-system,Arial';
ctx.textAlign='center'; ctx.fillText('🔇 muet (M)', W/2, 92); ctx.restore(); }
}
+function drawTrainingHUD(){
+ const labels={stand:'Immobile', block:'Garde', cpu:'CPU'};
+ ctx.save();
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillStyle='rgba(18,15,22,.72)'; roundedRect(W/2-195,18,390,42,10); ctx.fill();
+ ctx.strokeStyle='rgba(224,164,88,.4)'; ctx.lineWidth=1.5; ctx.stroke();
+ ctx.fillStyle='#e0a458'; ctx.font='700 12px -apple-system,Arial';
+ ctx.fillText(`ENTRAÎNEMENT · Sac: ${labels[dummyMode]} · Combo max: ${trainMax}`, W/2, 32);
+ ctx.fillStyle='rgba(240,230,210,.6)'; ctx.font='600 10px -apple-system,Arial';
+ ctx.fillText('[1] Immobile [2] Garde [3] CPU [R] Reset [P] Pause', W/2, 48);
+ ctx.restore();
+}
function hpBar(x,y,w,f,right,name,wins){
const h=20; ctx.save();
ctx.fillStyle='#f5f2ea'; ctx.font='700 15px -apple-system,Arial'; ctx.textBaseline='alphabetic';
ctx.textAlign=right?'right':'left'; ctx.fillText(name, right? x+w : x, y-6);
- for(let i=0;i<2;i++){ const px=right? x+w-8-i*16 : x+8+i*16;
+ if(!training) for(let i=0;i<2;i++){ const px=right? x+w-8-i*16 : x+8+i*16;
ctx.beginPath(); ctx.arc(px, y-11, 5, 0,7);
ctx.fillStyle = i<wins? '#e0a458':'rgba(255,255,255,.22)'; ctx.fill(); }
ctx.fillStyle='rgba(18,15,22,.6)'; roundedRect(x-3,y-3,w+6,h+6,7); ctx.fill();
@@ -917,10 +967,10 @@ function loop(now){
// idle facing during intro (light physics only)
if(f1&&f2){ f1.vx*=.8; f2.vx*=.8; f1.facing=f2.x<f1.x?-1:1; f2.facing=f1.x<f2.x?-1:1; }
} else if(roundPhase==='fight'){
- if(timeLeft>0) timeLeft-=dt;
+ if(!training && timeLeft>0) timeLeft-=dt;
updateFighter(f1,f2,dt);
updateFighter(f2,f1,dt);
- checkRoundEnd();
+ if(training) trainingTick(dt); else checkRoundEnd();
} else if(roundPhase==='ko'){
// let bodies settle in slow-mo
updateFighter(f1,f2,dt*0.4);
diff --git a/games/baguette-brawl/index.html b/games/baguette-brawl/index.html
index 2ef86e1..e958118 100644
--- a/games/baguette-brawl/index.html
+++ b/games/baguette-brawl/index.html
@@ -62,8 +62,10 @@ button.play{
box-shadow:0 8px 24px rgba(198,95,56,.4);transition:transform .16s ease,box-shadow .16s ease;
}
button.play.alt{background:linear-gradient(135deg,#5c7d8f,#38505d);box-shadow:0 8px 24px rgba(75,107,122,.4)}
+button.play.train{background:linear-gradient(135deg,#6a8f5c,#3d5d38);box-shadow:0 8px 24px rgba(106,143,92,.4)}
button.play:hover{transform:translateY(-2px);box-shadow:0 16px 40px rgba(198,95,56,.5)}
button.play.alt:hover{box-shadow:0 16px 40px rgba(75,107,122,.5)}
+button.play.train:hover{box-shadow:0 16px 40px rgba(106,143,92,.5)}
.diffrow{display:flex;align-items:center;gap:10px;font-size:13px;color:#e0d7c9;
animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.22s;}
@@ -132,6 +134,7 @@ kbd{
<div class="btns">
<button class="play" data-mode="1">Solo vs. Chef IA</button>
<button class="play alt" data-mode="2">Deux Joueurs</button>
+ <button class="play train" data-mode="3">Entraînement</button>
</div>
<div class="controls">
<div class="ctrlcard">
@@ -147,7 +150,7 @@ kbd{
<div class="row"><span>Guard · Super</span><span><kbd>/</kbd><kbd>;</kbd></span></div>
</div>
</div>
- <div class="foot">Pause <kbd>P</kbd> · Mute <kbd>M</kbd> · Un jeu de pain · Paris</div>
+ <div class="foot">Pause <kbd>P</kbd> · Mute <kbd>M</kbd> · Entraînement: <kbd>1</kbd>/<kbd>2</kbd>/<kbd>3</kbd> dummy, <kbd>R</kbd> reset · Un jeu de pain · Paris</div>
</div>
<!-- PAUSE -->
@@ -272,6 +275,12 @@ function handleGlobalKeys(k){
if(k==='m'){ muted=!muted; }
if(state==='fight'){
if(k==='p'||k==='Escape'){ togglePause(); }
+ if(training){
+ if(k==='1') dummyMode='stand';
+ else if(k==='2') dummyMode='block';
+ else if(k==='3') dummyMode='cpu';
+ else if(k==='r') resetTraining();
+ }
} else if(state==='paused'){
if(k==='p'||k==='Escape'){ togglePause(); }
}
@@ -310,7 +319,7 @@ function makeFighter(x, facing, cfg){
swing:0, walkPhase:0,
color:cfg.color, beret:cfg.beret, name:cfg.name,
isAI:cfg.isAI||false, ctrl:cfg.ctrl,
- hitFlash:0, wins:0, _t:0, _think:0, combo:0, comboT:0
+ hitFlash:0, wins:0, _t:0, _think:0, combo:0, comboT:0, isDummy:false
};
}
@@ -319,6 +328,7 @@ let roundNum=1, roundTime=60, timeLeft=60, roundPhase='intro';
let freeze=0, shake=0, particles=[], clouds=[], stars=[], koTimer=null;
let flash=0, flashCol='#ffffff', zoom=0, comboShow={n:0,x:W/2,t:0};
let showAnnounce='', announceT=0, countdownT=0, lastCount=99;
+let training=false, dummyMode='stand', trainMax=0; // training-room state
const AI_TUNE = {
easy: {react:.55, aggr:.35, blockCh:.30, superCh:.15, whiff:.25, jumpCh:.006, speed:2.6},
@@ -333,10 +343,12 @@ function initClouds(){
initClouds();
function startGame(m){
- mode=m; roundNum=1;
+ mode=m; training=(m===3); roundNum=1;
f1=makeFighter(300, 1,{color:'#e0a458',beret:'#c62f2f',name:'Pierre',ctrl:P1CTRL});
- f2=makeFighter(660,-1,{color:'#d9974a',beret:'#2f4b6b',name:'Gaston',ctrl:P2CTRL,isAI:(m===1)});
+ f2=makeFighter(660,-1,{color:'#d9974a',beret:'#2f4b6b',name:training?'Sac':'Gaston',ctrl:P2CTRL,isAI:(m===1)});
f1.wins=0; f2.wins=0;
+ f2.isDummy=training;
+ if(training){ dummyMode='stand'; trainMax=0; }
actx();
startRound();
}
@@ -351,8 +363,8 @@ function startRound(){
resetFighter(f1,300,1);
resetFighter(f2,660,-1);
timeLeft=roundTime; particles=[]; freeze=0; shake=0; flash=0; zoom=0; comboShow={n:0,x:W/2,t:0};
- showAnnounce=`MANCHE ${roundNum}`; announceT=1.6; countdownT=3.2; lastCount=99;
- roundPhase='intro';
+ showAnnounce=training?'ENTRAÎNEMENT':`MANCHE ${roundNum}`; announceT=1.6; countdownT=3.2; lastCount=99;
+ roundPhase = training ? 'fight' : 'intro'; // training skips the countdown — jump straight in
state='fight';
}
@@ -385,7 +397,9 @@ function updateFighter(f, other, dt){
if(f.onGround && f.state!=='attack'){ f.facing = other.x < f.x ? -1 : 1; }
- if(!f.isAI){
+ if(f.isDummy){
+ dummyControl(f, other, dt);
+ } else if(!f.isAI){
let mv=0;
if(canAct && f.state!=='attack'){
if(keys.has(c.left)) mv-=1;
@@ -484,15 +498,25 @@ function landHit(attacker, target, a){
if(a.unblock){ flash=Math.max(flash,.55); flashCol='#ffe7a0'; zoom=Math.max(zoom,.06); }
else if(a.dmg>10){ zoom=Math.max(zoom,.035); }
}
- target.hp=Math.max(0,target.hp-dmg);
+ target.hp=Math.max(target.isDummy?1:0, target.hp-dmg); // training dummy never dies
target.vx = attacker.facing*kb;
if(!blocked && a.dmg>10){ target.vy=-4-(a.unblock?3:0); target.onGround=false; }
- if(target.hp<=0){
+ if(target.hp<=0 && !target.isDummy){
target.state='ko'; target.vx=attacker.facing*5; target.vy=-8; target.onGround=false;
flash=Math.max(flash,.7); flashCol='#ffffff'; zoom=Math.max(zoom,.08); sfx.ko();
}
}
+// ---------------------------------------------------------------------------
+// Training dummy — Immobile (stand) / Garde (auto-block) / CPU (spar)
+// ---------------------------------------------------------------------------
+function dummyControl(f, o, dt){
+ f.vx=0;
+ if(dummyMode==='block'){ f.blocking = (f.state!=='attack'); } // "Guard: All" — blocks even through a string
+ else if(dummyMode==='cpu'){ aiControl(f, o, dt); }
+ // 'stand' → just take it (physics/gravity still run in updateFighter)
+}
+
// ---------------------------------------------------------------------------
// AI
// ---------------------------------------------------------------------------
@@ -616,7 +640,18 @@ function addBtn(label, fn, alt){
const b=document.createElement('button'); b.className='play'+(alt?' alt':''); b.textContent=label;
b.onclick=fn; resBtns.appendChild(b);
}
-function toMenu(){ clearTimeout(koTimer); koTimer=null; state='menu'; resEl.classList.add('hide'); pauseEl.classList.add('hide'); menuEl.classList.remove('hide'); }
+function toMenu(){ clearTimeout(koTimer); koTimer=null; training=false; state='menu'; resEl.classList.add('hide'); pauseEl.classList.add('hide'); menuEl.classList.remove('hide'); }
+
+// training room: infinite meter, track best combo, regen the dummy between strings
+function trainingTick(dt){
+ f1.meter=f1.maxMeter;
+ if(f1.combo>trainMax) trainMax=f1.combo;
+ if(f1.combo===0 && f2.state!=='hurt' && f2.hp<100) f2.hp=Math.min(100, f2.hp+70*dt);
+}
+function resetTraining(){
+ resetFighter(f1,300,1); resetFighter(f2,660,-1);
+ f1.meter=f1.maxMeter; trainMax=0; particles=[]; flash=0; zoom=0; comboShow={n:0,x:W/2,t:0};
+}
function togglePause(){
if(state==='fight'){ state='paused'; pauseEl.classList.remove('hide'); }
else if(state==='paused'){ state='fight'; pauseEl.classList.add('hide'); }
@@ -832,20 +867,35 @@ function drawHUD(){
hpBar(W-30-360,26,360,f2,true,'Gaston',f2.wins);
meterBar(30,52,360,f1,false);
meterBar(W-30-360,52,360,f2,true);
- ctx.save();
- ctx.fillStyle='rgba(18,15,22,.7)'; roundedRect(W/2-40,20,80,54,10); ctx.fill();
- ctx.strokeStyle='rgba(224,164,88,.5)'; ctx.lineWidth=1.5; ctx.stroke();
- ctx.fillStyle='#f5f2ea'; ctx.font='800 30px Georgia,serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
- ctx.fillText(Math.ceil(Math.max(0,timeLeft)), W/2, 48);
- ctx.restore();
+ if(training){ drawTrainingHUD(); }
+ else {
+ ctx.save();
+ ctx.fillStyle='rgba(18,15,22,.7)'; roundedRect(W/2-40,20,80,54,10); ctx.fill();
+ ctx.strokeStyle='rgba(224,164,88,.5)'; ctx.lineWidth=1.5; ctx.stroke();
+ ctx.fillStyle='#f5f2ea'; ctx.font='800 30px Georgia,serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillText(Math.ceil(Math.max(0,timeLeft)), W/2, 48);
+ ctx.restore();
+ }
if(muted){ ctx.save(); ctx.fillStyle='rgba(245,242,234,.6)'; ctx.font='600 12px -apple-system,Arial';
ctx.textAlign='center'; ctx.fillText('🔇 muet (M)', W/2, 92); ctx.restore(); }
}
+function drawTrainingHUD(){
+ const labels={stand:'Immobile', block:'Garde', cpu:'CPU'};
+ ctx.save();
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillStyle='rgba(18,15,22,.72)'; roundedRect(W/2-195,18,390,42,10); ctx.fill();
+ ctx.strokeStyle='rgba(224,164,88,.4)'; ctx.lineWidth=1.5; ctx.stroke();
+ ctx.fillStyle='#e0a458'; ctx.font='700 12px -apple-system,Arial';
+ ctx.fillText(`ENTRAÎNEMENT · Sac: ${labels[dummyMode]} · Combo max: ${trainMax}`, W/2, 32);
+ ctx.fillStyle='rgba(240,230,210,.6)'; ctx.font='600 10px -apple-system,Arial';
+ ctx.fillText('[1] Immobile [2] Garde [3] CPU [R] Reset [P] Pause', W/2, 48);
+ ctx.restore();
+}
function hpBar(x,y,w,f,right,name,wins){
const h=20; ctx.save();
ctx.fillStyle='#f5f2ea'; ctx.font='700 15px -apple-system,Arial'; ctx.textBaseline='alphabetic';
ctx.textAlign=right?'right':'left'; ctx.fillText(name, right? x+w : x, y-6);
- for(let i=0;i<2;i++){ const px=right? x+w-8-i*16 : x+8+i*16;
+ if(!training) for(let i=0;i<2;i++){ const px=right? x+w-8-i*16 : x+8+i*16;
ctx.beginPath(); ctx.arc(px, y-11, 5, 0,7);
ctx.fillStyle = i<wins? '#e0a458':'rgba(255,255,255,.22)'; ctx.fill(); }
ctx.fillStyle='rgba(18,15,22,.6)'; roundedRect(x-3,y-3,w+6,h+6,7); ctx.fill();
@@ -917,10 +967,10 @@ function loop(now){
// idle facing during intro (light physics only)
if(f1&&f2){ f1.vx*=.8; f2.vx*=.8; f1.facing=f2.x<f1.x?-1:1; f2.facing=f1.x<f2.x?-1:1; }
} else if(roundPhase==='fight'){
- if(timeLeft>0) timeLeft-=dt;
+ if(!training && timeLeft>0) timeLeft-=dt;
updateFighter(f1,f2,dt);
updateFighter(f2,f1,dt);
- checkRoundEnd();
+ if(training) trainingTick(dt); else checkRoundEnd();
} else if(roundPhase==='ko'){
// let bodies settle in slow-mo
updateFighter(f1,f2,dt*0.4);
← 4eb380d Refine Baguette Brawl: combo system + damage scaling, impact
·
back to Games Agentabrams
·
TK-11051: Baguette Brawl — add Breton striped shirt + French 738e98d →