Merge pull request #20 from outis1one/claude/fix-classical-pieces-abgA3

Add gamepad auto-detection, keyboard remapping, and smart lane enabling
This commit is contained in:
Outis
2026-03-31 00:01:30 -04:00
committed by GitHub
+80 -8
View File
@@ -55,10 +55,10 @@ canvas{display:block;}
#pause-overlay{display:none;position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:20;flex-direction:column;align-items:center;justify-content:center;gap:16px;} #pause-overlay{display:none;position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:20;flex-direction:column;align-items:center;justify-content:center;gap:16px;}
#pause-overlay.show{display:flex;} #pause-overlay.show{display:flex;}
#pause-overlay h2{font-size:28px;color:#fff;} #pause-overlay h2{font-size:28px;color:#fff;}
#remap-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:30;flex-direction:column;align-items:center;justify-content:center;gap:16px;} #remap-overlay,#keyremap-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:30;flex-direction:column;align-items:center;justify-content:center;gap:16px;}
#remap-overlay.show{display:flex;} #remap-overlay.show,#keyremap-overlay.show{display:flex;}
#remap-prompt{font-size:20px;font-weight:bold;text-align:center;} #remap-prompt,#keyremap-prompt{font-size:20px;font-weight:bold;text-align:center;}
#remap-status{font-size:13px;opacity:.5;text-align:center;} #remap-status,#keyremap-status{font-size:13px;opacity:.5;text-align:center;}
</style> </style>
</head> </head>
<body> <body>
@@ -137,6 +137,7 @@ canvas{display:block;}
<div class="sticky-start"> <div class="sticky-start">
<button class="smbtn" onclick="startRemap()">🎮 Remap Pads</button> <button class="smbtn" onclick="startRemap()">🎮 Remap Pads</button>
<button class="smbtn" onclick="startKeyRemap()">⌨ Remap Keys</button>
<button class="bigbtn" onclick="startGame()">▶ Start</button> <button class="bigbtn" onclick="startGame()">▶ Start</button>
</div> </div>
</div> </div>
@@ -167,8 +168,10 @@ canvas{display:block;}
</div> </div>
</div> </div>
<!-- REMAP --> <!-- REMAP (gamepad) -->
<div id="remap-overlay"><div id="remap-prompt"></div><div id="remap-status"></div><button class="smbtn red" onclick="cancelRemap()">Cancel</button></div> <div id="remap-overlay"><div id="remap-prompt"></div><div id="remap-status"></div><button class="smbtn red" onclick="cancelRemap()">Cancel</button></div>
<!-- REMAP (keyboard) -->
<div id="keyremap-overlay"><div id="keyremap-prompt"></div><div id="keyremap-status"></div><button class="smbtn red" onclick="cancelKeyRemap()">Cancel</button></div>
<!-- SCORES --> <!-- SCORES -->
<div class="screen" id="ss" style="padding:16px;"> <div class="screen" id="ss" style="padding:16px;">
@@ -4322,8 +4325,9 @@ function confirmReset(){
if(ans===null)return; if(ans===null)return;
if(parseInt(ans)===a+b){ if(parseInt(ans)===a+b){
localStorage.removeItem("drgame5");localStorage.removeItem("drgameSettings"); localStorage.removeItem("drgame5");localStorage.removeItem("drgameSettings");
localStorage.removeItem("drgameGpMap");localStorage.removeItem("drgameHidden");localStorage.removeItem("drgameHiddenSongs"); localStorage.removeItem("drgameGpMap");localStorage.removeItem("drgameKeyMap");localStorage.removeItem("drgameHidden");localStorage.removeItem("drgameHiddenSongs");
savedScores=[];hiddenGenres=[];hiddenSongs={};gpMap={0:2,1:3,2:0,3:1,4:4,8:4}; savedScores=[];hiddenGenres=[];hiddenSongs={};gpMap={0:2,1:3,2:0,3:1,4:4,5:5,6:6,7:7,8:4};customKeyMap=null;
LANES[0].key="f";LANES[1].key="g";LANES[2].key="h";LANES[3].key="j";LANES[4].key=" ";LANES[5].key="d";LANES[6].key="k";LANES[7].key="l";
refreshMiniLB();resetSettings();buildMenu(); refreshMiniLB();resetSettings();buildMenu();
alert("All data cleared!"); alert("All data cleared!");
} else {alert("Incorrect — data NOT cleared.");} } else {alert("Incorrect — data NOT cleared.");}
@@ -4841,19 +4845,87 @@ function handleRemapButton(bi){
return true; return true;
} }
// ── KEYBOARD REMAP ────────────────────────────────────
var keyRemapping=false,keyRemapStep=0,keyRemapResult={};
var customKeyMap=JSON.parse(localStorage.getItem("drgameKeyMap")||"null");
// Apply saved key mappings on load
if(customKeyMap){
LANES.forEach(function(ln,i){if(customKeyMap[i]!==undefined)ln.key=customKeyMap[i];});
}
function startKeyRemap(){
keyRemapping=true;keyRemapStep=0;keyRemapResult={};
document.getElementById("keyremap-overlay").classList.add("show");
showKeyRemapStep();
}
function cancelKeyRemap(){
keyRemapping=false;document.getElementById("keyremap-overlay").classList.remove("show");
}
function showKeyRemapStep(){
if(keyRemapStep>=LANES.length){
// Done — apply and save
LANES.forEach(function(ln,i){if(keyRemapResult[i]!==undefined)ln.key=keyRemapResult[i];});
var map={};LANES.forEach(function(ln,i){map[i]=ln.key;});
localStorage.setItem("drgameKeyMap",JSON.stringify(map));
customKeyMap=map;
keyRemapping=false;
document.getElementById("keyremap-overlay").classList.remove("show");
buildPadToggles();
return;
}
var ln=LANES[keyRemapStep];
document.getElementById("keyremap-prompt").innerHTML="Press key for <span style='color:"+ln.color+";font-size:24px'>"+ln.label.toUpperCase()+"</span>";
document.getElementById("keyremap-status").textContent="Step "+(keyRemapStep+1)+" of "+LANES.length+" (press any key)";
}
var keyRemapCooldown=false;
window.addEventListener("keydown",function(e){
if(keyRemapping){
if(e.key==="Escape"){cancelKeyRemap();e.preventDefault();return;}
if(keyRemapCooldown)return;
e.preventDefault();
keyRemapResult[keyRemapStep]=e.key===" "?" ":e.key.toLowerCase();
keyRemapStep++;
keyRemapCooldown=true;
setTimeout(function(){keyRemapCooldown=false;},300);
showKeyRemapStep();
return;
}
},true);
// ── INPUT ────────────────────────────────────────────── // ── INPUT ──────────────────────────────────────────────
window.addEventListener("keydown",e=>{ window.addEventListener("keydown",e=>{
if(keyRemapping)return;
if(e.key==="Escape"){e.preventDefault();togglePause();return;} if(e.key==="Escape"){e.preventDefault();togglePause();return;}
if(paused)return; if(paused)return;
const i=LANES.findIndex(l=>l.key===e.key.toLowerCase()||(l.key===" "&&e.key===" ")); const i=LANES.findIndex(l=>l.key===e.key.toLowerCase()||(l.key===" "&&e.key===" "));
if(i>=0){e.preventDefault();triggerPad(i);} if(i>=0){e.preventDefault();triggerPad(i);}
}); });
// ── GAMEPAD AUTO-DETECT ──────────────────────────────
var gpAutoDetected=false;
function autoDetectGamepad(gp){
if(gpAutoDetected)return;
gpAutoDetected=true;
var nButtons=gp.buttons.length;
// Enable lanes based on detected button count (5 = standard Rock Band, more = extended kit)
var lanesToEnable=Math.min(nButtons,LANES.length);
// Always enable at least the first 5 (standard kit) if we have 5+ buttons
if(lanesToEnable>=5){
for(var i=0;i<5;i++)activePads[i]=true;
// Enable extra lanes for extended kits (6+ buttons)
for(var j=5;j<lanesToEnable;j++)activePads[j]=true;
}
buildPadToggles();
var gpd=document.getElementById("gpd");
if(gpd)gpd.textContent="🎮 "+gp.id.slice(0,25)+" ("+nButtons+" buttons)";
}
function pollGP(){ function pollGP(){
const gps=navigator.getGamepads?navigator.getGamepads():[]; const gps=navigator.getGamepads?navigator.getGamepads():[];
let found=false; let found=false;
for(const gp of gps){ for(const gp of gps){
if(!gp)continue;found=true; if(!gp)continue;found=true;
document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20); if(!gpAutoDetected)autoDetectGamepad(gp);
else document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20);
var remapHandled=false; var remapHandled=false;
gp.buttons.forEach((btn,bi)=>{ gp.buttons.forEach((btn,bi)=>{
if(btn.pressed&&!prevBtns[bi]){ if(btn.pressed&&!prevBtns[bi]){