游戏贪吃蛇(c语言)

一、项目背景

​ 贪吃蛇是一款经典的小游戏,一条小蛇在一个区域里自由游走,目的是吃掉随机出现的食物,每吃掉一个食物蛇的身体会变长一点,如果在移动过程中蛇头碰到墙壁或者自己的身体游戏结束。所以吃掉的食物越多,游戏将变得更难**。**

二、设计目的

  • 如何设计贪吃蛇游戏中基础数据结构
  • 如何实现蛇的移动以及食物生成等的功能
  • 如何使用Windows编程的回调机制处理消息,实现游戏控制

三、项目功能需求

1、初始化游戏

  • 在窗口上画出游戏区域,并初始化长度为1的蛇,同时生成一个食物

2、控制蛇的运行轨迹

  • 通过键盘来控制蛇的运行方向

3、控制蛇的长度

  • 当蛇吃到食物时,蛇的身体会随之变长

4、控制食物生成

  • 当当前食物被吃掉时,需要在游戏区域内随机生成一个新的食物

5、计算分数

  • 计算玩家得分,每吃到一个食物时,分数加1

6、结束条件

  • 当玩家控制的蛇撞到墙壁或者自己的身体时,游戏结束

四、系统的功能结构图

在这里插入图片描述

五、总体设计

功能模块介绍

1、初始化模块

  • 实现游戏区域的绘制以及蛇与食物的生成**。**
  • 通过对键盘消息的监测实现游戏的暂停和重新开始**。**

2、游戏控制模块

  • 通过对蛇头位置的判断,判断在移动过程中蛇头碰到墙壁或者自己的身体,结束游戏

3、运行控制模块

  • 通过对键盘消息的监测与当前运行方向来实现蛇的运行控制

4、食物生成模块

  • 通过蛇头和食物相对位置的判断,实现蛇吃食物的效果,更新分数。同时在区域内随机生成新的食物**。**

六、详细设计

1、主函数

程序入口为WinMain函数,

函数中实现创建Windows

窗口功能,获取并分发消息。

处理流程如图。

在这里插入图片描述

2、初始化模块

功能设计

  • 绘制游戏运行的整个界面,监控键盘事件,实现游戏的暂停和重新开始**。**
  • 初始化蛇和食物。在游戏开始时,在游戏区域中心生成1个单位长度的蛇并在区域内随机生成食物。

3、游戏控制模块

功能设计

  • 主要是在游戏进行过程中检测蛇头是否撞墙或者撞到自己身体来控制游戏是否结束。

4、运行控制模块

功能设计

  • 根据键盘输入改变蛇的下一个前进方向。
  • 实现蛇的动态移动。

5、食物生成模块

功能设计

  • 判断贪吃蛇是否吃到食物。
  • 在游戏区域内随机生成新的食物。

七、代码

#include <windows.h>
#include <time.h>
#include <stdlib.h>
#include <conio.h>

/*定义4个方向*/
#define UP      1  /*向上,蛇头y坐标不断减小*/
#define DOWN    2  /*向下,蛇头y坐标不断增大*/
#define LEFT    3  /*向左,蛇头x坐标不断减小*/
#define RIGHT   4  /*向右,蛇头x坐标不断增大*/

#define SNAKE_WIDTH  20  /*单节蛇的大小*/
#define X_WIDTH      20  /*游戏区的宽度*/
#define Y_HEIGHT     20  /*游戏区的高度*/
#define GETSCORE     10  /*吃一个食物的得分*/
#define ID_TIMER     1   /*定时器ID*/
#define TIMERSET     600 /*定时间隔*/
#define FALSE 0
#define TRUE 1
#define APP_NAME "Snake"
#define APP_TITLE TEXT("贪吃蛇")  
#define P 80
#define R 82

static int	iDirect = RIGHT;  /*方向(注:方向由蛇头决定)*/
static int  iScore;  /*吃到的食物数量*/

int  iIsOver = FALSE;

struct Snake
{
	POINT   point;
	struct Snake *pstNext, *pstBefore;
} stSnake;  /*定义蛇头*/

struct Snake * pstLast = NULL, *pstFood = NULL;

int aiPointExist[X_WIDTH][Y_HEIGHT]; /*定义游戏区坐标布尔值,表示相应点的蛇身以及是否有食物。*/
int aiFood[X_WIDTH][Y_HEIGHT];       /*定义了食物的坐标*/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;
	WNDCLASS    wndcls;                                          /*WNDCLASS结构变量,包含窗口类全部信息*/

	/*设置窗口样式*/
	wndcls.cbClsExtra = 0;                                       /*窗口类无拓展*/
	wndcls.cbWndExtra = 0;                                       /*窗口实例无拓展*/
	wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);  /*窗口背景为白色*/
	wndcls.hCursor = LoadCursor(hInstance, IDC_ARROW);           /*窗口采用箭头光标*/
	wndcls.hIcon = LoadIcon(hInstance, IDI_APPLICATION);         /*窗口最小化图标为缺省图标*/
	wndcls.hInstance = hInstance;                                /*当前实例句柄*/
	wndcls.lpfnWndProc = WndProc;                                /*定义窗口处理函数*/
	wndcls.lpszClassName = APP_NAME;                             /*定义窗口类名*/
	wndcls.lpszMenuName = NULL;                                  /*窗口无菜单*/
	wndcls.style = CS_HREDRAW | CS_VREDRAW;                      /*当窗口水平或垂直宽度变化时重绘窗口*/
	RegisterClass(&wndcls);                                      /*注册窗口类*/
	/*创建Windows窗口*/
	HWND hwnd = CreateWindow(APP_NAME, APP_TITLE,
		WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU,
		CW_USEDEFAULT, CW_USEDEFAULT, (X_WIDTH + 15) * SNAKE_WIDTH, (Y_HEIGHT + 5) * SNAKE_WIDTH,
		NULL, NULL, hInstance, NULL);
	ShowWindow(hwnd, nCmdShow);                                  /*显示窗口*/
	UpdateWindow(hwnd);                                          /*绘制用户区*/
	/*判断是否有Windows消息*/
	while (GetMessage(&msg, NULL, 0, 0))                         /*从调用线程的消息队列中取出一条消息*/
	{
		TranslateMessage(&msg);                              /*对"消息对"的转化*/
		DispatchMessage(&msg);                               /*将取出的消息传到窗口的回调函数去处理*/
	}
	return msg.wParam;
}

/*新食物刷新*/
void NewFood(HWND hwnd)
{
	pstFood = (struct Snake *)malloc(sizeof(struct Snake));
	SYSTEMTIME st;
	/*在窗口位置内随机产生新的食物*/
	GetLocalTime(&st);                                           /*获取当地的当前系统日期和时间*/
	srand(st.wMilliseconds);
	pstFood->point.x = rand() % X_WIDTH;
	pstFood->point.y = rand() % Y_HEIGHT;
	/*判断新食物位置是否是蛇身位置*/
	while (aiPointExist[pstFood->point.x][pstFood->point.y])
	{
		GetLocalTime(&st);
		srand(st.wMilliseconds);
		pstFood->point.x = rand() % X_WIDTH;
		pstFood->point.y = rand() % Y_HEIGHT;
	}
	aiPointExist[pstFood->point.x][pstFood->point.y] = TRUE;
	InvalidateRect(hwnd, NULL, TRUE);                            /*重绘窗口区域*/
}

/*判断前方是否是食物*/
int IsFood()
{
	if (stSnake.point.x == pstFood->point.x && stSnake.point.y == pstFood->point.y)
		return TRUE;
	else
		return FALSE;
}

/*判断是否撞墙或者撞到自己身体*/
int TouchWall()
{
	struct Snake *pstTemp = pstLast;
	/*超越边界判断*/
	if (stSnake.point.x >= X_WIDTH ||
		stSnake.point.x < 0 ||
		stSnake.point.y < 0 ||
		stSnake.point.y >= Y_HEIGHT)
		return TRUE;
	/*碰撞自身身体判断*/
	while (pstTemp != &stSnake)
	{
		if (stSnake.point.x == pstTemp->point.x &&
			stSnake.point.y == pstTemp->point.y)
			return TRUE;
		pstTemp = pstTemp->pstBefore;
	}
	return FALSE;
}


void Move(HWND hwnd)
{
	int iPosition_x, iPosition_y;  /*用于记录蛇头的当前位置(未前进时)*/
	/*标记前方是否为食物,默认为不是食物,表示系通常的前进*/
	struct Snake * pstTemp = pstLast;

	/*记录蛇头当前坐标*/
	iPosition_x = stSnake.point.x;
	iPosition_y = stSnake.point.y;
	/*测试前进的地方是否是食物,同时将蛇头前进*/
	switch (iDirect)
	{
	case UP:
		--stSnake.point.y;
		break;
	case DOWN:
		++stSnake.point.y;
		break;
	case LEFT:
		--stSnake.point.x;
		break;
	case RIGHT:
		++stSnake.point.x;
		break;
	default:
		break;
	}
	if (!IsFood())/*如果不是食物,作如下处理*/
	{
		if (pstTemp != &stSnake)/*最后一节不是蛇头*/
		{
			aiPointExist[pstTemp->point.x][pstTemp->point.y] = FALSE;/*由于蛇要前进,原蛇尾位置标记为FALSE*/
			/*蛇尾到蛇头位置节点对应位置分别标记为TRUE*/
			while (pstTemp != stSnake.pstNext)
			{
				pstTemp->point.x = pstTemp->pstBefore->point.x;
				pstTemp->point.y = pstTemp->pstBefore->point.y;
				pstTemp = pstTemp->pstBefore;
			}
			pstTemp->point.x = iPosition_x;
			pstTemp->point.y = iPosition_y;
			aiPointExist[pstTemp->point.x][pstTemp->point.y] = TRUE;
		}
		else  /*pstLast == &snake,即蛇只有蛇头*/
			aiPointExist[iPosition_x][iPosition_y] = FALSE;
		if (TouchWall())
			iIsOver = TRUE;                      /*则标记游戏结束*/
		else
			aiPointExist[stSnake.point.x][stSnake.point.y] = TRUE;  /*不然移动后的蛇头位置标记为有方块了*/
		InvalidateRect(hwnd, NULL, TRUE);       /*刷新游戏区*/
	}
	/*如果是食物,作如下处理*/
	else
	{
		++iScore;
		pstFood->pstBefore = pstLast;
		pstLast->pstNext = pstFood;

		if (pstTemp != &stSnake)
		{
			pstFood->point.x = pstLast->point.x;
			pstFood->point.y = pstLast->point.y;
			while (pstTemp != stSnake.pstNext)
			{
				pstTemp->point.x = pstTemp->pstBefore->point.x;
				pstTemp->point.y = pstTemp->pstBefore->point.y;
				pstTemp = pstTemp->pstBefore;
			}
			pstTemp->point.x = iPosition_x;
			pstTemp->point.y = iPosition_y;
		}
		else  /*pstLast == &snake,即蛇只有蛇头*/
		{
			pstFood->point.x = iPosition_x; pstFood->point.y = iPosition_y;
		}
		pstLast = pstFood;
		NewFood(hwnd);
	}
}

void startGame(HWND hwnd)
{
	int x, y;
	for (x = 0; x < X_WIDTH; ++x)
	for (y = 0; y < Y_HEIGHT; ++y)
		aiPointExist[x][y] = FALSE;/*初始化变量为FALSE,表示没有食物和蛇*/
	aiFood[x][y] = FALSE;
	iDirect = RIGHT;   /*游戏开局,设定蛇的默认方向向右*/
	/*在游戏区中间位置生成长度为1的蛇*/
	stSnake.point.x = X_WIDTH / 2;
	stSnake.point.y = Y_HEIGHT / 2;
	pstLast = &stSnake;
	iIsOver = FALSE;/*将游戏结束变量设为FALSE*/
	aiPointExist[stSnake.point.x][stSnake.point.y] = TRUE;/*将新生成蛇的位置的aiPointExist变量设为TRUE*/
	NewFood(hwnd);/*随机生成一个食物*/
	SetTimer(hwnd, ID_TIMER, TIMERSET, NULL);/*设置计数器*/
}

void keyDown(HWND hwnd, WPARAM wParam, int iPause)
{
	if (!iPause&&!iIsOver)/*游戏运行时,才可以进行运行控制*/
	{
		switch (wParam)
		{
		case VK_UP:
			if (iDirect != DOWN)/*更改的移动方向不能与原方向相反*/
			{
				iDirect = UP;/*改变当前移动方向*/
				Move(hwnd);/*移动*/
			}
			break;
		case VK_DOWN:
			if (iDirect != UP)
			{
				iDirect = DOWN;
				Move(hwnd);
			}
			break;
		case VK_LEFT:
			if (iDirect != RIGHT)
			{
				iDirect = LEFT;
				Move(hwnd);
			}
			break;
		case VK_RIGHT:
			if (iDirect != LEFT)
			{
				iDirect = RIGHT;
				Move(hwnd);
			}
			break;
		default:
			break;
		}
	}
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC             hdc;
	PAINTSTRUCT     ps;
	TEXTMETRIC      tm;
	HBRUSH          hBrush;
	static int      cxChar, cyChar;
	TCHAR           szScore[] = TEXT("得分:"),
		szInfor_1[] = TEXT("========游戏说明========"),
		szInfor_2[] = TEXT("1.按方向键移动小蛇;"),
		szInfor_3[] = TEXT("2.按P暂停游戏;"),
		szInfor_4[] = TEXT("2.按R重新开始游戏;"),
		szInfor_5[] = TEXT("3.蛇头撞到墙,结束游戏;"),
		szInfor_6[] = TEXT("4.蛇头碰到蛇身,游戏结束;"),
		szInfor_7[] = TEXT("5.每吃一个米粒得1分"),
		szInfor_8[] = TEXT("========================"),
		szInfor_9[] = TEXT("========================"),
		szInfor_10[] = TEXT("   您的得分是:"),
		szInfor_11[] = TEXT("======================="),
		szGameOver[] = TEXT("游戏结束!"),
		szPause[] = TEXT("游戏暂停!"),
		szBuffer[X_WIDTH],
		*szText = NULL;
	int             x, y;
	static int     iPause = FALSE;

	switch (message)
	{
	case WM_CREATE:
		hdc = GetDC(hwnd);/*返回窗口客户区的设备上下文环境*/
		GetTextMetrics(hdc, &tm);/*该函数把程序当前的字体信息,存放到TEXTMETRIC*/
		cyChar = tm.tmExternalLeading + tm.tmHeight;
		ReleaseDC(hwnd, hdc);
		startGame(hwnd);
		return 0;
	case WM_TIMER:
		if (iPause) return 0;
		Move(hwnd);
		if (iIsOver)
		{
			KillTimer(hwnd, ID_TIMER);
			InvalidateRect(hwnd, NULL, TRUE);
		}
		return 0;

	case WM_KEYDOWN:
		keyDown(hwnd, wParam, iPause);
		if (wParam == P)
		{
			iPause = !iPause;
			InvalidateRect(hwnd, NULL, TRUE);
			return 0;
		}
		if (wParam == R)
		{
			startGame(hwnd);
			return 0;
		}

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
		SetViewportOrgEx(hdc, SNAKE_WIDTH, SNAKE_WIDTH, NULL);

		/*画游戏区的边框*/
		MoveToEx(hdc, -1, -1, NULL);
		LineTo(hdc, X_WIDTH * SNAKE_WIDTH + 1, -1);
		LineTo(hdc, X_WIDTH * SNAKE_WIDTH + 1, Y_HEIGHT * SNAKE_WIDTH + 1);
		LineTo(hdc, -1, Y_HEIGHT * SNAKE_WIDTH + 1);
		LineTo(hdc, -1, -1);

		/*显示屏幕右侧的游戏说明*/
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 0, szInfor_1, lstrlen(szInfor_1));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 20, szInfor_2, lstrlen(szInfor_2));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 50, szInfor_3, lstrlen(szInfor_3));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 80, szInfor_4, lstrlen(szInfor_4));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 110, szInfor_5, lstrlen(szInfor_5));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 140, szInfor_6, lstrlen(szInfor_6));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 170, szInfor_7, lstrlen(szInfor_7));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 200, szInfor_8, lstrlen(szInfor_8));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 300, szInfor_9, lstrlen(szInfor_9));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 320, szInfor_10, lstrlen(szInfor_10));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 340, szInfor_11, lstrlen(szInfor_11));
		TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH + 100, 320, szBuffer, wsprintf(szBuffer, TEXT("%4d"), iScore));
		if (iPause)
			TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 380, szPause, lstrlen(szPause));
		else if (iIsOver)
			TextOut(hdc, (X_WIDTH + 1) * SNAKE_WIDTH, 380, szGameOver, lstrlen(szGameOver));

		hBrush = CreateSolidBrush(RGB(0, 0, 255));  /*定义画刷为蓝色*/
		SelectObject(hdc, hBrush);

		for (x = 0; x < X_WIDTH; ++x)
		{
			for (y = 0; y < Y_HEIGHT; ++y)
			{
				if (aiPointExist[x][y])
					Rectangle(hdc, x * SNAKE_WIDTH, y * SNAKE_WIDTH,
					(x + 1) * SNAKE_WIDTH, (y + 1) * SNAKE_WIDTH);   /*绘制蛇身和食物的矩形*/
			}
		}
		return 0;

	case WM_DESTROY:
		KillTimer(hwnd, ID_TIMER);
		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
  • 7
    点赞
  • 106
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾亿-唯一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值