原文链接:C语言 贪吃蛇游戏
笔者使用 C 语言实现经典贪吃蛇游戏,其中开发环境为 Windows 平台下的 VisualStudio2019
本文在 原文 的基础上将原文源码进行模块化拆分,以面向过程的自顶向下模块化思想进行编程,代码可读性高、系统健壮性强、游戏界面友好美观,功能做出适当调整并修复了一系列已知问题
#pragma once// 设置光标
void setPox(int x, int y);// 设置文本颜色
void setTextColor(unsigned short color);
#include// 设置光标
void setPox(int x, int y) {COORD pox = {x, y};HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(hOut, pox);
}// 设置文本颜色
void setTextColor(unsigned short color) {HANDLE hCon = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(hCon, color);
}
#pragma once// 游戏排行榜,读取游戏数据
void rankingList();// 保存成绩,游戏存档
void saveGrade(int score);
#define _CRT_SECURE_NO_WARNINGS#include
#include#include "model.h"
#include "utils.h"// 游戏排行榜,读取游戏数据
void rankingList() {system("cls");// 打开游戏排行榜文件FILE *fp = fopen("rank.txt", "rb");if (fp == NULL) {setPox(56, 12);printf("游戏排行榜文件不存在");setPox(0, 0);return;}rewind(fp);// 读取文件中的游戏数据,最多读取 1000 条游戏记录record gameRecord[1000];int i = 0;// feof 检查文件是否结束,遇到结束符,返回非零while (!feof(fp)) {fread(&gameRecord[i], sizeof(struct record), 1, fp);i++;}// 按游戏得分排序qsort(gameRecord, i - 1, sizeof(record), compare);// 输出得分最高的 10 次游戏记录i = i > 10 ? 10 : i;// 输出游戏排行榜信息setPox(55, 3);setTextColor(12);printf("排行榜");setPox(42, 5);setTextColor(14);printf("得分\t\t\t时间\n");setTextColor(15);int j = 0;for (; j < i - 1; j++) {setPox(43, 7 + j * 2);printf("%d\t\t", gameRecord[j].grade);printf("%d/%02d/%02d ", gameRecord[j].year + 1900, gameRecord[j].mon + 1, gameRecord[j].day);printf("%02d:%02d:%02d\n", gameRecord[j].hour, gameRecord[j].min, gameRecord[j].sec);}setPox(43, 7 + j * 2);setTextColor(1);printf("注:按任意键继续···");fclose(fp);
}// 保存成绩,游戏存档
void saveGrade(int score) {// 获取系统时间time_t timestamp;time(×tamp);struct tm *ti = localtime(×tamp);// 为当前游戏数据分配空间record *gameRecord = (record *) malloc(sizeof(record));// 保存年月日时分秒以及分数gameRecord->year = ti->tm_year;gameRecord->mon = ti->tm_mon;gameRecord->day = ti->tm_mday;gameRecord->hour = ti->tm_hour;gameRecord->min = ti->tm_min;gameRecord->sec = ti->tm_sec;gameRecord->grade = score;// 打开文件并追加写入本次游戏数据FILE *fp = fopen("rank.txt", "ab");if (fp == NULL)fp = fopen("rank.txt", "wb");fwrite(gameRecord, sizeof(record), 1, fp);fclose(fp);free(gameRecord);
}
#pragma once// 蛇
typedef struct snake {int x;int y;struct snake *next;
} snake;// 游戏记录
typedef struct record {int grade;int year;int mon;int day;int hour;int min;int sec;
} record;// 游戏数据
typedef struct data {// 分数int score;// 速度,值越小蛇的速度越快int speed;// 速度等级,值越大小蛇速度越快int speedLevel;// 每个食物分数,加速一次 foodFraction 翻倍,减速一次 foodFraction 减半int foodFraction;// 速度变化前吃到的食物数,用于判断是否开启自动加速int eatenFoods;
} data;
#pragma once#include "model.h"// 初始化蛇
snake *initSnake();// 随机食物
snake *randomFood(snake *q);// 打印蛇身
void snakeBody(snake *p, int speed);// 边界碰撞判定
int collision(snake *q);// 释放蛇身
void destroy(snake *p);// 游戏暂停
void suspendGame(snake *q);// 蛇身传递
void snakeInherit(snake *p);// 吃到食物
snake *foodInMouth(snake *snakeHead, snake *food, data *curGameData);// 自动加速
void autoAccelerate(data *curGameData, int isAutoAccelerate);// 开始游戏
void startGame();
#define _CRT_SECURE_NO_WARNINGS#include#include "model.h"// 初始化蛇
snake *initSnake() {snake *q = (snake *) malloc(sizeof(snake));q->next = NULL;for (int i = 6; i < 19; i = i + 2) {snake *tmp = (snake *) malloc(sizeof(snake));tmp->x = i;tmp->y = 5;tmp->next = q->next;q->next = tmp;}return q;
}// 随机食物
snake *randomFood(snake *q) {snake *p, *k;k = (snake *) malloc(sizeof(snake));k->next = NULL;gotoHere:p = q->next;srand((unsigned) time(NULL));// 确保食物显示在游戏地图范围内while ((k->x = rand() % 57 + 4) % 2 != 0) { ;}k->y = rand() % 24 + 3;while (p != NULL) {// 如果新食物与蛇身重合,则重新生成if ((k->x == p->x && k->y == p->y))goto gotoHere;p = p->next;}setTextColor(12);setPox(k->x, k->y);printf("●");return k;
}// 打印蛇身
void snakeBody(snake *p, int speed) {snake *r, *k = p->next;setTextColor(14);while (p->next != NULL) {r = p->next;p = r;setPox(r->x, r->y);printf("★");}if (k->x != p->x || k->y != p->y) {// 覆盖尾迹setPox(p->x, p->y);setTextColor(3);printf("■");}setPox(0, 0);Sleep(speed);
}// 边界碰撞判定
int collision(snake *q) {snake *p = q->next, *r = p->next;// 撞墙if (p->x == 2 || p->x == 62 || p->y == 1 || p->y == 27) {return 1;}while (r->next != NULL) {// 咬到自己if (p->x == r->x && p->y == r->y)return 1;r = r->next;}return 0;
}// 释放蛇身
void destroy(snake *p) {snake *q = p, *r;while (q->next != NULL) {r = q;q = q->next;free(r);}free(q);
}// 游戏暂停
void suspendGame(snake *q) {setPox(0, 0);while (1) {// kbhit函数,非阻塞地响应键盘输入事件if (kbhit() && getch() == ' ')return;}
}// 蛇身传递
void snakeInherit(snake *p) {// p 为第一个结点,即蛇首snake *r = p->next;if (r->next != NULL)snakeInherit(r);// 把前一个结点的坐标传递给后一个结点(跟随)r->x = p->x;r->y = p->y;
}// 吃到食物
snake *foodInMouth(snake *snakeHead, snake *food, data *curGameData) {// 蛇身长度加 1food->next = snakeHead->next;snakeHead->next = food;// 新的随机食物food = randomFood(snakeHead);// 更新分数curGameData->score += curGameData->foodFraction;scoreHint(curGameData->score);// 吃到的食物数加 1curGameData->eatenFoods++;return food;
}// 自动加速
void autoAccelerate(data *curGameData, int isAutoAccelerate) {if (curGameData->eatenFoods % 3 == 0 && isAutoAccelerate == 1) {// 速度减半、速度等级增一、每个食物分数翻倍curGameData->speed /= 2;curGameData->speedLevel++;curGameData->foodFraction *= 2;curGameData->eatenFoods = 0;speedHint(curGameData->speedLevel);}
}// 开始游戏
void startGame() {// 初始化本次游戏数据data *curGameData = (data *) malloc(sizeof(data));curGameData->score = 0;curGameData->speed = 300;curGameData->speedLevel = 1;curGameData->foodFraction = 2;curGameData->eatenFoods = 0;// 是否开启自动加速:[0]不开启 [1]开启int isAutoAccelerate = 1;system("cls");// 游戏地图gameMap();// 初始速度展示speedHint(curGameData->speedLevel);// 初始分数展示scoreHint(curGameData->score);// 初始化蛇,初始长度为 6 个节点snake *q = initSnake();// 初始化随机食物snake *food = randomFood(q);// 当前敲下的按键,默认为 d 即蛇往右移动char hitKey = 'd';// 上一次敲下的按键char preKey = 'd';while (1) {// 打印蛇身snakeBody(q, curGameData->speed);// 撞墙或咬到自己,游戏结束if (collision(q)) {// 游戏存档saveGrade(curGameData->score);// 销毁蛇身结点,释放存储空间destroy(q);// 结束游戏gameOver(curGameData->score);break;}// 键盘监听,kbhit函数,非阻塞地响应键盘输入事件if (kbhit()) {hitKey = getch();}// 敲击空格,暂停游戏if (hitKey == ' ') {suspendGame(q);// 恢复上一次的操作,继续游戏后按原方向爬行hitKey = preKey;continue;}// 兼容大写字母hitKey = hitKey < 91 ? hitKey + 32 : hitKey;// 蛇首撞击蛇身,游戏结束(上至下、下至上、左至右、右至左)if ((hitKey == 'd' && preKey == 'a') || (hitKey == 's' && preKey == 'w') || (hitKey == 'a' && preKey == 'd') ||(hitKey == 'w' && preKey == 's')) {saveGrade(curGameData->score);destroy(q);gameOver(curGameData->score);break;}// 按 q 加速if (hitKey == 'q') {// 速度减半、速度等级增一、每个食物分数翻倍、不再自动加速curGameData->speed /= 2;curGameData->speedLevel++;curGameData->foodFraction *= 2;curGameData->eatenFoods = 0;isAutoAccelerate = 0;speedHint(curGameData->speedLevel);// 恢复上一次的操作,加速后按原方向爬行hitKey = preKey;continue;}// 按 e 减速if (hitKey == 'e') {// 速度翻倍、速度等级减一、每个食物分数减半、不再自动加速curGameData->speed *= 2;curGameData->speedLevel--;curGameData->foodFraction /= 2;curGameData->eatenFoods = 0;isAutoAccelerate = 0;speedHint(curGameData->speedLevel);// 恢复上一次的操作,减速后按原方向爬行hitKey = preKey;continue;}// 上if (hitKey == 'w') {// 吃到食物,蛇身长度加 1,创建新食物,更新分数if (q->next->x == food->x && q->next->y - 1 == food->y) {food = foodInMouth(q, food, curGameData);autoAccelerate(curGameData, isAutoAccelerate);} else {// 未吃到食物,传递上一次的舍身snakeInherit(q->next);q->next->y -= 1;}}// 下if (hitKey == 's') {if (q->next->x == food->x && q->next->y + 1 == food->y) {food = foodInMouth(q, food, curGameData);autoAccelerate(curGameData, isAutoAccelerate);} else {snakeInherit(q->next);q->next->y += 1;}}// 左if (hitKey == 'a') {if (q->next->x - 2 == food->x && q->next->y == food->y) {food = foodInMouth(q, food, curGameData);autoAccelerate(curGameData, isAutoAccelerate);} else {snakeInherit(q->next);q->next->x -= 2;}}// 右if (hitKey == 'd') {if (q->next->x + 2 == food->x && q->next->y == food->y) {food = foodInMouth(q, food, curGameData);autoAccelerate(curGameData, isAutoAccelerate);} else {snakeInherit(q->next);q->next->x += 2;}}// 记录上一次的操作preKey = hitKey;}
}
#pragma once// 界面边框
void frame(int type);// 欢迎页
void welcomePage();// 游戏规则
void gameRules();// 游戏结束
void gameOver(int score);// 游戏地图
void gameMap();// 速度提示
void speedHint(int speedLevel);// 输出分数
void scoreHint(int score);
// 界面边框
void frame(int type) {// 上边框setPox(17, 5);setTextColor(11);printf("⊙--------------------------");setTextColor(14);printf("oOOo");setTextColor(11);printf("----------");setTextColor(14);printf("(_)");setTextColor(11);printf("----------");setTextColor(14);printf("oOOo");setTextColor(11);printf("--------------------------⊙");// 左右竖边框for (int i = 6; i <= 19; i++) {setPox(17, i);printf("§");setPox(102, i);printf("§");}// 下边框setPox(17, 20);printf("⊙---------------------------------------");setTextColor(14);printf("☆☆☆");setTextColor(11);printf("--------------------------------------⊙");setPox(53, 23);printf("∵ˇˇˇˇˇˇˇ∵");setPox(53, 26);printf("∴^^^^^^^∴");// 启动页面字符图案if (type == 0) {setPox(57, 2);setTextColor(6);printf("\\\\\\|///");setPox(54, 3);printf("\\\\");setPox(58, 3);setTextColor(15);printf(".-.-");setPox(65, 3);setTextColor(6);printf("//");setPox(55, 4);setTextColor(14);printf("(");setPox(58, 4);setTextColor(15);printf(".@.@");setPox(65, 4);setTextColor(14);printf(")");} else {// 游戏结束字符图案setPox(57, 1);setTextColor(6);printf("∧ ∧");setPox(55, 2);printf(" / \\ / \\");setPox(54, 3);printf("( ︹ ˇ ︹ )");setPox(54, 4);printf("く ");setTextColor(15);printf("⊙ ⊙");setTextColor(14);printf(" / ");setPox(55, 5);printf("く い /");setPox(57, 6);printf("く 々 √");setPox(60, 7);printf("ˇ");}
}// 欢迎页
void welcomePage() {system("cls");setPox(53, 8);setTextColor(14);printf("贪 吃 蛇 大 作 战");setPox(26, 14);printf("1.开始游戏");setPox(46, 14);printf("2.游戏规则");setPox(66, 14);printf("3.得分排行");setPox(86, 14);printf("4.退出游戏");// 绘制界面边框frame(0);setPox(56, 24);setTextColor(14);printf("前往:");
}// 游戏规则
void gameRules() {system("cls");setPox(55, 5);printf("游戏规则");setTextColor(12);setPox(34, 8);printf("1. 'W''S''A''D' 控制上、下、左、右方向,空格键控制游戏暂停与继续");setPox(34, 10);printf("2. 按 Q 键可加速,按 E 键可减速,速度越快,单个食物分数越高");setPox(34, 12);printf("3. 在没有人为加速或减速的情况下,每吃到 3 个食物将会进行一次自动加速");setPox(34, 14);printf("4. 初始化每个食物 2 分,每加速一次单个食物分数翻倍,减速一次分数减半");setPox(34, 16);printf("5. 初始化小蛇长度为 6,每吃到一个食物长度就会加 1,分数相应地增加");setPox(34, 18);printf("6. 小蛇的初始速度为 300MS/格,速度等级为 1,等级值越高速度越快");setPox(34, 20);printf("7. 当蛇首撞墙或咬到蛇身时游戏结束");setPox(34, 22);printf("8. 以上按键皆不区分大小写");setPox(34, 24);setTextColor(1);printf("注:按任意键继续···");
}// 游戏结束
void gameOver(int score) {int choice = 1;gotoHere:system("cls");setTextColor(12);setPox(45, 8);printf("游戏结束,蛇首撞墙或咬到蛇身\n");setPox(58, 12);setTextColor(14);printf("得分:%d", score);setTextColor(14);setPox(40, 16);printf("1.重新开始");setPox(56, 16);printf("2.返回主页");setPox(70, 16);printf("3.退出游戏");// 绘制界面边框frame(1);setPox(56, 24);printf("前往:");scanf("%d", &choice);switch (choice) {case 1:system("cls");startGame();break;case 2:return;case 3:system("cls");setPox(50, 15);printf("游戏结束,欢迎再次登录");setPox(0, 0);exit(0);default:setPox(0, 0);printf("您的输入有误,请重新输入!按任意键继续···");getch();goto gotoHere;}
}// 游戏地图
void gameMap() {setTextColor(11);// 游戏界面边框:菱形边界,方块背景for (int i = 2; i < 27; i++) {// 左边框setPox(2, i);printf("◆");// 方块背景setTextColor(3);for (int j = 0; j < 29; j++)printf("■");// 右边框setTextColor(11);printf("◆");}// 上边框setPox(2, 1);for (int i = 0; i < 31; i++) {printf("◆");}// 下边框setPox(2, 27);for (int i = 0; i < 31; i++) {printf("◆");}// 游戏界面与游戏提示间隔框setTextColor(10);for (int i = 0; i < 30; i++) {setPox(70, i);printf("§");}// 得分提示边框setTextColor(6);setPox(82, 4);printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");setPox(82, 6);printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");setPox(82, 5);printf("φ");setPox(110, 5);printf("φ");// 操作提示setTextColor(12);setPox(94, 9);printf("操作提示");setPox(95, 11);printf("上:W");setPox(95, 12);printf("下:S");setPox(95, 13);printf("左:A");setPox(95, 14);printf("右:D");setPox(90, 16);printf("暂停/继续: 空格");// 速度提示框setTextColor(14);setPox(82, 19);printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");setPox(82, 21);printf("∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞");setPox(81, 20);printf("φ");setPox(111, 20);printf("φ");setPox(81, 23);printf("注:按 Q 键可加速,按 E 键可减速");
}// 速度提示
void speedHint(int speedLevel) {setTextColor(11);setPox(97, 20);printf("%d", speedLevel);
}// 输出分数
void scoreHint(int score) {setPox(97, 5);setTextColor(13);printf("%d", score);
}
#pragma once// 比较两个元素
int compare(const void *a, const void *b);
// 比较两个元素
int compare(const void *a, const void *b) {return (*(int *) b - *(int *) a);
}
#define _CRT_SECURE_NO_WARNINGS#include#include "model.h"
#include "cmd.h"
#include "utils.h"
#include "ui.h"
#include "io.h"
#include "service.h"/** 程序入口*/
int main() {int choice;gotoHere:// 启动页面welcomePage();scanf("%d", &choice);switch (choice) {case 1:system("cls");startGame();goto gotoHere;case 2:gameRules();getch();goto gotoHere;case 3:rankingList();getch();goto gotoHere;case 4:system("cls");setPox(50, 15);printf("游戏结束,欢迎再次登录");setPox(0, 0);exit(0);default:setPox(0, 0);printf("您的输入有误,请重新输入!按任意键继续···");getch();goto gotoHere;}return 0;
}
上一篇:【网络安全】提防黑客来“敲门”
下一篇:ReentrantLock详解