2048-by-deepsite / index.html
thinkall's picture
Add 2 files
db10b66 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2048 Game</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@keyframes tile-appear {
0% {
transform: scale(0.5);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes tile-merge {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
.tile-new {
animation: tile-appear 0.2s ease-out;
}
.tile-merged {
animation: tile-merge 0.2s ease-out;
}
.game-container {
perspective: 1000px;
}
.board {
transform-style: preserve-3d;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.tile {
transition: all 0.1s ease-in-out;
font-weight: 700;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center font-sans">
<div class="game-container w-full max-w-md px-4">
<div class="flex justify-between items-center mb-6">
<h1 class="text-4xl font-bold text-gray-800">2048</h1>
<div class="flex items-center space-x-4">
<div class="bg-gray-800 text-white px-4 py-2 rounded-lg text-center">
<div class="text-xs text-gray-300">SCORE</div>
<div id="score" class="text-xl font-bold">0</div>
</div>
<button id="restart" class="bg-amber-500 hover:bg-amber-600 text-white px-4 py-2 rounded-lg transition">
<i class="fas fa-redo mr-1"></i> New Game
</button>
</div>
</div>
<div class="mb-4 flex justify-between items-center">
<p class="text-gray-600">Join the numbers and get to the <span class="font-bold">2048 tile!</span></p>
<div id="best-score" class="bg-gray-800 text-white px-3 py-1 rounded text-sm">
Best: 0
</div>
</div>
<div class="board bg-gray-300 p-3 rounded-xl relative mb-6">
<div class="grid grid-cols-4 gap-3">
<!-- Empty cells for the board background -->
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
<div class="bg-gray-200 rounded-lg aspect-square"></div>
</div>
<!-- Tiles will be dynamically added here -->
<div id="tiles-container" class="absolute inset-0 grid grid-cols-4 gap-3 p-3"></div>
</div>
<div class="text-center text-gray-500 text-sm">
<p>Use arrow keys or swipe to move the tiles. When two tiles with the same number touch, they merge into one!</p>
</div>
</div>
<!-- Game Over Modal -->
<div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl p-6 max-w-sm w-full mx-4 text-center">
<h2 class="text-2xl font-bold text-gray-800 mb-2">Game Over!</h2>
<p class="text-gray-600 mb-4">Your score: <span id="final-score" class="font-bold">0</span></p>
<div class="flex space-x-3 justify-center">
<button id="try-again" class="bg-amber-500 hover:bg-amber-600 text-white px-4 py-2 rounded-lg transition">
Try Again
</button>
<button id="close-modal" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg transition">
Close
</button>
</div>
</div>
</div>
<!-- Win Modal -->
<div id="win-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white rounded-xl p-6 max-w-sm w-full mx-4 text-center">
<h2 class="text-2xl font-bold text-gray-800 mb-2">You Win! 🎉</h2>
<p class="text-gray-600 mb-4">You reached 2048 with a score of <span id="win-score" class="font-bold">0</span></p>
<div class="flex space-x-3 justify-center">
<button id="continue-playing" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg transition">
Keep Going
</button>
<button id="new-game-win" class="bg-amber-500 hover:bg-amber-600 text-white px-4 py-2 rounded-lg transition">
New Game
</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game state
let board = Array(16).fill(0);
let score = 0;
let bestScore = localStorage.getItem('2048-best-score') || 0;
let gameOver = false;
let won = false;
let touchStartX = 0;
let touchStartY = 0;
// DOM elements
const tilesContainer = document.getElementById('tiles-container');
const scoreElement = document.getElementById('score');
const bestScoreElement = document.getElementById('best-score');
const restartButton = document.getElementById('restart');
const gameOverModal = document.getElementById('game-over-modal');
const winModal = document.getElementById('win-modal');
const finalScoreElement = document.getElementById('final-score');
const winScoreElement = document.getElementById('win-score');
const tryAgainButton = document.getElementById('try-again');
const closeModalButton = document.getElementById('close-modal');
const continuePlayingButton = document.getElementById('continue-playing');
const newGameWinButton = document.getElementById('new-game-win');
// Tile colors based on value
const tileColors = {
0: 'bg-gray-200',
2: 'bg-amber-50 text-gray-800',
4: 'bg-amber-100 text-gray-800',
8: 'bg-amber-300 text-white',
16: 'bg-amber-400 text-white',
32: 'bg-orange-400 text-white',
64: 'bg-orange-500 text-white',
128: 'bg-yellow-300 text-white',
256: 'bg-yellow-400 text-white',
512: 'bg-yellow-500 text-white',
1024: 'bg-yellow-600 text-white',
2048: 'bg-yellow-700 text-white',
4096: 'bg-red-500 text-white',
8192: 'bg-red-600 text-white'
};
// Initialize the game
function initGame() {
board = Array(16).fill(0);
score = 0;
gameOver = false;
won = false;
updateScore();
addRandomTile();
addRandomTile();
renderBoard();
// Hide modals if they're open
gameOverModal.classList.add('hidden');
winModal.classList.add('hidden');
}
// Add a random tile (2 or 4) to an empty cell
function addRandomTile() {
const emptyCells = board.reduce((acc, val, index) => {
if (val === 0) acc.push(index);
return acc;
}, []);
if (emptyCells.length > 0) {
const randomIndex = emptyCells[Math.floor(Math.random() * emptyCells.length)];
board[randomIndex] = Math.random() < 0.9 ? 2 : 4;
return true;
}
return false;
}
// Render the board
function renderBoard() {
tilesContainer.innerHTML = '';
board.forEach((value, index) => {
if (value !== 0) {
const tile = document.createElement('div');
tile.className = `tile flex items-center justify-center rounded-lg aspect-square ${tileColors[value] || 'bg-red-700 text-white'}`;
tile.textContent = value;
// Calculate position based on index
const row = Math.floor(index / 4);
const col = index % 4;
tile.style.gridRow = row + 1;
tile.style.gridColumn = col + 1;
// Add animation class for new tiles (handled in game logic)
tilesContainer.appendChild(tile);
}
});
}
// Update the score display
function updateScore() {
scoreElement.textContent = score;
bestScoreElement.textContent = `Best: ${bestScore}`;
// Update best score if current score is higher
if (score > bestScore) {
bestScore = score;
localStorage.setItem('2048-best-score', bestScore);
}
}
// Check if the game is over
function checkGameOver() {
// If there are empty cells, game is not over
if (board.some(cell => cell === 0)) return false;
// Check for possible merges in rows
for (let row = 0; row < 4; row++) {
for (let col = 0; col < 3; col++) {
const index = row * 4 + col;
if (board[index] === board[index + 1]) {
return false;
}
}
}
// Check for possible merges in columns
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 3; row++) {
const index = row * 4 + col;
if (board[index] === board[index + 4]) {
return false;
}
}
}
return true;
}
// Check if the player has won (reached 2048)
function checkWin() {
return board.some(cell => cell === 2048);
}
// Move tiles in a direction
function move(direction) {
if (gameOver) return false;
let moved = false;
const newBoard = [...board];
// Helper function to process a line (row or column)
const processLine = (line) => {
// Remove zeros
let filtered = line.filter(val => val !== 0);
let result = [];
let merged = false;
// Merge adjacent equal values
for (let i = 0; i < filtered.length; i++) {
if (i < filtered.length - 1 && filtered[i] === filtered[i + 1] && !merged) {
result.push(filtered[i] * 2);
score += filtered[i] * 2;
merged = true;
i++; // Skip next element
} else {
result.push(filtered[i]);
merged = false;
}
}
// Pad with zeros
while (result.length < 4) {
result.push(0);
}
// Check if the line changed
if (line.some((val, i) => val !== result[i])) {
moved = true;
}
return result;
};
// Process based on direction
if (direction === 'left') {
for (let row = 0; row < 4; row++) {
const line = newBoard.slice(row * 4, row * 4 + 4);
const processed = processLine(line);
for (let col = 0; col < 4; col++) {
newBoard[row * 4 + col] = processed[col];
}
}
} else if (direction === 'right') {
for (let row = 0; row < 4; row++) {
const line = newBoard.slice(row * 4, row * 4 + 4).reverse();
const processed = processLine(line).reverse();
for (let col = 0; col < 4; col++) {
newBoard[row * 4 + col] = processed[col];
}
}
} else if (direction === 'up') {
for (let col = 0; col < 4; col++) {
const line = [
newBoard[col],
newBoard[col + 4],
newBoard[col + 8],
newBoard[col + 12]
];
const processed = processLine(line);
for (let row = 0; row < 4; row++) {
newBoard[row * 4 + col] = processed[row];
}
}
} else if (direction === 'down') {
for (let col = 0; col < 4; col++) {
const line = [
newBoard[col + 12],
newBoard[col + 8],
newBoard[col + 4],
newBoard[col]
];
const processed = processLine(line).reverse();
for (let row = 0; row < 4; row++) {
newBoard[row * 4 + col] = processed[row];
}
}
}
// If any movement occurred, update the board
if (moved) {
board = newBoard;
updateScore();
addRandomTile();
renderBoard();
// Check for win condition (only once)
if (!won && checkWin()) {
won = true;
winScoreElement.textContent = score;
winModal.classList.remove('hidden');
}
// Check for game over
if (checkGameOver()) {
gameOver = true;
finalScoreElement.textContent = score;
gameOverModal.classList.remove('hidden');
}
}
return moved;
}
// Handle keyboard events
function handleKeyDown(e) {
if (e.key === 'ArrowUp') {
move('up');
} else if (e.key === 'ArrowDown') {
move('down');
} else if (e.key === 'ArrowLeft') {
move('left');
} else if (e.key === 'ArrowRight') {
move('right');
}
}
// Handle touch events for mobile
function handleTouchStart(e) {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}
function handleTouchEnd(e) {
if (!touchStartX || !touchStartY) return;
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const dx = touchEndX - touchStartX;
const dy = touchEndY - touchStartY;
// Determine the direction based on the greatest change
if (Math.abs(dx) > Math.abs(dy)) {
if (dx > 0) {
move('right');
} else {
move('left');
}
} else {
if (dy > 0) {
move('down');
} else {
move('up');
}
}
// Reset touch coordinates
touchStartX = 0;
touchStartY = 0;
}
// Event listeners
document.addEventListener('keydown', handleKeyDown);
restartButton.addEventListener('click', initGame);
tryAgainButton.addEventListener('click', initGame);
closeModalButton.addEventListener('click', () => gameOverModal.classList.add('hidden'));
continuePlayingButton.addEventListener('click', () => winModal.classList.add('hidden'));
newGameWinButton.addEventListener('click', initGame);
// Touch events for mobile
document.addEventListener('touchstart', handleTouchStart, false);
document.addEventListener('touchend', handleTouchEnd, false);
// Prevent scrolling on touch move
document.addEventListener('touchmove', (e) => {
if (Math.abs(e.touches[0].clientX - touchStartX) > 10 ||
Math.abs(e.touches[0].clientY - touchStartY) > 10) {
e.preventDefault();
}
}, { passive: false });
// Start the game
initGame();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=thinkall/2048-by-deepsite" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>