Skip to content

Instantly share code, notes, and snippets.

@KevinWang15
Created February 9, 2025 06:17
Show Gist options
  • Save KevinWang15/39852939163fbf68c257705703fcd17c to your computer and use it in GitHub Desktop.
Save KevinWang15/39852939163fbf68c257705703fcd17c to your computer and use it in GitHub Desktop.
Tetris by Gemini Advanced 2.0 Pro
<!DOCTYPE html>
<html>
<head>
<title>Tetris</title>
<style>
body {
background-color: #222;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
#game-canvas {
border: 4px solid white;
}
#score, #level, #lines {
color: white;
font-family: sans-serif;
margin-left: 20px; /* Add some spacing from the canvas */
font-size: 1.2em;
}
#next-piece-canvas {
border: 2px solid white;
margin-left: 20px; /* Add some spacing from the canvas */
}
#game-info{
display: flex;
flex-direction: column;
}
#game-container {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.hidden {
display: none;
}
#game-over-screen {
position: absolute; /* Important for overlay */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 20px;
border: 2px solid white;
text-align: center;
z-index: 10; /* Make sure it's on top */
}
.button {
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas" width="300" height="600"></canvas>
<div id = "game-info">
<canvas id="next-piece-canvas" width="120" height="120"></canvas>
<div id="score">Score: 0</div>
<div id="level">Level: 1</div>
<div id="lines">Lines: 0</div>
</div>
</div>
<div id="game-over-screen" class="hidden">
<h2>Game Over</h2>
<p id="final-score"></p>
<button id="restart-button" class="button">Restart</button>
</div>
<script>
const canvas = document.getElementById('game-canvas');
const context = canvas.getContext('2d');
const nextPieceCanvas = document.getElementById('next-piece-canvas');
const nextPieceContext = nextPieceCanvas.getContext('2d');
const scoreElement = document.getElementById('score');
const levelElement = document.getElementById('level');
const linesElement = document.getElementById('lines');
const gameOverScreen = document.getElementById('game-over-screen');
const finalScoreElement = document.getElementById('final-score');
const restartButton = document.getElementById('restart-button');
const blockSize = 30;
const boardWidth = 10;
const boardHeight = 20;
const nextPieceBoardWidth = 4;
const nextPieceBoardHeight = 4;
// Game state
let board = [];
let currentPiece;
let nextPiece;
let score = 0;
let level = 1;
let linesCleared = 0;
let dropCounter = 0;
let dropInterval = 1000; // Initial drop interval (1 second)
let lastTime = 0;
let isGameOver = false;
// Initialize the game board
function createBoard(width, height) {
return Array(height).fill(null).map(() => Array(width).fill(0));
}
// Tetromino shapes (I, J, L, O, S, Z, T)
const tetrominoes = [
// I
[
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
// J
[
[2, 0, 0],
[2, 2, 2],
[0, 0, 0]
],
// L
[
[0, 0, 3],
[3, 3, 3],
[0, 0, 0]
],
// O
[
[4, 4],
[4, 4]
],
// S
[
[0, 5, 5],
[5, 5, 0],
[0, 0, 0]
],
// Z
[
[6, 6, 0],
[0, 6, 6],
[0, 0, 0]
],
// T
[
[0, 7, 0],
[7, 7, 7],
[0, 0, 0]
]
];
const colors = [
null,
'cyan', // I
'blue', // J
'orange', // L
'yellow', // O
'green', // S
'red', // Z
'purple' // T
];
// Create a new random tetromino
function createPiece() {
const randomIndex = Math.floor(Math.random() * tetrominoes.length);
const piece = tetrominoes[randomIndex];
return {
shape: piece,
x: Math.floor(boardWidth / 2) - Math.floor(piece[0].length / 2),
y: 0,
color: randomIndex + 1
};
}
function drawNextPiece() {
nextPieceContext.clearRect(0, 0, nextPieceCanvas.width, nextPieceCanvas.height); // Clear previous piece
if (!nextPiece) return; // No next piece yet
nextPiece.shape.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
nextPieceContext.fillStyle = colors[nextPiece.color];
nextPieceContext.fillRect((x) * blockSize, (y) * blockSize, blockSize, blockSize);
nextPieceContext.strokeRect((x) * blockSize, (y) * blockSize, blockSize, blockSize);
}
});
});
}
// Draw a single block
function drawBlock(x, y, color, ctx) {
ctx.fillStyle = color;
ctx.fillRect(x * blockSize, y * blockSize, blockSize, blockSize);
ctx.strokeRect(x * blockSize, y * blockSize, blockSize, blockSize);
}
// Draw the board and the current piece
function draw() {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
board.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
drawBlock(x, y, colors[value], context);
}
});
});
if (currentPiece) {
currentPiece.shape.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
drawBlock(currentPiece.x + x, currentPiece.y + y, colors[currentPiece.color], context);
}
});
});
}
drawNextPiece();
updateScore();
}
// Move the piece down
function drop() {
currentPiece.y++;
if (collide(board, currentPiece)) {
currentPiece.y--;
merge(board, currentPiece);
resetPiece();
clearLines();
if (collide(board, currentPiece)) {
// Game over
gameOver();
return;
}
}
dropCounter = 0;
}
// Move the piece left or right
function move(dir) {
currentPiece.x += dir;
if (collide(board, currentPiece)) {
currentPiece.x -= dir;
}
}
// Rotate the piece
function rotate(piece) {
const rotated = [];
for (let i = 0; i < piece.shape[0].length; i++) {
rotated.push([]);
for (let j = piece.shape.length - 1; j >= 0; j--) {
rotated[i].push(piece.shape[j][i]);
}
}
const tempPiece = { ...piece, shape: rotated };
//wall kicks, test different offsets
const offsets = [0, -1, 1, -2, 2]; // Common offsets
for (const offset of offsets) {
tempPiece.x = piece.x + offset; // Apply offset
if (!collide(board, tempPiece)) {
currentPiece.shape = rotated;
currentPiece.x +=offset;
return;
}
}
}
// Check for collisions
function collide(board, piece) {
for (let y = 0; y < piece.shape.length; y++) {
for (let x = 0; x < piece.shape[y].length; x++) {
if (piece.shape[y][x] !== 0 &&
(board[y + piece.y] && board[y + piece.y][x + piece.x]) !== 0) {
return true; // Collision detected
}
// Check for out-of-bounds
if (piece.shape[y][x] !== 0 &&
((y + piece.y >= boardHeight) || //bottom
(x + piece.x < 0 ) || //left
(x + piece.x >= boardWidth))) //right
{
return true;
}
}
}
return false; // No collision
}
// Merge the piece into the board
function merge(board, piece) {
piece.shape.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
board[y + piece.y][x + piece.x] = piece.color;
}
});
});
}
// Reset the piece to the top
function resetPiece() {
currentPiece = nextPiece;
nextPiece = createPiece();
currentPiece.y = 0;
currentPiece.x = Math.floor(boardWidth / 2) - Math.floor(currentPiece.shape[0].length / 2);
}
function clearLines() {
let linesToClear = [];
board.forEach((row, y) => {
if (row.every(value => value !== 0)) {
linesToClear.push(y);
}
});
linesToClear.forEach(y => {
board.splice(y, 1); //remove the line
board.unshift(Array(boardWidth).fill(0)); //add an empty line at the top
linesCleared++; //global lines cleared count
score += 100 * level;
});
//level and speed
if (linesCleared >= level * 10) {
level++;
dropInterval *= 0.8; // Speed up the game (e.g., 20% faster)
}
}
//update score
function updateScore() {
scoreElement.textContent = `Score: ${score}`;
levelElement.textContent = `Level: ${level}`;
linesElement.textContent = `Lines: ${linesCleared}`;
}
//game over
function gameOver(){
isGameOver = true;
finalScoreElement.textContent = `Final Score: ${score}`;
gameOverScreen.classList.remove('hidden');
}
// Game loop
function update(time = 0) {
if (isGameOver) return; // Stop the loop if game over
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
drop();
}
draw();
requestAnimationFrame(update);
}
function startGame() {
board = createBoard(boardWidth, boardHeight);
currentPiece = createPiece();
nextPiece = createPiece(); // Initialize nextPiece
score = 0;
level = 1;
linesCleared = 0;
dropInterval = 1000;
isGameOver = false;
gameOverScreen.classList.add('hidden'); //hide game over screen
update();
}
// Event listeners for keyboard input
document.addEventListener('keydown', event => {
if (event.key === 'ArrowLeft' || event.key === 'a') {
move(-1);
} else if (event.key === 'ArrowRight' || event.key === 'd') {
move(1);
} else if (event.key === 'ArrowDown' || event.key === 's') {
drop();
} else if (event.key === 'ArrowUp' || event.key === 'w') {
rotate(currentPiece);
}
});
restartButton.addEventListener('click', startGame);
startGame();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment