4DFIED WEB LITE v1.32

Duel / Battle

Loading game board…
▼ YOUR SIDE ▼
Move Stack / A1 takeover Capture Hop
Formation

Swap, then confirm.

window.addEventListener("error",function(e){var b=document.getElementById("board");if(b&&!b.querySelector(".sq")){b.innerHTML="
Board error: "+String(e.message||"Unknown").replace(/[<>]/g,"")+"
";}var n=document.getElementById("mobileNotice");if(n){n.style.display="block";n.textContent="Game error: "+(e.message||"unknown")+"."}}); (()=>{'use strict';if(!Array.prototype.at){Array.prototype.at=function(n){n=Math.trunc(n)||0;if(n<0)n+=this.length;return this[n]}};let N=6;const DIR={1:[[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]],2:[[-1,-1],[-1,1],[1,-1],[1,1]],3:[[-1,0],[1,0],[0,-1],[0,1]]};const $=id=>document.getElementById(id), idx=(r,c)=>r*N+c, inside=(r,c)=>r>=0&&r=0&&c[Math.floor(i/N),i%N], coord=i=>'ABCDEFGHIJ'[i%N]+(N-Math.floor(i/N));let humanSide=0,watchingBots=false,quietRounds=0,roundSlots=0,finalScores=null,endReason="",board,turn,selected,inspected=null,history,undoStack,finished,timer=null,setup=true,setupPick=null,gameRecorded=false,finaleTimer=null,finaleShown=false,eliminated=[],champion=null,pendingStack=null,battlePoints=[],battleEvents=[],profileTab="duel",dragState=null,suppressClick=false;const human=()=>four()?humanSide:0;const four=()=>$("mode").value==="four",names=["Red","Yellow","Blue","Green"],cssNames=["red","yellow","blue","green"],playerName=o=>names[o],playerClass=o=>cssNames[o],totalPlayers=()=>four()?4:2; const AudioContextCtor=window.AudioContext||window.webkitAudioContext; let audioCtx=null; function soundSettings(){try{return JSON.parse(localStorage.getItem('4dfied_sound_v1'))||{enabled:true,volume:35}}catch(e){return {enabled:true,volume:35}}} const soundPrefs=soundSettings(); function saveSoundSettings(){try{localStorage.setItem('4dfied_sound_v1',JSON.stringify({enabled:$('soundEnabled').checked,volume:Number($('soundVolume').value)}))}catch(e){}} function unlockAudio(){if(!$('soundEnabled').checked||!AudioContextCtor)return null;try{if(!audioCtx)audioCtx=new AudioContextCtor();if(audioCtx.state==='suspended')audioCtx.resume().catch(()=>{});return audioCtx}catch(e){return null}} function tone(freq,duration,opts={}){const ctx=unlockAudio();if(!ctx||ctx.state==='closed')return;const start=ctx.currentTime+(opts.delay||0),end=start+duration;const oscillator=ctx.createOscillator(),gain=ctx.createGain();oscillator.type=opts.wave||'sine';oscillator.frequency.setValueAtTime(Math.max(30,freq),start);if(opts.to)oscillator.frequency.exponentialRampToValueAtTime(Math.max(30,opts.to),end);const level=Math.min(.12,Math.max(0,Number($('soundVolume').value)/100)*.13)*(opts.level??1);gain.gain.setValueAtTime(.0001,start);gain.gain.exponentialRampToValueAtTime(Math.max(.00011,level),start+.012);gain.gain.exponentialRampToValueAtTime(.0001,end);oscillator.connect(gain);gain.connect(ctx.destination);oscillator.start(start);oscillator.stop(end+.012)} function playSound(name){if(!$('soundEnabled').checked||Number($('soundVolume').value)===0)return;switch(name){ case 'select':tone(600,.075,{to:790,level:.35});break; case 'cancel':tone(480,.075,{to:340,level:.28});break; case 'glide':tone(360,.13,{to:540,wave:'sine',level:.48});break; case 'slash':tone(1100,.17,{to:210,wave:'sawtooth',level:.48});tone(800,.09,{to:1600,delay:.035,level:.25});break; case 'stomp':tone(190,.22,{to:48,wave:'triangle',level:.95});tone(110,.14,{to:40,wave:'sawtooth',level:.35});break; case 'stack':tone(370,.12,{to:520,wave:'triangle',level:.55});tone(560,.17,{to:720,delay:.09,level:.5});break; case 'capture':tone(740,.15,{to:160,wave:'sawtooth',level:.7});tone(175,.25,{to:55,wave:'triangle',delay:.08,level:.8});break; case 'start':tone(440,.13,{to:550,level:.5});tone(660,.18,{to:880,delay:.14,level:.55});break; case 'victory':[[523,0],[659,.12],[784,.24],[1047,.39]].forEach(([f,d])=>tone(f,.36,{delay:d,wave:'triangle',level:.65}));break; case 'undo':tone(510,.13,{to:260,level:.4});break; }} const STATS_KEY='4dfied_stats_v1_5';const OLD_STATS_KEY='4dfied_stats_v1_4';const statMode=()=>four()?'four_'+$('difficulty').value:$('mode').value==='local'?'local':$('difficulty').value; function emptyStats(){return {easy:[],medium:[],hard:[],local:[],four:[],four_easy:[],four_medium:[],four_hard:[]}} function loadStats(){try{const v=JSON.parse(localStorage.getItem(STATS_KEY));if(v&&Array.isArray(v.easy)&&Array.isArray(v.medium)&&Array.isArray(v.hard)&&Array.isArray(v.local))return {...emptyStats(),...v};const old=JSON.parse(localStorage.getItem(OLD_STATS_KEY));if(old&&Array.isArray(old.bot)&&Array.isArray(old.local)){const migrated=emptyStats();migrated.medium=old.bot.slice();migrated.local=old.local.slice();localStorage.setItem(STATS_KEY,JSON.stringify(migrated));return migrated}return emptyStats()}catch(e){return emptyStats()}} let stats=loadStats(); const PROFILE_KEY='4dfied_progression_v2'; const rankNames=['Pawn','Guardian','Slasher','Crusher','General','Master','Grandmaster','GURU']; const difficulties=['easy','medium','hard','human']; const profileKey=(mode,diff)=>mode+'_'+diff; function blankProfile(){return {points:0,wins:0,losses:0,draws:0,matches:0,captures:[0,0,0],towers2:0,towers3:0,best:0}} function loadProfiles(){let saved={};try{saved=JSON.parse(localStorage.getItem(PROFILE_KEY))||{}}catch(e){}const result={};for(const mode of ['duel','four'])for(const d of difficulties){const key=profileKey(mode,d);result[key]={...blankProfile(),...(saved[key]||{})};if(!Array.isArray(result[key].captures))result[key].captures=[0,0,0]}return result} let profiles=loadProfiles(); function saveProfiles(){try{localStorage.setItem(PROFILE_KEY,JSON.stringify(profiles))}catch(e){}} const LEVEL_COSTS=(()=>{const a=Array.from({length:36},(_,i)=>500+i*100);const extra=19000;const weights=Array.from({length:26},(_,j)=>j+1);const total=weights.reduce((a,b)=>a+b,0);let distributed=0;for(let j=0;j<26;j++){const cumulative=Math.round(extra*weights.slice(0,j+1).reduce((a,b)=>a+b,0)/total);a[j+10]+=cumulative-distributed;distributed=cumulative}return a})(); const LEVEL_THRESHOLDS=(()=>{let total=0;return LEVEL_COSTS.map(x=>total+=x)})(); function rankAt(points){let achieved=0;while(achieved=LEVEL_THRESHOLDS[achieved])achieved++;if(achieved===36)return {name:'GURU',level:1,threshold:100000,next:null,stage:7};if(!achieved)return {name:'Recruit',level:0,threshold:0,next:LEVEL_THRESHOLDS[0],stage:0};const index=achieved-1;return {name:rankNames[Math.floor(index/5)],level:index%5+1,threshold:LEVEL_THRESHOLDS[achieved-1],next:LEVEL_THRESHOLDS[achieved],stage:Math.floor(index/5)}} function currentProfileKey(){return profileK

Nascetur

Dolore quam

Sem astrum tortor consequat porta. Ornare arcu odio ut sem nulla pharetra. Quam elementum pulvinar etiam quam lacus civitas integer vitae justo eget magna fermentum iaculis. Deliciae feugiat sed aliquanto vestibulum apricus amet justo donec enim diam. A condimentum vitae sapien pellentesque habitant. Proin nunc consequat interdum varius.

Faucibus

Felis et vitae

Gravida in fermentum sollicitudin orci phasellus odio feugiat pretium nibh ipsum consequat nisl vel pretium. Tortor condimentum lacinia quis.

Interdum

Done

Mattis rhoncus urna neque viverra venenatis lectus magna fringilla. Tempus quam pellentesque nec aliquam vitae nunc sed velit dignissim sodales. Tortor vitae purus faucibus ornare at ultrices tempus imperdiet nulla.