初始提交:网页版俄罗斯方块游戏
- 使用 HTML + CSS + JavaScript 实现 - 实现完整的游戏逻辑:方块移动、旋转、消行、计分 - 支持等级系统和加速机制 - 响应式设计,支持移动端 - 下一个方块预览功能 - 游戏暂停/继续功能
This commit is contained in:
386
script.js
Normal file
386
script.js
Normal file
@@ -0,0 +1,386 @@
|
||||
// 游戏常量
|
||||
const COLS = 10;
|
||||
const ROWS = 20;
|
||||
const BLOCK_SIZE = 30;
|
||||
|
||||
// 方块形状定义
|
||||
const SHAPES = [
|
||||
[[1, 1, 1, 1]], // I
|
||||
[[1, 1], [1, 1]], // O
|
||||
[[0, 1, 0], [1, 1, 1]], // T
|
||||
[[1, 0, 0], [1, 1, 1]], // L
|
||||
[[0, 0, 1], [1, 1, 1]], // J
|
||||
[[0, 1, 1], [1, 1, 0]], // S
|
||||
[[1, 1, 0], [0, 1, 1]] // Z
|
||||
];
|
||||
|
||||
// 方块颜色
|
||||
const COLORS = [
|
||||
'#00f0f0', // I - 青色
|
||||
'#f0f000', // O - 黄色
|
||||
'#a000f0', // T - 紫色
|
||||
'#f0a000', // L - 橙色
|
||||
'#0000f0', // J - 蓝色
|
||||
'#00f000', // S - 绿色
|
||||
'#f00000' // Z - 红色
|
||||
];
|
||||
|
||||
// 游戏状态
|
||||
let canvas, ctx, nextCanvas, nextCtx;
|
||||
let board = [];
|
||||
let currentPiece = null;
|
||||
let nextPiece = null;
|
||||
let score = 0;
|
||||
let level = 1;
|
||||
let lines = 0;
|
||||
let gameLoop = null;
|
||||
let isPaused = false;
|
||||
let isGameOver = false;
|
||||
let dropInterval = 1000;
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
canvas = document.getElementById('game-canvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
nextCanvas = document.getElementById('next-canvas');
|
||||
nextCtx = nextCanvas.getContext('2d');
|
||||
|
||||
// 初始化游戏板
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
board[r] = [];
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
board[r][c] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
document.getElementById('start-btn').addEventListener('click', startGame);
|
||||
document.getElementById('restart-btn').addEventListener('click', startGame);
|
||||
|
||||
// 绘制初始游戏板
|
||||
drawBoard();
|
||||
}
|
||||
|
||||
// 创建新方块
|
||||
function createPiece() {
|
||||
const shapeIndex = Math.floor(Math.random() * SHAPES.length);
|
||||
return {
|
||||
shape: SHAPES[shapeIndex],
|
||||
color: COLORS[shapeIndex],
|
||||
x: Math.floor(COLS / 2) - Math.floor(SHAPES[shapeIndex][0].length / 2),
|
||||
y: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 绘制方块
|
||||
function drawBlock(ctx, x, y, color, size = BLOCK_SIZE) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x * size, y * size, size, size);
|
||||
|
||||
// 添加3D效果
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
|
||||
ctx.fillRect(x * size, y * size, size, 2);
|
||||
ctx.fillRect(x * size, y * size, 2, size);
|
||||
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
|
||||
ctx.fillRect(x * size + size - 2, y * size, 2, size);
|
||||
ctx.fillRect(x * size, y * size + size - 2, size, 2);
|
||||
}
|
||||
|
||||
// 绘制游戏板
|
||||
function drawBoard() {
|
||||
// 清空画布
|
||||
ctx.fillStyle = '#1a1a2e';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 绘制网格
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
ctx.strokeRect(c * BLOCK_SIZE, r * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制已放置的方块
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
if (board[r][c]) {
|
||||
drawBlock(ctx, c, r, board[r][c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制当前方块
|
||||
function drawPiece() {
|
||||
if (!currentPiece) return;
|
||||
|
||||
for (let r = 0; r < currentPiece.shape.length; r++) {
|
||||
for (let c = 0; c < currentPiece.shape[r].length; c++) {
|
||||
if (currentPiece.shape[r][c]) {
|
||||
drawBlock(ctx, currentPiece.x + c, currentPiece.y + r, currentPiece.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制下一个方块
|
||||
function drawNextPiece() {
|
||||
if (!nextPiece) return;
|
||||
|
||||
nextCtx.fillStyle = '#fff';
|
||||
nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
|
||||
|
||||
const size = 20;
|
||||
const offsetX = (nextCanvas.width - nextPiece.shape[0].length * size) / 2;
|
||||
const offsetY = (nextCanvas.height - nextPiece.shape.length * size) / 2;
|
||||
|
||||
for (let r = 0; r < nextPiece.shape.length; r++) {
|
||||
for (let c = 0; c < nextPiece.shape[r].length; c++) {
|
||||
if (nextPiece.shape[r][c]) {
|
||||
nextCtx.fillStyle = nextPiece.color;
|
||||
nextCtx.fillRect(offsetX + c * size, offsetY + r * size, size, size);
|
||||
nextCtx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
|
||||
nextCtx.strokeRect(offsetX + c * size, offsetY + r * size, size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检测碰撞
|
||||
function collide(piece, offsetX = 0, offsetY = 0) {
|
||||
for (let r = 0; r < piece.shape.length; r++) {
|
||||
for (let c = 0; c < piece.shape[r].length; c++) {
|
||||
if (piece.shape[r][c]) {
|
||||
const newX = piece.x + c + offsetX;
|
||||
const newY = piece.y + r + offsetY;
|
||||
|
||||
if (newX < 0 || newX >= COLS || newY >= ROWS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newY >= 0 && board[newY][newX]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 锁定方块
|
||||
function lockPiece() {
|
||||
for (let r = 0; r < currentPiece.shape.length; r++) {
|
||||
for (let c = 0; c < currentPiece.shape[r].length; c++) {
|
||||
if (currentPiece.shape[r][c]) {
|
||||
if (currentPiece.y + r < 0) {
|
||||
gameOver();
|
||||
return;
|
||||
}
|
||||
board[currentPiece.y + r][currentPiece.x + c] = currentPiece.color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearLines();
|
||||
currentPiece = nextPiece;
|
||||
nextPiece = createPiece();
|
||||
drawNextPiece();
|
||||
|
||||
if (collide(currentPiece)) {
|
||||
gameOver();
|
||||
}
|
||||
}
|
||||
|
||||
// 消除行
|
||||
function clearLines() {
|
||||
let linesCleared = 0;
|
||||
|
||||
for (let r = ROWS - 1; r >= 0; r--) {
|
||||
if (board[r].every(cell => cell !== 0)) {
|
||||
board.splice(r, 1);
|
||||
board.unshift(new Array(COLS).fill(0));
|
||||
linesCleared++;
|
||||
r++;
|
||||
}
|
||||
}
|
||||
|
||||
if (linesCleared > 0) {
|
||||
updateScore(linesCleared);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新分数
|
||||
function updateScore(linesCleared) {
|
||||
const points = [0, 100, 300, 500, 800];
|
||||
score += points[linesCleared] * level;
|
||||
lines += linesCleared;
|
||||
|
||||
// 升级
|
||||
if (lines >= level * 10) {
|
||||
level++;
|
||||
dropInterval = Math.max(100, 1000 - (level - 1) * 100);
|
||||
resetGameLoop();
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
// 更新显示
|
||||
function updateDisplay() {
|
||||
document.getElementById('score').textContent = score;
|
||||
document.getElementById('level').textContent = level;
|
||||
document.getElementById('lines').textContent = lines;
|
||||
}
|
||||
|
||||
// 移动方块
|
||||
function movePiece(offsetX, offsetY) {
|
||||
if (!currentPiece || isPaused || isGameOver) return;
|
||||
|
||||
if (!collide(currentPiece, offsetX, offsetY)) {
|
||||
currentPiece.x += offsetX;
|
||||
currentPiece.y += offsetY;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
// 旋转方块
|
||||
function rotatePiece() {
|
||||
if (!currentPiece || isPaused || isGameOver) return;
|
||||
|
||||
const rotated = currentPiece.shape[0].map((_, index) =>
|
||||
currentPiece.shape.map(row => row[index]).reverse()
|
||||
);
|
||||
|
||||
const originalShape = currentPiece.shape;
|
||||
currentPiece.shape = rotated;
|
||||
|
||||
// 墙踢 - 尝试调整位置
|
||||
const kicks = [0, -1, 1, -2, 2];
|
||||
for (let kick of kicks) {
|
||||
if (!collide(currentPiece, kick, 0)) {
|
||||
currentPiece.x += kick;
|
||||
draw();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 无法旋转,恢复原状
|
||||
currentPiece.shape = originalShape;
|
||||
}
|
||||
|
||||
// 硬降落
|
||||
function hardDrop() {
|
||||
if (!currentPiece || isPaused || isGameOver) return;
|
||||
|
||||
while (!collide(currentPiece, 0, 1)) {
|
||||
currentPiece.y++;
|
||||
score += 2;
|
||||
}
|
||||
lockPiece();
|
||||
updateDisplay();
|
||||
draw();
|
||||
}
|
||||
|
||||
// 绘制
|
||||
function draw() {
|
||||
drawBoard();
|
||||
drawPiece();
|
||||
}
|
||||
|
||||
// 游戏循环
|
||||
function gameStep() {
|
||||
if (!isPaused && !isGameOver) {
|
||||
if (!collide(currentPiece, 0, 1)) {
|
||||
currentPiece.y++;
|
||||
draw();
|
||||
} else {
|
||||
lockPiece();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始游戏循环
|
||||
function startGameLoop() {
|
||||
gameLoop = setInterval(gameStep, dropInterval);
|
||||
}
|
||||
|
||||
// 重置游戏循环
|
||||
function resetGameLoop() {
|
||||
clearInterval(gameLoop);
|
||||
startGameLoop();
|
||||
}
|
||||
|
||||
// 开始游戏
|
||||
function startGame() {
|
||||
// 重置状态
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
board[r][c] = 0;
|
||||
}
|
||||
}
|
||||
score = 0;
|
||||
level = 1;
|
||||
lines = 0;
|
||||
dropInterval = 1000;
|
||||
isPaused = false;
|
||||
isGameOver = false;
|
||||
|
||||
// 隐藏游戏结束界面
|
||||
document.getElementById('game-over').classList.add('hidden');
|
||||
|
||||
// 创建方块
|
||||
currentPiece = createPiece();
|
||||
nextPiece = createPiece();
|
||||
|
||||
updateDisplay();
|
||||
drawNextPiece();
|
||||
draw();
|
||||
startGameLoop();
|
||||
}
|
||||
|
||||
// 游戏结束
|
||||
function gameOver() {
|
||||
isGameOver = true;
|
||||
clearInterval(gameLoop);
|
||||
document.getElementById('final-score').textContent = score;
|
||||
document.getElementById('game-over').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 键盘事件处理
|
||||
function handleKeyPress(e) {
|
||||
if (isGameOver) return;
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
movePiece(-1, 0);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
movePiece(1, 0);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
movePiece(0, 1);
|
||||
score += 1;
|
||||
updateDisplay();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
rotatePiece();
|
||||
break;
|
||||
case ' ':
|
||||
e.preventDefault();
|
||||
hardDrop();
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
isPaused = !isPaused;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 启动
|
||||
window.onload = init;
|
||||
Reference in New Issue
Block a user