c语言->贪吃蛇实战技巧结合EasyX简单实现页面管理(简单实现)

再无B~U~G 2024-07-05 10:05:03 阅读 84

✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉

🍎个人主页:再无B~U~G-CSDN博客

1. 游戏背景

贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。

在编程语⾔的教学中,我们以贪吃蛇为例,从设计到代码实现来提升学⽣的编程能⼒和逻辑能⼒。

2. 游戏效果演⽰ 

屏幕录制

3. 课程⽬标

使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇

实现基本的功能:

贪吃蛇地图绘制

蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作)

蛇撞墙死亡

蛇撞⾃⾝死亡

计算得分

蛇⾝加速、减速

暂停游戏

4. 课程定位

提⾼⼩⽐特对编程的兴趣

对C语⾔语法做⼀个基本的巩固。

对游戏开发有兴趣的同学做⼀个启发。

项⽬适合:C语⾔学完的同学,有⼀定的代码能⼒,初步接触数据结构中的链表。

5. 技术要点

C语⾔函数、枚举、结构体、动态

内存管理、预处理指令、链表、Win32 API等。

整体的框架:

 

课前准备:

调好项目适应本地化

了解EasyX的坐标体系

6. 控制台程序

6.1 控制台程序

设置如下:

6.1 相关函数的使用

地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。

当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等

<locale.h>本地化

全部来自于EasyX简单图形库

6.2pause

6.3 outtextxy

两个一个输出字符,一个输出字符串

案例:

6.4cleardevice

 6.5图片设置

相关用法:

 

 6.6rectangle

6.7setlinecolor设置线条颜色

7. 游戏实现

7.1地图坐标

我们假设实现⼀个棋盘27⾏,58列的棋盘(⾏和列可以根据⾃⼰的情况修改),再围绕地图画出墙,

如下:

7.2 蛇⾝和⻝物 

初始化状态,假设蛇的⻓度是5,

⽐如(24, 5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。

关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是1的15倍数),坐标不能和蛇的⾝体重合

7.3 数据结构设计

游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信

息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏,所以蛇节点结构如下:

要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇:

 

蛇的⽅向,可以⼀⼀列举,使⽤枚举 

游戏状态,可以⼀⼀列举,使⽤枚举 

7.4 游戏流程设计

8. 核⼼逻辑实现分析

8.1 游戏主逻辑

8.2 游戏开始前面的准备 

8.2.1 打印欢迎界⾯ 

看看效果:

8.2.2 创建地图

打印地图的关键是要算好坐标,才能在想要的位置打印墙体。

看看效果:

8.2.3 初始化蛇⾝

蛇最开始⻓度为5节,每节对应链表的⼀个节点,蛇⾝的每⼀个节点都有⾃⼰的坐标。

创建5个节点,然后将每个节点存放在链表中进⾏管理。创建完蛇⾝后,将蛇的每⼀节打印在屏幕上。

创建完蛇⾝后随便,初始化蛇的其他属性

蛇的图片是:

 8.2.4 创建第⼀个⻝物

先随机⽣成⻝物的坐标

x坐标必须是15的倍数 ⻝物的坐标不能和蛇⾝每个节点的坐标重复 创建⻝物节点,打印⻝物

创建⻝物的函数:CreateFood  

8.3 游戏运⾏过程

游戏运⾏期间,右侧打印帮助信息,提⽰玩家

根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。

如果游戏继续,就是检测按键情况,确定蛇下⼀步的⽅向,或者是否加速减速,是否暂停或者退出游戏。

确定了蛇的⽅向和速度,蛇就可以移动了。

8.3.1 KEY_PRESS

检测按键状态,我们封装了⼀个宏

#

define

KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

8.3.2 PrintHelpInfo 打印规则信息

 

8.3.3 蛇⾝移动

先创建下⼀个节点,根据移动⽅向和蛇头的坐标,蛇移动到下⼀个位置的坐标。

确定了下⼀个位置后,看下⼀个位置是否是⻝物(NextIsFood),是⻝物就做吃⻝物处理

(EatFood),如果不是⻝物则做前进⼀步的处理(NoFood)。

蛇⾝移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上⾃⼰蛇⾝(KillBySelf),从⽽影响游戏的状态。

8.3.3.1 NextIsFood  

8.3.3.2 EatFood

8.3.3.3 NoFood 

将下⼀个节点头插⼊蛇的⾝体,并将之前蛇⾝最后⼀个节点打印为空格,放弃掉蛇⾝的最后⼀个节点

8.3.3.4 KillByWall

8.3.3.5 KillBySelf

8.4 游戏结束

戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇⾝节点。

9. 参考代码

完整代码实现,分3个⽂件实现

test.cpp

<code>#include"snake.h"

void test()

{

//修改适配本地中文环境

setlocale(LC_ALL, "");

bool c = false;

char str[4] = " ";

do

{

Snake snake = { 0 };

GameStart(&snake);//游戏开始前的初始化

GameRun(&snake);//玩游戏的过程

GameEnd(&snake);//善后的工作

outtextxy(350, 500, "再来一局吗?(Yes/No):");

scanf("%s", str);

c = Determine(str);

} while (c);

}

int main()

{

//修改适配本地中文环境

setlocale(LC_ALL, "");

test();

return 0;

}

snake.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>

#include<stdlib.h>

#include<locale.h>

#include<windows.h>

#include<stdbool.h>

#include <graphics.h>

#include <conio.h>

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

//蛇默认的起始坐标

#define POS_X 300

#define POS_Y 200

//背景单位

#define SIZE_X 15

#define SIZE_Y 20

//游戏的状态

enum GAME_STATUS

{

OK = 1,//正常运行

ESC, //按了ESC键退出,正常退出

KILL_BY_WALL,//撞墙

KILL_BY_SELF //撞到自身

};

typedef struct snake {

int x;

int y;

struct snake* next;

}SnakeNode, * pSnakeNode;//结构体指针重命名

//蛇行走的方向

enum DIRECTION

{

UP = 1,

DOWN,

LEFT,

RIGHT//向右

};

//贪吃蛇应该有的属性

typedef struct Snake

{

pSnakeNode pSnake;//维护整条蛇的指针,是指向蛇头

pSnakeNode pFood;//指向食物的指针

int Score;//当前累积的分数

int FoodWeight;//一个食物的分数

int SleepTime;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢

enum GAME_STATUS status;//游戏当前的状态

enum DIRECTION dir;//蛇当前走的方向

//...

}Snake, * pSnake;

//游戏前的初始化

void GameStart(pSnake p);

//打印欢迎信息

void WelcomeToGame();

//绘制地图

void CreateMap();

//初始化蛇

void InitSnake(pSnake ps);

//打印图片

void picture(int x, int y);

//创建食物

void CreateFood(pSnake ps);

//玩游戏的过程

void GameRun(pSnake ps);

//打印帮助(规则)信息

void PrintHelpInfo();

//当前分数情况

void Score(pSnake ps);

//游戏要暂停

void pause();

//蛇走一步

void SnakeMove(pSnake ps);

//下一个坐标是不是食物

bool NextIsFood(pSnake ps, pSnakeNode pNext);

//是食物就吃掉

void EatFood(pSnake ps, pSnakeNode pNext);

//不是食物就正常一步

void NotEatFood(pSnake ps, pSnakeNode pNext);

//检测撞墙

void KillByWall(pSnake ps);

//检测撞到自己

void KillBySelf(pSnake ps);

//结束游戏后的工作

void GameEnd(pSnake ps);

//判断是否再来一局

bool Determine(char str[]);

snake.cpp

#include"snake.h"

void GameStart(pSnake ps) {

//设置控制台信息,窗口大小,窗口名

initgraph(1300, 720, EX_SHOWCONSOLE);

//打印欢迎信息

WelcomeToGame();

//绘制地图

CreateMap();

//初始化蛇

InitSnake(ps);

//创建食物

CreateFood(ps);

}

//打印欢迎信息

void WelcomeToGame() {

//欢迎信息

system("title 贪吃蛇");

outtextxy(500, 350, "欢迎来到贪吃蛇小游戏");

outtextxy(600, 400, "随意点击屏幕继续. . .");

//随意点击屏幕

system("pause");

//清空屏幕,换画面

cleardevice();

//功能介绍信息

outtextxy(400, 350, "1.用 ↑ ↓ ← → 来控制蛇的移动,F3是加速,F4是减速");

outtextxy(400, 370, "2.加速能得到更高的分数");

outtextxy(600, 400, "随意点击屏幕继续. . .");

//打印尾巴信息

system("pause");

//清空桌面

cleardevice();

}

//绘制地图

void CreateMap() {

IMAGE img1;

//玩游戏背景图

loadimage(&img1, "picture.jpg\\picture.jpg", 900, 700, true);

//从0,开始画图

putimage(0, 0, &img1);

//画线矩形

setlinecolor(GREEN);

//rectangle(0, 0, 900, 699);

//rectangle(3, 3, 897, 697);

//上

int i = 0;

for (i = 0; i <= 900; i += 15)

{

rectangle(i, 0, i + 13, 13);

}

//下

for (i = 0; i <= 900; i += 15)

{

rectangle(i, 700, i + 13, 700 + 13);

}

//左

for (i = 20; i < 700; i += 20)

{

rectangle(0, i , 0 + 13, i + 17);

}

//右

for (i = 1; i < 700; i += 20)

{

rectangle(900, i, 900 + 13, i + 17);

}

}

//初始化蛇

void InitSnake(pSnake ps) {

//创建5个节点

pSnakeNode cur = NULL;

for (int i = 0; i < 5; i++) {

cur = (pSnakeNode)malloc(sizeof(SnakeNode));

if (cur == NULL) {

perror("malloc fail!");

return;

}

cur->;x = POS_X + i * 15;

cur->y = POS_Y;

cur->next = NULL;

if (ps->pSnake == NULL) {

ps->pSnake = cur;

}

else {

cur->next = ps->pSnake;

ps->pSnake = cur;

}

}

//打印蛇的位置

cur = ps->pSnake;

while (cur) {

picture(cur->x, cur->y);

cur = cur->next;

}

//初始化贪吃蛇其他属性

ps->dir = RIGHT;//向右

ps->pFood = NULL;

ps->Score = 0;

ps->FoodWeight = 10;

ps->SleepTime = 200;

ps->status = OK;

}

//打印蛇身

void picture(int x, int y) {

IMAGE img1;

loadimage(&img1, "picture.jpg\\picture2.jpg", SIZE_X, SIZE_Y, true);

putimage(x, y, &img1);

}

//创建食物

void CreateFood(pSnake ps) {

pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));

if (pNext == NULL) {

perror("pNext malloc fail!");

return;

}

int x = 0;

int y = 0;

circulate:

do {

//避免出现在(0,0)

x = rand() % 871 + 15 ;

y = rand() % 681 + 20;

} while (x % 15 != 0 || y % 20 != 0);

pSnakeNode cur = ps->pSnake;

while (cur) {

if (cur->x == x && cur->y == y) {

goto circulate;

}

cur = cur->next;

}

pNext->x = x;

pNext->y = y;

pNext->next = NULL;

//维护食物

ps->pFood = pNext;

//打印食物

IMAGE img3;

loadimage(&img3, "picture.jpg\\R-C (1).jpg", 15, 20, true);

putimage(x, y, &img3);

}

//玩游戏的过程

void GameRun(pSnake ps) {

//打印帮助(规则)信息

PrintHelpInfo();

do {

//当前分数情况

Score(ps);

//向上

if (KEY_PRESS(VK_UP) && ps->dir != DOWN) {

ps->dir = UP;

}

//向下

else if (KEY_PRESS(VK_DOWN) && ps->dir != UP) {

ps->dir = DOWN;

}

//向左

else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT) {

ps->dir = LEFT;

}

//向右

else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT) {

ps->dir = RIGHT;

}

//暂停

else if (KEY_PRESS(VK_SPACE)) {

//游戏要暂停

pause();

}

//加速

else if (KEY_PRESS(VK_F3)) {

if (ps->FoodWeight < 16) {

ps->FoodWeight += 2;

ps->SleepTime -= 50;

}

}

//减速

else if (KEY_PRESS(VK_F4)) {

if (ps->FoodWeight > 4) {

ps->FoodWeight -= 2;

ps->SleepTime += 50;

}

}

//退出游戏

else if (KEY_PRESS(VK_ESCAPE)) {

ps->status = ESC;

}

//走一步

SnakeMove(ps);

//休眠

Sleep(ps->SleepTime);

} while (ps->status == OK);

}

//打印帮助(规则)信息

void PrintHelpInfo() {

//规则

setlinecolor(MAGENTA);

rectangle(918, 50, 1250, 180);

outtextxy(930, 60, "1.不能穿墙,不能咬到自己");

outtextxy(930, 80, "2.用 ↑.↓.←.→ 来控制蛇的移动");

outtextxy(930, 100, "3.F3是加速,F4是减速");

outtextxy(930, 120, "4.一个食物基础分10分");

outtextxy(930, 140, "5.加速一次加2分,最大加6分");

outtextxy(930, 160, "6.减速一次减2分,最大减6分");

}

//当前分数情况

void Score(pSnake ps) {

char num1[10];

char num2[10];

sprintf(num1, "%d", ps->Score);

sprintf(num2, "%d", ps->FoodWeight);

outtextxy(930, 300, "当前总积分:");

outtextxy(1020, 300, num1);

outtextxy(930, 340, "当前一个食物积分:");

outtextxy(1065, 340, num2);

}

//游戏要暂停

void pause() {

while (true) {

if (KEY_PRESS(VK_SPACE)) {

break;

}

Sleep(200);

}

}

//蛇走一步

void SnakeMove(pSnake ps) {

//生成下一个走过的节点

pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));

if (pNext == NULL) {

perror("malloc tail");

return;

}

pNext->next = NULL;

//向上

if (ps->dir == UP) {

pNext->x = ps->pSnake->x;

pNext->y = ps->pSnake->y - 20;

}

//向下

else if (ps->dir == DOWN) {

pNext->x = ps->pSnake->x;

pNext->y = ps->pSnake->y + 20;

}

//向右

else if (ps->dir == LEFT) {

pNext->x = ps->pSnake->x - 15;

pNext->y = ps->pSnake->y;

}

//向左

else if (ps->dir == RIGHT) {

pNext->x = ps->pSnake->x + 15;

pNext->y = ps->pSnake->y;

}

//下一个坐标处是否是食物

if (NextIsFood(ps, pNext))

{

//是食物就吃掉

EatFood(ps, pNext);

}

else

{

//不是食物就正常一步

NotEatFood(ps, pNext);

}

//检测撞墙

KillByWall(ps);

//检测撞到自己

KillBySelf(ps);

}

//判断下一个坐标是不是食物

bool NextIsFood(pSnake ps, pSnakeNode pNext) {

if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y) {

return true;

}

else {

return false;

}

}

//是食物就吃掉

void EatFood(pSnake ps, pSnakeNode pNext) {

//头插

pNext->next = ps->pSnake;

ps->pSnake = pNext;

//打印蛇

pSnakeNode cur = ps->pSnake;

while (cur) {

picture(cur->x, cur->y);S

cur = cur->next;

}

//改变分数

ps->Score += ps->FoodWeight;

//释放旧的食物

free(ps->pFood);

//重新生成食物

CreateFood(ps);

}

//不是食物就正常一步

void NotEatFood(pSnake ps, pSnakeNode pNext) {

//头插

pNext->next = ps->pSnake;

ps->pSnake = pNext;

pSnakeNode cur = ps->pSnake;

while (cur->next->next) {

picture(cur->x, cur->y);

cur = cur->next;

}

//把尾巴打印成背景图片

IMAGE img4;

loadimage(&img4, "picture.jpg\\picture4.jpg", SIZE_X, SIZE_Y, true);

putimage(cur->next->x, cur->next->y, &img4);

//释放尾部

free(cur->next);

cur->next = NULL;

}

//检测撞墙

void KillByWall(pSnake ps) {

if (ps->pSnake->x == 885 ||

ps->pSnake->y == 680 ||

ps->pSnake->x == 15 ||

ps->pSnake->y == 20)

{

ps->status = KILL_BY_WALL;

return;

}

}

//检测撞到自己

void KillBySelf(pSnake ps) {

pSnakeNode cur = ps->pSnake->next;

while (cur) {

if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)

{

ps->status = KILL_BY_SELF;

return;

}

cur = cur->next;

}

}

//结束游戏后的工作

void GameEnd(pSnake ps)

{

switch (ps->status)

{

case ESC:

outtextxy(330, 300, "主动退出游戏,正常退出\n");

break;

case KILL_BY_WALL:

outtextxy(330, 300, "很遗憾,撞墙了,游戏结束\n");

break;

case KILL_BY_SELF:

outtextxy(330, 300, "很遗憾,咬到自己了,游戏结束\n");

break;

}

//释放贪吃蛇的链表资源

pSnakeNode cur = ps->pSnake;

pSnakeNode del = NULL;

while (cur)

{

del = cur;

cur = cur->next;

free(del);

}

//把最后打印的食物打印成背景图片

IMAGE img4;

loadimage(&img4, "picture.jpg\\picture4.jpg", SIZE_X, SIZE_Y, true);

putimage(ps->pFood->x, ps->pFood->y, &img4);

free(ps->pFood);

ps = NULL;

}

//判断是否再来一局

bool Determine(char str[]) {

char arr[4] = "Yes";

int ret = strcmp(str, arr);

if (ret == 0) {

return true;

}

else {

return false;

}

}

好啦今天的分享就到这里了,感谢观看。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。