ini halaman login
This commit is contained in:
parent
fd8eb42c15
commit
1461276b81
114
assets/css/style.css
Normal file
114
assets/css/style.css
Normal file
@ -0,0 +1,114 @@
|
||||
body {
|
||||
font-family: Inter, system-ui, Arial, sans-serif;
|
||||
margin: 0;
|
||||
background: #f2f6f9;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
width: 100%;
|
||||
padding: 12px 0;
|
||||
background: #061d1d;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
max-width: 360px;
|
||||
margin: 6vh auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 18px rgba(2,6,23,0.08);
|
||||
}
|
||||
|
||||
.auth-card h1 {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.auth-card label {
|
||||
display: block;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.auth-card input {
|
||||
width: 95%;
|
||||
padding: 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.auth-card button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 12px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #0ea5a4;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.success {
|
||||
background: #ecfccb;
|
||||
color: #365314;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* GAME AREA */
|
||||
.game-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.board-wrap {
|
||||
position: relative !important;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 16px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
background: #334155;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
canvas#board {
|
||||
position: relative !important;
|
||||
z-index: 999999 !important;
|
||||
background: #000 !important;
|
||||
border: 2px solid red !important;
|
||||
|
||||
width: 640px !important;
|
||||
height: 640px !important;
|
||||
|
||||
display: block !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
431
assets/js/game.js
Normal file
431
assets/js/game.js
Normal file
@ -0,0 +1,431 @@
|
||||
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();
|
||||
BIN
assets/sound/capture.mp3
Normal file
BIN
assets/sound/capture.mp3
Normal file
Binary file not shown.
BIN
assets/sound/move.mp3
Normal file
BIN
assets/sound/move.mp3
Normal file
Binary file not shown.
8
dam_db.sql
Normal file
8
dam_db.sql
Normal file
@ -0,0 +1,8 @@
|
||||
USE dam_db;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB;
|
||||
23
db.php
Normal file
23
db.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
// Cegah file ini dipanggil dua kali (penting untuk mencegah infinite loop)
|
||||
if (defined('DB_CONNECTED')) return;
|
||||
define('DB_CONNECTED', true);
|
||||
|
||||
$DB_HOST = '127.0.0.1';
|
||||
$DB_USER = 'root';
|
||||
$DB_PASS = '';
|
||||
$DB_NAME = 'dam_db';
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
"mysql:host=$DB_HOST;dbname=$DB_NAME;charset=utf8mb4",
|
||||
$DB_USER,
|
||||
$DB_PASS,
|
||||
[
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
die('DB ERROR: ' . $e->getMessage());
|
||||
}
|
||||
189
game.php
Normal file
189
game.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$mode = $_GET['mode'] ?? "pvp";
|
||||
$username = htmlspecialchars($_SESSION['username']);
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Dam Inggris - Main</title>
|
||||
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.topbar {
|
||||
width: 100%;
|
||||
padding: 15px 20px;
|
||||
background: #1e293b;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.topbar a { color: #38bdf8; text-decoration:none; }
|
||||
|
||||
/* WRAPPER UTAMA */
|
||||
.main-wrapper {
|
||||
margin-top: 25px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
/* LEADERBOARD */
|
||||
.leaderboard-box {
|
||||
width: 220px;
|
||||
background: #1e293b;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
height: fit-content;
|
||||
box-shadow: 0 8px 18px rgba(0,0,0,0.35);
|
||||
}
|
||||
|
||||
.leaderboard-box h3 {
|
||||
margin: 0 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lb-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 6px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.lb-row.me {
|
||||
background: rgba(14,165,164,0.25);
|
||||
}
|
||||
|
||||
/* BOARD + CONTROLS */
|
||||
.board-area {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* CANVAS */
|
||||
canvas#board {
|
||||
width: 600px !important;
|
||||
height: 600px !important;
|
||||
background: #000;
|
||||
border: 3px solid #333;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* TOMBOL */
|
||||
.right-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.right-controls button {
|
||||
padding: 12px 18px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #0ea5a4;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.right-controls button:hover {
|
||||
background: #1e293b;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="topbar">
|
||||
Halo, <?= $username ?> | <a href="mode.php">Pilih Mode</a>
|
||||
<div id="credit" style="
|
||||
text-align:center;
|
||||
margin-top:12px;
|
||||
color:#ccc;
|
||||
font-size:14px;
|
||||
">
|
||||
Credits: Alvin, Basilius & Gray - Kelompok 11
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ====== WRAPPER UTAMA ====== -->
|
||||
<div class="main-wrapper">
|
||||
|
||||
<!-- LEADERBOARD -->
|
||||
<div class="leaderboard-box">
|
||||
<h3>🏆 Leaderboard</h3>
|
||||
<div id="leaderboardList">Memuat...</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== TENGAH: BOARD & CREDIT ====== -->
|
||||
<div class="center-panel" style="display:flex; flex-direction:column; align-items:center;">
|
||||
|
||||
<canvas id="board" width="640" height="640"></canvas>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ====== KANAN: TIMER & BUTTON ====== -->
|
||||
<div class="bottom-controls">
|
||||
|
||||
<div id="timerBox" style="
|
||||
background:#1e293b;
|
||||
padding:10px;
|
||||
border-radius:8px;
|
||||
text-align:center;
|
||||
margin-bottom:10px;
|
||||
color:white;
|
||||
font-size:18px;
|
||||
font-weight:bold;
|
||||
">
|
||||
⏱ <span id="timer">00:00</span>
|
||||
</div>
|
||||
|
||||
<button id="newBtn">New Game</button>
|
||||
<button id="undoBtn">Undo</button>
|
||||
|
||||
<button id="menu-btn" onclick="window.location='settings.php'">
|
||||
Settings
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const GAME_MODE = "<?= $mode ?>";
|
||||
const CURRENT_USER_ID = <?= intval($_SESSION['user_id']) ?>;
|
||||
const CURRENT_USERNAME = "<?= htmlspecialchars($_SESSION['username'], ENT_QUOTES) ?>";
|
||||
const ENABLE_HINTS = <?= $_SESSION['hints'] ? "true" : "false" ?>;
|
||||
</script>
|
||||
|
||||
|
||||
<!-- load script game.js -->
|
||||
<script src="assets/js/game.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
50
index.php
Normal file
50
index.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
session_start();
|
||||
require 'db.php';
|
||||
|
||||
|
||||
$err = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
|
||||
if ($username === '' || $password === '') {
|
||||
$err = 'Username dan password harus diisi.';
|
||||
} else {
|
||||
$stmt = $pdo->prepare('SELECT id, password FROM users WHERE username = ? LIMIT 1');
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch();
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $username;
|
||||
header('Location: menu.php');
|
||||
exit;
|
||||
} else {
|
||||
$err = 'Username atau password salah.';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Login - Dam Inggris</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="auth-card">
|
||||
<h1 style="text-align: center;">Login - Ke Main Menu</h1>
|
||||
<?php if ($err): ?><div class="error"><?php echo htmlspecialchars($err); ?></div><?php endif; ?>
|
||||
<form method="post" action="">
|
||||
<label style="text-align: center;">Username<input name="username" required></label>
|
||||
<label style="text-align: center;">Password<input type="password" name="password" required></label>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<p style="text-align: center;">Belum punya akun? <a href="register.php">Daftar di sini</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
23
load_leaderboard.php
Normal file
23
load_leaderboard.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require 'db.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$stmt = $pdo->query("
|
||||
SELECT u.id AS user_id, u.username, s.wins, s.losses
|
||||
FROM users u
|
||||
LEFT JOIN users_stats s ON s.user_id = u.id
|
||||
GROUP BY u.id
|
||||
ORDER BY s.wins DESC, s.losses ASC
|
||||
LIMIT 20
|
||||
");
|
||||
|
||||
$rows = $stmt->fetchAll();
|
||||
echo json_encode($rows);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
14
logout.php
Normal file
14
logout.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
session_start();
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000,
|
||||
$params['path'], $params['domain'],
|
||||
$params['secure'], $params['httponly']
|
||||
);
|
||||
}
|
||||
session_destroy();
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
?>
|
||||
94
menu.php
Normal file
94
menu.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$username = htmlspecialchars($_SESSION['username']);
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Menu Utama - Dam Inggris</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f0f4f8;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.menu-box {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
|
||||
text-align: center;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
color: #0ea5a4;
|
||||
font-size: 20px
|
||||
}
|
||||
|
||||
p {
|
||||
color: #555;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
a.menu-btn {
|
||||
display: block;
|
||||
width: 95%;
|
||||
background: #0ea5a4;
|
||||
padding: 12px;
|
||||
margin: 10px 0;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
a.menu-btn:hover {
|
||||
background: #0c8c8b;
|
||||
}
|
||||
|
||||
a.back-btn {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a.back-btn:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="menu-box">
|
||||
<h2>Selamat Datang Di Menu, <?= $username ?></h2>
|
||||
|
||||
<a class="menu-btn" href="mode.php">🎮 New Game</a>
|
||||
<a class="menu-btn" href="settings.php">⚙ Settings</a>
|
||||
|
||||
<a class="back-btn" href="logout.php">⬅ Logout</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
97
mode.php
Normal file
97
mode.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
$username = htmlspecialchars($_SESSION['username']);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dam Inggris - Menu</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: #f0f4f8;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.menu-box {
|
||||
background: white;
|
||||
padding: 30px 40px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
width: 340px;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.menu-box h1 {
|
||||
margin: 0 0 20px;
|
||||
font-size: 26px;
|
||||
color: #0ea5a4;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
font-size: 17px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-pvp {
|
||||
background: #0ea5a4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-pvai {
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: .85;
|
||||
background: #0c8c8b;
|
||||
}
|
||||
|
||||
.logout {
|
||||
margin-top: 18px;
|
||||
display: block;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="menu-box">
|
||||
<h1>Halo <?= $username ?>, Silahkan Pilih Mode Game</h1>
|
||||
|
||||
<form action="game.php" method="GET">
|
||||
<button type="submit" name="mode" value="pvp" class="btn btn-pvp">
|
||||
🧑🤝🧑 Player vs Player
|
||||
</button>
|
||||
|
||||
<button type="submit" name="mode" value="pvai" class="btn btn-pvai">
|
||||
🤖 Player vs AI
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
<a class="back-btn" href="menu.php">⬅ Kembali Ke Menu</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
83
register.php
Normal file
83
register.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
session_start();
|
||||
require 'db.php';
|
||||
|
||||
$err = '';
|
||||
$success = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$password2 = $_POST['password2'] ?? '';
|
||||
|
||||
if ($username === '' || $password === '' || $password2 === '') {
|
||||
$err = 'Semua field harus diisi.';
|
||||
} elseif ($password !== $password2) {
|
||||
$err = 'Password tidak cocok.';
|
||||
} else {
|
||||
|
||||
// Cek apakah username sudah ada
|
||||
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = ?');
|
||||
$stmt->execute([$username]);
|
||||
|
||||
if ($stmt->fetch()) {
|
||||
$err = 'Username sudah digunakan.';
|
||||
} else {
|
||||
// Insert ke tabel users
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$ins = $pdo->prepare('INSERT INTO users (username, password) VALUES (?, ?)');
|
||||
$ins->execute([$username, $hash]);
|
||||
|
||||
// AMBIL user_id baru
|
||||
$user_id = $pdo->lastInsertId();
|
||||
|
||||
// ⬇⬇ TAMBAHKAN users_stats OTOMATIS DI SINI ⬇⬇
|
||||
$stmt = $pdo->prepare("INSERT INTO users_stats (user_id) VALUES (?)");
|
||||
$stmt->execute([$user_id]);
|
||||
// ⬆⬆ TAMBAHAN WAJIB ADA ⬆⬆
|
||||
|
||||
$success = 'Pendaftaran berhasil. Silakan login.';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Register - Dam Inggris</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="auth-card">
|
||||
<h1 style="text-align: center;">Daftar</h1>
|
||||
|
||||
<?php if ($err): ?>
|
||||
<div class="error"><?php echo htmlspecialchars($err); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="success"><?php echo htmlspecialchars($success); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="">
|
||||
<label style="text-align: center;">Username
|
||||
<input name="username" required>
|
||||
</label>
|
||||
|
||||
<label style="text-align: center;">Password
|
||||
<input type="password" name="password" required>
|
||||
</label>
|
||||
|
||||
<label style="text-align: center;">Konfirmasi Password
|
||||
<input type="password" name="password2" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">Daftar</button>
|
||||
</form>
|
||||
|
||||
<p style="text-align: center;">Sudah punya akun? <a href="index.php">Login</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
44
save_result.php
Normal file
44
save_result.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
session_start();
|
||||
header("Content-Type: application/json");
|
||||
require 'db.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(["error" => "Not logged in"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
$input = json_decode(file_get_contents("php://input"), true);
|
||||
$result = $input['result'] ?? '';
|
||||
|
||||
if (!in_array($result, ['win','loss','draw'])) {
|
||||
echo json_encode(["error" => "Invalid result"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Pastikan row user di users_stats ADA
|
||||
$stmt = $pdo->prepare("SELECT id FROM users_stats WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
|
||||
// Jika belum ada, buat kosong
|
||||
if (!$stmt->fetch()) {
|
||||
$pdo->prepare("INSERT INTO users_stats (user_id, wins, losses, draws) VALUES (?, 0, 0, 0)")
|
||||
->execute([$user_id]);
|
||||
}
|
||||
|
||||
// Update sesuai hasil
|
||||
if ($result === 'win') {
|
||||
$sql = "UPDATE users_stats SET wins = wins + 1, updated_at = NOW() WHERE user_id = ?";
|
||||
}
|
||||
else if ($result === 'loss') {
|
||||
$sql = "UPDATE users_stats SET losses = losses + 1, updated_at = NOW() WHERE user_id = ?";
|
||||
}
|
||||
else if ($result === 'draw') {
|
||||
$sql = "UPDATE users_stats SET draws = draws + 1, updated_at = NOW() WHERE user_id = ?";
|
||||
}
|
||||
|
||||
$pdo->prepare($sql)->execute([$user_id]);
|
||||
|
||||
echo json_encode(["success" => true]);
|
||||
127
settings.php
Normal file
127
settings.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['hints'])) {
|
||||
$_SESSION['hints'] = 1; // default ON
|
||||
}
|
||||
|
||||
$success = "";
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['hints'] = isset($_POST['hints']) ? 1 : 0;
|
||||
|
||||
// ❌ Dihapus agar tidak redirect ke menu.php
|
||||
// header("Location: menu.php");
|
||||
// exit;
|
||||
|
||||
$success = "Pengaturan berhasil disimpan!";
|
||||
}
|
||||
|
||||
$checked = $_SESSION['hints'] ? "checked" : "";
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Settings - Dam Inggris</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #eef2f7;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.settings-box {
|
||||
width: 350px;
|
||||
background: white;
|
||||
padding: 28px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #0ea5a4;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
margin: 25px 0;
|
||||
font-size: 17px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.success-box {
|
||||
padding: 10px;
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 18px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
background: #0ea5a4;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #0c8c8b;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="settings-box">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<!-- Pesan sukses -->
|
||||
<?php if (!empty($success)): ?>
|
||||
<div class="success-box"><?= $success ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="POST" action="settings.php">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="hints" <?= $checked ?>>
|
||||
Aktifkan Hints / Arah Petunjuk
|
||||
</label>
|
||||
|
||||
<button type="submit">Simpan</button>
|
||||
</form>
|
||||
|
||||
<a href="menu.php">⬅ Kembali</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user