metin2 Chat
chat
"use client";
import React from "react";
function MainComponent() {
const [grid, setGrid] = React.useState([]);
const [currentNumber, setCurrentNumber] = React.useState(1);
const [gameStarted, setGameStarted] = React.useState(false);
const [gameCompleted, setGameCompleted] = React.useState(false);
const [gameOver, setGameOver] = React.useState(false);
const [selectedCell, setSelectedCell] = React.useState(null);
const [validMoves, setValidMoves] = React.useState([]);
const [startTime, setStartTime] = React.useState(null);
const [completionTime, setCompletionTime] = React.useState(null);
const [showNicknameInput, setShowNicknameInput] = React.useState(false);
const [nickname, setNickname] = React.useState("");
const [leaderboard, setLeaderboard] = React.useState([]);
const [showLeaderboard, setShowLeaderboard] = React.useState(false);
const [audioContext, setAudioContext] = React.useState(null);
const [backgroundMusic, setBackgroundMusic] = React.useState(null);
const [darkMode, setDarkMode] = React.useState(false);
const [musicEnabled, setMusicEnabled] = React.useState(true);
const [hapticEnabled, setHapticEnabled] = React.useState(true);
const [moveHistory, setMoveHistory] = React.useState([]);
const [showSettings, setShowSettings] = React.useState(false);
const [installPrompt, setInstallPrompt] = React.useState(null);
const [isInstallable, setIsInstallable] = React.useState(false);
React.useEffect(() => {
const manifest = {
name: "Centum Grid LM Edition",
short_name: "Centum Grid",
description:
"Un puzzle game logico dove devi completare una griglia 10x10",
start_url: "/",
display: "standalone",
background_color: "#4f46e5",
theme_color: "#4f46e5",
icons: [
{
src:
"data:image/svg+xml;base64," +
btoa(`
`),
sizes: "192x192",
type: "image/svg+xml",
},
{
src:
"data:image/svg+xml;base64," +
btoa(`
`),
sizes: "512x512",
type: "image/svg+xml",
},
],
};
const manifestBlob = new Blob([JSON.stringify(manifest)], {
type: "application/json",
});
const manifestUrl = URL.createObjectURL(manifestBlob);
let manifestLink = document.querySelector('link[rel="manifest"]');
if (!manifestLink) {
manifestLink = document.createElement("link");
manifestLink.rel = "manifest";
document.head.appendChild(manifestLink);
}
manifestLink.href = manifestUrl;
const swCode = `
const CACHE_NAME = 'centum-grid-v1';
const urlsToCache = [
'/',
'/static/js/bundle.js',
'/static/css/main.css'
];
self.addEventListener('install', (event) => {
console.log('Service Worker installing...');
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Opened cache');
return cache.addAll(urlsToCache.filter(url => url !== '/static/js/bundle.js' && url !== '/static/css/main.css'));
})
.catch(err => {
console.log('Cache failed:', err);
return Promise.resolve();
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', (event) => {
console.log('Service Worker activated');
});
`;
let swUrl = null;
if ("serviceWorker" in navigator) {
const swBlob = new Blob([swCode], { type: "application/javascript" });
swUrl = URL.createObjectURL(swBlob);
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
console.log("SW registered: ", registration);
registration.update();
})
.catch((registrationError) => {
console.log("SW registration failed: ", registrationError);
});
}
const handleBeforeInstallPrompt = (e) => {
console.log("beforeinstallprompt event fired");
e.preventDefault();
setInstallPrompt(e);
setIsInstallable(true);
};
const handleAppInstalled = () => {
console.log("appinstalled event fired");
setInstallPrompt(null);
setIsInstallable(false);
};
window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);
window.addEventListener("appinstalled", handleAppInstalled);
const debugTimer = setTimeout(() => {
console.log("Debug: Forcing install button to show");
setIsInstallable(true);
}, 3000);
return () => {
window.removeEventListener(
"beforeinstallprompt",
handleBeforeInstallPrompt
);
window.removeEventListener("appinstalled", handleAppInstalled);
clearTimeout(debugTimer);
URL.revokeObjectURL(manifestUrl);
if (swUrl) {
URL.revokeObjectURL(swUrl);
}
};
}, []);
const handleInstallClick = async () => {
if (!installPrompt) return;
const result = await installPrompt.prompt();
console.log("Install prompt result:", result);
setInstallPrompt(null);
setIsInstallable(false);
};
const initializeGrid = React.useCallback(() => {
const newGrid = [];
for (let i = 0; i < 10; i++) {
const row = [];
for (let j = 0; j < 10; j++) {
row.push({
number: null,
row: i,
col: j,
filled: false,
});
}
newGrid.push(row);
}
setGrid(newGrid);
}, []);
const calculateValidMoves = React.useCallback((row, col, currentGrid) => {
const moves = [];
const directions = [
[-3, 0],
[3, 0],
[0, -3],
[0, 3],
];
const diagonalDirections = [
[-2, -2],
[-2, 2],
[2, -2],
[2, 2],
];
[...directions, ...diagonalDirections].forEach(([dRow, dCol]) => {
const newRow = row + dRow;
const newCol = col + dCol;
if (newRow >= 0 && newRow < 10 && newCol >= 0 && newCol < 10) {
if (!currentGrid[newRow][newCol].filled) {
moves.push({ row: newRow, col: newCol });
}
}
});
return moves;
}, []);
const triggerHaptic = React.useCallback(() => {
if (hapticEnabled && navigator.vibrate) {
navigator.vibrate(50);
}
}, [hapticEnabled]);
React.useEffect(() => {
const saved = localStorage.getItem("centum-leaderboard");
if (saved) {
setLeaderboard(JSON.parse(saved));
}
const savedSettings = localStorage.getItem("centum-settings");
if (savedSettings) {
const settings = JSON.parse(savedSettings);
setDarkMode(settings.darkMode || false);
setMusicEnabled(settings.musicEnabled !== false);
setHapticEnabled(settings.hapticEnabled !== false);
}
initializeGrid();
}, [initializeGrid]);
React.useEffect(() => {
const settings = { darkMode, musicEnabled, hapticEnabled };
localStorage.setItem("centum-settings", JSON.stringify(settings));
}, [darkMode, musicEnabled, hapticEnabled]);
React.useEffect(() => {
const setupAudio = async () => {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
setAudioContext(ctx);
const createTone = (frequency, duration, startTime) => {
const oscillator = ctx.createOscillator();
const gainNode = ctx.createGain();
oscillator.connect(gainNode);
gainNode.connect(ctx.destination);
oscillator.frequency.setValueAtTime(frequency, startTime);
oscillator.type = "sine";
gainNode.gain.setValueAtTime(0, startTime);
gainNode.gain.linearRampToValueAtTime(0.08, startTime + 0.1);
gainNode.gain.linearRampToValueAtTime(
0.04,
startTime + duration - 0.1
);
gainNode.gain.linearRampToValueAtTime(0, startTime + duration);
oscillator.start(startTime);
oscillator.stop(startTime + duration);
};
const playBackgroundMusic = () => {
if (!musicEnabled || ctx.state === "suspended") {
return;
}
if (ctx.state === "suspended") {
ctx.resume();
}
const melody = [
261.63, 293.66, 329.63, 349.23, 392.0, 440.0, 493.88, 523.25,
];
const currentTime = ctx.currentTime;
melody.forEach((freq, index) => {
createTone(freq, 0.6, currentTime + index * 0.7);
});
if (musicEnabled) {
setTimeout(playBackgroundMusic, 6000);
}
};
setBackgroundMusic({ play: playBackgroundMusic, context: ctx });
} catch (error) {
console.error("Audio setup failed:", error);
}
};
setupAudio();
}, [musicEnabled]);
const handleCellClick = React.useCallback(
(cell) => {
if (gameCompleted || gameOver) return;
if (!gameStarted) {
setGameStarted(true);
setStartTime(Date.now());
if (backgroundMusic && musicEnabled) {
backgroundMusic.play();
}
}
if (currentNumber === 1 && !cell.filled) {
const newGrid = grid.map((row) =>
row.map((c) =>
c.row === cell.row && c.col === cell.col
? { ...c, number: 1, filled: true }
: c
)
);
setGrid(newGrid);
setCurrentNumber(2);
setSelectedCell({ row: cell.row, col: cell.col });
setMoveHistory([
{
grid: JSON.parse(JSON.stringify(grid)),
currentNumber: 1,
selectedCell: null,
validMoves: [],
},
]);
const moves = calculateValidMoves(cell.row, cell.col, newGrid);
setValidMoves(moves);
triggerHaptic();
return;
}
if (currentNumber > 1 && !cell.filled) {
const isValidMove = validMoves.some(
(move) => move.row === cell.row && move.col === cell.col
);
if (isValidMove) {
setMoveHistory((prev) => [
...prev,
{
grid: JSON.parse(JSON.stringify(grid)),
currentNumber,
selectedCell,
validMoves: [...validMoves],
},
]);
const newGrid = grid.map((row) =>
row.map((c) =>
c.row === cell.row && c.col === cell.col
? { ...c, number: currentNumber, filled: true }
: c
)
);
setGrid(newGrid);
setSelectedCell({ row: cell.row, col: cell.col });
if (currentNumber === 100) {
const endTime = Date.now();
const timeTaken = Math.floor((endTime - startTime) / 1000);
setCompletionTime(timeTaken);
setGameCompleted(true);
setShowNicknameInput(true);
setValidMoves([]);
triggerHaptic();
} else {
const nextNumber = currentNumber + 1;
setCurrentNumber(nextNumber);
const moves = calculateValidMoves(cell.row, cell.col, newGrid);
setValidMoves(moves);
if (moves.length === 0) {
setGameOver(true);
}
triggerHaptic();
}
}
}
},
[
gameStarted,
gameCompleted,
gameOver,
currentNumber,
grid,
validMoves,
calculateValidMoves,
startTime,
backgroundMusic,
musicEnabled,
triggerHaptic,
selectedCell,
]
);
const undoMove = React.useCallback(() => {
if (moveHistory.length === 0 || gameCompleted || gameOver) return;
const lastState = moveHistory[moveHistory.length - 1];
setGrid(lastState.grid);
setCurrentNumber(lastState.currentNumber);
setSelectedCell(lastState.selectedCell);
setValidMoves(lastState.validMoves);
setMoveHistory((prev) => prev.slice(0, -1));
triggerHaptic();
}, [moveHistory, gameCompleted, gameOver, triggerHaptic]);
const saveScore = React.useCallback(() => {
if (nickname.trim() && completionTime) {
const newScore = {
nickname: nickname.trim(),
time: completionTime,
moves: currentNumber - 1,
date: new Date().toLocaleDateString(),
};
const updatedLeaderboard = [...leaderboard, newScore]
.sort((a, b) => {
if (a.moves === 100 && b.moves === 100) {
return a.time - b.time;
}
return b.moves - a.moves;
})
.slice(0, 10);
setLeaderboard(updatedLeaderboard);
localStorage.setItem(
"centum-leaderboard",
JSON.stringify(updatedLeaderboard)
);
setShowNicknameInput(false);
setShowLeaderboard(true);
}
}, [nickname, completionTime, currentNumber, leaderboard]);
const resetGame = React.useCallback(() => {
setCurrentNumber(1);
setGameStarted(false);
setGameCompleted(false);
setGameOver(false);
setSelectedCell(null);
setValidMoves([]);
setStartTime(null);
setCompletionTime(null);
setShowNicknameInput(false);
setShowLeaderboard(false);
setNickname("");
setMoveHistory([]);
initializeGrid();
}, [initializeGrid]);
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, "0")}`;
};
const progress = ((currentNumber - 1) / 100) * 100;
const themeClasses = darkMode
? {
bg: "bg-gradient-to-br from-gray-900 to-gray-800",
cardBg: "bg-gray-800",
text: "text-white",
textSecondary: "text-gray-300",
textAccent: "text-blue-400",
border: "border-gray-600",
cellEmpty: "bg-gray-700 text-gray-400 hover:bg-gray-600",
cellFilled: "bg-green-600 text-white",
cellValid: "bg-yellow-500 text-gray-900 ring-yellow-400",
cellLast: "bg-blue-600 text-white ring-blue-400",
}
: {
bg: "bg-gradient-to-br from-blue-50 to-indigo-100",
cardBg: "bg-white",
text: "text-gray-900",
textSecondary: "text-gray-600",
textAccent: "text-indigo-800",
border: "border-gray-300",
cellEmpty: "bg-gray-100 text-gray-400 hover:bg-gray-200",
cellFilled: "bg-green-500 text-white",
cellValid: "bg-yellow-200 text-gray-800 ring-yellow-400",
cellLast: "bg-blue-500 text-white ring-blue-300",
};
return (
setMusicEnabled(!musicEnabled)}
className={`p-2 ${themeClasses.cardBg} ${themeClasses.text} rounded-lg shadow-md hover:scale-105 transition-all`}
title={musicEnabled ? "Disattiva musica" : "Attiva musica"}
>
{!gameStarted && (
{showSettings && (
Impostazioni
setShowSettings(false)}
className="w-full mt-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
>
Chiudi
)}
{showNicknameInput && (
Salva Punteggio
setShowNicknameInput(false)}
className="flex-1 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400 transition-colors"
>
Salta
)}
{showLeaderboard && (
)}
setShowLeaderboard(false)}
className="w-full mt-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
>
Chiudi
)}
Nuovo Gioco
setShowLeaderboard(true)}
className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
Classifica
{validMoves.length > 0 &&
gameStarted &&
!gameCompleted &&
!gameOver && (
);
}
export default MainComponent;
LM
{Array.from({ length: 9 }).map((_, i) => (
))}
Centum Grid
LM Edition
{currentNumber}
Prossima mossa
{currentNumber - 1}/100
Mosse completate
{validMoves.length}
Mosse possibili
Regole del gioco:
- • Clicca una cella per iniziare con il numero 1
- • Orizzontalmente/verticalmente: salta 2 celle
- • Diagonalmente: salta 1 cella
- • Completa tutte le 100 mosse o fino a quando non ci sono più mosse possibili
{gameOver && !gameCompleted && (
Riprova
)}
Game Over! Nessuna mossa possibile. Hai raggiunto{" "} {currentNumber - 1} mosse!
{grid.map((row, rowIndex) =>
row.map((cell, colIndex) => {
const isValidMove = validMoves.some(
(move) => move.row === cell.row && move.col === cell.col
);
const isLastPlaced =
selectedCell &&
selectedCell.row === cell.row &&
selectedCell.col === cell.col;
let cellClass = "";
if (cell.filled) {
cellClass = isLastPlaced
? themeClasses.cellLast
: themeClasses.cellFilled;
} else if (isValidMove && !gameCompleted && !gameOver) {
cellClass = themeClasses.cellValid;
} else if (currentNumber === 1 && !gameStarted) {
cellClass = `${themeClasses.cellEmpty} hover:text-gray-600`;
} else {
cellClass = themeClasses.cellEmpty;
}
return (
handleCellClick(cell)}
disabled={gameCompleted || gameOver}
className={`
aspect-square text-xs md:text-sm font-bold rounded transition-all duration-200
${cellClass}
${
(isValidMove ||
(currentNumber === 1 && !gameStarted)) &&
!gameCompleted &&
!gameOver
? "hover:scale-105 cursor-pointer"
: "cursor-default"
}
${isLastPlaced ? "ring-2" : ""}
${
isValidMove && !gameCompleted && !gameOver
? "ring-2 hover:bg-yellow-300"
: ""
}
`}
>
{cell.number || ""}
);
})
)}
Impostazioni
Modalità scura
setDarkMode(!darkMode)}
className={`w-12 h-6 rounded-full transition-colors ${
darkMode ? "bg-blue-600" : "bg-gray-300"
} relative`}
>
Musica di sottofondo
setMusicEnabled(!musicEnabled)}
className={`w-12 h-6 rounded-full transition-colors ${
musicEnabled ? "bg-blue-600" : "bg-gray-300"
} relative`}
>
Modalità scura
setDarkMode(!darkMode)}
className={`w-12 h-6 rounded-full transition-colors ${
darkMode ? "bg-blue-600" : "bg-gray-300"
} relative`}
>
Feedback tattile
setHapticEnabled(!hapticEnabled)}
className={`w-12 h-6 rounded-full transition-colors ${
hapticEnabled ? "bg-blue-600" : "bg-gray-300"
} relative`}
>
{gameCompleted ? "Complimenti!" : "Partita terminata!"}
{gameCompleted ? `Hai completato tutte le 100 mosse in ${formatTime( completionTime )}!` : `Hai raggiunto ${currentNumber - 1} mosse in ${formatTime( completionTime )}!`}
setNickname(e.target.value)} className={`w-full p-3 border ${themeClasses.border} rounded-lg mb-4 focus:outline-none focus:ring-2 focus:ring-indigo-500 ${themeClasses.cardBg} ${themeClasses.text}`} maxLength={20} />Classifica {leaderboard.length === 0 ? (
Nessun punteggio ancora
) : (
{leaderboard.map((score, index) => (
))}
#{index + 1}
{score.nickname}
{score.moves}/100
{formatTime(score.completion_time || score.time)} •{" "}
{new Date(
score.date_played || score.date
).toLocaleDateString()}
{validMoves.length}{" "} {validMoves.length === 1 ? "mossa possibile" : "mosse possibili"}
)}