Delete assets/js/game.js
This commit is contained in:
parent
0eba4f6564
commit
6ad8ac1397
@ -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<size && c>=0 && c<size; }
|
|
||||||
function belongsTo(v, player){
|
|
||||||
return (player===1 && (v===1||v===3)) || (player===2 && (v===2||v===4));
|
|
||||||
}
|
|
||||||
|
|
||||||
/********** RESET BOARD **********/
|
|
||||||
function resetBoard(){
|
|
||||||
console.debug("resetBoard()");
|
|
||||||
board = Array.from({length:size}, ()=>Array(size).fill(0));
|
|
||||||
for(let r=0;r<3;r++) for(let c=0;c<size;c++) if((r+c)%2===1) board[r][c] = 2;
|
|
||||||
for(let r=size-3;r<size;r++) for(let c=0;c<size;c++) if((r+c)%2===1) board[r][c] = 1;
|
|
||||||
|
|
||||||
currentTurn = 1;
|
|
||||||
selected = null;
|
|
||||||
history = [];
|
|
||||||
gameOver = false;
|
|
||||||
|
|
||||||
const old = document.getElementById("endPopup");
|
|
||||||
if(old) old.remove();
|
|
||||||
|
|
||||||
resetTimer();
|
|
||||||
startTimer();
|
|
||||||
|
|
||||||
draw();
|
|
||||||
// small defer so UI painted before AI moves
|
|
||||||
if(aiEnabled && currentTurn === aiPlays){
|
|
||||||
setTimeout(()=> 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;r<size;r++){
|
|
||||||
for(let c=0;c<size;c++){
|
|
||||||
const x = c * tileSize;
|
|
||||||
const y = r * tileSize;
|
|
||||||
ctx.fillStyle = (r+c)%2===0? "#f3f4f6" : "#111827";
|
|
||||||
ctx.fillRect(x,y,tileSize,tileSize);
|
|
||||||
|
|
||||||
if(selected && selected.r===r && selected.c===c){
|
|
||||||
ctx.fillStyle = "rgba(152, 253, 0, 0.58)";
|
|
||||||
ctx.fillRect(x,y,tileSize,tileSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
const v = board[r][c];
|
|
||||||
if(v!==0) drawPiece(x+tileSize/2, y+tileSize/2, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selected && hintsEnabled && !gameOver){
|
|
||||||
const moves = generateMovesForPiece(selected.r, selected.c, currentTurn);
|
|
||||||
drawMoveHints(moves);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawPiece(cx,cy,v){
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx,cy,tileSize*0.35,0,Math.PI*2);
|
|
||||||
ctx.fillStyle = (v===1||v===3) ? "#ef4444" : "#ffffff";
|
|
||||||
ctx.fill(); ctx.closePath();
|
|
||||||
if(v===3||v===4){
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(cx,cy,tileSize*0.15,0,Math.PI*2);
|
|
||||||
ctx.fillStyle = "#fbbf24"; ctx.fill(); ctx.closePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/********** HINTS DRAW **********/
|
|
||||||
function drawMoveHints(moves){
|
|
||||||
if(!moves || moves.length===0) return;
|
|
||||||
for(const m of moves){
|
|
||||||
if(!inside(m.r2,m.c2)) continue;
|
|
||||||
const x = m.c2*tileSize, y = m.r2*tileSize;
|
|
||||||
ctx.fillStyle = m.capture ? "rgba(220,38,38,0.25)" : "rgba(34,197,94,0.25)";
|
|
||||||
ctx.fillRect(x,y,tileSize,tileSize);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(x+tileSize/2, y+tileSize/2, tileSize*0.10, 0, Math.PI*2);
|
|
||||||
ctx.fillStyle = m.capture ? "#dc2626" : "#22c55e";
|
|
||||||
ctx.fill(); ctx.closePath();
|
|
||||||
|
|
||||||
// line
|
|
||||||
const sx = selected.c*tileSize + tileSize/2;
|
|
||||||
const sy = selected.r*tileSize + tileSize/2;
|
|
||||||
const tx = x+tileSize/2, ty = y+tileSize/2;
|
|
||||||
ctx.beginPath(); ctx.moveTo(sx,sy); ctx.lineWidth = 2; ctx.strokeStyle = "rgba(255,255,255,0.3)"; ctx.lineTo(tx,ty); ctx.stroke(); ctx.closePath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/********** MOVE LOGIC **********/
|
|
||||||
function tryMove(r1,c1,r2,c2){
|
|
||||||
console.debug("tryMove", {r1,c1,r2,c2, gameOver, currentTurn, aiEnabled});
|
|
||||||
if(gameOver) { console.debug("move blocked: gameOver"); return false; }
|
|
||||||
if(!inside(r1,c1) || !inside(r2,c2)) { console.debug("move blocked: outside"); return false; }
|
|
||||||
const v = board[r1][c1];
|
|
||||||
if(!v) { console.debug("move blocked: no piece at source"); return false; }
|
|
||||||
if(!belongsTo(v, currentTurn)) { console.debug("move blocked: piece does not belong to currentTurn", currentTurn); return false; }
|
|
||||||
if(board[r2][c2] !== 0) { console.debug("move blocked: dest occupied"); return false; }
|
|
||||||
|
|
||||||
const dr = r2 - r1, dc = c2 - c1;
|
|
||||||
const isKing = (v===3||v===4);
|
|
||||||
const dir = (v===1||v===3) ? -1 : 1;
|
|
||||||
|
|
||||||
// move
|
|
||||||
if(Math.abs(dr)===1 && Math.abs(dc)===1){
|
|
||||||
if(isKing || dr === dir){
|
|
||||||
saveHistory();
|
|
||||||
board[r2][c2] = v;
|
|
||||||
board[r1][c1] = 0;
|
|
||||||
|
|
||||||
const before = v;
|
|
||||||
crownIfNeeded(r2,c2);
|
|
||||||
|
|
||||||
playSound("move");
|
|
||||||
|
|
||||||
if((before === 1 && board[r2][c2] === 3) || (before === 2 && board[r2][c2] === 4)){
|
|
||||||
playSound("king");
|
|
||||||
}
|
|
||||||
|
|
||||||
switchTurn();
|
|
||||||
draw();
|
|
||||||
if(!checkWinCondition()) triggerAI();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else { console.debug("move blocked: wrong direction for non-king"); return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// capture
|
|
||||||
if(Math.abs(dr)===2 && Math.abs(dc)===2){
|
|
||||||
const mr = r1 + dr/2, mc = c1 + dc/2;
|
|
||||||
if(!inside(mr,mc)) { console.debug("capture blocked: middle outside"); return false; }
|
|
||||||
const mid = board[mr][mc];
|
|
||||||
if(mid===0) { console.debug("capture blocked: no mid piece"); return false; }
|
|
||||||
if(belongsTo(mid, currentTurn)) { console.debug("capture blocked: mid belongs to same player"); return false; }
|
|
||||||
if(isKing || (dr/2) === dir){
|
|
||||||
saveHistory();
|
|
||||||
board[r2][c2] = v;
|
|
||||||
board[r1][c1] = 0;
|
|
||||||
board[mr][mc] = 0;
|
|
||||||
|
|
||||||
const before = v;
|
|
||||||
crownIfNeeded(r2,c2);
|
|
||||||
|
|
||||||
playSound("capture");
|
|
||||||
|
|
||||||
if((before === 1 && board[r2][c2] === 3) || (before === 2 && board[r2][c2] === 4)){
|
|
||||||
playSound("king");
|
|
||||||
}
|
|
||||||
|
|
||||||
switchTurn();
|
|
||||||
draw();
|
|
||||||
if(!checkWinCondition()) triggerAI();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else { console.debug("capture blocked: wrong direction for non-king"); return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug("move blocked: invalid delta");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function crownIfNeeded(r,c){
|
|
||||||
if(board[r][c] === 1 && r === 0) board[r][c] = 3;
|
|
||||||
if(board[r][c] === 2 && r === size - 1) board[r][c] = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchTurn(){ currentTurn = (currentTurn===1?2:1); }
|
|
||||||
function saveHistory(){ history.push(JSON.parse(JSON.stringify(board))); if(history.length>60) 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<size;r++) for(let c=0;c<size;c++) if(belongsTo(board[r][c], player)) all.push(...generateMovesForPiece(r,c,player));
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/********** CHECK WIN **********/
|
|
||||||
function checkWinCondition(){
|
|
||||||
if(gameOver) return true;
|
|
||||||
let red=0, white=0;
|
|
||||||
for(let r=0;r<size;r++) for(let c=0;c<size;c++){
|
|
||||||
const v = board[r][c];
|
|
||||||
if(v===1||v===3) red++;
|
|
||||||
if(v===2||v===4) white++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(red===0){ showEnd("KALAH 😢","Semua pion merah hilang","loss"); return true; }
|
|
||||||
if(white===0){ showEnd("MENANG 🎉","Semua pion putih hilang","win"); return true; }
|
|
||||||
|
|
||||||
const redMoves = getAllMovesFor(1), whiteMoves = getAllMovesFor(2);
|
|
||||||
if(currentTurn===1 && redMoves.length===0){ showEnd("KALAH 😢","Merah tidak dapat bergerak","loss"); return true; }
|
|
||||||
if(currentTurn===2 && whiteMoves.length===0){ showEnd("MENANG 🎉","Putih tidak dapat bergerak","win"); return true; }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/********** AI **********/
|
|
||||||
function aiMakeMove(){
|
|
||||||
if(!aiEnabled || currentTurn !== aiPlays || gameOver){
|
|
||||||
console.debug("aiMakeMove blocked", {aiEnabled, currentTurn, aiPlays, gameOver});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moves = getAllMovesFor(aiPlays);
|
|
||||||
if(moves.length === 0){
|
|
||||||
checkWinCondition();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const promotionRow = (aiPlays === 2) ? size - 1 : 0;
|
|
||||||
|
|
||||||
// pisahkan capture dan non-capture
|
|
||||||
const captureMoves = moves.filter(m => !!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 = "<p>Belum ada data</p>"; 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 = `<span>${i+1}. ${escapeHtml(row.username)}</span><span>${row.wins}W | ${row.losses}L</span>`;
|
|
||||||
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 = `<div style="background:#1e293b;padding:24px;border-radius:12px;width:310px;text-align:center;color:white;"><h2 style="margin:0 0 8px">${title}</h2><p style="margin:0 0 16px">${msg}</p><div style="display:flex;gap:8px;justify-content:center;"><button id="popupNew" style="background:#0ea5a4;color:white;border:none;padding:10px 18px;border-radius:8px;">New Game</button><button id="popupClose" style="background:#64748b;color:white;border:none;padding:10px 18px;border-radius:8px;">Close</button></div></div>`;
|
|
||||||
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();
|
|
||||||
Loading…
x
Reference in New Issue
Block a user