程序员节手把手教你用HTML5 Canvas编写1024游戏

文章目录

程序员节手把手教你用HTML5 Canvas编写1024游戏

接下来将详细拆解使用HTML5 Canvas编写1024游戏的每个环节,从基础概念到完整实现,让你一步步掌握游戏开发的核心技巧。
游戏效果图如下:
在这里插入图片描述

一、游戏概述与规则

1024是一款经典的滑块益智游戏,玩家通过上下左右移动方块,使相同数字的方块碰撞合并,目标是合成1024这个数字。

1. 游戏结构

  • 使用HTML5 Canvas绘制游戏界面
  • 使用CSS美化界面,包括颜色、字体和布局
  • 通过JavaScript实现游戏逻辑

2. 核心功能

  • 游戏初始化:创建4x4网格并添加初始方块
  • 方块移动:实现上下左右四个方向的移动逻辑
  • 方块合并:相同数字的方块在移动时合并
  • 分数计算:合并时累加分数
  • 游戏状态检测:判断游戏是否胜利或失败

3. 关键算法

  • 移动算法:通过收集非零元素、合并相同数字、填充零的方式实现
  • 随机生成:在空位置随机生成新方块(90%概率为2,10%概率为4)
  • 游戏结束检测:检查是否还有空位置或可合并的相邻方块

4. 用户体验

  • 键盘控制(方向键)
  • 分数实时显示
  • 游戏状态提示(胜利/失败)
  • 重新开始功能

5. 游戏规则

  • 4×4的网格中,初始有两个数字2或4
  • 每次移动后,空白处随机出现一个2或4
  • 相同数字的方块碰撞时会合并成它们的和
  • 当网格填满且无法合并时游戏结束
  • 合成1024即获胜

二、项目结构与初始化

首先创建基础HTML结构,设置Canvas画布和基本样式:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1024游戏 - HTML5 Canvas实现</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #faf8ef;
            color: #776e65;
            margin: 0;
            padding: 20px;
        }
        
        .game-container {
            position: relative;
            margin: 20px auto;
        }
        
        canvas {
            border-radius: 6px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
    </style>
</head>
<body>
    <h1>1024游戏</h1>
    
    <div class="game-container">
        <canvas id="game-canvas" width="450" height="450"></canvas>
    </div>

    <script>
        // 游戏代码将在这里实现
    </script>
</body>
</html>

三、游戏配置与状态管理

在script标签中,首先定义游戏的核心配置和状态变量:

// 游戏配置
const GRID_SIZE = 4;           // 网格大小(4x4)
const CELL_SIZE = 100;         // 每个单元格的尺寸
const CELL_MARGIN = 10;        // 单元格之间的间距

// 方块颜色映射(根据数值不同显示不同颜色)
const TILE_COLORS = {
    0: "#cdc1b4",      // 空单元格
    2: "#eee4da",
    4: "#ede0c8",
    8: "#f2b179",
    16: "#f59563",
    32: "#f67c5f",
    64: "#f65e3b",
    128: "#edcf72",
    256: "#edcc61",
    512: "#edc850",
    1024: "#edc53f",
    2048: "#edc22e"
};

// 方块文字颜色
const TEXT_COLORS = {
    light: "#f9f6f2",  // 浅色文字(用于深色背景)
    dark: "#776e65"    // 深色文字(用于浅色背景)
};

// 游戏状态
let grid = [];          // 游戏网格
let score = 0;          // 当前分数
let gameOver = false;   // 游戏是否结束
let won = false;        // 是否获胜
let moved = false;      // 本次移动是否有方块移动

// 获取Canvas上下文
const canvas = document.getElementById("game-canvas");
const ctx = canvas.getContext("2d");

四、游戏初始化

实现游戏初始化函数,创建空网格并添加初始方块:

// 初始化游戏
function initGame() {
    // 重置游戏状态
    grid = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0));
    score = 0;
    gameOver = false;
    won = false;
    moved = false;
    
    // 添加初始的两个方块
    addRandomTile();
    addRandomTile();
    
    // 绘制游戏界面
    drawGame();
}

// 在随机空位置添加一个方块(90%概率为2,10%概率为4)
function addRandomTile() {
    // 获取所有空单元格的位置
    const emptyCells = [];
    for (let row = 0; row < GRID_SIZE; row++) {
        for (let col = 0; col < GRID_SIZE; col++) {
            if (grid[row][col] === 0) {
                emptyCells.push({ row, col });
            }
        }
    }
    
    // 如果有空位置,随机选择一个并放置新方块
    if (emptyCells.length > 0) {
        const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
        grid[randomCell.row][randomCell.col] = Math.random() < 0.9 ? 2 : 4;
    }
}

五、游戏界面绘制

实现绘制游戏界面的核心函数:

// 绘制游戏界面
function drawGame() {
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 绘制背景
    ctx.fillStyle = "#bbada0";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // 绘制所有方块
    for (let row = 0; row < GRID_SIZE; row++) {
        for (let col = 0; col < GRID_SIZE; col++) {
            drawTile(row, col, grid[row][col]);
        }
    }
}

// 绘制单个方块
function drawTile(row, col, value) {
    // 计算方块位置
    const x = col * (CELL_SIZE + CELL_MARGIN) + CELL_MARGIN;
    const y = row * (CELL_SIZE + CELL_MARGIN) + CELL_MARGIN;
    
    // 绘制方块背景
    ctx.fillStyle = TILE_COLORS[value] || TILE_COLORS[0];
    ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
    
    // 如果方块有值,绘制数字
    if (value > 0) {
        // 根据数值大小调整字体大小
        const fontSize = value < 100 ? 36 : value < 1000 ? 32 : 24;
        ctx.font = `bold ${fontSize}px Arial`;
        
        // 根据背景颜色深浅选择文字颜色
        ctx.fillStyle = value <= 4 ? TEXT_COLORS.dark : TEXT_COLORS.light;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        
        // 绘制数字
        ctx.fillText(
            value.toString(), 
            x + CELL_SIZE / 2, 
            y + CELL_SIZE / 2
        );
    }
}

六、核心移动逻辑

实现四个方向的移动逻辑,这是游戏最核心的部分:

// 移动方块(核心逻辑)
function move(direction) {
    // 如果游戏已结束,不处理移动
    if (gameOver) return;
    
    // 保存移动前的网格状态,用于检测是否发生了移动
    const prevGrid = JSON.parse(JSON.stringify(grid));
    
    // 根据方向移动方块
    switch (direction) {
        case "left":
            moveLeft();
            break;
        case "right":
            moveRight();
            break;
        case "up":
            moveUp();
            break;
        case "down":
            moveDown();
            break;
    }
    
    // 检查网格是否发生了变化
    moved = !isEqual(grid, prevGrid);
    
    // 如果发生了移动,添加新方块并检查游戏状态
    if (moved) {
        addRandomTile();
        checkGameState();
        drawGame();
    }
}

// 向左移动
function moveLeft() {
    for (let row = 0; row < GRID_SIZE; row++) {
        // 处理当前行
        let newRow = [];
        
        // 收集非零元素
        for (let col = 0; col < GRID_SIZE; col++) {
            if (grid[row][col] !== 0) {
                newRow.push(grid[row][col]);
            }
        }
        
        // 合并相同数字
        for (let i = 0; i < newRow.length - 1; i++) {
            if (newRow[i] === newRow[i + 1]) {
                newRow[i] *= 2;
                newRow.splice(i + 1, 1);
                score += newRow[i]; // 增加分数
            }
        }
        
        // 填充剩余位置为零
        while (newRow.length < GRID_SIZE) {
            newRow.push(0);
        }
        
        // 更新网格
        grid[row] = newRow;
    }
}

// 向右移动(其他方向类似,只是处理顺序不同)
function moveRight() {
    for (let row = 0; row < GRID_SIZE; row++) {
        // 处理当前行
        let newRow = [];
        
        // 从右向左收集非零元素
        for (let col = GRID_SIZE - 1; col >= 0; col--) {
            if (grid[row][col] !== 0) {
                newRow.unshift(grid[row][col]);
            }
        }
        
        // 合并相同数字(从右向左)
        for (let i = 0; i < newRow.length - 1; i++) {
            if (newRow[i] === newRow[i + 1]) {
                newRow[i] *= 2;
                newRow.splice(i + 1, 1);
                score += newRow[i];
            }
        }
        
        // 填充剩余位置为零(在左侧填充)
        while (newRow.length < GRID_SIZE) {
            newRow.unshift(0);
        }
        
        // 更新网格
        grid[row] = newRow;
    }
}

// 向上移动
function moveUp() {
    for (let col = 0; col < GRID_SIZE; col++) {
        // 处理当前列
        let newCol = [];
        
        // 收集非零元素
        for (let row = 0; row < GRID_SIZE; row++) {
            if (grid[row][col] !== 0) {
                newCol.push(grid[row][col]);
            }
        }
        
        // 合并相同数字
        for (let i = 0; i < newCol.length - 1; i++) {
            if (newCol[i] === newCol[i + 1]) {
                newCol[i] *= 2;
                newCol.splice(i + 1, 1);
                score += newCol[i];
            }
        }
        
        // 填充剩余位置为零
        while (newCol.length < GRID_SIZE) {
            newCol.push(0);
        }
        
        // 更新网格
        for (let row = 0; row < GRID_SIZE; row++) {
            grid[row][col] = newCol[row];
        }
    }
}

// 向下移动
function moveDown() {
    for (let col = 0; col < GRID_SIZE; col++) {
        // 处理当前列
        let newCol = [];
        
        // 从下向上收集非零元素
        for (let row = GRID_SIZE - 1; row >= 0; row--) {
            if (grid[row][col] !== 0) {
                newCol.unshift(grid[row][col]);
            }
        }
        
        // 合并相同数字
        for (let i = 0; i < newCol.length - 1; i++) {
            if (newCol[i] === newCol[i + 1]) {
                newCol[i] *= 2;
                newCol.splice(i + 1, 1);
                score += newCol[i];
            }
        }
        
        // 填充剩余位置为零(在上方填充)
        while (newCol.length < GRID_SIZE) {
            newCol.unshift(0);
        }
        
        // 更新网格
        for (let row = 0; row < GRID_SIZE; row++) {
            grid[row][col] = newCol[row];
        }
    }
}

七、游戏状态检测

实现游戏状态检测,包括胜利和失败条件:

// 检查游戏状态(是否获胜或失败)
function checkGameState() {
    // 检查是否获胜(有1024方块)
    if (!won) {
        for (let row = 0; row < GRID_SIZE; row++) {
            for (let col = 0; col < GRID_SIZE; col++) {
                if (grid[row][col] === 1024) {
                    won = true;
                    gameOver = true;
                    showMessage("恭喜你赢了!");
                    return;
                }
            }
        }
    }
    
    // 检查是否还有空位置
    for (let row = 0; row < GRID_SIZE; row++) {
        for (let col = 0; col < GRID_SIZE; col++) {
            if (grid[row][col] === 0) {
                return; // 还有空位置,游戏继续
            }
        }
    }
    
    // 检查是否还能移动(有相邻相同数字)
    for (let row = 0; row < GRID_SIZE; row++) {
        for (let col = 0; col < GRID_SIZE; col++) {
            const current = grid[row][col];
            
            // 检查右侧
            if (col < GRID_SIZE - 1 && grid[row][col + 1] === current) {
                return; // 可以向右合并
            }
            
            // 检查下方
            if (row < GRID_SIZE - 1 && grid[row + 1][col] === current) {
                return; // 可以向下合并
            }
        }
    }
    
    // 没有空位置且不能合并,游戏结束
    gameOver = true;
    showMessage("游戏结束!");
}

// 辅助函数:比较两个网格是否相同
function isEqual(grid1, grid2) {
    for (let row = 0; row < GRID_SIZE; row++) {
        for (let col = 0; col < GRID_SIZE; col++) {
            if (grid1[row][col] !== grid2[row][col]) {
                return false;
            }
        }
    }
    return true;
}

八、用户交互与界面完善

添加用户交互和界面元素:

// 获取DOM元素
const scoreElement = document.getElementById("score");
const restartButton = document.getElementById("restart-button");
const tryAgainButton = document.getElementById("try-again-button");
const gameMessage = document.getElementById("game-message");
const messageText = document.getElementById("message-text");

// 更新分数显示
function updateScore() {
    scoreElement.textContent = score;
}

// 显示游戏消息
function showMessage(text) {
    messageText.textContent = text;
    gameMessage.style.display = "flex";
}

// 键盘事件监听
document.addEventListener("keydown", function(event) {
    switch (event.key) {
        case "ArrowLeft":
            event.preventDefault();
            move("left");
            break;
        case "ArrowRight":
            event.preventDefault();
            move("right");
            break;
        case "ArrowUp":
            event.preventDefault();
            move("up");
            break;
        case "ArrowDown":
            event.preventDefault();
            move("down");
            break;
    }
});

// 重新开始按钮事件
restartButton.addEventListener("click", initGame);
tryAgainButton.addEventListener("click", initGame);

// 初始化游戏
initGame();

九、完整HTML结构

最后,将以上所有代码整合到完整的HTML文件中:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1024游戏 - HTML5 Canvas实现</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #faf8ef;
            color: #776e65;
            margin: 0;
            padding: 20px;
        }
        
        h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
        }
        
        .game-container {
            position: relative;
            margin: 20px auto;
        }
        
        .game-info {
            display: flex;
            justify-content: space-between;
            width: 450px;
            margin-bottom: 20px;
        }
        
        .score-container {
            background: #bbada0;
            padding: 10px 20px;
            border-radius: 6px;
            color: white;
            font-weight: bold;
            text-align: center;
        }
        
        .score-title {
            font-size: 0.9em;
        }
        
        .score-value {
            font-size: 1.5em;
        }
        
        .restart-button {
            background: #8f7a66;
            color: white;
            border: none;
            border-radius: 6px;
            padding: 10px 20px;
            font-size: 1em;
            cursor: pointer;
            font-weight: bold;
        }
        
        .game-message {
            display: none;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(238, 228, 218, 0.73);
            z-index: 100;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            font-size: 2em;
            font-weight: bold;
        }
        
        .game-message p {
            margin: 0;
        }
        
        .game-message button {
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <h1>1024游戏</h1>
    
    <div class="game-info">
        <div class="score-container">
            <div class="score-title">分数</div>
            <div class="score-value" id="score">0</div>
        </div>
        <button class="restart-button" id="restart-button">重新开始</button>
    </div>
    
    <div class="game-container">
        <canvas id="game-canvas" width="450" height="450"></canvas>
        <div class="game-message" id="game-message">
            <p id="message-text"></p>
            <button class="restart-button" id="try-again-button">再试一次</button>
        </div>
    </div>

    <script>
        // 此处放置所有JavaScript代码
        // 包括游戏配置、状态变量和所有函数实现
    </script>
</body>
</html>

十、总结与扩展

通过以上步骤,我们完整实现了一个功能齐全的1024游戏。这个实现展示了:

  1. Canvas绘图:如何使用Canvas API绘制游戏界面
  2. 游戏状态管理:如何跟踪和管理游戏状态
  3. 核心算法:如何实现方块移动和合并的逻辑
  4. 用户交互:如何处理键盘事件和按钮点击
  5. 游戏逻辑:如何判断游戏胜利和失败条件

扩展思路

  • 添加动画效果,使方块移动更平滑
  • 实现最高分记录功能
  • 添加音效增强游戏体验
  • 实现撤销操作功能
  • 添加不同难度级别(如3×3或5×5网格)

通过这个手把手的教程,你不仅学会了如何实现1024游戏,还掌握了使用HTML5 Canvas开发游戏的基本技能。这些知识可以应用到其他类似的游戏开发项目中。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值