Download brain_lab/static/mario.js from Chungulus/brain-lab: direct link, hf CLI and curl.
- Browser
- Download file 23.4 kB
-
https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/static/mario.js
- Command line
-
hf download hf://spaces/Chungulus/brain-lab/brain_lab/static/mario.js
-
curl -L -o mario.js https://huggingface.co/spaces/Chungulus/brain-lab/resolve/main/brain_lab/static/mario.js
23.4 kB
| import {api,card,escape,toast} from './common.js'; | |
| export async function mario(root){ | |
| root.innerHTML=card('Level 1 to Bowser’s castle.',`<p>Start at Level 1 and clear the 15 fixed Mario levels in order, then face Bowser in Level 16. Learners share an action-value table. When a round finishes with a win, the class advances to the next level. Choose Auto to advance after a clear, or Manual to use the Next level button. You can switch without resetting your learner.</p> | |
| <div class="mario-settings"><label>Course<select id="mario-course"><option value="campaign" selected>Campaign · Level 1 → Bowser</option>${Array.from({length:15},(_,i)=>`<option value="level-${i+1}">Level ${i+1} · practice this level</option>`).join('')}<option value="bowser">Level 16 · Bowser’s castle</option><option value="practice">Jump school · obstacles</option><option value="gaps">Gap school · pits + obstacles</option></select></label><label>Agents per round<input id="mario-population" type="number" min="1" max="128" value="32"></label><label>Target ticks per second<select id="mario-speed"><option value="30">30 · watch closely</option><option value="120" selected>120 · faster</option><option value="600">600 · fast learning</option><option value="2400">2,400 · turbo</option></select></label></div> | |
| <div class="world-controls"><label>Level progression <select id="mario-progression"><option value="auto" selected>Auto · advance after a clear</option><option value="manual">Manual · I choose when to advance</option></select></label><button id="mario-next" class="secondary" hidden>Next level</button><span class="note" id="mario-progression-note">Auto is on. Levels advance after a clear.</span></div> | |
| <p class="message" id="mario-level" aria-live="polite">Level 1 of 16 · Fixed campaign</p><p class="note" id="mario-objective">Reach the flag to advance. The levels always appear in the same order.</p><div class="mario-stage"><canvas id="mario-canvas" width="1000" height="420" tabindex="0" aria-label="Live Mario RL game. In human mode use arrow keys and Space to jump."></canvas><span id="mario-mode">FRESH POLICY · READY AT THE START</span></div> | |
| <div class="world-controls"><button id="mario-start" class="primary">Start learning</button><button id="mario-step" class="secondary">One tick</button><button id="mario-burst" class="secondary">Train 20 rounds</button><button id="mario-test" class="secondary">Test without exploration</button><button id="mario-human" class="secondary">Play yourself</button><button id="mario-reset" class="secondary">New learner from settings</button></div> | |
| <p id="mario-message" class="message" role="status">Your learners start with an empty action-value table. Try a test before learning, train a few rounds, then test again.</p> | |
| <div class="race-stats mario-stats"><div><strong id="mario-round">1</strong><span>round</span></div><div><strong id="mario-active">32</strong><span>active agents</span></div><div><strong id="mario-wins">0</strong><span>finishes this run</span></div><div><strong id="mario-experiences">0</strong><span>experiences</span></div><div><strong id="mario-updates">0</strong><span>Q-value updates</span></div><div><strong id="mario-states">0</strong><span>situations stored</span></div></div> | |
| <div class="mario-learning-controls"><label>Random exploration <output id="mario-exploration-value">35%</output><input id="mario-exploration" type="range" min="0" max="1" step=".05" value=".35"></label><label>Penalty for falling or being hit <output id="mario-penalty-value">12</output><input id="mario-penalty" type="range" min="0" max="30" value="12"></label></div> | |
| <p class="note">A tick advances the original game engine once. Speed changes how many game ticks we run per real second. The physics and learning rule stay the same. At high speeds the display samples frames, while all engine ticks still run. Ghost Marios are independent attempts; they do not collide with one another.</p> | |
| <div class="mario-analysis"><div><h3>How far is the class getting?</h3><canvas id="mario-chart" width="720" height="180" aria-label="Mean and best progress for each completed training round"></canvas><p class="note">Dark green: mean progress. Gold: best progress. A test uses no random exploration and makes no learning updates.</p></div><div><h3>The leader’s action values</h3><div id="mario-values"></div><p class="note">These are learned estimates of future reward, not probabilities. The agent sees nearby tiles, gaps, enemies, velocity, and whether it can jump.</p></div></div> | |
| <div class="world-controls"><button id="mario-save" class="secondary">Save learning locally</button><button id="mario-load" class="secondary">Load saved learning</button><span id="mario-save-note" class="note">Saving keeps the learned table for this classroom session.</span></div> | |
| <details class="simple-details"><summary>Rewards, source & what is actually learning</summary><p>Real tabular Q-learning updates one shared table after four game ticks. New forward distance earns points, coins earn extra points, falling or enemy collisions lose points, and reaching the flag earns a bonus. Random actions create new experience. There is no scripted jump controller, imitation teacher, pretrained policy, or search planner.</p><p>Jump school and Gap school are short teaching courses we made for the downloaded engine. Levels 1–15 are the framework’s fixed original level maps. Level 16 is our custom castle: Bowser patrols, jumps, and breathes fire. Stomp him three times or jump past him and touch the axe to drop his bridge, then reach the castle exit. The campaign never chooses a random world. Finishing a teaching course does not establish mastery of the full game.</p><div class="source-links"><a href="https://github.com/amidos2006/Mario-AI-Framework" target="_blank" rel="noreferrer">Downloaded Mario AI Framework ↗</a><a href="/static/mario-assets/SOURCE_LOCK.json" target="_blank">Pinned source receipt ↗</a></div><p class="note">Local, noncommercial educational research. Mario and the original graphical assets belong to Nintendo; the framework’s attribution notice is included with the downloaded source.</p></details>`,'REAL GAME ENGINE · ONLINE Q-LEARNING'); | |
| const $=s=>root.querySelector(s),canvas=$('#mario-canvas'),ctx=canvas.getContext('2d');let dirty=false,data,frame,tiles,width,alive=true,running=false,busy=false,human=false,humanView=false,generation=0,timer,raf,animationDone;const maps=new Map(); | |
| root.querySelectorAll('button').forEach(b=>b.disabled=true); | |
| const assets=await Promise.all(['mapsheet','smallmariosheet','enemysheet','mariosheet','firemariosheet','itemsheet'].map(name=>new Promise(resolve=>{const image=new Image();image.onload=()=>resolve(image);image.onerror=()=>resolve(null);image.src='/static/mario-assets/'+name+'.png'}))); | |
| if(!root.isConnected)return()=>{}; | |
| const keys=new Set(); | |
| function controls(){const auto=data?.auto_advance!==false,pending=humanView?Boolean(frame?.next_level):Boolean(data?.awaiting_next_level);$('#mario-progression').disabled=busy||running;$('#mario-next').hidden=auto||data?.course!=='campaign';$('#mario-next').disabled=busy||running||dirty||!pending;$('#mario-progression-note').textContent=auto?'Auto is on. Levels advance after a clear.':'Manual is on. Clear a level, then press Next level.';for(const id of ['step','burst','test','human','reset','save','load','course','population'])$('#mario-'+id).disabled=busy||running;for(const id of ['step','burst','test','human'])$('#mario-'+id).disabled=busy||running||dirty;$('#mario-start').disabled=(busy&&!running)||dirty||(!auto&&!humanView&&pending);$('#mario-start').textContent=running?'Pause learning':'Start / resume learning'} | |
| function draw(){ | |
| if(!frame)return;if(frame.map)maps.set(frame.level_number,frame.map);const currentMap=maps.get(frame.level_number);if(currentMap){tiles=currentMap.tiles;width=currentMap.width;}if(!tiles)return;const exitX=currentMap?.exit_x??data.exit_x,boss=frame.boss;$('#mario-level').textContent=`${frame.level_number?'Level '+frame.level_number+' of 16':frame.level_name}${boss?' · Bowser’s castle':''}${data.course==='campaign'?' · Fixed campaign':''}`;$('#mario-objective').textContent=boss?(boss.hp>0?'Bowser: '+boss.hp+'/3 health · Stomp him three times, or reach the axe beyond him. Then reach the castle exit.':'Bowser defeated! Reach the castle exit.'):'Reach the flag'+(data.course==='campaign'?(data.auto_advance===false?' to unlock the Next level button.':' to advance to the next level.'):' to finish this level.');const desiredHeight=canvas.clientWidth<600?800:420;if(canvas.height!==desiredHeight)canvas.height=desiredHeight;const lead=frame.runners?.[frame.leader||0]||frame.runner;const scale=canvas.height/256,view=1000/scale,cam=Math.max(0,Math.min(Math.max(0,width-view),lead.x-view*.35));ctx.setTransform(1,0,0,1,0,0);ctx.fillStyle=boss?'#242738':'#86bfd6';ctx.fillRect(0,0,1000,canvas.height);ctx.imageSmoothingEnabled=false;ctx.save();ctx.scale(scale,scale);ctx.translate(-cam,0); | |
| ctx.fillStyle=boss?'#343545':'#cbe0d4';for(let x=0;x<width;x+=240){ctx.beginPath();ctx.ellipse(x+90,256,85,64,0,Math.PI,0);ctx.fill()} | |
| const changed=new Map((frame.tile_changes||[]).map(([x,y,t])=>[x+','+y,t]));tiles.forEach((row,y)=>row.forEach((value,x)=>{const t=changed.get(x+','+y)??value;if(!t||x*16<cam-16||x*16>cam+view)return;if(assets[0])ctx.drawImage(assets[0],t%8*16,Math.floor(t/8)*16,16,16,x*16,y*16,16,16);else{ctx.fillStyle='#a77848';ctx.fillRect(x*16,y*16,16,16)}})); | |
| if(boss){ | |
| ctx.fillStyle='#f36d30';for(let x=22*16;x<98*16;x+=16){if(!tiles[14]?.[Math.floor(x/16)]||x>=74*16)ctx.fillRect(x,242+Math.sin((frame.tick||0)/5+x)*2,16,14)} | |
| ctx.fillStyle='#101522';ctx.fillRect(exitX-12,177,28,47);ctx.strokeStyle='#d5aa61';ctx.strokeRect(exitX-12,177,28,47); | |
| if(!boss.axe_activated){ctx.fillStyle='#b48a50';ctx.fillRect(boss.axe_x,193,4,31);ctx.fillStyle='#e5e5cf';ctx.fillRect(boss.axe_x-8,189,18,12);} | |
| if(boss.y<290){const x=boss.x,y=boss.y;ctx.fillStyle='#b4d35b';ctx.fillRect(x-18,y-30,32,26);ctx.fillStyle='#426840';ctx.fillRect(x-8,y-40,25,29);ctx.fillStyle='#f4d693';for(let i=0;i<3;i++){ctx.beginPath();ctx.moveTo(x-8+i*8,y-38);ctx.lineTo(x-4+i*8,y-47);ctx.lineTo(x+i*8,y-38);ctx.fill()}ctx.fillStyle='#dfad55';ctx.fillRect(x-25,y-31,19,18);ctx.fillRect(x-17,y-6,12,7);ctx.fillRect(x+7,y-6,12,7);ctx.fillStyle='#f0eee0';ctx.fillRect(x-25,y-30,9,7);ctx.fillStyle='#a52e31';ctx.fillRect(x-24,y-29,4,4);ctx.fillStyle='#d25b3b';ctx.fillRect(x-24,y-38,12,6);} | |
| for(const [x,y] of boss.fireballs){ctx.fillStyle='#f08b35';ctx.beginPath();ctx.ellipse(x,y,9,5,0,0,Math.PI*2);ctx.fill();ctx.fillStyle='#ffe39b';ctx.fillRect(x-4,y-2,5,4)} | |
| }else{ctx.fillStyle='#edf5df';ctx.fillRect(exitX,48,2,176);ctx.fillStyle='#568368';ctx.beginPath();ctx.moveTo(exitX+2,50);ctx.lineTo(exitX+30,60);ctx.lineTo(exitX+2,72);ctx.fill();} | |
| for(const [x,y,type] of frame.enemies||[]){if(assets[2])ctx.drawImage(assets[2],0,({2:2,3:2,4:0,5:0,6:1,7:1,8:3,9:3,10:5,11:6}[type]??2)*32,16,32,x-8,y-32,16,32);else{ctx.fillStyle='#8c503c';ctx.fillRect(x-7,y-12,14,12)}} | |
| for(const [x,y,type] of frame.items||[]){if(type===16){ctx.fillStyle='#ffb34d';ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()}else if(assets[5])ctx.drawImage(assets[5],(type===13?1:type===15?3:0)*16,0,16,16,x-8,y-16,16,16)} | |
| const runners=frame.runners||[frame.runner];runners.forEach((r,i)=>{if(r.y>290)return;ctx.globalAlpha=r.done&&r.reason!=='win'?.18:r===lead||r.reason==='win'?1:.25;const size=r.large?32:16,sprite=r.large?assets[r.fire?4:3]:assets[1];if(sprite)ctx.drawImage(sprite,(r.grounded?Math.floor((frame.tick||0)/5)%3+1:4)*size,0,size,size,r.x-size/2,r.y-size,size,size);else{ctx.fillStyle='#bf4738';ctx.fillRect(r.x-6,r.y-16,12,16)}if(r===lead){ctx.globalAlpha=1;ctx.strokeStyle='#fcf4bd';ctx.lineWidth=1.3;ctx.strokeRect(r.x-10,r.y-19,20,22)}});ctx.globalAlpha=1;ctx.restore(); | |
| const hudScale=1000/Math.max(250,canvas.clientWidth);ctx.fillStyle='#204b47dd';ctx.fillRect(14,14,Math.min(972,350*hudScale),34*hudScale);ctx.fillStyle='#f7f4da';ctx.font=(12*hudScale)+'px monospace';ctx.fillText(`L${frame.level_number||'—'} · ${human?'YOU':frame.runners?'ROUND '+frame.round:'POLICY TEST'} · ${(lead.progress*100).toFixed(0)}%${boss?' · BOWSER '+boss.hp+'/3':''}`,26*hudScale,36*hudScale); | |
| } | |
| function chart(){const c=$('#mario-chart').getContext('2d'),h=data.history||[];c.clearRect(0,0,720,180);c.fillStyle='#eef3e6';c.fillRect(0,0,720,180);c.strokeStyle='#d5dfcf';for(let y=20;y<170;y+=35){c.beginPath();c.moveTo(32,y);c.lineTo(710,y);c.stroke()}for(const [key,color] of [['best_progress','#ca9b4d'],['mean_progress','#397e69']]){c.strokeStyle=color;c.lineWidth=2;c.beginPath();h.forEach((r,i)=>{const x=34+i/Math.max(1,h.length-1)*670,y=160-r[key]*140;i?c.lineTo(x,y):c.moveTo(x,y)});c.stroke()}c.fillStyle='#6e8266';c.font='12px sans-serif';c.fillText('100%',0,18);c.fillText('0%',8,165)} | |
| function update(s,map=false){data={...data,...s};$('#mario-progression').value=data.auto_advance===false?'manual':'auto';if(map){dirty=false;tiles=s.tiles;width=s.width;$('#mario-course').value=s.course==='original'?'level-1':s.course;$('#mario-population').value=s.population;}if(s.tiles)maps.set(s.level_number,{tiles:s.tiles,width:s.width,exit_x:s.exit_x});frame=s.frame;for(const k of ['round','active','wins','experiences','updates','states'])$('#mario-'+k).textContent=(s[k]??data[k]??0).toLocaleString();$('#mario-values').innerHTML=(s.q_values||[]).map((v,i)=>`<div class="mario-value"><span>${escape(['Run right','Run + jump','Move left','Left + jump','Wait','Jump in place'][i])}</span><b>${v.toFixed(2)}</b></div>`).join('');chart();draw()} | |
| const onResize=()=>draw();window.addEventListener('resize',onResize); | |
| function stop(){running=false;human=false;clearTimeout(timer);generation++;cancelAnimationFrame(raf);animationDone?.();keys.clear();controls()} | |
| function animate(frames,duration,seen){cancelAnimationFrame(raf);const start=performance.now();return new Promise(resolve=>{let done=false;const finish=()=>{if(done)return;done=true;clearTimeout(fallback);cancelAnimationFrame(raf);if(alive&&seen===generation){for(const f of frames)if(f.map)maps.set(f.level_number,f.map);frame=frames.at(-1);draw()}animationDone=null;resolve()};animationDone=finish;const fallback=setTimeout(finish,duration+50);function tick(now){if(!alive||seen!==generation){finish();return}for(const f of frames)if(f.map)maps.set(f.level_number,f.map);frame=frames[Math.min(frames.length-1,Math.floor((now-start)/duration*frames.length))];draw();if(now-start>=duration)finish();else raf=requestAnimationFrame(tick)}raf=requestAnimationFrame(tick)})} | |
| async function train(){if(!alive||!running||busy)return;busy=true;controls();const seen=generation;try{const speed=Number($('#mario-speed').value),s=await api('mario/train',{ticks:Math.max(1,Math.round(speed/5))});if(!alive||seen!==generation)return;update(s);$('#mario-mode').textContent=`${s.population} INDEPENDENT AGENTS · SHARED LEARNING`;$('#mario-message').textContent=`Round ${s.round}: ${s.active} still playing. ${s.wins} training finishes so far. Random exploration is ${(s.exploration*100).toFixed(0)}%.`;await animate(s.frames,200,seen);if(s.awaiting_next_level&&s.auto_advance===false){stop();$('#mario-mode').textContent='LEVEL CLEARED · READY FOR NEXT LEVEL';$('#mario-message').textContent='Level cleared. Press Next level when you are ready.';}if(s.campaign_complete){stop();$('#mario-mode').textContent='CAMPAIGN COMPLETE · BOWSER DEFEATED';$('#mario-message').textContent='All 16 levels cleared! Start a new learner to play the campaign again.'}}catch(e){if(alive){stop();$('#mario-message').textContent=e.message}}finally{busy=false;if(alive){controls();if(running)timer=setTimeout(train,0)}}} | |
| async function once(fn){stop();busy=true;controls();try{await fn()}catch(e){if(alive)$('#mario-message').textContent=e.message}finally{busy=false;if(alive)controls()}} | |
| for(const id of ['course','population'])$('#mario-'+id).onchange=()=>{dirty=Number($('#mario-population').value)!==data.population||$('#mario-course').value!==(data.course==='original'?'level-1':data.course);if(dirty)$('#mario-message').textContent='Settings changed. Choose New learner from settings to apply them with an empty Q table.';controls()}; | |
| $('#mario-start').onclick=()=>{if(running){stop();$('#mario-message').textContent='Paused. Test the learned policy or change the reward and exploration controls.'}else{stop();humanView=false;running=true;train()}}; | |
| $('#mario-step').onclick=()=>once(async()=>{humanView=false;const s=await api('mario/train',{ticks:1});update(s);if(s.awaiting_next_level&&s.auto_advance===false){$('#mario-message').textContent='Level cleared. Press Next level when you are ready.';return;}if(s.campaign_complete){$('#mario-message').textContent='Campaign complete! All 16 levels cleared.';return;}$('#mario-mode').textContent='SINGLE GAME TICK · SHARED LEARNING';$('#mario-message').textContent=`Advanced one game tick for ${s.active} active agents. The shared table updates after each four-tick action, or when an attempt ends.`}); | |
| $('#mario-reset').onclick=()=>once(async()=>{humanView=false;const n=Number($('#mario-population').value);if(!Number.isInteger(n)||n<1||n>128)throw Error('Choose between 1 and 128 agents.');update(await api('mario/reset',{population:n,course:$('#mario-course').value,seed:7}),true);$('#mario-message').textContent='New learner: empty Q table, every Mario at the starting line.';$('#mario-mode').textContent='FRESH POLICY · ALL AT THE START'}); | |
| $('#mario-burst').onclick=()=>once(async()=>{humanView=false;const target=data.round+20;let s=data;$('#mario-mode').textContent='ACCELERATED LEARNING';while(alive&&s.round<target&&!s.campaign_complete&&!(s.awaiting_next_level&&s.auto_advance===false)){s=await api('mario/train',{ticks:240});if(!alive)return;update(s);$('#mario-message').textContent=`Training round ${s.round} of ${target}…`;}$('#mario-message').textContent=s.campaign_complete?'Campaign complete! All 16 levels cleared.':s.awaiting_next_level&&s.auto_advance===false?'Level cleared. Press Next level when you are ready.':`Reached round ${s.round}. Press Test without exploration to see what the learned policy can do.`}); | |
| $('#mario-test').onclick=()=>once(async()=>{humanView=false;const s=await api('mario/evaluate',{});if(!alive)return;const r=s.evaluations[0];$('#mario-mode').textContent='POLICY TEST · NO EXPLORATION · NO UPDATES';$('#mario-message').textContent=`Test: ${(r.progress*100).toFixed(0)}% of the level · ${r.win?'level cleared':r.reason}. Tested after ${s.training_experiences.toLocaleString()} training experiences.`;await animate(r.frames,Math.min(7000,Math.max(1400,r.frames.length*40)),generation)}); | |
| const onDown=e=>{if(!human||!['ArrowLeft','ArrowRight',' ','ArrowUp'].includes(e.key))return;e.preventDefault();keys.add(e.key)},onUp=e=>keys.delete(e.key);canvas.addEventListener('keydown',onDown);canvas.addEventListener('keyup',onUp);canvas.addEventListener('blur',()=>keys.clear()); | |
| async function play(seen=generation){if(!alive||!human||seen!==generation)return;try{const jump=keys.has(' ')||keys.has('ArrowUp'),left=keys.has('ArrowLeft'),right=keys.has('ArrowRight'),a=left?(jump?3:2):right?(jump?1:0):jump?5:4;const nextFrame=await api('mario/human',{action:a});if(!alive||!human||seen!==generation)return;frame=nextFrame;draw();if(frame.runner.done){ | |
| if(frame.next_level){keys.clear();if(data.auto_advance===false){human=false;$('#mario-mode').textContent='LEVEL CLEARED · READY FOR NEXT LEVEL';$('#mario-message').textContent=`Level ${frame.level_number} cleared! Press Next level when you are ready.`;controls();return;}$('#mario-message').textContent=`Level ${frame.level_number} cleared! Next: Level ${frame.next_level}${frame.next_level===16?' · Bowser’s castle':''}.`;timer=setTimeout(()=>play(seen),900);return;} | |
| human=false;$('#mario-human').textContent=frame.runner.reason==='win'?'Play yourself again':`Retry Level ${frame.level_number||'practice'}`;$('#mario-message').textContent=frame.campaign_complete?'You defeated Bowser and cleared all 16 levels!':frame.runner.reason==='win'?'Level cleared!':'Attempt ended: '+frame.runner.reason+'. Retry this level to continue.';if(frame.campaign_complete)$('#mario-mode').textContent='CAMPAIGN COMPLETE · BOWSER DEFEATED';return;}timer=setTimeout(()=>play(seen),120)}catch(e){stop();if(alive)$('#mario-message').textContent=e.message}} | |
| $('#mario-human').onclick=()=>once(async()=>{humanView=true;frame=await api('mario/human/reset',{});if(!alive)return;human=true;$('#mario-human').textContent='Play yourself';draw();canvas.focus();$('#mario-mode').textContent='YOUR TURN · ARROWS + SPACE';$('#mario-message').textContent='Click the game, use left/right arrows to move and Space or up to jump. Your run does not train the learner. Clear the level to continue the campaign; a failed attempt retries the same level.';play()}); | |
| $('#mario-progression').onchange=()=>{const resumeHuman=human;once(async()=>{const s=await api('mario/progression',{auto_advance:$('#mario-progression').value==='auto'});if(!alive)return;data={...data,...s};$('#mario-progression').value=s.auto_advance?'auto':'manual';draw();$('#mario-message').textContent=s.auto_advance?'Auto-advance is on.':'Manual progression is on. Clear a level, then press Next level.';if(humanView&&((resumeHuman&&!frame.runner.done)||(s.auto_advance&&frame.next_level))){human=true;canvas.focus();play();}})}; | |
| $('#mario-next').onclick=()=>once(async()=>{if(humanView){frame=await api('mario/human/next',{});if(!alive)return;human=true;draw();canvas.focus();$('#mario-mode').textContent='YOUR TURN · ARROWS + SPACE';$('#mario-message').textContent=`Level ${frame.level_number} · Your turn.`;play();}else{update(await api('mario/next',{}));$('#mario-mode').textContent='NEXT LEVEL · READY AT THE START';$('#mario-message').textContent='Next level loaded. Start learning when you are ready.';}}); | |
| $('#mario-exploration').oninput=e=>$('#mario-exploration-value').textContent=Math.round(e.target.value*100)+'%';$('#mario-penalty').oninput=e=>$('#mario-penalty-value').textContent=e.target.value; | |
| async function config(){try{await api('mario/config',{exploration:Number($('#mario-exploration').value),death_penalty:Number($('#mario-penalty').value)})}catch(e){toast(e.message,true)}}$('#mario-exploration').onchange=config;$('#mario-penalty').onchange=config; | |
| $('#mario-save').onclick=()=>once(async()=>{const r=await api('mario/save',{});$('#mario-save-note').textContent=`Saved after ${r.experiences.toLocaleString()} experiences.`});$('#mario-load').onclick=()=>once(async()=>{humanView=false;update(await api('mario/load',{}),true);$('#mario-mode').textContent='SAVED LEARNER · NEW ROUND AT THE START';$('#mario-message').textContent='Saved action values loaded. A new round starts at the beginning.'}); | |
| data=await api('mario');if(!root.isConnected)return()=>{alive=false};$('#mario-exploration').value=data.exploration;$('#mario-exploration-value').textContent=Math.round(data.exploration*100)+'%';$('#mario-penalty').value=data.death_penalty;$('#mario-penalty-value').textContent=data.death_penalty;update(data,true);controls(); | |
| return()=>{alive=false;stop();window.removeEventListener('resize',onResize);canvas.removeEventListener('keydown',onDown);canvas.removeEventListener('keyup',onUp)}; | |
| } | |