From c5e49b636d1e6c8008b35de2657c27d022d45590 Mon Sep 17 00:00:00 2001 From: basilius leta Date: Thu, 11 Dec 2025 11:02:29 +0700 Subject: [PATCH] logika game agar bisa dijalankan --- assets/js/game.js | 379 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 assets/js/game.js diff --git a/assets/js/game.js b/assets/js/game.js new file mode 100644 index 0000000..c33f5dd --- /dev/null +++ b/assets/js/game.js @@ -0,0 +1,379 @@ +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 bestScore){ bestScore = s; bestMoves = [m]; } else if(s === bestScore) bestMoves.push(m); + } + const chosen = bestMoves[Math.floor(Math.random()*bestMoves.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();