引言
当谈到经典的电子游戏时,贪吃蛇(Snake)无疑是首选之一。贪吃蛇游戏以简单却具有挑战性的玩法深受玩家喜爱。在游戏中,玩家需要操控一条蛇在游戏界面上不断移动,吃掉食物并尽可能长大,同时避免撞到墙壁或者自己的身体。其简单却耐玩的特点使得贪吃蛇成为了许多人童年时的美好回忆。
笔者选择了一个开源的贪吃蛇项目进行二次开发,以提升游戏的可玩性和趣味性,增加新的游戏元素,改进游戏界面设计,以期打造出更加吸引人的游戏版本。
原始代码
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
void welcome(); //欢迎界面
void border(); //创建边界
void Pos(int x, int y); //改变控制台光标位置
void getRandomPos(int *x, int *y); //获取随机坐标
void initSnake(); //蛇的初始化
void createFood(); //生成食物
int inSnake(int x, int y); //判断(x, y)是否在蛇上,1表示在,0表示不在
void snakeMove(); //蛇的移动
int getDirection(); //获取移动方向,0、1、2、3分别代表上、下、左、右
typedef struct Node{
int x;
int y;
Node* next;
}node;
//全局变量
node* head; //蛇头
node food; //食物
int reCreateFood; //判断食物是否被吃掉
int fail; //如果蛇头碰到蛇身或边界则失败
int score, add = 10; //定义分数和每个食物的分数
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0}; //(dx[0], dy[0])为(0, 1),即代表“上”
int d = -1; //表示蛇前进的方向,与上一行对应,0为上,1为下,2为左,3为右
//开局时初始化为-1,表示还未按方向键
int main(){
welcome(); //欢迎界面
border(); //创建边界
initSnake(); //蛇的初始化
createFood(); //生成食物
//fail != 0就一直进行
while(1){
if(fail) break;
//食物被吃掉需要重新生成
if(reCreateFood){
createFood();
reCreateFood = 0;
}
snakeMove(); //蛇的移动
Sleep(500); //程序暂停500毫秒,注释掉运行一下就懂了
//获取新的方向
d = getDirection();
}
//这个也是注释掉对比一下就懂了
Pos(0, 24);
printf("\n");
return 0;
}
void welcome(){
system("mode con cols=100 lines=30");
system("cls");
Pos(38,6);
printf("welcome come to SnakeGame\n");
Pos(38,8);
printf("↑↓←→control direction\n");
Pos(45,10);
printf("ESC For Exit\n");
Pos(42,12);
printf("Enter For Begin\n");
getchar();
system("cls");
}
void border(){ //创建以(0, 0), (59, 25)为顶点的矩形边界
for(int i = 0; i < 60; i ++ ){
Pos(i, 0); //上行
printf("▇");
Pos(i, 25); //下行
printf("▇");
}
for(int i = 1; i < 25; i ++ ){
Pos(0, i); //左列
printf("▇");
Pos(59, i); //右列
printf("▇");
}
}
void Pos(int x, int y){
COORD pos; //位置坐标
pos.X = x;
pos.Y = y;
HANDLE handleOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取输出句柄
SetConsoleCursorPosition(handleOutput, pos); //定位
}
void getRandomPos(int *x, int *y){
srand((unsigned int)time(NULL));
*x = rand() % 58 + 1;
*y = rand() % 24 + 1;
}
void initSnake(){
head = (node*)malloc(sizeof(node));
//保证在边界内
head->x = rand() % 58 + 1;
head->y = rand() % 24 + 1;
head->next = NULL;
Pos(head->x, head->y);
printf("*");
}
void createFood(){
int x, y;
getRandomPos(&x, &y);
if(inSnake(x, y)) createFood(); //重新生成
food.x = x;
food.y = y;
Pos(x, y);
printf("$");
}
int inSnake(int x, int y){
node* p = head->next;
while(p){ //从头遍历蛇身
if(x == p->x && y == p->y)
return 1;
p = p->next;
}
return 0;
}
void snakeMove(){
//刚开局未按方向键的情况
if(d == -1)
while(d == -1) d = getDirection();
node* newHead = (node*)malloc(sizeof(node));
newHead->x = head->x + dx[d];
newHead->y = head->y + dy[d];
newHead->next = head;
head = newHead;
node* p = head;
if(inSnake(p->x, p->y) || (p->x == 0 || p->x == 59 || p->y == 0 || p->y == 25))
fail = 1;
else if(p->x != food.x || p->y != food.y){
Pos(p->x, p->y);
printf("*");
while(p->next->next) p = p->next;
Pos(p->next->x, p->next->y);
printf(" ");
p->next = NULL;
}
else{
reCreateFood = 1;
score += add;
Pos(p->x, p->y);
printf("*");
}
Pos(61, 25);
printf("您的得分为:%d", score);
}
int getDirection(){
//不能往反方向走
if((GetAsyncKeyState(VK_UP) & 0x8000) && d != 1) d = 0;
else if((GetAsyncKeyState(VK_DOWN) & 0x8000) && d != 0) d = 1;
else if((GetAsyncKeyState(VK_LEFT) & 0x8000) && d != 3) d = 2;
else if((GetAsyncKeyState(VK_RIGHT) & 0x8000) && d != 2) d = 3;
return d;
}
游戏主界面
游戏运行界面
原程序存在的缺点
- 原程序无法保存玩家之前的积分记录,缺少游戏的挑战性及成就感;
- 游戏界面活动范围过小,游戏画面单调、缺乏设计,游戏过程中不可暂停或重新开始,导致游戏的体验感较差;
- “贪吃蛇”的移动速度不可改变,且移动方向不可随意操作,只能上下左右移动,缺乏趣味性。
改进方案
- 添加积分排行榜,即记录玩家每次的积分,以及耗时;
void ReadGrade()
{
FILE* pf = fopen("贪吃蛇最高得分记录.txt", "r"); //以只读的方式打开文件
if (pf == NULL) //打开文件失败
{
pf = fopen("贪吃蛇最高得分记录.txt", "w"); //以只写的方式打开文件
fwrite(&max, sizeof(int), 1, pf); //将max写入文件(此时max为0),即将最高得分初始化为0
}
fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头
fread(&max, sizeof(int), 1, pf); //读取文件当中的最高得分到max当中
fclose(pf); //关闭文件
pf = NULL; //文件指针及时置空
}
//更新最高分到文件
void WriteGrade()
{
FILE* pf = fopen("贪吃蛇最高得分记录.txt", "w"); //以只写的方式打开文件
if (pf == NULL) //打开文件失败
{
printf("保存最高得分记录失败\n");
exit(0);
}
fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中
fclose(pf); //关闭文件
pf = NULL; //文件指针及时置空
}
- 界面设计:
扩大“贪吃蛇”的活动范围;
void border(){
for(int i = 0; i < 80; i ++ ){ // 扩大边界为 80 * 30
Pos(i, 0);
printf("■");
Pos(i, 29);
printf("■");
}
for(int i = 1; i < 29; i ++ ){
Pos(0, i);
printf("■");
Pos(79, i);
printf("■");
}
}
美化游戏界面;
struct Snake
{
int len; //记录蛇身长度
int x; //蛇头横坐标
int y; //蛇头纵坐标
}snake;
//蛇身
struct Body
{
int x; //蛇身横坐标
int y; //蛇身纵坐标
}body[ROW*COL]; //开辟足以存储蛇身的结构体数组
int face[ROW][COL]; //标记游戏区各个位置的状态
void InitInterface()
{
color(6); //颜色设置为土黄色
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (j == 0 || j == COL - 1)
{
face[i][j] = WALL; //标记该位置为墙
CursorJump(2 * j, i);
printf("■");
}
else if (i == 0 || i == ROW - 1)
{
face[i][j] = WALL; //标记该位置为墙
printf("■");
}
else
{
face[i][j] = KONG; //标记该位置为空
}
}
}
color(7); //颜色设置为白色
CursorJump(0, ROW);
printf("当前得分:%d", grade);
CursorJump(COL, ROW);
printf("历史最高得分:%d", max);
}
- 改善“贪吃蛇”的移动问题;
改进后效果
结语
通过这次对贪吃蛇游戏的二次开发,我收获了更多宝贵的经验和教训。尽管经过一番努力和改进,程序仍存在一些缺陷和不足,但在这个过程中,不仅让我更加熟悉了项目的开发流程,也让我更加自信地面对编程中的挑战与困难。在迭代优化过程中,我逐渐提升了自己的问题分析能力、解决问题的能力以及编程实践能力。
未来,我会继续努力学习,不断提升自己的编程水平,追求更高的技术成就。编程之路漫长而又充满挑战,但正是这种挑战让我不断前行,不断进步。感谢这次作业给我带来的成长与启示!