单片机贪吃蛇C代码如何实现?

99ANYc3cd6
预计阅读时长 35 分钟
位置: 首页 C语言 正文

硬件准备

在开始之前,请确保您有以下硬件:

单片机贪吃蛇c语言代码
(图片来源网络,侵删)
  1. 51单片机开发板 (STC89C52RC)
  2. LCD1602液晶显示屏 (带I2C或并行接口,本代码使用并行接口)
  3. 4x4矩阵键盘 或 4个独立按键 (用于控制方向和开始/暂停)
  4. 杜邦线若干
  5. 面包板 (可选,方便搭建)

硬件连接 (并行接口LCD1602)

我们将使用LCD1602的8位数据模式,连接如下:

LCD1602 引脚 单片机引脚 (P口) 功能说明
VSS (GND) GND 电源地
VDD +5V 电源正极
V0 (Contrast) 电位器中间脚 对比度调节
RS (Register Select) P2.5 寄存器选择 (1=数据, 0=指令)
RW (Read/Write) GND 始终写模式
EN (Enable) P2.6 使能信号
D0 - 未使用 (4位模式时)
D1 - 未使用 (4位模式时)
D2 - 未使用 (4位模式时)
D3 - 未使用 (4位模式时)
D4 P2.0 数据线 4
D5 P2.1 数据线 5
D6 P2.2 数据线 6
D7 P2.3 数据线 7
A (Backlight +) +5V 背光正极
K (Backlight -) GND 背光负极

键盘连接示例 (使用P1口):

按键功能 连接到单片机引脚
P1.0
P1.1
P1.2
P1.3
开始/暂停 P1.4

完整C语言代码

将以下代码保存为 snake.c,并使用Keil C51或其他51开发环境进行编译。

#include <reg52.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// --- 硬件引脚定义 ---
sbit LCD_RS = P2^5;
sbit LCD_RW = P2^6;
sbit LCD_EN = P2^7;
#define LCD_DATA_PORT P0 // LCD数据总线连接到P0口
// --- 按键定义 (连接到P1口) ---
sbit KEY_UP    = P1^0;
sbit KEY_DOWN  = P1^1;
sbit KEY_LEFT  = P1^2;
sbit KEY_RIGHT = P1^3;
sbit KEY_START = P1^4;
// --- 游戏参数定义 ---
#define LCD_WIDTH  16  // LCD1602每行显示16个字符
#define LCD_HEIGHT 2   // LCD1602共2行
#define SNAKE_MAX_LENGTH 64 // 蛇的最大长度
// 游戏状态
enum GameState {
    GAME_STOPPED,
    GAME_RUNNING,
    GAME_PAUSED,
    GAME_OVER
};
// 蛇身节点结构体
typedef struct {
    int x;
    int y;
} SnakeNode;
// --- 全局变量 ---
SnakeNode snake[SNAKE_MAX_LENGTH]; // 蛇身数组
int snake_length = 3;             // 当前蛇的长度
int food_x, food_y;               // 食物的位置
int direction = 1;                // 蛇的移动方向 (1:右, 2:左, 3:上, 4:下)
enum GameState game_state = GAME_STOPPED;
unsigned int score = 0;
// --- LCD1602 函数库 ---
void LcdDelay(unsigned int ms) {
    unsigned int i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 120; j++);
}
void LcdWriteCmd(unsigned char cmd) {
    LCD_RS = 0; // 选择指令寄存器
    LCD_RW = 0; // 选择写操作
    LCD_DATA_PORT = cmd;
    LCD_EN = 1; // 使能信号高电平
    LcdDelay(5);
    LCD_EN = 0; // 使能信号低电平
    LcdDelay(5);
}
void LcdWriteData(unsigned char dat) {
    LCD_RS = 1; // 选择数据寄存器
    LCD_RW = 0; // 选择写操作
    LCD_DATA_PORT = dat;
    LCD_EN = 1; // 使能信号高电平
    LcdDelay(5);
    LCD_EN = 0; // 使能信号低电平
    LcdDelay(5);
}
void LcdInit() {
    LcdDelay(15);
    LcdWriteCmd(0x38); // 设置16x2显示,5x7点阵,8位数据接口
    LcdWriteCmd(0x0C); // 显示开,光标关,闪烁关
    LcdWriteCmd(0x06); // 读写后指针自动加1
    LcdWriteCmd(0x01); // 清屏
    LcdDelay(5);
}
void LcdSetCursor(unsigned char x, unsigned char y) {
    unsigned char addr;
    if (y == 0)
        addr = 0x80 + x;
    else
        addr = 0xC0 + x;
    LcdWriteCmd(addr);
}
void LcdShowString(unsigned char x, unsigned char y, unsigned char *str) {
    LcdSetCursor(x, y);
    while (*str != '\0') {
        LcdWriteData(*str++);
    }
}
// --- 游戏逻辑函数 ---
void GenerateFood() {
    // 简单的随机数生成,基于定时器溢出
    food_x = rand() % (LCD_WIDTH - 2) + 1;
    food_y = rand() % (LCD_HEIGHT - 2) + 1;
    // 确保食物不出现在蛇身上
    for (int i = 0; i < snake_length; i++) {
        if (snake[i].x == food_x && snake[i].y == food_y) {
            GenerateFood(); // 如果冲突,递归生成
            return;
        }
    }
}
void InitGame() {
    // 初始化蛇的位置 (从屏幕中央开始)
    snake[0].x = LCD_WIDTH / 2;
    snake[0].y = LCD_HEIGHT / 2;
    snake[1].x = snake[0].x - 1;
    snake[1].y = snake[0].y;
    snake[2].x = snake[1].x - 1;
    snake[2].y = snake[0].y;
    snake_length = 3;
    direction = 1; // 初始向右
    score = 0;
    GenerateFood();
    game_state = GAME_RUNNING;
}
void UpdateGame() {
    if (game_state != GAME_RUNNING) return;
    // 移动蛇身
    // 1. 保存尾部节点
    SnakeNode tail = snake[snake_length - 1];
    // 2. 移动身体节点 (除了头部)
    for (int i = snake_length - 1; i > 0; i--) {
        snake[i] = snake[i - 1];
    }
    // 3. 根据方向移动头部
    switch (direction) {
        case 1: snake[0].x++; break; // 右
        case 2: snake[0].x--; break; // 左
        case 3: snake[0].y--; break; // 上
        case 4: snake[0].y++; break; // 下
    }
    // 4. 检查碰撞
    // 4.1 撞墙
    if (snake[0].x <= 0 || snake[0].x >= LCD_WIDTH - 1 || 
        snake[0].y <= 0 || snake[0].y >= LCD_HEIGHT - 1) {
        game_state = GAME_OVER;
        return;
    }
    // 4.2 撞到自己
    for (int i = 1; i < snake_length; i++) {
        if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
            game_state = GAME_OVER;
            return;
        }
    }
    // 5. 检查是否吃到食物
    if (snake[0].x == food_x && snake[0].y == food_y) {
        score += 10;
        snake_length++; // 蛇变长
        if (snake_length >= SNAKE_MAX_LENGTH) {
            game_state = GAME_OVER;
        } else {
            GenerateFood(); // 生成新食物
        }
    } else {
        // 如果没吃到食物,尾部节点消失 (在渲染时处理)
    }
}
void RenderGame() {
    // 清屏
    LcdWriteCmd(0x01);
    LcdDelay(5);
    // 绘制边界
    for (int i = 0; i < LCD_WIDTH; i++) {
        LcdSetCursor(i, 0);
        LcdWriteData(0xFF); // 0xFF是LCD1602的实心块字符
        LcdSetCursor(i, LCD_HEIGHT - 1);
        LcdWriteData(0xFF);
    }
    for (int i = 0; i < LCD_HEIGHT; i++) {
        LcdSetCursor(0, i);
        LcdWriteData(0xFF);
        LcdSetCursor(LCD_WIDTH - 1, i);
        LcdWriteData(0xFF);
    }
    // 绘制蛇
    for (int i = 0; i < snake_length; i++) {
        LcdSetCursor(snake[i].x, snake[i].y);
        // 蛇头用 'O',蛇身用 '='
        LcdWriteData((i == 0) ? 'O' : '=');
    }
    // 绘制食物
    LcdSetCursor(food_x, food_y);
    LcdWriteData('*');
    // 显示分数和状态
    char score_str[20];
    sprintf(score_str, "Score: %d", score);
    LcdShowString(1, 0, score_str);
    if (game_state == GAME_STOPPED) {
        LcdShowString(4, 1, "Press Start");
    } else if (game_state == GAME_PAUSED) {
        LcdShowString(5, 1, "Paused");
    } else if (game_state == GAME_OVER) {
        LcdShowString(4, 1, "Game Over");
    }
}
// --- 按键处理函数 ---
void ReadKeys() {
    // 检测开始/暂停键
    if (KEY_START == 0) {
        LcdDelay(10); // 消抖
        if (KEY_START == 0) {
            if (game_state == GAME_STOPPED || game_state == GAME_OVER) {
                InitGame();
            } else if (game_state == GAME_RUNNING) {
                game_state = GAME_PAUSED;
            } else if (game_state == GAME_PAUSED) {
                game_state = GAME_RUNNING;
            }
            while (!KEY_START); // 等待按键释放
        }
    }
    // 检测方向键 (只在游戏运行时响应)
    if (game_state == GAME_RUNNING) {
        // 防止180度掉头
        if (KEY_UP == 0 && direction != 4) {
            direction = 3;
            LcdDelay(100);
        } else if (KEY_DOWN == 0 && direction != 3) {
            direction = 4;
            LcdDelay(100);
        } else if (KEY_LEFT == 0 && direction != 1) {
            direction = 2;
            LcdDelay(100);
        } else if (KEY_RIGHT == 0 && direction != 2) {
            direction = 1;
            LcdDelay(100);
        }
    }
}
// --- 主函数 ---
void main() {
    // 初始化随机数种子
    srand(time(NULL)); // 在51单片机上,time(NULL)可能返回0,但没关系
    // 初始化硬件
    LcdInit();
    // 主循环
    while (1) {
        ReadKeys(); // 读取按键输入
        UpdateGame(); // 更新游戏逻辑
        RenderGame(); // 渲染游戏画面
        // 控制游戏速度
        LcdDelay(200000); // 调整这个值可以改变蛇的移动速度
    }
}

代码讲解

1 头文件和引脚定义

  • #include <reg52.h>: 包含51单片机的特殊功能寄存器定义。
  • sbit LCD_RS = P2^5;: 定义LCD1602的控制引脚,方便后续调用。

2 LCD1602驱动函数

这部分是标准的LCD1602驱动代码,用于初始化、写指令、写数据和显示字符串。

单片机贪吃蛇c语言代码
(图片来源网络,侵删)
  • LcdInit(): 初始化LCD,设置显示模式等。
  • LcdWriteCmd(): 向LCD发送指令。
  • LcdWriteData(): 向LCD发送要显示的字符数据。
  • LcdShowString(): 在指定位置显示一个字符串。

3 游戏核心逻辑

  • SnakeNode 结构体: 用来表示蛇的每一个节点的坐标。
  • enum GameState: 定义了游戏的四种状态,让代码逻辑更清晰。
  • InitGame(): 游戏开始时调用,初始化蛇的位置、长度、方向和分数,并生成第一个食物。
  • GenerateFood(): 在屏幕的随机位置生成食物,并确保不会和蛇身重叠。
  • UpdateGame(): 这是游戏的核心。
    1. 移动蛇: 通过循环将蛇身向前移动一位,然后根据direction变量更新蛇头位置。
    2. 碰撞检测: 检查蛇头是否撞到墙壁或自己的身体,如果发生碰撞,则将游戏状态设为GAME_OVER
    3. 吃食物: 检查蛇头是否与食物坐标重合,如果重合,增加分数,蛇身长度加一,并调用GenerateFood()生成新食物。
  • RenderGame(): 负责将游戏状态绘制到LCD1602上。
    1. 清屏并画边框: 用0xFF(实心块字符)画出游戏区域的边界。
    2. 画蛇: 遍历蛇身数组,在对应坐标画上'O'(蛇头)或(蛇身)。
    3. 画食物: 在食物坐标画上。
    4. 显示信息: 在屏幕上显示当前分数和游戏状态(如 "Game Over")。

4 按键处理

  • ReadKeys(): 检测按键输入。
    • 开始/暂停键: 按下后根据当前游戏状态进行切换(STOPPED -> RUNNING, RUNNING -> PAUSED, PAUSED -> RUNNING)。
    • 方向键: 只有在游戏运行状态下才响应,增加了防止180度掉头的逻辑(当前向右走时,不能直接按左键)。

5 主函数 main()

  • srand(time(NULL));: 初始化随机数生成器,让食物的随机位置每次都不同。
  • LcdInit();: 初始化LCD。
  • while(1): 无限循环,是单片机程序的标志。
    • 在循环中,依次执行ReadKeys()UpdateGame()RenderGame()
    • LcdDelay(200000);: 这个延时函数控制了游戏循环的频率,也就是蛇的移动速度,数值越大,蛇移动越慢。

如何编译和烧录

  1. 创建Keil项目: 打开Keil C51,新建一个项目,并选择你的单片机型号(如STC89C52RC)。
  2. 添加源文件: 将上面的代码保存为snake.c,并添加到项目中。
  3. 配置选项: 在"Options for Target"中,确保晶振频率设置正确(通常是11.0592MHz或12MHz,因为代码中的延时是基于这个频率的)。
  4. 编译: 点击"Build"按钮,生成.hex文件。
  5. 烧录: 使用USB-TTL下载器(如CH340)将.hex文件烧录到你的单片机中。

可能遇到的问题与调试

  1. LCD显示乱码或不显示:
    • 检查接线: 确保V0引脚连接了电位器用于调节对比度。
    • 检查初始化: 确认LcdInit()函数中的指令是否正确(如0x38)。
    • 检查数据/指令选择: 确认RSRWEN引脚的逻辑是否正确。
  2. 按键无响应:
    • 检查按键电路: 按键另一端是否正确连接到GND(通常采用上拉电阻+按键接地的方式)。
    • 检查消抖: 代码中已经有简单的延时消抖,如果按键抖动严重,可以增加延时时间。
  3. 游戏速度过快/过慢:
    • 调整main函数中的LcdDelay(200000)的参数,数值越大,延时越长,速度越慢。
  4. 蛇移动不流畅:
    • 这是由main循环中的延时决定的,如果想实现更平滑的移动,可以考虑使用定时器中断来控制游戏逻辑的更新频率,而main循环可以专注于渲染和按键读取,这样可以实现更精确的时序控制。

希望这份详细的代码和教程能帮助你成功运行单片机贪吃蛇游戏!祝你学习愉快!

单片机贪吃蛇c语言代码
(图片来源网络,侵删)
-- 展开阅读全文 --
头像
如何关闭dede副栏目?
« 上一篇 04-26
c程序设计语言 徐宝文
下一篇 » 04-26

相关文章

取消
微信二维码
支付宝二维码

目录[+]