anycoder-69a622ba / index.html
hiepnh's picture
Upload folder using huggingface_hub
7ccaf4a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Neon Tetris | Ultimate Edition</title>
<style>
/* =========================================
1. CSS VARIABLES & RESET
========================================= */
:root {
--bg-color: #0f172a;
--surface-color: #1e293b;
--primary-color: #38bdf8;
--accent-color: #f472b6;
--text-color: #f1f5f9;
--grid-line: rgba(255, 255, 255, 0.05);
--glass-bg: rgba(30, 41, 59, 0.7);
--glass-border: rgba(255, 255, 255, 0.1);
/* Tetromino Colors */
--color-i: #00f0f0;
--color-j: #0000f0;
--color-l: #f0a000;
--color-o: #f0f000;
--color-s: #00f000;
--color-t: #a000f0;
--color-z: #f00000;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
background-image:
radial-gradient(circle at 10% 20%, rgba(56, 189, 248, 0.1) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(244, 114, 182, 0.1) 0%, transparent 20%);
color: var(--text-color);
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* =========================================
2. LAYOUT & CONTAINERS
========================================= */
.game-wrapper {
display: flex;
gap: 2rem;
padding: 2rem;
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 24px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
position: relative;
max-width: 95vw;
}
/* Main Game Canvas */
.canvas-container {
position: relative;
border: 2px solid var(--surface-color);
border-radius: 8px;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
background-color: #000;
}
canvas {
display: block;
border-radius: 6px;
}
/* Sidebar UI */
.sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
min-width: 200px;
}
.panel {
background: rgba(15, 23, 42, 0.6);
padding: 1rem;
border-radius: 12px;
border: 1px solid var(--glass-border);
}
.panel-title {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 2px;
color: #94a3b8;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--primary-color);
text-shadow: 0 0 10px rgba(56, 189, 248, 0.3);
}
/* Next Piece Preview */
#next-canvas {
background: transparent;
margin: 0 auto;
border-radius: 4px;
}
/* Controls Guide */
.controls {
font-size: 0.8rem;
color: #64748b;
line-height: 1.6;
}
.key {
display: inline-block;
background: var(--surface-color);
padding: 2px 6px;
border-radius: 4px;
color: var(--text-color);
font-family: monospace;
border: 1px solid var(--glass-border);
}
/* Buttons */
.btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
font-size: 0.9rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), #2563eb);
color: white;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(37, 99, 235, 0.4);
}
.btn-primary:active {
transform: translateY(0);
}
/* Overlay (Start/Game Over) */
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(8px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 24px;
z-index: 10;
transition: opacity 0.3s ease;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
}
.overlay h1 {
font-size: 3rem;
margin-bottom: 0.5rem;
background: linear-gradient(to right, var(--primary-color), var(--accent-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(56, 189, 248, 0.2);
}
.overlay p {
font-size: 1.2rem;
color: #cbd5e1;
margin-bottom: 2rem;
}
/* =========================================
3. RESPONSIVE DESIGN
========================================= */
@media (max-width: 768px) {
.game-wrapper {
flex-direction: column;
padding: 1rem;
gap: 1rem;
}
.sidebar {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
min-width: auto;
}
.panel {
padding: 0.5rem 1rem;
flex: 1;
}
.controls {
display: none; /* Hide complex controls on mobile to save space */
}
canvas {
max-height: 60vh;
width: auto;
}
}
/* Footer Credit */
footer {
margin-top: 1rem;
font-size: 0.75rem;
color: #475569;
}
footer a {
color: var(--primary-color);
text-decoration: none;
}
</style>
</head>
<body>
<div class="game-wrapper">
<!-- Game Canvas -->
<div class="canvas-container">
<canvas id="tetris" width="300" height="600"></canvas>
<!-- Start / Game Over Overlay -->
<div id="overlay" class="overlay">
<h1 id="overlay-title">NEON TETRIS</h1>
<p id="overlay-msg">Press Start to Play</p>
<button id="start-btn" class="btn btn-primary">Start Game</button>
</div>
</div>
<!-- Sidebar -->
<aside class="sidebar">
<!-- Score Panel -->
<div class="panel">
<div class="panel-title">Score</div>
<div id="score" class="stat-value">0</div>
</div>
<!-- Level Panel -->
<div class="panel">
<div class="panel-title">Level</div>
<div id="level" class="stat-value">1</div>
</div>
<!-- Lines Panel -->
<div class="panel">
<div class="panel-title">Lines</div>
<div id="lines" class="stat-value">0</div>
</div>
<!-- Next Piece -->
<div class="panel" style="text-align: center;">
<div class="panel-title">Next</div>
<canvas id="next-canvas" width="100" height="100"></canvas>
</div>
<!-- Controls -->
<div class="panel controls">
<div class="panel-title">Controls</div>
<p><span class="key"></span> <span class="key"></span> Move</p>
<p><span class="key"></span> Rotate</p>
<p><span class="key"></span> Soft Drop</p>
<p><span class="key">Space</span> Hard Drop</p>
<p><span class="key">P</span> Pause</p>
</div>
<button id="reset-btn" class="btn btn-primary" style="background: var(--accent-color);">Reset</button>
</aside>
</div>
<footer>
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</footer>
<script>
/**
* TETRIS GAME ENGINE
* Uses HTML5 Canvas for rendering.
* Implements standard Tetris mechanics with modern JS classes.
*/
// --- Configuration ---
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30; // Base size, scaled by canvas
const COLORS = [
null,
'#00f0f0', // I - Cyan
'#0000f0', // J - Blue
'#f0a000', // L - Orange
'#f0f000', // O - Yellow
'#00f000', // S - Green
'#a000f0', // T - Purple
'#f00000' // Z - Red
];
// --- Shapes Definitions ---
const PIECES = [
[],
[[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], // I
[[2, 0, 0], [2, 2, 2], [0, 0, 0]], // J
[[0, 0, 3], [3, 3, 3], [0, 0, 0]], // L
[[4, 4], [4, 4]], // O
[[0, 5, 5], [5, 5, 0], [0, 0, 0]], // S
[[0, 6, 0], [6, 6, 6], [0, 0, 0]], // T
[[7, 7, 0], [0, 7, 7], [0, 0, 0]] // Z
];
// --- State Management ---
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');
const nextCanvas = document.getElementById('next-canvas');
const nextContext = nextCanvas.getContext('2d');
// Scale canvas for high DPI
function scaleCanvas(c, ctx, w, h) {
const dpr = window.devicePixelRatio || 1;
c.width = w * dpr;
c.height = h * dpr;
c.style.width = `${w}px`;
c.style.height = `${h}px`;
ctx.scale(dpr, dpr);
}
scaleCanvas(canvas, context, COLS * BLOCK_SIZE, ROWS * BLOCK_SIZE);
scaleCanvas(nextCanvas, nextContext, 4 * BLOCK_SIZE, 4 * BLOCK_SIZE);
let arena = createMatrix(COLS, ROWS);
let player = {
pos: {x: 0, y: 0},
matrix: null,
score: 0,
lines: 0,
level: 1,
next: null
};
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
let isPaused = false;
let isGameOver = false;
let requestId = null;
// --- Core Functions ---
function createMatrix(w, h) {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
}
function createPiece(type) {
// Returns a deep copy of the piece definition
return PIECES[type].map(row => [...row]);
}
function drawMatrix(matrix, offset, ctx = context) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
// Main Block
ctx.fillStyle = COLORS[value];
ctx.fillRect(
(x + offset.x) * BLOCK_SIZE,
(y + offset.y) * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
// Inner Bevel/Highlight (Pseudo-3D)
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgba(255,255,255,0.5)';
ctx.strokeRect(
(x + offset.x) * BLOCK_SIZE,
(y + offset.y) * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(
(x + offset.x) * BLOCK_SIZE + 5,
(y + offset.y) * BLOCK_SIZE + 5,
BLOCK_SIZE - 10,
BLOCK_SIZE - 10
);
}
});
});
}
function draw() {
// Fill background with semi-transparent black for trail effect? No, clean clear is better for Tetris.
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
drawMatrix(arena, {x: 0, y: 0});
drawMatrix(player.matrix, player.pos);
}
function drawNext() {
nextContext.fillStyle = '#1e293b'; // Match surface color
nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
// Center the piece
const offsetX = (4 - player.next[0].length) / 2;
const offsetY = (4 - player.next.length) / 2;
drawMatrix(player.next, {x: offsetX, y: offsetY}, nextContext);
}
function merge(arena, player) {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
arena[y + player.pos.y][x + player.pos.x] = value;
}
});
});
}
function rotate(matrix, dir) {
for (let y = 0; y < matrix.length; ++y) {
for (let x = 0; x < y; ++x) {
[matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
}
}
if (dir > 0) {
matrix.forEach(row => row.reverse());
} else {
matrix.reverse();
}
}
function playerRotate(dir) {
const pos = player.pos.x;
let offset = 1;
rotate(player.matrix, dir);
// Wall kick logic (basic)
while (collide(arena, player)) {
player.pos.x += offset;
offset = -(offset + (offset > 0 ? 1 : -1));
if (offset > player.matrix[0].length) {
rotate(player.matrix, -dir); // Rotate back if fails
player.pos.x = pos;
return;
}
}
}
function collide(arena, player) {
const [m, o] = [player.matrix, player.pos];
for (let y = 0; y < m.length; ++y) {
for (let x = 0; x < m[y].length; ++x) {
if (m[y][x] !== 0 &&
(arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
return true;
}
}
}
return false;
}
function playerReset() {
if (player.next === null) {
player.next = createPiece((Math.random() * 7 | 0) + 1);
}
player.matrix = player.next;
player.next = createPiece((Math.random() * 7 | 0) + 1);
drawNext();
player.pos.y = 0;
player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);
if (collide(arena, player)) {
gameOver();
}
}
function arenaSweep() {
let rowCount = 0;
outer: for (let y = arena.length - 1; y > 0; --y) {
for (let x = 0; x < arena[y].length; ++x) {
if (arena[y][x] === 0) {
continue outer;
}
}
const row = arena.splice(y, 1)[0].fill(0);
arena.unshift(row);
++y;
rowCount++;
}
if (rowCount > 0) {
// Scoring: 100, 300, 500, 800
const lineScores = [0, 100, 300, 500, 800];
player.score += lineScores[rowCount] * player.level;
player.lines += rowCount;
player.level = Math.floor(player.lines / 10) + 1;
// Speed up
dropInterval = Math.max(100, 1000 - (player.level - 1) * 100);
updateScore();
}
}
function playerDrop() {
player.pos.y++;
if (collide(arena, player)) {
player.pos.y--;
merge(arena, player);
playerReset();
arenaSweep();
updateScore();
}
dropCounter = 0;
}
function playerMove(dir) {
player.pos.x += dir;
if (collide(arena, player)) {
player.pos.x -= dir;
}
}
function playerHardDrop() {
while (!collide(arena, player)) {
player.pos.y++;
}
player.pos.y--; // Back up one step
merge(arena, player);
playerReset();
arenaSweep();
updateScore();
dropCounter = 0;
}
function updateScore() {
document.getElementById('score').innerText = player.score;
document.getElementById('level').innerText = player.level;
document.getElementById('lines').innerText = player.lines;
}
function gameOver() {
isGameOver = true;
cancelAnimationFrame(requestId);
const overlay = document.getElementById('overlay');
const title = document.getElementById('overlay-title');
const msg = document.getElementById('overlay-msg');
const btn = document.getElementById('start-btn');
title.innerText = "GAME OVER";
title.style.background = "linear-gradient(to right, #f472b6, #ef4444)";
title.style.webkitBackgroundClip = "text";
title.style.webkitTextFillColor = "transparent";
msg.innerText = `Final Score: ${player.score}`;
btn.innerText = "Try Again";
overlay.classList.remove('hidden');
}
function resetGame() {
arena.forEach(row => row.fill(0));
player.score = 0;
player.lines = 0;
player.level = 1;
player.next = null;
dropInterval = 1000;
updateScore();
playerReset();
isGameOver = false;
isPaused = false;
}
function update(time = 0) {
if (isPaused || isGameOver) return;
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
requestId = requestAnimationFrame(update);
}
// --- Input Handling ---
document.addEventListener('keydown', event => {
if (isGameOver) return;
// Prevent default scrolling for game keys
if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"," "].indexOf(event.code) > -1) {
event.preventDefault();
}
if (event.code === 'KeyP') {
isPaused = !isPaused;
if (!isPaused) {
lastTime = performance.now(); // Reset time to prevent jump
update();
}
return;
}
if (isPaused) return;
if (event.code === 'ArrowLeft') {
playerMove(-1);
} else if (event.code === 'ArrowRight') {
playerMove(1);
} else if (event.code === 'ArrowDown') {
playerDrop();
} else if (event.code === 'ArrowUp') {
playerRotate(1);
} else if (event.code === 'Space') {
playerHardDrop();
}
});
document.getElementById('start-btn').addEventListener('click', () => {
document.getElementById('overlay').classList.add('hidden');
resetGame();
update();
});
document.getElementById('reset-btn').addEventListener('click', () => {
// If game is running, pause it
if (!isGameOver && !isPaused) {
isPaused = true;
}
resetGame();
document.getElementById('overlay').classList.add('hidden');
update();
});
// Initial Draw (Empty Board)
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
</script>
</body>
</html>