|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Neon 2048 - Futuristic Edition</title> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); |
|
|
|
:root { |
|
--primary-color: #00f7ff; |
|
--secondary-color: #ff00f7; |
|
--bg-color: #0a0a20; |
|
--grid-bg: rgba(15, 15, 40, 0.7); |
|
--text-color: #e0e0ff; |
|
--tile-bg: rgba(30, 30, 60, 0.5); |
|
--glow: 0 0 10px var(--primary-color), 0 0 20px var(--primary-color); |
|
--glow-secondary: 0 0 10px var(--secondary-color), 0 0 20px var(--secondary-color); |
|
} |
|
|
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: 'Orbitron', sans-serif; |
|
background-color: var(--bg-color); |
|
color: var(--text-color); |
|
min-height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
overflow: hidden; |
|
position: relative; |
|
} |
|
|
|
body::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: |
|
radial-gradient(circle at 20% 30%, var(--primary-color), transparent 30%), |
|
radial-gradient(circle at 80% 70%, var(--secondary-color), transparent 30%); |
|
opacity: 0.1; |
|
z-index: -1; |
|
} |
|
|
|
.header { |
|
text-align: center; |
|
margin-bottom: 2rem; |
|
position: relative; |
|
} |
|
|
|
h1 { |
|
font-size: 3rem; |
|
margin-bottom: 1rem; |
|
text-transform: uppercase; |
|
letter-spacing: 3px; |
|
color: var(--primary-color); |
|
text-shadow: var(--glow); |
|
position: relative; |
|
} |
|
|
|
h1::after { |
|
content: ''; |
|
position: absolute; |
|
bottom: -10px; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
width: 100px; |
|
height: 3px; |
|
background: linear-gradient(90deg, transparent, var(--primary-color), transparent); |
|
box-shadow: var(--glow); |
|
} |
|
|
|
.scores { |
|
display: flex; |
|
justify-content: center; |
|
gap: 2rem; |
|
margin-bottom: 1rem; |
|
} |
|
|
|
.score-box { |
|
background: var(--tile-bg); |
|
border: 1px solid var(--primary-color); |
|
border-radius: 5px; |
|
padding: 0.5rem 1rem; |
|
min-width: 100px; |
|
box-shadow: 0 0 5px var(--primary-color); |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.score-box::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: linear-gradient(45deg, transparent, rgba(0, 247, 255, 0.1), transparent); |
|
z-index: -1; |
|
} |
|
|
|
.score-title { |
|
font-size: 0.8rem; |
|
color: var(--secondary-color); |
|
text-transform: uppercase; |
|
letter-spacing: 1px; |
|
} |
|
|
|
.score-value { |
|
font-size: 1.5rem; |
|
font-weight: bold; |
|
color: var(--primary-color); |
|
} |
|
|
|
.game-container { |
|
position: relative; |
|
width: 100%; |
|
max-width: 500px; |
|
padding: 20px; |
|
} |
|
|
|
.grid-container { |
|
background: var(--grid-bg); |
|
border-radius: 10px; |
|
padding: 15px; |
|
position: relative; |
|
overflow: hidden; |
|
box-shadow: 0 0 20px rgba(0, 247, 255, 0.2); |
|
} |
|
|
|
.grid-container::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: |
|
linear-gradient(45deg, transparent 48%, var(--primary-color) 49%, var(--primary-color) 51%, transparent 52%), |
|
linear-gradient(-45deg, transparent 48%, var(--primary-color) 49%, var(--primary-color) 51%, transparent 52%); |
|
background-size: 20px 20px; |
|
opacity: 0.1; |
|
z-index: -1; |
|
} |
|
|
|
.grid { |
|
display: grid; |
|
grid-template-columns: repeat(4, 1fr); |
|
grid-template-rows: repeat(4, 1fr); |
|
gap: 15px; |
|
width: 100%; |
|
aspect-ratio: 1/1; |
|
position: relative; |
|
} |
|
|
|
.grid-cell { |
|
background: var(--tile-bg); |
|
border-radius: 5px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
position: relative; |
|
overflow: hidden; |
|
border: 1px solid rgba(0, 247, 255, 0.2); |
|
} |
|
|
|
.grid-cell::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: linear-gradient(135deg, transparent, rgba(0, 247, 255, 0.05), transparent); |
|
} |
|
|
|
.tile { |
|
position: absolute; |
|
width: calc(25% - 15px); |
|
height: calc(25% - 15px); |
|
border-radius: 5px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 1.8rem; |
|
font-weight: bold; |
|
transition: all 0.15s ease-out; |
|
z-index: 10; |
|
background: var(--tile-bg); |
|
border: 1px solid; |
|
animation: appear 0.2s ease-out; |
|
} |
|
|
|
@keyframes appear { |
|
0% { |
|
opacity: 0; |
|
transform: scale(0.5); |
|
} |
|
100% { |
|
opacity: 1; |
|
transform: scale(1); |
|
} |
|
} |
|
|
|
.tile-2 { |
|
color: #e0e0ff; |
|
border-color: rgba(224, 224, 255, 0.3); |
|
box-shadow: 0 0 5px rgba(224, 224, 255, 0.5); |
|
} |
|
|
|
.tile-4 { |
|
color: #a0a0ff; |
|
border-color: rgba(160, 160, 255, 0.4); |
|
box-shadow: 0 0 8px rgba(160, 160, 255, 0.6); |
|
} |
|
|
|
.tile-8 { |
|
color: #8080ff; |
|
border-color: rgba(128, 128, 255, 0.5); |
|
box-shadow: 0 0 10px rgba(128, 128, 255, 0.7); |
|
background: rgba(30, 30, 80, 0.6); |
|
} |
|
|
|
.tile-16 { |
|
color: #6060ff; |
|
border-color: rgba(96, 96, 255, 0.6); |
|
box-shadow: 0 0 12px rgba(96, 96, 255, 0.8); |
|
background: rgba(30, 30, 100, 0.6); |
|
} |
|
|
|
.tile-32 { |
|
color: #4040ff; |
|
border-color: rgba(64, 64, 255, 0.7); |
|
box-shadow: 0 0 14px rgba(64, 64, 255, 0.9); |
|
background: rgba(30, 30, 120, 0.6); |
|
} |
|
|
|
.tile-64 { |
|
color: #2020ff; |
|
border-color: rgba(32, 32, 255, 0.8); |
|
box-shadow: 0 0 16px rgba(32, 32, 255, 1); |
|
background: rgba(30, 30, 140, 0.6); |
|
} |
|
|
|
.tile-128 { |
|
color: var(--primary-color); |
|
border-color: rgba(0, 247, 255, 0.7); |
|
box-shadow: var(--glow); |
|
background: rgba(30, 60, 150, 0.6); |
|
font-size: 1.6rem; |
|
} |
|
|
|
.tile-256 { |
|
color: var(--primary-color); |
|
border-color: rgba(0, 247, 255, 0.8); |
|
box-shadow: var(--glow); |
|
background: rgba(30, 80, 160, 0.6); |
|
font-size: 1.6rem; |
|
} |
|
|
|
.tile-512 { |
|
color: var(--primary-color); |
|
border-color: rgba(0, 247, 255, 0.9); |
|
box-shadow: var(--glow); |
|
background: rgba(30, 100, 170, 0.6); |
|
font-size: 1.6rem; |
|
} |
|
|
|
.tile-1024 { |
|
color: var(--primary-color); |
|
border-color: rgba(0, 247, 255, 1); |
|
box-shadow: var(--glow); |
|
background: rgba(30, 120, 180, 0.6); |
|
font-size: 1.4rem; |
|
} |
|
|
|
.tile-2048 { |
|
color: var(--primary-color); |
|
border-color: rgba(0, 247, 255, 1); |
|
box-shadow: var(--glow), var(--glow-secondary); |
|
background: rgba(30, 150, 200, 0.6); |
|
font-size: 1.4rem; |
|
animation: pulse 1.5s infinite; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { |
|
box-shadow: var(--glow), var(--glow-secondary); |
|
} |
|
50% { |
|
box-shadow: 0 0 20px var(--primary-color), 0 0 40px var(--primary-color), 0 0 20px var(--secondary-color), 0 0 40px var(--secondary-color); |
|
} |
|
100% { |
|
box-shadow: var(--glow), var(--glow-secondary); |
|
} |
|
} |
|
|
|
.tile-super { |
|
color: var(--secondary-color); |
|
border-color: rgba(255, 0, 247, 0.7); |
|
box-shadow: var(--glow-secondary); |
|
background: rgba(150, 30, 150, 0.6); |
|
font-size: 1.2rem; |
|
} |
|
|
|
.merge-effect { |
|
position: absolute; |
|
width: calc(25% - 15px); |
|
height: calc(25% - 15px); |
|
border-radius: 5px; |
|
background: radial-gradient(circle, var(--primary-color), transparent 70%); |
|
opacity: 0; |
|
z-index: 5; |
|
animation: mergePulse 0.4s ease-out; |
|
} |
|
|
|
@keyframes mergePulse { |
|
0% { |
|
transform: scale(0.5); |
|
opacity: 0.8; |
|
} |
|
100% { |
|
transform: scale(1.5); |
|
opacity: 0; |
|
} |
|
} |
|
|
|
.game-message { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
display: none; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
background: rgba(10, 10, 32, 0.9); |
|
border-radius: 10px; |
|
z-index: 100; |
|
animation: fadeIn 0.3s ease-out; |
|
} |
|
|
|
@keyframes fadeIn { |
|
0% { |
|
opacity: 0; |
|
} |
|
100% { |
|
opacity: 1; |
|
} |
|
} |
|
|
|
.game-message.game-won { |
|
background: rgba(10, 10, 32, 0.95); |
|
} |
|
|
|
.game-message.game-over { |
|
background: rgba(32, 10, 10, 0.95); |
|
} |
|
|
|
.game-message p { |
|
font-size: 3rem; |
|
font-weight: bold; |
|
margin-bottom: 2rem; |
|
text-align: center; |
|
text-transform: uppercase; |
|
} |
|
|
|
.game-won p { |
|
color: var(--primary-color); |
|
text-shadow: var(--glow); |
|
} |
|
|
|
.game-over p { |
|
color: #ff4040; |
|
text-shadow: 0 0 10px #ff4040, 0 0 20px #ff4040; |
|
} |
|
|
|
.btn { |
|
background: var(--tile-bg); |
|
border: 1px solid var(--primary-color); |
|
color: var(--primary-color); |
|
padding: 0.8rem 1.5rem; |
|
font-family: 'Orbitron', sans-serif; |
|
font-size: 1rem; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
transition: all 0.2s; |
|
text-transform: uppercase; |
|
letter-spacing: 1px; |
|
box-shadow: 0 0 5px var(--primary-color); |
|
} |
|
|
|
.btn:hover { |
|
background: rgba(0, 247, 255, 0.1); |
|
box-shadow: 0 0 10px var(--primary-color); |
|
} |
|
|
|
.btn:active { |
|
transform: scale(0.98); |
|
} |
|
|
|
.instructions { |
|
margin-top: 2rem; |
|
text-align: center; |
|
max-width: 500px; |
|
padding: 0 20px; |
|
color: rgba(224, 224, 255, 0.7); |
|
font-size: 0.9rem; |
|
line-height: 1.5; |
|
} |
|
|
|
.instructions strong { |
|
color: var(--primary-color); |
|
} |
|
|
|
.particles { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
pointer-events: none; |
|
z-index: -1; |
|
} |
|
|
|
.particle { |
|
position: absolute; |
|
background: var(--primary-color); |
|
border-radius: 50%; |
|
pointer-events: none; |
|
opacity: 0.5; |
|
} |
|
|
|
@media (max-width: 600px) { |
|
h1 { |
|
font-size: 2rem; |
|
} |
|
|
|
.tile { |
|
font-size: 1.4rem; |
|
} |
|
|
|
.tile-128, .tile-256, .tile-512 { |
|
font-size: 1.2rem; |
|
} |
|
|
|
.tile-1024, .tile-2048 { |
|
font-size: 1rem; |
|
} |
|
|
|
.grid { |
|
gap: 10px; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="particles" id="particles"></div> |
|
|
|
<div class="header"> |
|
<h1>Neon 2048</h1> |
|
<div class="scores"> |
|
<div class="score-box"> |
|
<div class="score-title">Score</div> |
|
<div class="score-value" id="score">0</div> |
|
</div> |
|
<div class="score-box"> |
|
<div class="score-title">Best</div> |
|
<div class="score-value" id="best-score">0</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="game-container"> |
|
<div class="grid-container"> |
|
<div class="grid" id="grid"> |
|
|
|
</div> |
|
|
|
<div class="game-message" id="game-message"> |
|
<p id="message-text">You Win!</p> |
|
<button class="btn" id="restart-btn">Play Again</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="instructions"> |
|
<p><strong>HOW TO PLAY:</strong> Use your arrow keys or swipe to move the tiles. When two tiles with the same number touch, they merge into one!</p> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', () => { |
|
const grid = document.getElementById('grid'); |
|
const scoreDisplay = document.getElementById('score'); |
|
const bestScoreDisplay = document.getElementById('best-score'); |
|
const gameMessage = document.getElementById('game-message'); |
|
const messageText = document.getElementById('message-text'); |
|
const restartBtn = document.getElementById('restart-btn'); |
|
const particlesContainer = document.getElementById('particles'); |
|
|
|
let board = []; |
|
let score = 0; |
|
let bestScore = localStorage.getItem('bestScore') || 0; |
|
let isGameOver = false; |
|
let isGameWon = false; |
|
let touchStartX = 0; |
|
let touchStartY = 0; |
|
let touchEndX = 0; |
|
let touchEndY = 0; |
|
|
|
bestScoreDisplay.textContent = bestScore; |
|
|
|
|
|
function initGame() { |
|
|
|
grid.innerHTML = ''; |
|
for (let i = 0; i < 16; i++) { |
|
const cell = document.createElement('div'); |
|
cell.classList.add('grid-cell'); |
|
grid.appendChild(cell); |
|
} |
|
|
|
|
|
board = [ |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0] |
|
]; |
|
|
|
score = 0; |
|
scoreDisplay.textContent = score; |
|
isGameOver = false; |
|
isGameWon = false; |
|
|
|
gameMessage.style.display = 'none'; |
|
gameMessage.classList.remove('game-won', 'game-over'); |
|
|
|
|
|
addRandomTile(); |
|
addRandomTile(); |
|
|
|
updateView(); |
|
} |
|
|
|
|
|
function addRandomTile() { |
|
const emptyCells = []; |
|
|
|
for (let r = 0; r < 4; r++) { |
|
for (let c = 0; c < 4; c++) { |
|
if (board[r][c] === 0) { |
|
emptyCells.push({ r, c }); |
|
} |
|
} |
|
} |
|
|
|
if (emptyCells.length > 0) { |
|
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)]; |
|
board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4; |
|
|
|
|
|
const tileElement = createTileElement(randomCell.r, randomCell.c, board[randomCell.r][randomCell.c]); |
|
tileElement.style.animation = 'appear 0.2s ease-out'; |
|
grid.appendChild(tileElement); |
|
} |
|
} |
|
|
|
|
|
function createTileElement(row, col, value) { |
|
const tile = document.createElement('div'); |
|
tile.classList.add('tile', `tile-${value}`); |
|
if (value > 2048) tile.classList.add('tile-super'); |
|
tile.textContent = value; |
|
tile.id = `tile-${row}-${col}`; |
|
updateTilePosition(tile, row, col); |
|
return tile; |
|
} |
|
|
|
|
|
function updateTilePosition(tile, row, col) { |
|
const cellSize = grid.offsetWidth / 4; |
|
tile.style.left = `${col * cellSize + 15}px`; |
|
tile.style.top = `${row * cellSize + 15}px`; |
|
} |
|
|
|
|
|
function updateView() { |
|
|
|
document.querySelectorAll('.tile').forEach(tile => tile.remove()); |
|
|
|
|
|
for (let r = 0; r < 4; r++) { |
|
for (let c = 0; c < 4; c++) { |
|
if (board[r][c] !== 0) { |
|
const tile = createTileElement(r, c, board[r][c]); |
|
grid.appendChild(tile); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
function moveLeft() { |
|
let moved = false; |
|
|
|
for (let r = 0; r < 4; r++) { |
|
|
|
let row = board[r].filter(val => val !== 0); |
|
const zeros = Array(4 - row.length).fill(0); |
|
row = row.concat(zeros); |
|
|
|
|
|
if (JSON.stringify(row) !== JSON.stringify(board[r])) { |
|
moved = true; |
|
board[r] = row; |
|
} |
|
|
|
|
|
for (let c = 0; c < 3; c++) { |
|
if (board[r][c] !== 0 && board[r][c] === board[r][c + 1]) { |
|
board[r][c] *= 2; |
|
board[r][c + 1] = 0; |
|
score += board[r][c]; |
|
moved = true; |
|
|
|
|
|
createMergeEffect(r, c); |
|
|
|
|
|
if (score > bestScore) { |
|
bestScore = score; |
|
localStorage.setItem('bestScore', bestScore); |
|
bestScoreDisplay.textContent = bestScore; |
|
} |
|
|
|
|
|
if (board[r][c] === 2048 && !isGameWon) { |
|
gameWon(); |
|
} |
|
|
|
|
|
row = board[r].filter(val => val !== 0); |
|
const newZeros = Array(4 - row.length).fill(0); |
|
board[r] = row.concat(newZeros); |
|
} |
|
} |
|
} |
|
|
|
return moved; |
|
} |
|
|
|
|
|
function moveRight() { |
|
let moved = false; |
|
|
|
for (let r = 0; r < 4; r++) { |
|
|
|
let row = board[r].filter(val => val !== 0); |
|
const zeros = Array(4 - row.length).fill(0); |
|
row = zeros.concat(row); |
|
|
|
|
|
if (JSON.stringify(row) !== JSON.stringify(board[r])) { |
|
moved = true; |
|
board[r] = row; |
|
} |
|
|
|
|
|
for (let c = 3; c > 0; c--) { |
|
if (board[r][c] !== 0 && board[r][c] === board[r][c - 1]) { |
|
board[r][c] *= 2; |
|
board[r][c - 1] = 0; |
|
score += board[r][c]; |
|
moved = true; |
|
|
|
|
|
createMergeEffect(r, c); |
|
|
|
|
|
if (score > bestScore) { |
|
bestScore = score; |
|
localStorage.setItem('bestScore', bestScore); |
|
bestScoreDisplay.textContent = bestScore; |
|
} |
|
|
|
|
|
if (board[r][c] === 2048 && !isGameWon) { |
|
gameWon(); |
|
} |
|
|
|
|
|
row = board[r].filter(val => val !== 0); |
|
const newZeros = Array(4 - row.length).fill(0); |
|
board[r] = newZeros.concat(row); |
|
} |
|
} |
|
} |
|
|
|
return moved; |
|
} |
|
|
|
|
|
function moveUp() { |
|
let moved = false; |
|
|
|
for (let c = 0; c < 4; c++) { |
|
|
|
let column = [board[0][c], board[1][c], board[2][c], board[3][c]]; |
|
|
|
|
|
let newColumn = column.filter(val => val !== 0); |
|
const zeros = Array(4 - newColumn.length).fill(0); |
|
newColumn = newColumn.concat(zeros); |
|
|
|
|
|
if (JSON.stringify(newColumn) !== JSON.stringify(column)) { |
|
moved = true; |
|
for (let r = 0; r < 4; r++) { |
|
board[r][c] = newColumn[r]; |
|
} |
|
} |
|
|
|
|
|
for (let r = 0; r < 3; r++) { |
|
if (board[r][c] !== 0 && board[r][c] === board[r + 1][c]) { |
|
board[r][c] *= 2; |
|
board[r + 1][c] = 0; |
|
score += board[r][c]; |
|
moved = true; |
|
|
|
|
|
createMergeEffect(r, c); |
|
|
|
|
|
if (score > bestScore) { |
|
bestScore = score; |
|
localStorage.setItem('bestScore', bestScore); |
|
bestScoreDisplay.textContent = bestScore; |
|
} |
|
|
|
|
|
if (board[r][c] === 2048 && !isGameWon) { |
|
gameWon(); |
|
} |
|
|
|
|
|
column = [board[0][c], board[1][c], board[2][c], board[3][c]]; |
|
newColumn = column.filter(val => val !== 0); |
|
const newZeros = Array(4 - newColumn.length).fill(0); |
|
newColumn = newColumn.concat(newZeros); |
|
for (let r = 0; r < 4; r++) { |
|
board[r][c] = newColumn[r]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return moved; |
|
} |
|
|
|
|
|
function moveDown() { |
|
let moved = false; |
|
|
|
for (let c = 0; c < 4; c++) { |
|
|
|
let column = [board[0][c], board[1][c], board[2][c], board[3][c]]; |
|
|
|
|
|
let newColumn = column.filter(val => val !== 0); |
|
const zeros = Array(4 - newColumn.length).fill(0); |
|
newColumn = zeros.concat(newColumn); |
|
|
|
|
|
if (JSON.stringify(newColumn) !== JSON.stringify(column)) { |
|
moved = true; |
|
for (let r = 0; r < 4; r++) { |
|
board[r][c] = newColumn[r]; |
|
} |
|
} |
|
|
|
|
|
for (let r = 3; r > 0; r--) { |
|
if (board[r][c] !== 0 && board[r][c] === board[r - 1][c]) { |
|
board[r][c] *= 2; |
|
board[r - 1][c] = 0; |
|
score += board[r][c]; |
|
moved = true; |
|
|
|
|
|
createMergeEffect(r, c); |
|
|
|
|
|
if (score > bestScore) { |
|
bestScore = score; |
|
localStorage.setItem('bestScore', bestScore); |
|
bestScoreDisplay.textContent = bestScore; |
|
} |
|
|
|
|
|
if (board[r][c] === 2048 && !isGameWon) { |
|
gameWon(); |
|
} |
|
|
|
|
|
column = [board[0][c], board[1][c], board[2][c], board[3][c]]; |
|
newColumn = column.filter(val => val !== 0); |
|
const newZeros = Array(4 - newColumn.length).fill(0); |
|
newColumn = newZeros.concat(newColumn); |
|
for (let r = 0; r < 4; r++) { |
|
board[r][c] = newColumn[r]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return moved; |
|
} |
|
|
|
|
|
function createMergeEffect(row, col) { |
|
const effect = document.createElement('div'); |
|
effect.classList.add('merge-effect'); |
|
updateTilePosition(effect, row, col); |
|
grid.appendChild(effect); |
|
|
|
|
|
createParticles(row, col); |
|
|
|
|
|
setTimeout(() => { |
|
effect.remove(); |
|
}, 400); |
|
} |
|
|
|
|
|
function createParticles(row, col) { |
|
const cellSize = grid.offsetWidth / 4; |
|
const x = col * cellSize + cellSize / 2; |
|
const y = row * cellSize + cellSize / 2; |
|
|
|
for (let i = 0; i < 10; i++) { |
|
const particle = document.createElement('div'); |
|
particle.classList.add('particle'); |
|
|
|
const size = Math.random() * 5 + 2; |
|
particle.style.width = `${size}px`; |
|
particle.style.height = `${size}px`; |
|
|
|
const angle = Math.random() * Math.PI * 2; |
|
const distance = Math.random() * 30 + 20; |
|
const duration = Math.random() * 0.5 + 0.3; |
|
|
|
particle.style.left = `${x}px`; |
|
particle.style.top = `${y}px`; |
|
particle.style.opacity = '0.8'; |
|
|
|
particlesContainer.appendChild(particle); |
|
|
|
setTimeout(() => { |
|
particle.style.transition = `all ${duration}s ease-out`; |
|
particle.style.transform = `translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px)`; |
|
particle.style.opacity = '0'; |
|
}, 10); |
|
|
|
setTimeout(() => { |
|
particle.remove(); |
|
}, duration * 1000 + 100); |
|
} |
|
} |
|
|
|
|
|
function checkGameOver() { |
|
|
|
for (let r = 0; r < 4; r++) { |
|
for (let c = 0; c < 4; c++) { |
|
if (board[r][c] === 0) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
|
|
for (let r = 0; r < 4; r++) { |
|
for (let c = 0; c < 3; c++) { |
|
if (board[r][c] === board[r][c + 1]) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
for (let c = 0; c < 4; c++) { |
|
for (let r = 0; r < 3; r++) { |
|
if (board[r][c] === board[r + 1][c]) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
function gameWon() { |
|
isGameWon = true; |
|
gameMessage.classList.add('game-won'); |
|
messageText.textContent = 'You Win!'; |
|
gameMessage.style.display = 'flex'; |
|
} |
|
|
|
|
|
function gameOver() { |
|
isGameOver = true; |
|
gameMessage.classList.add('game-over'); |
|
messageText.textContent = 'Game Over!'; |
|
gameMessage.style.display = 'flex'; |
|
} |
|
|
|
|
|
function handleKeyDown(e) { |
|
if (isGameOver && !isGameWon) return; |
|
|
|
let moved = false; |
|
|
|
switch (e.key) { |
|
case 'ArrowLeft': |
|
moved = moveLeft(); |
|
break; |
|
case 'ArrowRight': |
|
moved = moveRight(); |
|
break; |
|
case 'ArrowUp': |
|
moved = moveUp(); |
|
break; |
|
case 'ArrowDown': |
|
moved = moveDown(); |
|
break; |
|
default: |
|
return; |
|
} |
|
|
|
if (moved) { |
|
scoreDisplay.textContent = score; |
|
addRandomTile(); |
|
updateView(); |
|
|
|
if (!isGameWon && checkGameOver()) { |
|
gameOver(); |
|
} |
|
} |
|
} |
|
|
|
|
|
function handleTouchStart(e) { |
|
touchStartX = e.touches[0].clientX; |
|
touchStartY = e.touches[0].clientY; |
|
} |
|
|
|
function handleTouchEnd(e) { |
|
if (isGameOver && !isGameWon) return; |
|
|
|
touchEndX = e.changedTouches[0].clientX; |
|
touchEndY = e.changedTouches[0].clientY; |
|
|
|
const dx = touchEndX - touchStartX; |
|
const dy = touchEndY - touchStartY; |
|
|
|
|
|
if (Math.abs(dx) > Math.abs(dy)) { |
|
|
|
if (dx > 50) { |
|
|
|
if (moveRight()) { |
|
scoreDisplay.textContent = score; |
|
addRandomTile(); |
|
updateView(); |
|
|
|
if (!isGameWon && checkGameOver()) { |
|
gameOver(); |
|
} |
|
} |
|
} else if (dx < -50) { |
|
|
|
if (moveLeft()) { |
|
scoreDisplay.textContent = score; |
|
addRandomTile(); |
|
updateView(); |
|
|
|
if (!isGameWon && checkGameOver()) { |
|
gameOver(); |
|
} |
|
} |
|
} |
|
} else { |
|
|
|
if (dy > 50) { |
|
|
|
if (moveDown()) { |
|
scoreDisplay.textContent = score; |
|
addRandomTile(); |
|
updateView(); |
|
|
|
if (!isGameWon && checkGameOver()) { |
|
gameOver(); |
|
} |
|
} |
|
} else if (dy < -50) { |
|
|
|
if (moveUp()) { |
|
scoreDisplay.textContent = score; |
|
addRandomTile(); |
|
updateView(); |
|
|
|
if (!isGameWon && checkGameOver()) { |
|
gameOver(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
restartBtn.addEventListener('click', initGame); |
|
|
|
|
|
document.addEventListener('keydown', handleKeyDown); |
|
document.addEventListener('touchstart', handleTouchStart, false); |
|
document.addEventListener('touchend', handleTouchEnd, false); |
|
|
|
|
|
initGame(); |
|
|
|
|
|
function createBackgroundParticles() { |
|
const particleCount = 30; |
|
|
|
for (let i = 0; i < particleCount; i++) { |
|
const particle = document.createElement('div'); |
|
particle.classList.add('particle'); |
|
|
|
const size = Math.random() * 3 + 1; |
|
particle.style.width = `${size}px`; |
|
particle.style.height = `${size}px`; |
|
|
|
|
|
const x = Math.random() * 100; |
|
const y = Math.random() * 100; |
|
particle.style.left = `${x}%`; |
|
particle.style.top = `${y}%`; |
|
|
|
|
|
particle.style.opacity = Math.random() * 0.5 + 0.1; |
|
|
|
|
|
const duration = Math.random() * 20 + 10; |
|
const delay = Math.random() * 5; |
|
|
|
particle.style.animation = `float ${duration}s ease-in-out ${delay}s infinite`; |
|
|
|
particlesContainer.appendChild(particle); |
|
} |
|
} |
|
|
|
|
|
const style = document.createElement('style'); |
|
style.textContent = ` |
|
@keyframes float { |
|
0%, 100% { |
|
transform: translate(0, 0); |
|
} |
|
25% { |
|
transform: translate(${Math.random() * 50 - 25}px, ${Math.random() * 50 - 25}px); |
|
} |
|
50% { |
|
transform: translate(${Math.random() * 50 - 25}px, ${Math.random() * 50 - 25}px); |
|
} |
|
75% { |
|
transform: translate(${Math.random() * 50 - 25}px, ${Math.random() * 50 - 25}px); |
|
} |
|
} |
|
`; |
|
document.head.appendChild(style); |
|
|
|
createBackgroundParticles(); |
|
}); |
|
</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 <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> |
|
</html> |