版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/leolinsheng/article/details/22899911
贪吃蛇
用C++和少量win32 Api实现一个经典小游戏:贪吃蛇,主要是为了学习之用。
一.首先构思贪吃蛇类应该具有哪些属性和功能,并且需要用什么样的数据结构来存储数据。我们把贪吃蛇和它将要吃的贪物看成一个整体来考虑。
属性有:
1.贪吃蛇的蛇头Head
2.贪吃蛇的身长Length
3.是否存在食物isExistFood
4.食物所在的坐标(Food_x,Food_y)
5.蛇头的方向direction ={(-1,0)(1,0)(0,1)(0,-1)}
6.游戏的速度Speed
应该具有的功能:
1.根据方向进行挪动Move()
2.重绘贪吃蛇redraw(),因为需要不停地移动,由此需要经常重绘
3.判断游戏是否结束,isGameOver()程序终止条件
扫描二维码关注公众号,回复:
4708398 查看本文章
4.随机在地图中生成食物createfood()
5.游戏进程函数onGame()
6.初始游戏界面Init()
7.删除蛇身结点函数,避免内存泄露。del()
因此,贪吃蛇类的大体框架就可以出来了。
class Snake
{
public:
node *head; //蛇头指针
int Length; //蛇长
int level; //速度(等级)
bool isFoodExist;
int Food_x,Food_y; //食物存在标记
int dir[2]; //蛇头方向
public:
void del(); //删除结点,避免内存泄露
void Init(); //初始化界面
void ReDraw(); //重绘蛇身函数
void CreateFood(); //随机生成食物
bool isGameOver(); //判断游戏是否结束
void onGame(); //游戏进程函数
void Move(); //移动
};
我们在初始化的时候,贪吃蛇的身长应该有三个结点,因此可以添加
Snake():Length(3),isFoodExist(false),level(1) //构造函数
{
dir[0] = 1,dir[1] = 0;
head = new node(4,2); //申请起始蛇身3节
node *temp1 = new node(3,2);
node *temp2 = new node(2,2);
head->next = temp1;
temp1->next = temp2;
temp2->next = NULL;
}
接下来就实现各个部分的功能代码:
最主要的部分是游戏进程函数onGame(),它的主要功能有
1.贪吃蛇的移动,Move
2.贪吃蛇的重绘
3.处理上下左右的方向
4.处理吃到食物和不吃到食物的处理方法。吃到食物,则置食物标示为false,如果没吃到食物,则删除蛇尾结点。
完整代码
#include <iostream>
#include <windows.h>
#include <conio.h>
#include <time.h>
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
using namespace std;
#define RIGHT 40
#define LEFT 1
#define TOP 1
#define BOTTOM 25
struct node //蛇身(链表结构)
{
int x,y; //蛇身结点坐标
node *next; //下一个结点
node(){}
node(int _x,int _y):x(_x),y(_y),next(NULL){}
node(const node&){}
~node(){}
};
void SetPos(int i,int j) //设定光标位置,win32 API
{
COORD pos = {i-1,j-1}; //坐标变量
HANDLE Out = GetStdHandle(STD_OUTPUT_HANDLE); //获取输出句柄
SetConsoleCursorPosition(Out,pos); //设定控制台光标位置
}
class Snake
{
public:
node *head; //蛇头指针
int Length; //蛇长
int level; //速度(等级)
bool isFoodExist;
int Food_x,Food_y; //食物存在标记
int dir[2]; //蛇头方向
public:
Snake():Length(3),isFoodExist(false),level(1) //构造函数
{
dir[0] = 1,dir[1] = 0;
head = new node(4,2); //申请起始蛇身3节
node *temp1 = new node(3,2);
node *temp2 = new node(2,2);
head->next = temp1;
temp1->next = temp2;
temp2->next = NULL;
}
void del(); //删除结点,避免内存泄露
void Init(); //初始化界面
void ReDraw(); //重绘蛇身函数
void CreateFood(); //随机生成食物
bool isGameOver(); //判断游戏是否结束
void onGame(); //游戏进程函数
void Move(); //移动
};
void Snake::del()
{
node *p = head;
while (p != NULL)
{
p = p->next;
delete head;
head = p;
}
}
void Snake::Init() //初始化界面
{
int i;
for (i=1; i <= RIGHT; ++i)
{
SetPos(i,1);
cout << "-";
SetPos(i,25);
cout << "-";
}
for (i=2; i <= BOTTOM; ++i)
{
SetPos(1,i);
cout << "|";
SetPos(RIGHT,i);
cout << "|";
}
SetPos(54,3);
cout << "贪吃蛇";
SetPos(54,5);
cout << "长度:" << Length;
SetPos(54,7);
cout << "等级:" << level;
}
void Snake::CreateFood() //随机出食物的坐标并判断坐标是否合法
{
node *p = NULL;
srand((unsigned int)time(0)); //用系统时间来做随机数种子
while (true)
{
Food_x = rand() % 38 + 2; //随机出食物的坐标
Food_y = rand() % 23 + 2;
p = head;
while (p!=NULL) //判断食物是否产生在蛇体内
{
if (p->x==Food_x && p->y==Food_y) //有冲突
break;
p = p->next;
}
if (p==NULL) //所有结点都不冲突。生成成功
break;
}
}
bool Snake::isGameOver()
{
if (head->x >= RIGHT || head->x <=LEFT ||
head->y <= TOP || head->y >= BOTTOM) //是否撞到边缘
return true;
node *p = head->next;
while (p!=NULL)
{
if ((head->x==p->x) && (head->y==p->y)) //撞到蛇身
return true;
p = p->next;
}
return false;
}
void Snake::ReDraw() //重绘蛇身
{
node *p = head;
while (p!=NULL)
{
SetPos(p->x,p->y);
cout << "*";
p = p->next;
}
SetPos(Food_x,Food_y);
cout << "*";
}
void Snake::Move()
{
node *New;
New = new node(head->x+dir[0],head->y+dir[1]); //新的蛇身结点
New->next = head;
head = New;
}
void Snake::onGame()
{
system("cls"); //刷新屏幕
node *p = NULL;
Init(); //初始化界面
ReDraw(); //重绘蛇身
char x;
while (true)
{
if (_kbhit()) //_kbhit()判断是否有键盘操作
{
x = _getch(); //重缓冲区读出一个字符赋给x
if ((x=='W'||x=='w') && !(dir[0]==0&&dir[1]==1)) //改变蛇的方向(不可以是反方向)下
dir[0] = 0, dir[1] = -1;
if ((x=='S'||x=='s') && !(dir[0]==0&&dir[1]==-1)) //上
dir[0] = 0, dir[1] = 1;
if ((x=='A'||x=='a') && !(dir[0]==1&&dir[1]==0)) //右
dir[0] = -1, dir[1] = 0;
if ((x=='D'||x=='d') && !(dir[0]==-1&&dir[1]==0)) //左
dir[0] = 1, dir[1] = 0;
while (_kbhit()) //读掉这之后所有的键盘输入
_getch();
}
if (!isFoodExist) //如果视图中没有食物,则随机生成食物
{
CreateFood();
isFoodExist = true;
}
Move(); //移动蛇
if (head->x==Food_x && head->y==Food_y) //如果蛇吃到了食物
{
isFoodExist = false;
++Length; //蛇身+1
SetPos(60,5);
cout << "长度 : " << Length; //改变界面信息
if (Length%10==0) //每十个蛇身升一级
{
++level;
SetPos(60,7);
cout << "等级 : " << level;
}
if (level==10) //最高等级达成。退出游戏
break;
}
ReDraw(); //重绘蛇身
if (isFoodExist) //如果没有吃到食物,需要删除蛇尾。
{
p=head;
while ((p->next)->next!=NULL)
p=p->next;
SetPos(p->next->x,p->next->y);
cout << " ";
delete(p->next);
p->next=NULL;
}
if(isGameOver()) //判断是否游戏结束
break;
Sleep(500-level*50); //等待,具体时间和等级有关
}
system("cls");
if (level==10) //通关
{
SetPos(10,5);
cout << "成功通关!" << endl;
}
else
{
SetPos(10,5);
cout << "游戏结束!你的成绩为:" << Length << endl;
}
}
int main()
{
Snake game;
system("cls");
cout<<"* 贪吃蛇 *"<<endl;
cout<<"* W,A,S,D控制移动 *"<<endl;
cout<<"* 每10节蛇身升一级,并提高速度,10级通关 *"<<endl;
_getch();
game.onGame();
game.del();
_CrtDumpMemoryLeaks(); //内存泄露检测!
return 0;
}
2.将上面的程序用SDK程序改写成图形化界面,while(true){}逻辑需要用SetTimer来改写.其它大体逻辑不变.
#include <Windows.h>
#include <time.h>
#define UNIT 20
struct NODE //蛇身(链表结构)
{
int x, y;
NODE *next;
NODE(int _x,int _y):x(_x),y(_y),next(NULL){}
};
RECT RectWindow;
NODE *head = NULL;
int Length = 3;
int level = 1;
bool isFoodExist = FALSE;
int Food_x,Food_y;
int dir[2];
bool isGameOver()
{
if (head->x >= RectWindow.right || head->x <= RectWindow.left
|| head->y <= RectWindow.top || head->y >= RectWindow.bottom) //是否撞到边缘
return true;
NODE *p = head->next;
while (p)
{
if ((head->x==p->x) && (head->y==p->y)) //撞到蛇身
return true;
p = p->next;
}
return false;
}
void AddNewHead()
{
NODE *New = new NODE(head->x+dir[0],head->y+dir[1]); //新的蛇身结点
New->next = head;
head = New;
}
void CreateFood() //随机出食物的坐标并判断坐标是否合法
{
NODE *p = NULL;
srand((unsigned int)time(0)); //用系统时间来做随机数种子
while (true)
{
Food_x = (rand() % (RectWindow.right-60)) / UNIT * UNIT + UNIT ; //保证产生的食物与蛇可以连接
Food_y = (rand() % (RectWindow.bottom-60)) / UNIT * UNIT + UNIT;
p = head;
while (p) //判断食物是否产生在蛇体内
{
if (p->x==Food_x && p->y==Food_y) //有冲突
break;
p = p->next;
}
if (p==NULL) //所有结点都不冲突。生成成功
break;
}
}
void DrawSnake(HWND hwnd) //重绘蛇身
{
NODE *p = head;
HDC hdc = GetDC(hwnd);
// HBRUSH brush = (HBRUSH)SelectObject(hdc,GetStockObject(WHITE_BRUSH));//把黑色画刷选进hdc
while (p)
{
Rectangle(hdc,p->x,p->y,p->x+UNIT,p->y+UNIT);
p = p->next;
}
Rectangle(hdc,Food_x,Food_y,Food_x+UNIT,Food_y+UNIT);
// SelectObject(hdc,brush); //恢复handle
ReleaseDC(hwnd,hdc);
}
LRESULT CALLBACK WndProc(HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam); // second message parameter);
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "ls";
wc.lpszMenuName = "ls";
wc.style = CS_VREDRAW | CS_HREDRAW;
RegisterClass(&wc);
HWND hwnd = CreateWindow("ls","ls",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
if (!hwnd)
{
return FALSE;
}
ShowWindow(hwnd,nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg,NULL,0,0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter);
{
PAINTSTRUCT ps ;
HDC hdc;
switch (uMsg)
{
case WM_CREATE:
{
isFoodExist = FALSE;
dir[0] = -UNIT,dir[1] = 0; //debug
head = new NODE(400,200); //申请起始蛇身3节
NODE *temp1 = new NODE(420,200);
NODE *temp2 = new NODE(440,200);
head->next = temp1;
temp1->next = temp2;
temp2->next = NULL;
GetClientRect(hwnd, &RectWindow); //获取窗口大小
SetTimer (hwnd, 1, 200, NULL) ; //设置时间
break;
}
case WM_PAINT:
{
hdc = BeginPaint (hwnd, &ps);
if (!isFoodExist) //如果视图中没有食物,则随机生成食物
{
CreateFood();
isFoodExist = true;
}
DrawSnake(hwnd);
EndPaint(hwnd,&ps);
break;
}
case WM_KEYDOWN:
{
if ((wParam=='W'||wParam=='w') && !(dir[0]==0&&dir[1]==UNIT)) //改变蛇的方向(不可以是反方向)下
dir[0] = 0, dir[1] = -UNIT;
if ((wParam=='S'||wParam=='s') && !(dir[0]==0&&dir[1]==-UNIT)) //上
dir[0] = 0, dir[1] = UNIT;
if ((wParam=='A'||wParam=='a') && !(dir[0]==UNIT&&dir[1]==0)) //右
dir[0] = -UNIT, dir[1] = 0;
if ((wParam=='D'||wParam=='d') && !(dir[0]==-UNIT&&dir[1]==0)) //左
dir[0] = UNIT, dir[1] = 0;
}
case WM_TIMER:
{
if (isGameOver())
{
KillTimer(hwnd,1);
return 0;
}
AddNewHead();
if (head->x==Food_x && head->y==Food_y) //如果蛇吃到了食物
{
isFoodExist = false;
}
if (isFoodExist) //如果没有吃到食物,需要删除蛇尾。
{
NODE *p = head;
while ((p->next)->next!=NULL)
p=p->next;
delete(p->next);
p->next=NULL;
}
InvalidateRect(hwnd, NULL, TRUE);
break;
}
case WM_DESTROY:
PostQuitMessage(0) ;
break;
default:
return DefWindowProc(hwnd,uMsg,wParam,lParam);
}
return 0;
}