#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h> // 用于随机数种子
// ==================== 跨平台适配(Windows/Linux/Mac) ====================
#ifdef _WIN32
// Windows 环境
#include <windows.h>
#include <conio.h> // Windows 保留 conio.h
#define CLEAR_SCREEN() system("cls")
#define SLEEP(ms) Sleep(ms)
#define KBHIT() _kbhit()
#define GETCH() _getch()
#else
// Linux/Mac 环境(使用 ncurses 库)
#include <ncurses.h>
#include <unistd.h>
#define CLEAR_SCREEN() clear()
#define SLEEP(ms) usleep(ms * 1000)
#define KBHIT() (getch() != ERR) // ncurses 非阻塞输入
#define GETCH() getch()
#endif
// ==================== 游戏配置 ====================
#define WIDTH 30 // 游戏区域宽度
#define HEIGHT 20 // 游戏区域高度
#define INIT_LEN 3 // 蛇初始长度
// 蛇身节点结构体
typedef struct {
int x; // 横坐标
int y; // 纵坐标
} SnakeNode;
// 游戏全局变量
SnakeNode snake[WIDTH * HEIGHT]; // 蛇身数组(最大长度=区域总格子数)
int snake_len = INIT_LEN; // 当前蛇长度
int food_x, food_y; // 食物坐标
char direction = 'r'; // 初始方向(r=右, l=左, u=上, d=下)
int score = 0; // 分数
int game_over = 0; // 游戏结束标记
// ==================== 函数声明 ====================
void init_game(); // 初始化游戏
void draw_game(); // 绘制游戏界面
void handle_input(); // 处理键盘输入
void move_snake(); // 移动蛇
void generate_food(); // 生成食物
int check_collision(); // 碰撞检测(边界/自身)
// ==================== 主函数 ====================
int main() {
// Linux/Mac 初始化 ncurses
#ifndef _WIN32
initscr(); // 初始化 ncurses
cbreak(); // 禁用行缓冲
noecho(); // 不回显输入字符
keypad(stdscr, TRUE); // 启用功能键(方向键)
nodelay(stdscr, TRUE);// 设置非阻塞输入
#endif
srand(time(NULL)); // 初始化随机数种子(食物生成)
init_game(); // 初始化游戏
// 游戏主循环
while (!game_over) {
draw_game(); // 绘制界面
handle_input(); // 检测键盘输入
move_snake(); // 移动蛇
SLEEP(200); // 控制游戏速度(数值越小速度越快)
// 碰撞检测:边界/自身
if (check_collision()) {
game_over = 1;
CLEAR_SCREEN();
printf("\n====================\n");
printf(" 游戏结束!\n");
printf(" 最终得分:%d\n", score);
printf("====================\n");
printf("按任意键退出...\n");
GETCH(); // 等待按键
break;
}
}
// Linux/Mac 释放 ncurses 资源
#ifndef _WIN32
endwin();
#endif
return 0;
}
// ==================== 初始化游戏 ====================
void init_game() {
// 初始化蛇身(初始在屏幕左侧,水平向右)
for (int i = 0; i < snake_len; i++) {
snake[i].x = WIDTH / 2 - i; // 蛇头在中间,身体向左延伸
snake[i].y = HEIGHT / 2;
}
// 生成第一个食物
generate_food();
// 初始化分数和游戏状态
score = 0;
game_over = 0;
direction = 'r'; // 初始向右
}
// ==================== 绘制游戏界面 ====================
void draw_game() {
CLEAR_SCREEN(); // 清屏
// 绘制上边界
printf("+");
for (int i = 0; i < WIDTH; i++) printf("-");
printf("+\n");
// 绘制游戏区域(每行)
for (int y = 0; y < HEIGHT; y++) {
printf("|"); // 左边界
for (int x = 0; x < WIDTH; x++) {
int is_snake = 0;
int is_food = 0;
// 判断当前位置是否是蛇身
for (int i = 0; i < snake_len; i++) {
if (snake[i].x == x && snake[i].y == y) {
is_snake = 1;
// 蛇头用 '@',蛇身用 '○'
printf(i == 0 ? "@" : "○");
break;
}
}
if (is_snake) continue;
// 判断当前位置是否是食物
if (x == food_x && y == food_y) {
is_food = 1;
printf("●"); // 食物用 '●'
}
if (is_food) continue;
// 空区域
printf(" ");
}
printf("|\n"); // 右边界
}
// 绘制下边界
printf("+");
for (int i = 0; i < WIDTH; i++) printf("-");
printf("+\n");
// 显示分数和操作提示
printf("分数:%d | 操作:方向键控制 | 撞墙/撞自己=游戏结束\n", score);
fflush(stdout); // 强制刷新输出(避免界面卡顿)
}
// ==================== 处理键盘输入(方向键) ====================
void handle_input() {
// 检测是否有按键按下(非阻塞)
if (KBHIT()) {
int key = GETCH(); // 获取按键
// 适配方向键码(Windows/ncurses 统一)
switch (key) {
#ifdef _WIN32
// Windows 方向键(两字节:0xE0 后接键码)
case 0xE0:
key = GETCH();
switch (key) {
case 72: if (direction != 'd') direction = 'u'; break; // 上
case 80: if (direction != 'u') direction = 'd'; break; // 下
case 75: if (direction != 'r') direction = 'l'; break; // 左
case 77: if (direction != 'l') direction = 'r'; break; // 右
}
break;
#else
// Linux/Mac ncurses 方向键
case KEY_UP: if (direction != 'd') direction = 'u'; break;
case KEY_DOWN: if (direction != 'u') direction = 'd'; break;
case KEY_LEFT: if (direction != 'r') direction = 'l'; break;
case KEY_RIGHT: if (direction != 'l') direction = 'r'; break;
#endif
}
}
}
// ==================== 移动蛇 ====================
void move_snake() {
// 1. 保存旧蛇头位置,计算新蛇头位置
SnakeNode old_head = snake[0];
SnakeNode new_head = old_head;
// 根据方向更新新蛇头坐标
switch (direction) {
case 'u': new_head.y--; break; // 上
case 'd': new_head.y++; break; // 下
case 'l': new_head.x--; break; // 左
case 'r': new_head.x++; break; // 右
}
// 2. 移动蛇身(从尾到头,每个节点继承前一个节点的位置)
for (int i = snake_len - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
// 3. 更新蛇头为新位置
snake[0] = new_head;
// 4. 判断是否吃到食物
if (new_head.x == food_x && new_head.y == food_y) {
snake_len++; // 蛇身变长
score += 10; // 加分
generate_food(); // 生成新食物
}
}
// ==================== 生成食物(避免生成在蛇身上) ====================
void generate_food() {
// 循环生成,直到食物不在蛇身上
while (1) {
// 随机生成食物坐标(范围:0~WIDTH-1, 0~HEIGHT-1)
food_x = rand() % WIDTH;
food_y = rand() % HEIGHT;
// 检查是否和蛇身重叠
int overlap = 0;
for (int i = 0; i < snake_len; i++) {
if (snake[i].x == food_x && snake[i].y == food_y) {
overlap = 1;
break;
}
}
// 无重叠则退出循环
if (!overlap) break;
}
}
// ==================== 碰撞检测(边界/自身) ====================
int check_collision() {
SnakeNode head = snake[0];
// 1. 边界碰撞(超出游戏区域)
if (head.x < 0 || head.x >= WIDTH || head.y < 0 || head.y >= HEIGHT) {
return 1;
}
// 2. 自身碰撞(蛇头撞到身体)
for (int i = 1; i < snake_len; i++) {
if (head.x == snake[i].x && head.y == snake[i].y) {
return 1;
}
}
return 0; // 无碰撞
}