一.编程环境:
二.编程思路:
-
打开一个图形窗口。
-
定义一条蛇。
-
定义(生成)一只小鸡
-
定义蛇的初始化方向
-
do{
获取键盘输入按键。
把蛇显示在图形窗口中。
把小鸡显示在图形窗口中。
移动蛇(其中包括:1.把原来的蛇在图形窗口中隐藏 2.移动蛇3.移动后看有没有撞墙或咬到自己4.看有没有吃到鸡(若吃到鸡,把原来的鸡隐藏,生成新的鸡的坐标,把蛇的长度加长))。}while(输入的按键不是ESC);
(while循环结束,游戏结束) -
当蛇的长度小于游戏目标时,在窗口上显示“You lost!” 否则显示“Congratulations! you won!”
关闭程序
三.数据结构的选取{逻辑结构,存储结构,数据的运算}:
- 程序设中的蛇选取什么样的数据结构。对于数据结构的选取主要看对数据进行什么样的操作。
- 蛇:在以上的编程思路中,蛇有两个动作,一是移动,二是增长。蛇的移动即蛇各部分坐标的改变,蛇的增长即链表或者数组中元素的增多。
- 蛇:对于逻辑结构需选举用线性结构,对于存储结构,可选顺序存储结构和链式存储结构。具体选择哪一种先分析蛇的增长。
- 假设蛇增长的一段加在蛇末尾,但蛇末尾有三个方向如下图1.0,具体加在什么坐标上害的判断最后一块的移动方向比较麻烦,所以加在末尾的方法舍去。
图1.0:
- 假设加在蛇中间,中间有这么多快加在哪两块中间也不好判断,所以舍去。
- 假设加在头的位置,这就好实现,只需要在头后面加一块,让这块继承头的坐标,同时更根据蛇移动方向,让蛇头沿着移动方向移动一块的距离即可,即更新头的坐标,如下图2.0。
- 图2.0:
- 既然要在顶头插入元素,为了避免大量元素移动应该选择链式存储结构。
- 链式存储结构有单链表,单循环链表,双链表,双循环链表等,因为蛇的移动方式:每一块移动后的坐标为其前驱的坐标,头结点则沿着移动方向移动一块的距离。根据移动方式需要找前驱结点,所以采用双向链表,且为带头结点的双向链表。又因为更改每一块的坐标时需要从蛇尾开始改,所以还需一个尾指针,故最终选取带头结点和尾指针的双链表。
四.编程中部分关键代码实现细节:
1.蛇的数据类型的设计:
typedef struct body_ {
int x;//蛇身体所在横坐标
int y;//蛇身体所在纵坐标
struct body_* prex;//前驱指针
struct body_* next;//后继指针
}*body, BODY;
2.小鸡的数据类型的设计:
typedef struct Chicken_ {
//鸡所在的横纵坐标
int x;
int y;
}*chicken,CHICKEN;
3.定义蛇:
body rear = NULL;//蛇尾指针
//生成蛇头
BODY Head;
Head.next = NULL;
Head.prex = NULL;
Head.x = 400;//蛇头的x,y坐标
Head.y = 400;
//蛇的长度
int Snake_len = 1;
4.定义小鸡:
CHICKEN Chi;
Chi.x = 20 + rand() % (800 - 20 + 1);//随机生成小鸡坐标 (20~800间)
Chi.y = 20 + rand() %(800 - 20 + 1);
5.图形窗口建立:
initgraph(820, 920);//打开一个长820像素,宽920像素的窗口
//该窗口大小的设计得先经过设计如下图3.0,黑色区域为蛇和鸡出现的区域,外面为边框
图3.0:
6.画地图蓝色边框(该边框算法见杭电oj 2052题:Picture)
void Creat_map()
{
//画边框
setfillcolor(BLUE); //选择填充颜色
for (int i = 0; i <= 820; i = i + size)
{
for (int j = 0; j <= 820; j = j + size)
{
if (i == 0 || i == 820)
{ //bar3d(int left, int top, int right, int bottom, int depth, int topflag, PIMAGE pimg = NULL); // 画有边框三维填充矩形
fillrectangle(i, j, i + size, j + size);
}
else
{
if (j == 0 || j == 820)
{
fillrectangle(i, j, i + size, j + size);
}
}
}
}
}
7.显示蛇:
void Show_Snake(body Head)
{
body p = Head;
setfillcolor(RED); //选择蛇的填充颜色
while (p)
{
fillrectangle(p->x, p->y, p->x + size, p->y + size);
p = p->next;
}
}
//把蛇的每一个节点显示出来,显示为一个正方形,蛇的节点坐标为正方形的左上角坐标,正方形左下角坐标等于左上角分别加上宽度
8.显示小鸡:
void Show_Chicken(chicken Chi)
{
setfillcolor(YELLOW); //选择鸡的填充颜色
fillcircle(Chi->x+size/2, Chi->y+size/2 ,size/2);
}
//和显示蛇的方式差不多,这里显示的是以鸡的坐标为正方形的左上角坐标且宽度为size的正方形的内切圆
9.蛇的移动:
void Snake_move(int *button, int *len,body Head, body* rear,chicken Chi)
{
//把原来的蛇隐藏
Hide_Snake(Head);
//移动蛇身
body p= (*rear);
while (p&&p!=Head)
{
p->x = p->prex->x;
p->y = p->prex->y;
p = p->prex;
}
//移动蛇头
switch (*button)
{
case 72:Head->y = Head->y - 20; break; //向上走一步
case 80:Head->y = Head->y + 20; break; //向下走一步
case 75:Head->x = Head->x - 20; break; //向左走一步
case 77:Head->x = Head->x + 20; break; //向右走一步
default:break;
}
//判断是不是撞到墙或者咬到自己
if (Ifdead(Head))
{
(*button) = 27;//button ==27时结束do while循环 游戏结束
}
//看有没有吃到鸡
if (Head->x+size/2>= Chi->x&&Head->x+size/2<Chi->x+size && Head->y+size/2 >= Chi->y&&Head->y+size/2<Chi->y+size)
{
//把原先的鸡隐藏
Hide_Chicjen(Chi);
//更新鸡的位置
Rnew_Chi(Head, Chi);
//蛇的身子变长
Snake_Grow(Head, len, rear, button);
}
}
/*判断有没有吃到鸡的方法:
获得蛇头所在矩形(正方形)区域的中心点坐标:A:(Head->x+size/2,Head->y+size/2)
获得鸡所在圆形区域的外切正方形区域的范围:B:{横坐标范围:[Chi->x,Chi->x+size];纵坐标范围[Chi->y,Chi->y+size]}
当点A在区域B内则吃到鸡*/
五.参考代码:
#include<graphics.h>
#include <iostream>
#include<conio.h>
#include<Windows.h>
#include<stdio.h>
#include <cstdlib>
#include<malloc.h>
int speed = 150;//蛇的速度 数值越大越慢
int goal = 50;//达到goal的长度即获胜
#define size 20 //边框填充正方形的宽度
typedef struct body_ {
int x;//蛇身体所在横坐标
int y;//蛇身体所在纵坐标
struct body_* prex;//前驱指针
struct body_* next;//后继指针
}*body, BODY;
typedef struct Chicken_ {
//鸡所在的横纵坐标
int x;
int y;
}*chicken,CHICKEN;
void Creat_map();//创建地图边框
void Snake_move(int *button,int *len,body Head, body *rear,chicken Chi);//移动蛇
void Snake_Grow(body Head,int *len,body *rear,int *button);//蛇变长
void Show_Snake(body Head);//展示蛇
void Hide_Snake(body Head);//隐藏蛇
void Show_Chicken(chicken Chi);//显示小鸡
void Hide_Chicjen(chicken Chi);//隐藏小鸡
int Ifdead(body Head);//判断蛇是否活
void Rnew_Chi(body Head,chicken Chi);//更新鸡的位置
int main()
{
initgraph(840, 920);//打开一个长820像素,宽920像素的窗口
Creat_map();//生成棋盘
body rear = NULL;//蛇尾指针
//生成蛇头
BODY Head;
Head.next = NULL;
Head.prex = NULL;
Head.x = 400;//蛇头的x,y坐标
Head.y = 400;
//蛇的长度
int Snake_len = 1;
//生成小鸡
CHICKEN Chi;
Chi.x = 20 + rand() % (800 - 20 + 1);//随机生成小鸡坐标 (20~800间)
Chi.y = 20 + rand() %(800 - 20 + 1);
int button=72;//开始时默认蛇向上走
do {
//cleardevice();
while(_kbhit())//检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。
{
button = _getch();
}
//展示蛇
Show_Snake(&Head);
//展示小鸡
Show_Chicken(&Chi);
//移动蛇
Sleep(speed);
Snake_move(&button,&Snake_len, &Head,&rear,&Chi);
} while (button != 27);//esc 的ascii值时27
if (Snake_len < goal)
{
TCHAR Str[] = _T("You lost!"); //在窗口500 600 的地方显示you lost
outtextxy(500, 600, Str);
}
else
{
TCHAR Str[] = _T("Congratulations! you won!"); //在窗口500 600 的地方显示you lost
outtextxy(500, 600, Str);
}
Sleep(2000);
}
/***************************************************************/
void Creat_map()
{
//画边框
setfillcolor(BLUE); //选择填充颜色
for (int i = 0; i <= 820; i = i + size)
{
for (int j = 0; j <= 820; j = j + size)
{
if (i == 0 || i == 820)
{ //bar3d(int left, int top, int right, int bottom, int depth, int topflag, PIMAGE pimg = NULL); // 画有边框三维填充矩形
fillrectangle(i, j, i + size, j + size);
}
else
{
if (j == 0 || j == 820)
{
fillrectangle(i, j, i + size, j + size);
}
}
}
}
}
/***************************************************************/
void Show_Snake(body Head)
{
body p = Head;
setfillcolor(RED); //选择蛇的填充颜色
while (p)
{
fillrectangle(p->x, p->y, p->x + size, p->y + size);
p = p->next;
}
}
/***************************************************************/
void Hide_Snake(body Head)
{
body p = Head;
setfillcolor(BLACK); //选择蛇的填充颜色
while (p)
{
bar(p->x, p->y, p->x + size, p->y + size); //无边框矩形
p = p->next;
}
}
/***************************************************************/
void Show_Chicken(chicken Chi)
{
setfillcolor(YELLOW); //选择鸡的填充颜色
fillcircle(Chi->x+size/2, Chi->y+size/2 ,size/2);
}
/***************************************************************/
void Hide_Chicjen(chicken Chi)
{
setfillcolor(BLACK); //选择鸡的填充颜色
fillcircle(Chi->x + size / 2, Chi->y + size / 2, size / 2);
}
/***************************************************************/
void Snake_move(int *button, int *len,body Head, body* rear,chicken Chi)
{
//把原来的蛇隐藏
Hide_Snake(Head);
//移动蛇身
body p= (*rear);
while (p&&p!=Head)
{
p->x = p->prex->x;
p->y = p->prex->y;
p = p->prex;
}
//移动蛇头
switch (*button)
{
case 72:Head->y = Head->y - 20; break; //向上走一步
case 80:Head->y = Head->y + 20; break; //向下走一步
case 75:Head->x = Head->x - 20; break; //向左走一步
case 77:Head->x = Head->x + 20; break; //向右走一步
default:break;
}
//判断是不是撞到墙或者咬到自己
if (Ifdead(Head))
{
(*button) = 27;//button ==27时结束do while循环 游戏结束
}
//看有没有吃到鸡
if (Head->x+size/2>= Chi->x&&Head->x+size/2<Chi->x+size && Head->y+size/2 >= Chi->y&&Head->y+size/2<Chi->y+size)
{
//把原先的鸡隐藏
Hide_Chicjen(Chi);
//更新鸡的位置
Rnew_Chi(Head, Chi);
//蛇的身子变长
Snake_Grow(Head, len, rear, button);
}
}
/***************************************************************/
int Ifdead(body Head)
{
int term_h = 0;//表示蛇没撞到墙
int term_b = 0;//表示蛇没咬到自己
//判断是否撞到墙
if (Head->x < 20 || Head->x>800 || Head->y < 20 || Head->y>800)
term_h = 1;//表示撞到墙
body p = Head->next;
while (p)
{
if (Head->x == p->x && Head->y == p->y)
{
term_b = 1;//表示咬到自己
break;
}
p = p->next;
}
return term_h + term_b;
}
/***************************************************************/
void Rnew_Chi(body Head, chicken Chi)//更新鸡的位置
{
//看鸡出现的位置是不是在蛇上
int temp = 0;//表示鸡出现的位置在蛇上
do {
//生成鸡的位置
Chi->x = 20 + rand() % (800 - 20 + 1);
Chi->y = 20 + rand() % (800 - 20 + 1);
body p = Head;
while (p)
{
if (p->x == Chi->x && p->y == Chi->y)
{
temp = 1; //在蛇的上则重新生成鸡
break;
}
p = p->next;
}
} while (temp == 1);
}
/***************************************************************/
void Snake_Grow(body Head, int* len, body* rear,int *button)
{
(*len)++;
body q = (body)malloc(sizeof(BODY));
//前插法
q->next = Head->next;
if(q->next!=NULL)
{
q->next->prex = q;
}
Head->next = q;
q->prex = Head;
//更新插入节点坐标
q->x = Head->x;
q->y = Head->y;
switch (*button)
{
case 72:Head->y = Head->y - 20; break; //向上走一步
case 80:Head->y = Head->y + 20; break; //向下走一步
case 75:Head->x = Head->x - 20; break; //向左走一步
case 77:Head->x = Head->x + 20; break; //向右走一步
default:break;
}
if ((*rear) == NULL)//的到尾指针的值
*rear = q;
if ((*len) > goal) //长度达到goal时 游戏结束取得胜利
(*button) = 27;
}
六.程序运行截图:
七.注意事项:
- 在建立工程时应建立c++工程而非c,否则无法使用graphics。但程序大部分是c语言写法,这个不要紧,c++继承了c
八.欢迎大家关注我的博客
- 喜欢c语言的同学可以关注 Manchester(https://www.dotcpp.com/home/wenyajie)