<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>2048</title>
<style>
* { box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background: #faf8ef;
color: #776e65;
}
#header {
text-align: center;
margin-bottom: 20px;
}
#score, #best {
font-size: 20px;
font-weight: bold;
}
#grid {
position: relative;
width: 400px;
height: 400px;
background: #bbada0;
border-radius: 6px;
padding: 16px;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 16px;
}
.cell {
width: 100%;
aspect-ratio: 1 / 1;
background: rgba(238, 228, 218, 0.35);
border-radius: 6px;
}
.tile {
position: absolute;
width: calc(25% - 20px);
height: calc(25% - 20px);
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 32px;
transition: all 0.1s ease;
}
/* Цвета плиток — по оригиналу */
.tile-2 { background: #eee4da; color: #776e65; }
.tile-4 { background: #ede0c8; color: #776e65; }
.tile-8 { background: #f2b179; color: #f9f6f2; }
.tile-16 { background: #f59563; color: #f9f6f2; }
.tile-32 { background: #f67c5f; color: #f9f6f2; }
.tile-64 { background: #f65e3b; color: #f9f6f2; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 28px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 28px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 28px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 24px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 24px; }
#message {
margin-top: 20px;
font-size: 24px;
font-weight: bold;
height: 30px;
}
#restart {
margin-top: 10px;
padding: 8px 16px;
font-size: 16px;
background: #8f7a66;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="header">
<h1>2048</h1>
<div>
<span id="score">Счёт: 0</span> |
<span id="best">Рекорд: 0</span>
</div>
</div>
<div id="grid"></div>
<div id="message"></div>
<button id="restart">Новая игра</button>
<script>
const gridEl = document.getElementById('grid');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const messageEl = document.getElementById('message');
const restartBtn = document.getElementById('restart');
let board = Array(4).fill().map(() => Array(4).fill(0));
let score = 0;
let best = localStorage.getItem('2048-best') || 0;
bestEl.textContent = `Рекорд: ${best}`;
// Инициализация сетки визуальных ячеек (фон)
for (let i = 0; i < 16; i++) {
const cell = document.createElement('div');
cell.className = 'cell';
gridEl.appendChild(cell);
}
function addTile(value, row, col) {
const tile = document.createElement('div');
tile.className = `tile tile-${value}`;
tile.textContent = value;
tile.style.top = `${row * 25 + 4}%`;
tile.style.left = `${col * 25 + 4}%`;
tile.dataset.row = row;
tile.dataset.col = col;
gridEl.appendChild(tile);
}
function clearTiles() {
const tiles = gridEl.querySelectorAll('.tile');
tiles.forEach(t => t.remove());
}
function render() {
clearTiles();
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (board[r][c] !== 0) {
addTile(board[r][c], r, c);
}
}
}
scoreEl.textContent = `Счёт: ${score}`;
}
function addRandomTile() {
const empty = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (board[r][c] === 0) empty.push([r, c]);
}
}
if (empty.length === 0) return false;
const [r, c] = empty[Math.floor(Math.random() * empty.length)];
board[r][c] = Math.random() < 0.9 ? 2 : 4;
return true;
}
// Сжатие строки влево (без учёта границ)
function slide(row) {
const filtered = row.filter(v => v !== 0);
const merged = [];
let i = 0;
while (i < filtered.length) {
if (i + 1 < filtered.length && filtered[i] === filtered[i + 1]) {
const newVal = filtered[i] * 2;
merged.push(newVal);
score += newVal;
i += 2;
} else {
merged.push(filtered[i]);
i += 1;
}
}
while (merged.length < 4) merged.push(0);
return merged;
}
function moveLeft() {
let moved = false;
for (let r = 0; r < 4; r++) {
const original = [...board[r]];
board[r] = slide(board[r]);
if (!arraysEqual(original, board[r])) moved = true;
}
return moved;
}
function moveRight() {
let moved = false;
for (let r = 0; r < 4; r++) {
const original = [...board[r]];
board[r] = slide([...board[r]].reverse()).reverse();
if (!arraysEqual(original, board[r])) moved = true;
}
return moved;
}
function moveUp() {
let moved = false;
for (let c = 0; c < 4; c++) {
const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
const original = [...col];
const newCol = slide(col);
for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
if (!arraysEqual(original, newCol)) moved = true;
}
return moved;
}
function moveDown() {
let moved = false;
for (let c = 0; c < 4; c++) {
const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
const original = [...col];
const newCol = slide([...col].reverse()).reverse();
for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
if (!arraysEqual(original, newCol)) moved = true;
}
return moved;
}
function arraysEqual(a, b) {
return a.length === b.length && a.every((v, i) => v === b[i]);
}
function canMove() {
// Есть пустые ячейки?
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++)
if (board[r][c] === 0) return true;
// Есть соседи для слияния?
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
const v = board[r][c];
if (
(r > 0 && board[r - 1][c] === v) ||
(r < 3 && board[r + 1][c] === v) ||
(c > 0 && board[r][c - 1] === v) ||
(c < 3 && board[r][c + 1] === v)
) return true;
}
}
return false;
}
function gameOver() {
messageEl.textContent = 'Игра окончена';
if (score > best) {
best = score;
localStorage.setItem('2048-best', best);
bestEl.textContent = `Рекорд: ${best}`;
}
}
function winCheck() {
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++)
if (board[r][c] === 2048) {
messageEl.textContent = 'Победа!';
return true;
}
return false;
}
function handleMove(moved) {
if (!moved) return;
if (addRandomTile()) {
render();
if (!canMove()) gameOver();
else winCheck();
} else {
gameOver();
}
}
function resetGame() {
board = Array(4).fill().map(() => Array(4).fill(0));
score = 0;
messageEl.textContent = '';
addRandomTile();
addRandomTile();
render();
}
window.addEventListener('keydown', e => {
if (messageEl.textContent) 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;
case 'a': case 'A': moved = moveLeft(); break;
case 'd': case 'D': moved = moveRight(); break;
case 'w': case 'W': moved = moveUp(); break;
case 's': case 'S': moved = moveDown(); break;
}
if (moved) handleMove(true);
});
restartBtn.addEventListener('click', resetGame);
// Запуск
resetGame();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>2048</title>
<style>
* { box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background: #faf8ef;
color: #776e65;
}
#header {
text-align: center;
margin-bottom: 20px;
}
#score, #best {
font-size: 20px;
font-weight: bold;
}
#grid {
position: relative;
width: 400px;
height: 400px;
background: #bbada0;
border-radius: 6px;
padding: 16px;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 16px;
}
.cell {
width: 100%;
aspect-ratio: 1 / 1;
background: rgba(238, 228, 218, 0.35);
border-radius: 6px;
}
.tile {
position: absolute;
width: calc(25% - 20px);
height: calc(25% - 20px);
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
font-size: 32px;
transition: all 0.1s ease;
}
/* Цвета плиток — по оригиналу */
.tile-2 { background: #eee4da; color: #776e65; }
.tile-4 { background: #ede0c8; color: #776e65; }
.tile-8 { background: #f2b179; color: #f9f6f2; }
.tile-16 { background: #f59563; color: #f9f6f2; }
.tile-32 { background: #f67c5f; color: #f9f6f2; }
.tile-64 { background: #f65e3b; color: #f9f6f2; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 28px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 28px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 28px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 24px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 24px; }
#message {
margin-top: 20px;
font-size: 24px;
font-weight: bold;
height: 30px;
}
#restart {
margin-top: 10px;
padding: 8px 16px;
font-size: 16px;
background: #8f7a66;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="header">
<h1>2048</h1>
<div>
<span id="score">Счёт: 0</span> |
<span id="best">Рекорд: 0</span>
</div>
</div>
<div id="grid"></div>
<div id="message"></div>
<button id="restart">Новая игра</button>
<script>
const gridEl = document.getElementById('grid');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const messageEl = document.getElementById('message');
const restartBtn = document.getElementById('restart');
let board = Array(4).fill().map(() => Array(4).fill(0));
let score = 0;
let best = localStorage.getItem('2048-best') || 0;
bestEl.textContent = `Рекорд: ${best}`;
// Инициализация сетки визуальных ячеек (фон)
for (let i = 0; i < 16; i++) {
const cell = document.createElement('div');
cell.className = 'cell';
gridEl.appendChild(cell);
}
function addTile(value, row, col) {
const tile = document.createElement('div');
tile.className = `tile tile-${value}`;
tile.textContent = value;
tile.style.top = `${row * 25 + 4}%`;
tile.style.left = `${col * 25 + 4}%`;
tile.dataset.row = row;
tile.dataset.col = col;
gridEl.appendChild(tile);
}
function clearTiles() {
const tiles = gridEl.querySelectorAll('.tile');
tiles.forEach(t => t.remove());
}
function render() {
clearTiles();
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (board[r][c] !== 0) {
addTile(board[r][c], r, c);
}
}
}
scoreEl.textContent = `Счёт: ${score}`;
}
function addRandomTile() {
const empty = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (board[r][c] === 0) empty.push([r, c]);
}
}
if (empty.length === 0) return false;
const [r, c] = empty[Math.floor(Math.random() * empty.length)];
board[r][c] = Math.random() < 0.9 ? 2 : 4;
return true;
}
// Сжатие строки влево (без учёта границ)
function slide(row) {
const filtered = row.filter(v => v !== 0);
const merged = [];
let i = 0;
while (i < filtered.length) {
if (i + 1 < filtered.length && filtered[i] === filtered[i + 1]) {
const newVal = filtered[i] * 2;
merged.push(newVal);
score += newVal;
i += 2;
} else {
merged.push(filtered[i]);
i += 1;
}
}
while (merged.length < 4) merged.push(0);
return merged;
}
function moveLeft() {
let moved = false;
for (let r = 0; r < 4; r++) {
const original = [...board[r]];
board[r] = slide(board[r]);
if (!arraysEqual(original, board[r])) moved = true;
}
return moved;
}
function moveRight() {
let moved = false;
for (let r = 0; r < 4; r++) {
const original = [...board[r]];
board[r] = slide([...board[r]].reverse()).reverse();
if (!arraysEqual(original, board[r])) moved = true;
}
return moved;
}
function moveUp() {
let moved = false;
for (let c = 0; c < 4; c++) {
const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
const original = [...col];
const newCol = slide(col);
for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
if (!arraysEqual(original, newCol)) moved = true;
}
return moved;
}
function moveDown() {
let moved = false;
for (let c = 0; c < 4; c++) {
const col = [board[0][c], board[1][c], board[2][c], board[3][c]];
const original = [...col];
const newCol = slide([...col].reverse()).reverse();
for (let r = 0; r < 4; r++) board[r][c] = newCol[r];
if (!arraysEqual(original, newCol)) moved = true;
}
return moved;
}
function arraysEqual(a, b) {
return a.length === b.length && a.every((v, i) => v === b[i]);
}
function canMove() {
// Есть пустые ячейки?
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++)
if (board[r][c] === 0) return true;
// Есть соседи для слияния?
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
const v = board[r][c];
if (
(r > 0 && board[r - 1][c] === v) ||
(r < 3 && board[r + 1][c] === v) ||
(c > 0 && board[r][c - 1] === v) ||
(c < 3 && board[r][c + 1] === v)
) return true;
}
}
return false;
}
function gameOver() {
messageEl.textContent = 'Игра окончена';
if (score > best) {
best = score;
localStorage.setItem('2048-best', best);
bestEl.textContent = `Рекорд: ${best}`;
}
}
function winCheck() {
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++)
if (board[r][c] === 2048) {
messageEl.textContent = 'Победа!';
return true;
}
return false;
}
function handleMove(moved) {
if (!moved) return;
if (addRandomTile()) {
render();
if (!canMove()) gameOver();
else winCheck();
} else {
gameOver();
}
}
function resetGame() {
board = Array(4).fill().map(() => Array(4).fill(0));
score = 0;
messageEl.textContent = '';
addRandomTile();
addRandomTile();
render();
}
window.addEventListener('keydown', e => {
if (messageEl.textContent) 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;
case 'a': case 'A': moved = moveLeft(); break;
case 'd': case 'D': moved = moveRight(); break;
case 'w': case 'W': moved = moveUp(); break;
case 's': case 'S': moved = moveDown(); break;
}
if (moved) handleMove(true);
});
restartBtn.addEventListener('click', resetGame);
// Запуск
resetGame();
</script>
</body>
</html>