diff --git a/assets/js/game.js b/assets/js/game.js deleted file mode 100644 index 7ee8af4..0000000 --- a/assets/js/game.js +++ /dev/null @@ -1,431 +0,0 @@ -const canvas = document.getElementById("board"); -if (!canvas) alert("Canvas tidak ditemukan! ID harus 'board'"); -const ctx = canvas.getContext("2d"); - -// ensure canvas actual pixel size used for tileSize calculation -const CANVAS_W = canvas.width; -const CANVAS_H = canvas.height; - -const size = 8; -const tileSize = CANVAS_W / size; - -let board = []; -let currentTurn = 1; // 1 = red, 2 = white -let selected = null; -let history = []; -let gameOver = false; - -let aiEnabled = (typeof GAME_MODE !== "undefined" && GAME_MODE === "pvai"); -let aiPlays = 2; -let aiThinkingDelay = 300; - -let hintsEnabled = (typeof ENABLE_HINTS !== "undefined") ? !!ENABLE_HINTS : true; -/********** SOUND EFFECT **********/ -const SFX = { - move: new Audio("assets/sound/move.mp3"), - capture: new Audio("assets/sound/capture.mp3") -}; - -let soundEnabled = true; -function playSound(name){ - if(!soundEnabled || !SFX[name]) return; - SFX[name].currentTime = 0; - SFX[name].play().catch(()=>{}); -} - - -/********** TIMER (unchanged) **********/ -let timerSeconds = 0; -let timerInterval = null; -let timerRunning = false; -function startTimer(){ if(timerRunning) return; timerRunning=true; timerInterval=setInterval(()=>{ timerSeconds++; const min=String(Math.floor(timerSeconds/60)).padStart(2,'0'); const sec=String(timerSeconds%60).padStart(2,'0'); const dom=document.getElementById('timer'); if(dom) dom.textContent = `${min}:${sec}`; },1000); } -function stopTimer(){ timerRunning=false; clearInterval(timerInterval); } -function resetTimer(){ stopTimer(); timerSeconds=0; const dom=document.getElementById('timer'); if(dom) dom.textContent='00:00'; } - -/********** HELPERS **********/ -function opponentOf(p){ return p===1?2:1; } -function inside(r,c){ return Number.isInteger(r) && Number.isInteger(c) && r>=0 && r=0 && cArray(size).fill(0)); - for(let r=0;r<3;r++) for(let c=0;c triggerAI(), 120); - } -} - -/********** DRAW **********/ -function draw(){ - // safety: re-compute tileSize if canvas resized via CSS (keep consistent) - // (we keep original tileSize computed from initial canvas.width) - ctx.clearRect(0,0,CANVAS_W,CANVAS_H); - - for(let r=0;r60) history.shift(); } - -/********** GENERATE MOVES **********/ -function generateMovesForPiece(r,c,player){ - const v = board[r][c]; - if(v===0) return []; - const isKing = (v===3||v===4); - const dir = player===1? -1 : 1; - const res = []; - - const dirs = isKing ? [[1,1],[1,-1],[-1,1],[-1,-1]] : [[dir,1],[dir,-1]]; - for(const [dr,dc] of dirs){ - const r2 = r+dr, c2 = c+dc; - if(inside(r2,c2) && board[r2][c2]===0) res.push({r1:r,c1:c,r2:r2,c2:c2,capture:false}); - } - - const capDirs = [[1,1],[1,-1],[-1,1],[-1,-1]]; - for(const [dr,dc] of capDirs){ - const mr = r+dr, mc = c+dc; - const r2 = r+dr*2, c2 = c+dc*2; - if(!inside(mr,mc) || !inside(r2,c2)) continue; - if(board[r2][c2] !== 0) continue; - const mid = board[mr][mc]; - if(mid !== 0 && !belongsTo(mid, player)){ - if(isKing || dr === dir) res.push({r1:r,c1:c,r2:r2,c2:c2,capture:true, mr, mc}); - } - } - - return res; -} - -function getAllMovesFor(player){ - const all = []; - for(let r=0;r !!m.capture); - const nonCaptureMoves = moves.filter(m => !m.capture); - - // jika ada capture -> pilih di antara capture (prioritas tetap di sini) - if(captureMoves.length > 0){ - let bestScore = -Infinity, best = []; - for(const m of captureMoves){ - // skor sederhana untuk capture: dasar tinggi + prefer capture yang mengarah ke king sedikit - let s = 200; - // small preference untuk multi-direction / maju (lebih baik) - s += (Math.abs(m.dr || 0) + Math.abs(m.dc || 0)) * 5; - // sedikit randomness untuk variasi - s += Math.random() * 20; - if(s > bestScore){ bestScore = s; best = [m]; } else if(s === bestScore){ best.push(m); } - } - const chosen = best[Math.floor(Math.random()*best.length)]; - setTimeout(()=>{ tryMove(chosen.r1, chosen.c1, chosen.r2, chosen.c2); draw(); checkWinCondition(); }, aiThinkingDelay); - return; - } - - // jika tidak ada capture -> prioritaskan non-promotion moves bila memungkinkan - const nonPromotion = nonCaptureMoves.filter(m => m.r2 !== promotionRow); - let pool = nonPromotion.length > 0 ? nonPromotion : nonCaptureMoves; // jika semua move adalah promotion, gunakan semua - - // beri skor supaya AI tidak selalu memilih move yang mempromote: prefer pusat & variasi - let bestScore = -Infinity, best = []; - const centerR = (size - 1) / 2, centerC = (size - 1) / 2; - for(const m of pool){ - // prefer bergerak menuju pusat papan (lebih "aman" / seimbang) - const distToCenter = Math.abs(m.r2 - centerR) + Math.abs(m.c2 - centerC); - // kecil penalti jika langkah ini menuju promosi (agar tidak selalu memilih king) - const promoPenalty = (m.r2 === promotionRow) ? 40 : 0; - // prefer maju sedikit (untuk white: r increasing; for red: r decreasing) - const advanceBonus = (aiPlays === 2) ? (m.r2) : (size - 1 - m.r2); - - // skor komposit - let s = 50; // baseline - s += (100 - distToCenter*10); // lebih kecil jarak ke pusat => lebih besar skor - s += advanceBonus * 0.5; // sedikit nilai untuk maju - s -= promoPenalty; // penalti promosi agar tidak selalu memilih king - s += Math.random() * 20; // variasi randomisasi - - if(s > bestScore){ bestScore = s; best = [m]; } else if(s === bestScore){ best.push(m); } - } - - const chosen = best[Math.floor(Math.random()*best.length)]; - setTimeout(()=>{ tryMove(chosen.r1, chosen.c1, chosen.r2, chosen.c2); draw(); checkWinCondition(); }, aiThinkingDelay); -} - - - -function triggerAI(){ setTimeout(()=>{ if(aiEnabled && currentTurn===aiPlays && !gameOver) aiMakeMove(); }, 120); } - -/********** MOUSE **********/ -canvas.addEventListener("click", (e)=>{ - console.debug("canvas click", {gameOver, aiEnabled, currentTurn}); - if(gameOver) return; - if(aiEnabled && currentTurn === aiPlays) { console.debug("click ignored: AI turn"); return; } - - const rect = canvas.getBoundingClientRect(); - const x = e.clientX - rect.left, y = e.clientY - rect.top; - const c = Math.floor(x / tileSize), r = Math.floor(y / tileSize); - - console.debug("click coords", {x,y, r, c}); - - if(!inside(r,c)) return; - const v = board[r][c]; - - if(selected){ - if(tryMove(selected.r, selected.c, r, c)){ - selected = null; - } else if(v!==0 && belongsTo(v, currentTurn)){ - selected = {r,c}; - } else { - selected = null; - } - } else { - if(v!==0 && belongsTo(v, currentTurn)){ - selected = {r,c}; - } - } - draw(); -}); - -/********** KEY H TOGGLE HINTS **********/ -document.addEventListener("keydown", (e)=>{ if(e.key.toLowerCase()==='h'){ hintsEnabled = !hintsEnabled; draw(); } }); - -/********** BUTTONS **********/ -const newBtn = document.getElementById("newBtn"); -if(newBtn) newBtn.onclick = ()=> resetBoard(); - -const undoBtn = document.getElementById("undoBtn"); -if(undoBtn) undoBtn.onclick = ()=>{ if(history.length>0){ board = history.pop(); draw(); } }; - -/********** LEADERBOARD (unchanged) **********/ -async function loadLeaderboard(){ - const box = document.getElementById("leaderboardList"); - if(!box) return; - try{ - const res = await fetch("load_leaderboard.php"); - if(!res.ok) throw new Error("Network"); - const data = await res.json(); - box.innerHTML = ""; - if(!data || data.length===0){ box.innerHTML = "

Belum ada data

"; return; } - data.forEach((row,i)=>{ - const div = document.createElement("div"); - div.className = "lb-row" + (typeof CURRENT_USERNAME!=='undefined' && row.username === CURRENT_USERNAME ? " me" : ""); - div.innerHTML = `${i+1}. ${escapeHtml(row.username)}${row.wins}W | ${row.losses}L`; - box.appendChild(div); - }); - }catch(err){ console.error("loadLeaderboard error", err); } -} -function escapeHtml(s){ if(!s) return ''; return String(s).replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[m]); } -window.addEventListener("load", ()=>{ loadLeaderboard(); setInterval(loadLeaderboard,10000); }); - -async function saveResult(result){ - if(typeof CURRENT_USER_ID === 'undefined') return; - try{ - const res = await fetch("save_result.php",{ method:'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({result}) }); - if(res.ok) await loadLeaderboard(); - }catch(err){ console.error("saveResult error", err); } -} - -/********** POPUP **********/ -function showEnd(title, msg, result){ - if(document.getElementById("endPopup")) return; - gameOver = true; - stopTimer(); - - const div = document.createElement("div"); - div.id = "endPopup"; - div.style = `position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.75);display:flex;justify-content:center;align-items:center;z-index:999999999;pointer-events:auto;`; - div.innerHTML = `

${title}

${msg}

`; - document.body.appendChild(div); - // save result async - saveResult(result).catch(()=>{}); - document.getElementById("popupNew").onclick = ()=>{ div.remove(); resetBoard(); }; - document.getElementById("popupClose").onclick = ()=> div.remove(); -} - -/********** START **********/ -resetBoard(); \ No newline at end of file