

引言:
就是练手用的,这个真没什么好说的了。
代码:
头文件(snake.h)
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
// 宏定义
#define MAP_WIDTH 60 // 地图宽度
#define MAP_HEIGHT 30 // 地图高度
#define BRICK "□" // 四周的墙
#define GRID "■" // 内部的网格
#define SNAKE_MAX_SIZE (MAP_WIDTH / 2 * MAP_HEIGHT) // 蛇最大节数
#define SNAKE_INIT_SIZE 3 // 蛇初始化节数
#define SNAKE_HEAD "★" // 蛇头
#define SNAKE_BODY "⊙" // 蛇身体
#define FOOD "◆" // 食物
// 方向枚举
typedef enum _enum_direction
{
DIR_UP, // 上
DIR_DOWN, // 下
DIR_LEFT, // 左
DIR_RIGHT // 右
} enum_direction;
// 游戏状态枚举
typedef enum _enum_status
{
RUNNING,
PAUSE,
GAMEOVER
} enum_status;
// 蛇身结构
typedef struct _stu_body
{
int x; // 蛇身X坐标
int y; // 蛇身Y坐标
} stu_body;
// 蛇结构体
struct _stu_snake
{
int size; // 实际大小
stu_body body[SNAKE_MAX_SIZE]; // 蛇身(body[0]为蛇头)位置
enum_direction direction; // 运动方向
enum_status status; // 游戏状态
} snake;
// 食物结构体
struct _stu_food
{
int x;
int y;
} food;
// 公用变量
int gTopScore = 0; // 最高分数
int gMyScore = 0; // 我的分数
int gSpeed = 300; // 原始速度
int gLevelSpeed = 300; // 第一关速度(每吃10个增加一次速度)
int gCurSpeed = 300; // 当前速度
int gMaxSpeed = 50; // 最大速度
// 函数声明
void gameInit(); // 游戏初始化
void hideCursor(); // 隐藏光标
void welcome(); // 欢迎界面
void setCursorPosition(int x, int y); // 设置光标所在位置(x,y)
void setFontColor(int color); // 设置字体颜色
void gameStart(); // 游戏开始
void drawMap(); // 画地图
void drawScore(); // 画得分
void drawTip(); // 画游戏提示
void drawSnake(); // 画蛇
void drawFood(); // 画食物
void keyPress(); // 键盘事件
void moveSnake(); // 蛇移动
void pause(); // 游戏暂停
void eatFood(); // 吃食物
int collide(); // 蛇碰撞
int gameOver(); // 游戏结束
int getTopScore(); // 读取记录
void setTopScore(int score); // 保存记录
主文件(snake.c)
#include "snake.h"
// 贪吃蛇
int main()
{
// 欢迎界面
welcome();
// 游戏初始化
gameInit();
// 游戏开始
gameStart();
return 0;
}
// 欢迎界面
void welcome()
{
// 隐藏光标
hideCursor();
// 设置窗口标题
SetConsoleTitle("贪吃蛇"); // 如果用visual studio软件则需要项目属性中的字符集(使用 Unicode 字符集 -> 使用多字节字符集)
// 设置窗口大小像素(宽100,高30)[利用dos命令]
system("mode con cols=96 lines=30");
// 设置窗口背景与文字颜色(可以在dos下用命令color -h查看)
//system("color 8F");
// 设置操作说明
setCursorPosition(40, 8);
printf("欢迎来到贪吃蛇的世界");
setCursorPosition(33, 12);
printf("提示:↑ ↓ ← → 控制贪吃蛇方向");
setCursorPosition(30, 16);
printf(" 空格暂停或继续游戏,ESC退出游戏");
setCursorPosition(41, 20);
system("pause");
system("cls");
}
// 游戏初始化
void gameInit()
{
// 随机种子
srand((unsigned int)time(NULL));
// 蛇初始化
snake.size = SNAKE_INIT_SIZE;
snake.direction = DIR_RIGHT;
snake.status = RUNNING;
// 速度初始化
gSpeed = 300;
gLevelSpeed = 300;
gCurSpeed = 300;
gMaxSpeed = 50;
// 得分初始化
gTopScore = getTopScore();
gMyScore = 0;
}
// 隐藏光标
void hideCursor()
{
// 得到标准的输入(STD_INPUT_HANDLE)、输出(STD_OUTPUT_HANDLE)或错误(STD_ERROR_HANDLE)的设备的句柄
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
// 光标信息变量(typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;WINBOOL bVisible;} CONSOLE_CURSOR_INFO)
CONSOLE_CURSOR_INFO cursorInfo;
// 获取当前光标信息
GetConsoleCursorInfo(hOut, &cursorInfo);
// 设置当前光标不可见
cursorInfo.bVisible = 0;
SetConsoleCursorInfo(hOut, &cursorInfo);
}
// 设置光标所在位置
void setCursorPosition(int x, int y)
{
// 文字在控制台的坐标结构变量
COORD pos = { x,y };
// 设置控制台光标所在位置
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
// 游戏开始
void gameStart()
{
// 画地图
drawMap();
// 画蛇
drawSnake();
// 画随机生成的食物
drawFood();
// 游戏运行中
while (snake.status == RUNNING)
{
// 键盘事件
keyPress();
// 蛇移动
moveSnake();
// 吃食物
eatFood();
// 更新分数
drawScore();
// 休眠
Sleep(gCurSpeed);
}
// 游戏结束
if (gameOver() == 1)
{
system("cls");
exit(-1);
}
else
{
gameInit();
gameStart();
}
}
// 画地图
void drawMap()
{
/*== 重点:"■"与"□"都是长=2,宽=1的长方形(可以理解为一个汉字,占2个字节),而不是正方形啊。==*/
// 清屏
system("cls");
setFontColor(2); // 绿色
// 画内部网格(30*30)
for (int i = 0; i < MAP_WIDTH; i += 2)
{
for (int j = 0; j < MAP_HEIGHT; j++)
{
setCursorPosition(i, j);
printf(GRID);
}
}
setFontColor(6); // 黄色
// 画左右墙
for (int i = 0; i < MAP_HEIGHT; i++)
{
setCursorPosition(0, i);
printf(BRICK);
setCursorPosition(MAP_WIDTH - 2, i);
printf(BRICK);
}
// 画上下墙
for (int i = 0; i < MAP_WIDTH; i += 2)
{
setCursorPosition(i, 0);
printf(BRICK);
setCursorPosition(i, MAP_HEIGHT - 1);
printf(BRICK);
}
// 画得分
drawScore();
// 画提示
drawTip();
}
// 画得分
void drawScore()
{
setFontColor(15); // 亮白色
setCursorPosition(MAP_WIDTH + 4, 2);
printf("最高得分:%5d", gTopScore);
setCursorPosition(MAP_WIDTH + 4, 4);
printf("我的得分:%5d", gMyScore);
setCursorPosition(MAP_WIDTH + 4, 6);
printf("我的速度:%5d米/秒", (2 * gSpeed - gCurSpeed));
}
// 画游戏提示
void drawTip()
{
setFontColor(9);
setCursorPosition(MAP_WIDTH + 2, 9);
printf(" 游戏提示");
setCursorPosition(MAP_WIDTH + 2, 10);
printf("┌─────────────┐");
setCursorPosition(MAP_WIDTH + 2, 11);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 12);
printf("│每个食物得分:1 分 │");
setCursorPosition(MAP_WIDTH + 2, 13);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 14);
printf("│不能穿墙也不能咬到自己哦 │");
setCursorPosition(MAP_WIDTH + 2, 15);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 16);
printf("│用↑ ↓ ← → 控制蛇移动 │");
setCursorPosition(MAP_WIDTH + 2, 17);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 18);
printf("│同一方向连续点击可以加速 │");
setCursorPosition(MAP_WIDTH + 2, 19);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 20);
printf("│按空格可以暂停或继续游戏 │");
setCursorPosition(MAP_WIDTH + 2, 21);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 22);
printf("│按ESC键可以退出游戏 │");
setCursorPosition(MAP_WIDTH + 2, 23);
printf("│ │");
setCursorPosition(MAP_WIDTH + 2, 24);
printf("└─────────────┘");
setFontColor(6);
setCursorPosition(MAP_WIDTH + 2, 26);
printf(" 作者:我是小木鱼(Lag)");
setCursorPosition(MAP_WIDTH + 2, 28);
printf(" 2023.02.15");
}
// 画蛇
void drawSnake()
{
// 画蛇头
setFontColor(14); // 淡黄色
snake.body[0].x = (MAP_WIDTH / 2);
if (snake.body[0].x % 2 != 0) { snake.body[0].x--; } // x坐标必须是偶数
snake.body[0].y = MAP_HEIGHT / 2;
setCursorPosition(snake.body[0].x, snake.body[0].y);
printf(SNAKE_HEAD);
// 画蛇身
for (int i = 1; i < SNAKE_INIT_SIZE; i++)
{
snake.body[i].x = snake.body[i - 1].x - 2;
snake.body[i].y = snake.body[i - 1].y;
setCursorPosition(snake.body[i].x, snake.body[i].y);
printf(SNAKE_BODY);
}
}
// 画食物
void drawFood()
{
/*
重点:X轴方向由于墙、网格、食物等都是长2宽1的长方体,
所以MAP_WIDTH=60即(0-59)可以划分为{(0-1),(2-3),(4-5)...,(56-57),(58-59)},
其中(0-1)是左边墙,(58-59)是右边墙,剩下{(2-3),(4-5)...,(56-57)},
所以食物的X坐标可以为2,4,6,8...,54,56这些数中的一个即可。
这些数不连续不好随机,因此改为(1,2,3,4...27.28)*2就可以了。
*/
// rand() % 10 + i => [0-10) + i
food.x = (rand() % ((MAP_WIDTH - 4) / 2) + 1) * 2; // 必须为偶数,否则蛇吃不到
food.y = rand() % (MAP_HEIGHT - 2) + 1;
// 判断是否与蛇重合
while (1)
{
int flag = 0;
for (int i = 0; i < snake.size; i++)
{
if (food.x == snake.body[i].x && food.y == snake.body[i].y)
{
flag = 1;
// 重新随机食物
food.x = (rand() % ((MAP_WIDTH - 4) / 2) + 1) * 2;
food.y = rand() % (MAP_HEIGHT - 2) + 1;
break;
}
}
if (flag == 0) { break; }
}
setCursorPosition(food.x, food.y);
setFontColor(12);
printf(FOOD);
}
// 键盘事件
void keyPress()
{
// 判断键盘
if (GetAsyncKeyState(VK_LEFT)) // 左键
{
if (snake.direction != DIR_RIGHT) // 禁止调头
{
if (snake.direction == DIR_LEFT)
{
if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; } // 方向相同,临时加速
}
else
{
snake.direction = DIR_LEFT;
if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; } // 方向不同,恢复原来速度
}
}
}
else if (GetAsyncKeyState(VK_UP)) // 上键
{
if (snake.direction != DIR_DOWN)
{
if (snake.direction == DIR_UP)
{
if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; } // 方向相同,临时加速
}
else
{
snake.direction = DIR_UP;
if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; } // 方向不同,恢复原来速度
}
}
}
else if (GetAsyncKeyState(VK_RIGHT)) // 右键
{
if (snake.direction != DIR_LEFT)
{
if (snake.direction == DIR_RIGHT)
{
if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; } // 方向相同,临时加速
}
else
{
snake.direction = DIR_RIGHT;
if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; } // 方向不同,恢复原来速度
}
}
}
else if (GetAsyncKeyState(VK_DOWN)) // 下键
{
if (snake.direction != DIR_UP)
{
if (snake.direction == DIR_DOWN)
{
if (gCurSpeed > gMaxSpeed) { gCurSpeed -= 10; } // 方向相同,临时加速
}
else
{
snake.direction = DIR_DOWN;
if (gCurSpeed != gLevelSpeed) { gCurSpeed = gLevelSpeed; } // 方向不同,恢复原来速度
}
}
}
else if (GetAsyncKeyState(VK_SPACE)) // 空格暂停
{
pause();
}
else if (GetAsyncKeyState(VK_ESCAPE)) // 退出
{
snake.status = GAMEOVER;
}
}
// 蛇移动
void moveSnake()
{
// 判断该方向是否可以移动(要发生碰撞)
if (collide() == 0) { return; }
// 擦除最后一节
setCursorPosition(snake.body[snake.size - 1].x, snake.body[snake.size - 1].y);
setFontColor(2);
printf(GRID);
// 循环替换除蛇头外的其他节
for (int i = snake.size - 1; i > 0; i--)
{
snake.body[i].x = snake.body[i - 1].x;
snake.body[i].y = snake.body[i - 1].y;
setCursorPosition(snake.body[i].x, snake.body[i].y);
setFontColor(14);
printf(SNAKE_BODY);
}
// 处理蛇头
switch (snake.direction)
{
case DIR_UP:
snake.body[0].y--;
break;
case DIR_DOWN:
snake.body[0].y++;
break;
case DIR_LEFT:
snake.body[0].x -= 2;
break;
case DIR_RIGHT:
snake.body[0].x += 2;
break;
}
setCursorPosition(snake.body[0].x, snake.body[0].y);
setFontColor(14);
printf(SNAKE_HEAD);
}
// 游戏暂停
void pause()
{
while (1)
{
Sleep(300);
if (GetAsyncKeyState(VK_SPACE)) { break; } // 再按一次空格继续游戏
}
}
// 吃食物
void eatFood()
{
if (snake.body[0].x == food.x && snake.body[0].y == food.y)
{
// 蛇长一节
if (snake.size < SNAKE_MAX_SIZE)
{
snake.body[snake.size].x = snake.body[snake.size - 1].x;
snake.body[snake.size].y = snake.body[snake.size - 1].y;
setCursorPosition(snake.body[snake.size].x, snake.body[snake.size].y);
setFontColor(14);
printf(SNAKE_BODY);
snake.size++;
}
// 记录得分
gMyScore++;
// 判断速度是否加快
if (gMyScore % 10 == 0) // 10个为一关
{
if (gLevelSpeed > gMaxSpeed)
{
gLevelSpeed -= 50;
gCurSpeed = gLevelSpeed;
}
}
// 重新生成食物
drawFood();
}
}
// 蛇碰撞
int collide()
{
int newX = snake.body[0].x;
int newY = snake.body[0].y;
switch (snake.direction)
{
case DIR_UP:
newY = snake.body[0].y - 1;
break;
case DIR_DOWN:
newY = snake.body[0].y + 1;
break;
case DIR_LEFT:
newX = snake.body[0].x - 2;
break;
case DIR_RIGHT:
newX = snake.body[0].x + 2;
break;
}
// 判断是否撞墙
if (newX < 2 || newX > MAP_WIDTH - 4 || newY < 1 || newY > MAP_HEIGHT - 2)
{
// 游戏失败
snake.status = GAMEOVER;
return 0;
}
// 判断是否撞自身
for (int i = 1; i < snake.size; i++)
{
if (newX == snake.body[i].x && newY == snake.body[i].y)
{
// 游戏失败
snake.status = GAMEOVER;
return 0;
}
}
return 1;
}
// 游戏结束
int gameOver()
{
if (snake.status == RUNNING) { return 0; }
// 记录得分
if (gMyScore > gTopScore) { setTopScore(gMyScore); }
// 是否重新开始游戏
HANDLE curHandle = FindWindow(NULL, "贪吃蛇"); // 得到当前窗口句柄
int rtn = MessageBox(curHandle, "胜败乃兵家常事,大虾要重新来过吗?", "提示", MB_YESNO | MB_ICONQUESTION);
if (rtn == IDYES)
{
return 0;
}
else
{
return 1;
}
}
// 读取记录
int getTopScore()
{
int score = 0;
// 打开文件
FILE* fp = fopen("./score.dat", "rb");
if (!fp){return 0;}
// 读取得分
fread(&score, sizeof(int), 1, fp);
// 关闭文件
fclose(fp);
return score;
}
// 保存记录
void setTopScore(int score)
{
// 打开文件
FILE* fp = fopen("./score.dat", "wb");
// 写入得分
fwrite(&score, sizeof(int), 1, fp);
// 关闭文件
fclose(fp);
}
// 设置字体颜色
void setFontColor(int color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
下载:
这个就不需要了吧,代码就这么几行而已。