+ login Google
58
admin.php
@ -15,7 +15,6 @@ $pdo = db();
|
||||
// --- IMAGE SLICING FUNCTION ---
|
||||
function create_puzzle_pieces($source_image_path, $puzzle_id, $pieces_count) {
|
||||
if (!function_exists('gd_info')) {
|
||||
// GD library not available
|
||||
error_log("GD Library is not installed or enabled.");
|
||||
return false;
|
||||
}
|
||||
@ -93,34 +92,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_puzzle'])) {
|
||||
$is_public = isset($_POST['is_public']) ? 1 : 0;
|
||||
$image_file = $_FILES['puzzle_image'] ?? null;
|
||||
|
||||
// Basic validation
|
||||
if (!empty($puzzle_name) && $image_file && $image_file['error'] === UPLOAD_ERR_OK) {
|
||||
$upload_dir = __DIR__ . '/uploads/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
mkdir($upload_dir, 0777, true);
|
||||
}
|
||||
|
||||
// Sanitize filename and create a unique name
|
||||
$original_filename = basename($image_file['name']);
|
||||
$image_extension = pathinfo($original_filename, PATHINFO_EXTENSION);
|
||||
$safe_filename = 'puzzle_' . uniqid() . '.' . $image_extension;
|
||||
$upload_path = $upload_dir . $safe_filename;
|
||||
|
||||
// Move the file
|
||||
if (move_uploaded_file($image_file['tmp_name'], $upload_path)) {
|
||||
// Insert into database
|
||||
$stmt = $pdo->prepare('INSERT INTO puzzles (name, original_image, pieces, is_public, created_at) VALUES (?, ?, ?, ?, NOW())');
|
||||
$stmt->execute([$puzzle_name, $safe_filename, $pieces, $is_public]);
|
||||
$stmt = $pdo->prepare('INSERT INTO puzzles (name, original_image, file_name, pieces, is_public, created_at) VALUES (?, ?, ?, ?, ?, NOW())');
|
||||
$stmt->execute([$puzzle_name, $safe_filename, $safe_filename, $pieces, $is_public]);
|
||||
$puzzle_id = $pdo->lastInsertId();
|
||||
|
||||
// Create puzzle pieces
|
||||
create_puzzle_pieces($upload_path, $puzzle_id, $pieces);
|
||||
|
||||
header("Location: admin.php?success=1");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
// Handle error case
|
||||
header("Location: admin.php?error=1");
|
||||
exit;
|
||||
}
|
||||
@ -130,27 +123,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_puzzle'])) {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_puzzle'])) {
|
||||
$puzzle_id_to_delete = $_POST['puzzle_id'];
|
||||
|
||||
// First, get the original image filename to delete it
|
||||
$stmt = $pdo->prepare('SELECT original_image FROM puzzles WHERE id = ?');
|
||||
$stmt->execute([$puzzle_id_to_delete]);
|
||||
$puzzle = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($puzzle) {
|
||||
// Delete original image file
|
||||
$original_image_path = __DIR__ . '/uploads/' . $puzzle['original_image'];
|
||||
if (file_exists($original_image_path)) {
|
||||
unlink($original_image_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from DB
|
||||
$stmt = $pdo->prepare('DELETE FROM puzzles WHERE id = ?');
|
||||
$stmt->execute([$puzzle_id_to_delete]);
|
||||
|
||||
// Delete puzzle pieces directory
|
||||
$puzzle_dir = __DIR__ . '/puzzles/' . $puzzle_id_to_delete;
|
||||
if (is_dir($puzzle_dir)) {
|
||||
// A simple recursive delete function
|
||||
function delete_directory($dir) {
|
||||
if (!is_dir($dir)) return;
|
||||
$files = array_diff(scandir($dir), array('.','..'));
|
||||
@ -162,13 +150,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_puzzle'])) {
|
||||
delete_directory($puzzle_dir);
|
||||
}
|
||||
|
||||
header("Location: admin.php?deleted=1"); // Redirect to avoid re-posting
|
||||
header("Location: admin.php?deleted=1");
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
$puzzles = $pdo->query('SELECT id, name, original_image, pieces, is_public, created_at FROM puzzles ORDER BY created_at DESC')->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch users
|
||||
$users = $pdo->query('SELECT id, username, email, created_at FROM users ORDER BY created_at DESC')->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
@ -264,10 +255,9 @@ $puzzles = $pdo->query('SELECT id, name, original_image, pieces, is_public, crea
|
||||
<?php foreach ($puzzles as $puzzle):
|
||||
$image_path = __DIR__ . '/uploads/' . $puzzle['original_image'];
|
||||
if (file_exists($image_path)) {
|
||||
// Cache busting for images
|
||||
$imageUrl = 'uploads/' . htmlspecialchars($puzzle['original_image']) . '?v=' . filemtime($image_path);
|
||||
} else {
|
||||
$imageUrl = 'assets/images/placeholder.png'; // A placeholder if image is missing
|
||||
$imageUrl = 'assets/images/placeholder.png';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
@ -289,6 +279,40 @@ $puzzles = $pdo->query('SELECT id, name, original_image, pieces, is_public, crea
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- User Management Section -->
|
||||
<div class="mt-5">
|
||||
<h1 class="mb-4">Gestione Utenti</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Registrato il</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($users)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">Nessun utente trovato.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($user['id']); ?></td>
|
||||
<td><?php echo htmlspecialchars($user['username']); ?></td>
|
||||
<td><?php echo htmlspecialchars($user['email']); ?></td>
|
||||
<td><?php echo htmlspecialchars($user['created_at']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="text-center mt-5 py-3 bg-light">
|
||||
|
||||
6
composer.json
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
{
|
||||
"require": {
|
||||
"google/apiclient": "^2.0"
|
||||
}
|
||||
}
|
||||
1283
composer.lock
generated
Normal file
@ -9,6 +9,11 @@ define('DB_PASS', '7d2d5a2d-6e5e-4580-a9b7-3f8e7288a494');
|
||||
define('ADMIN_USER', 'admin');
|
||||
define('ADMIN_PASS_HASH', '$2y$10$WnTccK10o9fJmz3xfs6gV.0GB2PH.Smtm4NOlqlnr7SmEgMcY8uC.'); // Hash for 'MaviGames'
|
||||
|
||||
// Google API Credentials - ** PLEASE REPLACE WITH YOUR OWN CREDENTIALS **
|
||||
define('GOOGLE_CLIENT_ID', 'YOUR_GOOGLE_CLIENT_ID');
|
||||
define('GOOGLE_CLIENT_SECRET', 'YOUR_GOOGLE_CLIENT_SECRET');
|
||||
define('GOOGLE_REDIRECT_URI', 'https://mavigames-puzzle.dev.flatlogic.app/google-callback.php');
|
||||
|
||||
function db() {
|
||||
static $pdo;
|
||||
if (!$pdo) {
|
||||
|
||||
16
google-auth.php
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
$client = new Google_Client();
|
||||
$client->setClientId(GOOGLE_CLIENT_ID);
|
||||
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
|
||||
$client->setRedirectUri(GOOGLE_REDIRECT_URI);
|
||||
$client->addScope('email');
|
||||
$client->addScope('profile');
|
||||
|
||||
$auth_url = $client->createAuthUrl();
|
||||
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
|
||||
exit();
|
||||
63
google-callback.php
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
$client = new Google_Client();
|
||||
$client->setClientId(GOOGLE_CLIENT_ID);
|
||||
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
|
||||
$client->setRedirectUri(GOOGLE_REDIRECT_URI);
|
||||
$client->addScope('email');
|
||||
$client->addScope('profile');
|
||||
|
||||
if (isset($_GET['code'])) {
|
||||
$token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
|
||||
$client->setAccessToken($token);
|
||||
|
||||
$google_oauth = new Google_Service_Oauth2($client);
|
||||
$google_account_info = $google_oauth->userinfo->get();
|
||||
|
||||
$email = $google_account_info->email;
|
||||
$name = $google_account_info->name;
|
||||
$google_id = $google_account_info->id;
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Check if user exists
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user) {
|
||||
// User exists, log them in
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
if ($user['username'] === ADMIN_USER) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
}
|
||||
} else {
|
||||
// User doesn't exist, create a new one
|
||||
$username = strtok($email, '@'); // Create a username from email
|
||||
$password = password_hash(random_bytes(16), PASSWORD_BCRYPT); // Create a random password
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, email, password, google_id) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $email, $password, $google_id]);
|
||||
$user_id = $pdo->lastInsertId();
|
||||
|
||||
$_SESSION['user_id'] = $user_id;
|
||||
$_SESSION['username'] = $username;
|
||||
}
|
||||
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
} else {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
35
index.php
@ -136,16 +136,31 @@ if (isset($_SESSION['selected_puzzle'])) {
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-bold text-cyan" href="index.php">Photo Puzzle</a>
|
||||
|
||||
<div class="d-flex align-items-center ms-auto">
|
||||
<div class="me-2 me-md-3">
|
||||
<label for="difficulty" class="form-label visually-hidden">Difficoltà</label>
|
||||
<select class="form-select form-select-sm" id="difficulty">
|
||||
<option value="32">Facile</option>
|
||||
<option value="48">Medio</option>
|
||||
<option value="64">Difficile</option>
|
||||
</select>
|
||||
</div>
|
||||
<a href="?action=new" class="btn btn-cyan btn-sm">Nuova Partita</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.php">Gioca</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="leaderboard.php">Classifica</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="logout.php">Logout (<?php echo htmlspecialchars($_SESSION['username']); ?>)</a>
|
||||
</li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="login.php">Login</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="register.php">Registrati</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
97
leaderboard.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Gatekeeper: redirect if not logged in
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
s.score,
|
||||
s.time_taken,
|
||||
s.moves,
|
||||
s.completed_at,
|
||||
u.username,
|
||||
p.name as puzzle_name
|
||||
FROM scores s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
JOIN puzzles p ON s.puzzle_id = p.id
|
||||
ORDER BY s.score DESC
|
||||
LIMIT 100
|
||||
");
|
||||
$scores = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Classifica</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.php">Puzzle Game</a>
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.php">Gioca</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="leaderboard.php">Classifica</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="logout.php">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h1 class="mb-4 text-center">Classifica Generale</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Utente</th>
|
||||
<th>Puzzle</th>
|
||||
<th>Punteggio</th>
|
||||
<th>Tempo</th>
|
||||
<th>Mosse</th>
|
||||
<th>Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($scores)): ?>
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">Nessun punteggio registrato.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($scores as $index => $score): ?>
|
||||
<tr>
|
||||
<td><?php echo $index + 1; ?></td>
|
||||
<td><?php echo htmlspecialchars($score['username']); ?></td>
|
||||
<td><?php echo htmlspecialchars($score['puzzle_name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($score['score']); ?></td>
|
||||
<td><?php echo htmlspecialchars($score['time_taken']); ?>s</td>
|
||||
<td><?php echo htmlspecialchars($score['moves']); ?></td>
|
||||
<td><?php echo htmlspecialchars(date("d/m/Y H:i", strtotime($score['completed_at']))); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-center mt-5 py-3 bg-light">
|
||||
<p>© <?php echo date("Y"); ?> Puzzle Game</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
60
login.php
@ -1,11 +1,10 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
// If already logged in, redirect to admin panel
|
||||
if (isset($_SESSION['is_admin']) && $_SESSION['is_admin'] === true) {
|
||||
header('Location: admin.php');
|
||||
// Redirect if already logged in
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
@ -15,13 +14,37 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if ($username === ADMIN_USER && password_verify($password, ADMIN_PASS_HASH)) {
|
||||
$_SESSION['is_admin'] = true;
|
||||
session_write_close(); // Force session to save before redirect
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
if (empty($username) || empty($password)) {
|
||||
$error_message = 'Username e password sono obbligatori.';
|
||||
} else {
|
||||
$error_message = 'Credenziali non valide. Riprova.';
|
||||
// Admin login check
|
||||
if ($username === ADMIN_USER && password_verify($password, ADMIN_PASS_HASH)) {
|
||||
$_SESSION['user_id'] = 0; // Special ID for admin
|
||||
$_SESSION['username'] = ADMIN_USER;
|
||||
$_SESSION['is_admin'] = true;
|
||||
header('Location: admin.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Regular user login check
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['is_admin'] = false; // Ensure this is false for regular users
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
$error_message = 'Credenziali non valide. Riprova.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error_message = "Errore del database: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -30,7 +53,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Login</title>
|
||||
<title>Login</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
@ -51,10 +74,20 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h2 class="text-center mb-4">Accesso Admin</h2>
|
||||
<h2 class="text-center mb-4">Login</h2>
|
||||
<?php if ($error_message): ?>
|
||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error_message); ?></div>
|
||||
<?php endif; ?>
|
||||
<a href="google-auth.php" class="btn btn-danger w-100 mb-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-google" viewBox="0 0 16 16">
|
||||
<path d="M15.545 6.558a9.42 9.42 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.689 7.689 0 0 1 5.352 2.082l-2.284 2.284A4.347 4.347 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.25C2.806 7.48 2.5 8.522 2.5 9.5s.306 2.02.812 2.834c.632 1.842 2.405 3.25 4.492 3.25 1.257 0 2.34-.47 3.162-1.224l.064-.064a3.837 3.837 0 0 0 1.15-2.487H8v-2h7.545z"/>
|
||||
</svg> Accedi con Google
|
||||
</a>
|
||||
<div class="d-flex align-items-center my-3">
|
||||
<hr class="flex-grow-1">
|
||||
<span class="px-2 text-muted">oppure</span>
|
||||
<hr class="flex-grow-1">
|
||||
</div>
|
||||
<form method="post" action="login.php">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nome Utente</label>
|
||||
@ -66,6 +99,9 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Accedi</button>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<p>Non hai un account? <a href="register.php">Registrati</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
106
puzzle.php
@ -1,9 +1,40 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Gatekeeper: redirect if not logged in
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Handle Score Submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_score') {
|
||||
header('Content-Type: application/json');
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$puzzle_id = (int)$_POST['puzzle_id'];
|
||||
$time_taken = (int)$_POST['time_taken'];
|
||||
$moves = (int)$_POST['moves'];
|
||||
|
||||
// Simple scoring formula
|
||||
$score = max(0, 10000 - ($time_taken * 10) - ($moves * 5));
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO scores (user_id, puzzle_id, time_taken, moves, score) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$user_id, $puzzle_id, $time_taken, $moves, $score]);
|
||||
echo json_encode(['success' => true, 'score' => $score]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
exit; // Important: stop script execution after AJAX response
|
||||
}
|
||||
|
||||
|
||||
// --- VARIABLES ---
|
||||
$puzzle_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
$difficulty = isset($_GET['difficulty']) ? (int)$_GET['difficulty'] : 16;
|
||||
@ -44,7 +75,6 @@ if ($puzzle_id > 0) {
|
||||
$source_width = $image_info[0];
|
||||
$source_height = $image_info[1];
|
||||
|
||||
// Use a difficulty-specific directory
|
||||
$pieces_dir = sprintf('puzzles/%d/%d', $puzzle['id'], $difficulty);
|
||||
if (!is_dir($pieces_dir)) {
|
||||
mkdir($pieces_dir, 0777, true);
|
||||
@ -100,15 +130,7 @@ if ($puzzle_id > 0) {
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<style>
|
||||
#puzzle-board {
|
||||
border: 2px dashed #ccc;
|
||||
background-color: #f8f9fa;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(<?php echo $cols; ?>, 1fr);
|
||||
grid-template-rows: repeat(<?php echo $rows; ?>, 1fr);
|
||||
aspect-ratio: <?php echo $source_width; ?> / <?php echo $source_height; ?>;
|
||||
max-width: 100%;
|
||||
}
|
||||
#puzzle-board { border: 2px dashed #ccc; background-color: #f8f9fa; display: grid; grid-template-columns: repeat(<?php echo $cols; ?>, 1fr); grid-template-rows: repeat(<?php echo $rows; ?>, 1fr); aspect-ratio: <?php echo $source_width; ?> / <?php echo $source_height; ?>; max-width: 100%; }
|
||||
.drop-zone { border: 1px solid #eee; width: 100%; height: 100%; }
|
||||
.drop-zone.hovered { background-color: #e9ecef; }
|
||||
.puzzle-piece { cursor: grab; width: 100%; height: 100%; object-fit: cover; box-shadow: 0 0 5px rgba(0,0,0,0.5); }
|
||||
@ -121,13 +143,19 @@ if ($puzzle_id > 0) {
|
||||
<div id="win-message">
|
||||
<h2>Complimenti!</h2>
|
||||
<p>Hai risolto il puzzle!</p>
|
||||
<p>Punteggio: <strong id="final-score">0</strong></p>
|
||||
<a href="leaderboard.php" class="btn btn-warning">Classifica</a>
|
||||
<a href="index.php" class="btn btn-light">Gioca Ancora</a>
|
||||
</div>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="text-center mb-4">
|
||||
<h1><?php echo $puzzle ? htmlspecialchars($puzzle['name']) : 'Risolvi il Puzzle'; ?></h1>
|
||||
<a href="index.php" class="btn btn-secondary btn-sm">Torna alla Galleria</a>
|
||||
<div class="d-flex justify-content-center align-items-center gap-3">
|
||||
<a href="index.php" class="btn btn-secondary btn-sm">Torna alla Galleria</a>
|
||||
<span class="badge bg-primary">Tempo: <span id="timer">0s</span></span>
|
||||
<span class="badge bg-info">Mosse: <span id="move-counter">0</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error_message): ?>
|
||||
@ -168,8 +196,30 @@ if ($puzzle_id > 0) {
|
||||
const pieces = document.querySelectorAll('.puzzle-piece');
|
||||
const cols = <?php echo $cols; ?>;
|
||||
const rows = <?php echo $rows; ?>;
|
||||
const puzzleId = <?php echo $puzzle_id; ?>;
|
||||
|
||||
let draggedPiece = null;
|
||||
let moves = 0;
|
||||
let gameStarted = false;
|
||||
let startTime = 0;
|
||||
let timerInterval = null;
|
||||
|
||||
function startTimer() {
|
||||
if (gameStarted) return;
|
||||
gameStarted = true;
|
||||
startTime = Date.now();
|
||||
timerInterval = setInterval(updateTimer, 1000);
|
||||
}
|
||||
|
||||
function updateTimer() {
|
||||
const seconds = Math.floor((Date.now() - startTime) / 1000);
|
||||
document.getElementById('timer').textContent = `${seconds}s`;
|
||||
}
|
||||
|
||||
function incrementMoves() {
|
||||
moves++;
|
||||
document.getElementById('move-counter').textContent = moves;
|
||||
}
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < cols; x++) {
|
||||
@ -184,6 +234,8 @@ if ($puzzle_id > 0) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('hovered');
|
||||
if (draggedPiece && dropZone.children.length === 0) {
|
||||
startTimer();
|
||||
incrementMoves();
|
||||
dropZone.appendChild(draggedPiece);
|
||||
checkWin();
|
||||
}
|
||||
@ -194,7 +246,11 @@ if ($puzzle_id > 0) {
|
||||
piecesTray.addEventListener('dragover', e => e.preventDefault());
|
||||
piecesTray.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
if (draggedPiece) { piecesTray.appendChild(draggedPiece); }
|
||||
if (draggedPiece) {
|
||||
startTimer();
|
||||
incrementMoves();
|
||||
piecesTray.appendChild(draggedPiece);
|
||||
}
|
||||
});
|
||||
|
||||
pieces.forEach(piece => {
|
||||
@ -218,8 +274,30 @@ if ($puzzle_id > 0) {
|
||||
zone.children.length > 0 &&
|
||||
zone.children[0].dataset.position === zone.dataset.position
|
||||
)) {
|
||||
document.getElementById('win-message').style.display = 'block';
|
||||
board.style.borderColor = '#28a745';
|
||||
clearInterval(timerInterval);
|
||||
const timeTaken = Math.floor((Date.now() - startTime) / 1000);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'save_score');
|
||||
formData.append('puzzle_id', puzzleId);
|
||||
formData.append('time_taken', timeTaken);
|
||||
formData.append('moves', moves);
|
||||
|
||||
fetch('puzzle.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('final-score').textContent = data.score;
|
||||
document.getElementById('win-message').style.display = 'block';
|
||||
board.style.borderColor = '#28a745';
|
||||
} else {
|
||||
console.error('Failed to save score:', data.error);
|
||||
alert('Si è verificato un errore durante il salvataggio del punteggio.');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BIN
puzzles/7/16/piece_0_0.jpg
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
puzzles/7/16/piece_0_1.jpg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
puzzles/7/16/piece_0_2.jpg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
puzzles/7/16/piece_0_3.jpg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
puzzles/7/16/piece_1_0.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
puzzles/7/16/piece_1_1.jpg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
puzzles/7/16/piece_1_2.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
puzzles/7/16/piece_1_3.jpg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
puzzles/7/16/piece_2_0.jpg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
puzzles/7/16/piece_2_1.jpg
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
puzzles/7/16/piece_2_2.jpg
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
puzzles/7/16/piece_2_3.jpg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
puzzles/7/16/piece_3_0.jpg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
puzzles/7/16/piece_3_1.jpg
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
puzzles/7/16/piece_3_2.jpg
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
puzzles/7/16/piece_3_3.jpg
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
puzzles/7/32/piece_0_0.jpg
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
puzzles/7/32/piece_0_1.jpg
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
puzzles/7/32/piece_0_2.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
puzzles/7/32/piece_0_3.jpg
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
puzzles/7/32/piece_0_4.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
puzzles/7/32/piece_0_5.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
puzzles/7/32/piece_0_6.jpg
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
puzzles/7/32/piece_0_7.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
puzzles/7/32/piece_1_0.jpg
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
puzzles/7/32/piece_1_1.jpg
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
puzzles/7/32/piece_1_2.jpg
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
puzzles/7/32/piece_1_3.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
puzzles/7/32/piece_1_4.jpg
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
puzzles/7/32/piece_1_5.jpg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
puzzles/7/32/piece_1_6.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
puzzles/7/32/piece_1_7.jpg
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
puzzles/7/32/piece_2_0.jpg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
puzzles/7/32/piece_2_1.jpg
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
puzzles/7/32/piece_2_2.jpg
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
puzzles/7/32/piece_2_3.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
puzzles/7/32/piece_2_4.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
puzzles/7/32/piece_2_5.jpg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
puzzles/7/32/piece_2_6.jpg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
puzzles/7/32/piece_2_7.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
puzzles/7/32/piece_3_0.jpg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
puzzles/7/32/piece_3_1.jpg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
puzzles/7/32/piece_3_2.jpg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
puzzles/7/32/piece_3_3.jpg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
puzzles/7/32/piece_3_4.jpg
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
puzzles/7/32/piece_3_5.jpg
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
puzzles/7/32/piece_3_6.jpg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
puzzles/7/32/piece_3_7.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
puzzles/7/64/piece_0_0.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_0_1.jpg
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
puzzles/7/64/piece_0_2.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
puzzles/7/64/piece_0_3.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
puzzles/7/64/piece_0_4.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
puzzles/7/64/piece_0_5.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_0_6.jpg
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
puzzles/7/64/piece_0_7.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_1_0.jpg
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
puzzles/7/64/piece_1_1.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
puzzles/7/64/piece_1_2.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_1_3.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_1_4.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
puzzles/7/64/piece_1_5.jpg
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
puzzles/7/64/piece_1_6.jpg
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
puzzles/7/64/piece_1_7.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
puzzles/7/64/piece_2_0.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_2_1.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_2_2.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_2_3.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_2_4.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_2_5.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
puzzles/7/64/piece_2_6.jpg
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
puzzles/7/64/piece_2_7.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
puzzles/7/64/piece_3_0.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_3_1.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_3_2.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
puzzles/7/64/piece_3_3.jpg
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
puzzles/7/64/piece_3_4.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_3_5.jpg
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
puzzles/7/64/piece_3_6.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_3_7.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_4_0.jpg
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
puzzles/7/64/piece_4_1.jpg
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
puzzles/7/64/piece_4_2.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
puzzles/7/64/piece_4_3.jpg
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
puzzles/7/64/piece_4_4.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
puzzles/7/64/piece_4_5.jpg
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
puzzles/7/64/piece_4_6.jpg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
puzzles/7/64/piece_4_7.jpg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
puzzles/7/64/piece_5_0.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
puzzles/7/64/piece_5_1.jpg
Normal file
|
After Width: | Height: | Size: 2.8 KiB |