ini halaman login

This commit is contained in:
alvin 2025-12-10 10:07:18 +07:00
parent fd8eb42c15
commit 1461276b81
15 changed files with 1297 additions and 0 deletions

114
assets/css/style.css Normal file
View 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
View 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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[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

Binary file not shown.

BIN
assets/sound/move.mp3 Normal file

Binary file not shown.

8
dam_db.sql Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>