计算机软件技术实习项目二(二) 贪吃蛇项目实现

计算机软件技术实习日志项目二(二) 贪吃蛇项目实现



前言

大家好,我将为大家介绍我实现贪吃蛇的具体细节,做软件是我之前没有接触过的领域,做得不好,大家不要见怪。本文章的思路是按照控件,参数定义,画图以及其他控制的思路讲解的。本文着重介绍项目实现,原理知识请参考《计算机软件技术实习日志项目二(一) 贪吃蛇项目准备》


一、控件

1.控件插入

我们需要两个按钮一个用来开始/暂停,一个用来重启游戏,我们还需要两个文字框用来表示速度选择和分数然后我们需要一个combox装速度,一个edit框动态显示分数,一个check框用来勾选音乐,最后需要一个picture control显示地图。
在这里插入图片描述

2.控件实现

2.1开始键

void CsnakeDlg::OnBnClickedstart()
{
	// TODO: 在此添加控件通知处理程序代码
	CString start, pause;
	start = "start";									//游戏暂停时显示
	pause = "pause";									//游戏开始时显示
	if (is_dead == false) {								//没有死才进入判断
		if (is_start) {									//游戏开始的话,需要暂停
			is_start = false;							//游戏暂停
			KillTimer(1);								//关闭定时器
			SetDlgItemText(button_start, start);		//开始键设置成“start”
		}
		else {											//说明游戏暂停了
			is_start = true;							//游戏设置为开始
			SetTimer(1, speed, NULL);					//开启定时器
			SetDlgItemText(button_start, pause);		//开始键显示“pause”
		}
		this->GetDlgItem(game_area)->SetFocus();		//焦点设置到游戏地图
	}
}

2.2重启键

void CsnakeDlg::OnBnClickedrestart() 
{
	// TODO: 在此添加控件通知处理程序代码
	gameover();
	snakeinit();
	this->GetDlgItem(game_area)->SetFocus();
}

void CsnakeDlg::gameover() {
	KillTimer(1);
	is_start = false;
}

2.3速度

速度控件对应函数

void CsnakeDlg::OnCbnSelchangespeed()
{
	// TODO: 在此添加控件通知处理程序代码
	CString mode;
	box.GetLBText(box.GetCurSel(), mode);				//读取选中的速度等级
	int tmp = 0;
	tmp = mode[0] - '0';
	speed = 300 / tmp;									//设置周期
	SetTimer(1, speed, NULL);							//设置定时每一个周期发出一个WM_TIMER的信息
	this->GetDlgItem(game_area)->SetFocus();			//设置焦点
}

WM_TIMER响应函数

void CsnakeDlg::OnTimer(UINT_PTR nIDEvent)							//WM——TIMER消息的相应函数
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	UpdateData(true);
	if (is_start && is_dead == false) {								//如果游戏开始且没死进入判断
		if (is_live() == false) {									//如果你死了
			is_dead = true;											//设置死亡
			CString str;											//设置结束窗口语句
			str.Format(_T("You're died and your score is %d ~ \nIf you want to try again, please click ok and click start ~ "), score);

			if (MessageBox(str, TEXT("INFO"), MB_ICONINFORMATION) == IDOK) {
				OnBnClickedrestart();								//点击确定就重启
			}
			gameover();
		}
		switch (my_snake.direc) {									//每到一个周期就根据贪吃蛇的方向移动
			case 1:up(); break;
			case 2:down(); break;
			case 3:left(); break;
			case 4:right(); break;
			default:break;
		}
		eatbean();													//判断是否吃豆子了
	}

	CDialogEx::OnTimer(nIDEvent);
}

设置豆子

bool CsnakeDlg::setbean() {
	srand((unsigned)time(NULL));
	bool setB = false;								//是否设置了
	bool safe = true;								//豆子位置是否合理
	while (safe) {
		pos.x = rand() % 30;
		pos.y = rand() % 38;
		for (int i = 0; i < my_snake.len; ++i) {	//判断是否在蛇的身子上
			if (pos.x == my_snake.body[i].x && pos.y == my_snake.body[i].y) {
				safe = false;
				break;
			}
		}
		if (safe) {									//如果合理,就设置豆子,然后退出
			setB = true;
			safe = false;
		}
		else {										//否则接着生成
			safe = true;
		}
	}
	CDC* pdc = GetDlgItem(game_area)->GetWindowDC();//画豆子
	pdc->SelectObject(&pen1);
	pdc->SelectObject(&game_brush[3]);
	pdc->Rectangle(map[pos.x][pos.y]);
	return setB;
}

吃豆子

void CsnakeDlg::eatbean() {
	if (pos.x == my_snake.body[0].x && pos.y == my_snake.body[0].y) {				//如果吃到豆子
		my_snake.len++;																//长度加一
		setbean();																	//设置豆子
		score = score + (abs(pos.x - my_snake.body[0].x) + abs(pos.y - my_snake.body[0].y));//加分
		CString str;
		str.Format(_T("%d"), score);
		change_score.SetWindowTextW(str);											//更新分数
	}
}

判断死活

bool CsnakeDlg::is_live() {
	if (my_snake.body[0].x < 0 || my_snake.body[0].x > 29 || my_snake.body[0].y < 0 || my_snake.body[0].y > 37) return false;
	//出界

	//或吃到自己的身子
	for (int i = 1; i < my_snake.len; ++i) {
		if (my_snake.body[0].x == my_snake.body[i].x && my_snake.body[0].y == my_snake.body[i].y) return false;
	}
	//都没有的话就没死
	return true;
}

2.4音乐

void CsnakeDlg::OnBnClickedmusic()
{
	// TODO: 在此添加控件通知处理程序代码
	int state = ((CButton*)GetDlgItem(check_music))->GetCheck();					//检查打钩情况
	if (state == 0) {																//如果没打勾,停止播放
		PlaySound(NULL, NULL, NULL);
	}
	else {																			//如果打钩,播放
		PlaySound(LPWSTR(love_me_right), GetModuleHandle(NULL), SND_ASYNC | SND_LOOP);
	}
}

二、参数定义

主要罗列了一些我自己定义的参数。
部分函数在前面由相关的控件引出讲过了
1.对话框头文件:

	CEdit change_score;
	CComboBox box;										//控制速度
	CBrush game_brush[5];								//画刷
	CRect map[30][38];									//地图
	CPen pen1;											//画笔
	CFont showfont1;									//字体1
	CFont showfont2;									//字体2
	CFont showfont3;									//字体3
	Point pos = { 0,0 };								//豆子
	gamesnake my_snake;									//蛇
	int speed = 150;									//初始速度
	int score = 0;										//分数
	bool is_start = false;								//游戏开始状态
	bool is_dead = false;								//游戏结束状态
	bool setbean();										//放豆子
	void snakeinit();									//初始蛇身
	void drawsnake();									//画
	void up();									
	void down();
	void left();
	void right();
	void eatbean();										//吃
	bool is_live();										//判断是否生存
	void gameover();									//游戏结束

首先在对话框里写了四个移动函数
以up为例:

oid CsnakeDlg::up() {
	CDC* pdc = GetDlgItem(game_area)->GetWindowDC();
	pdc->SelectObject(&pen1);
	pdc->SelectObject(&game_brush[4]);
	pdc->Rectangle(map[my_snake.body[my_snake.len - 1].x][my_snake.body[my_snake.len - 1].y]);		//蛇尾画为背景
	my_snake.up();																					//蛇向上移动
	drawsnake();																					//画蛇

}

自定义了一个蛇类
自定义头文件:

struct Point {				//点类
	int x;
	int y;
};

class gamesnake {			//蛇类
public:
	gamesnake();
	~gamesnake();
	int len;
	Point body[1255];
	int direc;
	void up();
	void down();
	void left();
	void right();
	void init();
};

蛇的移动
上:

void gamesnake::up(){
	for (int i = len - 1; i > 0; --i) {		//一次向前移动
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	body[0].x--;							//舌头向前移动
	direc = 1;								//方向设为1
}

蛇的初始化:

void gamesnake::init(){	
	srand((unsigned)time(NULL));
	body[0].x = rand()%10+10;
	body[0].y = rand()%10+10;
	len = 1;
	direc = rand()%4+1;
}

三、画图

1.初始化

首先我配置画图用到的对象

void CsnakeDlg::OnPaint() {
	
	CRect re;
	(this->GetDlgItem(game_area))->GetWindowRect(&re);							//获取相对屏幕的大小
	ScreenToClient(re);															//相对屏幕转换到相对客户区
	GetDlgItem(game_area)->MoveWindow(re.left, re.top , 760, 600, false);		//设置客户区大小

	CDC* clientdc = GetDC();													//设备环境
	(this->GetDlgItem(game_area))->GetWindowRect(&re);
	ScreenToClient(re);
	clientdc->Rectangle(re);
	re.left -= 5;																//扩一个框
	re.right += 5;
	re.top -= 5;
	re.bottom += 5;
	clientdc->SelectObject(&pen1);												//选中画笔
	clientdc->Rectangle(re);													//画
	clientdc->DeleteDC();
	

	CBitmap bodybmp, headbmp, beanbmp, bgbmp;
	bgbmp.LoadBitmapW(bmp_bg);
	beanbmp.LoadBitmapW(bmp_bean);
	bodybmp.LoadBitmapW(bmp_body);
	headbmp.LoadBitmapW(bmp_head);
	game_brush[1].CreatePatternBrush(&bodybmp);			//身子,用位图创建位图画刷
	game_brush[2].CreatePatternBrush(&headbmp);			//头	
	game_brush[3].CreatePatternBrush(&beanbmp);			//豆子
	game_brush[4].CreatePatternBrush(&bgbmp);			//背景

	box.SetCurSel(1);
	change_score.SetReadOnly(1);
	showfont1.CreatePointFont(200,L"黑体");				//设置字体格式
	GetDlgItem(edit_score)->SetFont(&showfont1);		//设置控件字体
	GetDlgItem(button_start)->SetFont(&showfont1);
	GetDlgItem(button_restart)->SetFont(&showfont1);
	showfont2.CreatePointFont(150, L"Consolas");
	GetDlgItem(word_score)->SetFont(&showfont2);
	GetDlgItem(word_speed)->SetFont(&showfont2);
	showfont3.CreatePointFont(100, L"Consolas");
	((CButton*)GetDlgItem(check_music))->SetFont(&showfont3);
	((CButton*)GetDlgItem(check_music))->SetCheck(BST_CHECKED);		//把按钮设置为选中状态
	PlaySound(LPWSTR(love_me_right), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC | SND_LOOP);
	//设置播放模式,循环播放

	snakeinit();

	CPaintDC pDC(this);

}

2.作画

void CsnakeDlg::snakeinit() {

	CDC* pdc = GetDlgItem(game_area)->GetWindowDC();						//生成设备环境

	CPen* p01dPen = pdc->SelectObject(&pen1);								//设置画笔花边框
	CBrush* p01dbrs = pdc->SelectObject(&game_brush[4]);					//设置背景位图画地图块内部,
	for (int i = 0; i < 30; ++i) {											//地图设置成地图块组成的
		for (int j = 0; j < 38; ++j) {										//大小设置成20*20				
			map[i][j].left = 0 + j * 20;
			map[i][j].right = 20 + j * 20;
			map[i][j].top = 0 + i * 20;
			map[i][j].bottom = 20 + i * 20;

			pdc->Rectangle(map[i][j]);
		}
	}
	my_snake.init();														//初始蛇
	score = 0;																//参数初始化
	is_start = 0;
	is_dead = false;
	drawsnake();															//画蛇		
	setbean();																//画豆子	

	change_score.SetWindowTextW(_T("0"));									//分数显示
	SetDlgItemText(button_start, _T("start"));
}
void CsnakeDlg::drawsnake() {
	CDC* pdc = GetDlgItem(game_area)->GetWindowDC();
	CBrush* poldbrs = pdc->SelectObject(&game_brush[1]);
	for (int i = 1; i < my_snake.len; ++i) {							//画蛇身
		pdc->SelectObject(game_brush[1]);
		pdc->SelectObject(&pen1);
		pdc->Rectangle(map[my_snake.body[i].x][my_snake.body[i].y]);
	}
	pdc->SelectObject(&game_brush[2]);									//花蛇头
	pdc->SelectObject(&pen1);
	pdc->Rectangle(map[my_snake.body[0].x][my_snake.body[0].y]);
	pdc->DeleteDC();
}

四、其他控制

键盘控制:

BOOL CsnakeDlg::PreTranslateMessage(MSG* pMsg)
{
	// TODO: 在此添加专用代码和/或调用基类
	if (is_start && is_dead == false) {															//pMsg->wParam表示按的键
		if (my_snake.direc != 2 && (pMsg->wParam == VK_UP ) )my_snake.direc = 1;				//上
		else if (my_snake.direc != 1 && (pMsg->wParam == VK_DOWN )) my_snake.direc = 2;			//下
		else if (my_snake.direc != 4 && (pMsg->wParam == VK_LEFT )) my_snake.direc = 3;			//左
		else if (my_snake.direc != 3 && (pMsg->wParam == VK_RIGHT )) my_snake.direc = 4;		//右
	}
	//return CDialogEx::PreTranslateMessage(pMsg);
	return false;
}

窗口总保持在屏幕内:

void CsnakeDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
{
	CDialogEx::OnWindowPosChanging(lpwndpos);

	// TODO: 在此处添加消息处理程序代码
	//获取屏幕宽度
	int wth = GetSystemMetrics(SM_CXSCREEN);
	// 获取屏幕高度
	int hgih = GetSystemMetrics(SM_CYSCREEN);

	// 判断窗口X坐标有无超过左边桌面。
	if (lpwndpos->x < 0)
	{
		lpwndpos->x = 0;
	}

	// 判断窗口X坐标有无超过右边桌面。
	if (lpwndpos->x + lpwndpos->cx > wth)
	{
		lpwndpos->x = wth - lpwndpos->cx;
	}

	// 判断窗口Y坐标有无超过顶部桌面。
	if (lpwndpos->y < 0)
	{
		lpwndpos->y = 0;
	}

	// 判断窗口Y坐标有无超过底部桌面。
	if (lpwndpos->y + lpwndpos->cy > hgih)
	{
		lpwndpos->y = hgih - lpwndpos->cy;
	}
}

五、AI贪吃蛇

我是现在项目三里学习的A星,回过来写的AI贪吃蛇,AI贪吃蛇是A星寻路的,所以大家参考项目三的文章,这里直贴代码了


//定时器响应函数里将switch语句换为下面的句子
//请将fax,fay理解为要去的下一个结点,不会父节点
		int nowx = my_snake.body[0].x, nowy = my_snake.body[0].y;
		int fax = ma[nowx][nowy].fax, fay = ma[nowx][nowy].fay;
		if (fax == nowx - 1 && fay == nowy) up();
		if (fax == nowx + 1 && fay == nowy) down();
		if (fax == nowx  && fay == nowy-1) left();
		if (fax == nowx  && fay == nowy+1) right();

void CsnakeDlg::astar() {
	/*
	和迷宫的A星基本相同不做具体讲解。
	该函数在setbean里调用,然后通过深搜找路径
	这里是从豆子找到蛇,通过fa回溯,比正着来方便
	*/
	init();
	int tx, ty,ttx,tty;
	node tmp;
	int ex =  my_snake.body[0].x,ey=my_snake.body[0].y ;
	int sx = pos.x, sy = pos.y;
	ma[sx][sy].x = sx, ma[sx][sy].y = sy;
	ma[sx][sy].in_open = 1;
	open_queue.push(ma[sx][sy]);
	while (!ma[ex][ey].in_open) {
		tmp = open_queue.top();
		tx = tmp.x, ty = tmp.y;
		ma[tx][ty].in_close = 1;
		open_queue.pop();
		for (int i = 1; i <= 4; ++i) {
			ttx = tx + dr[i][0], tty = ty + dr[i][1];
			if (!(ttx >= 0 && ttx <30 && tty >= 0 && tty <38)) continue;
			bool ok = 1;
			ok = !vis[ttx][tty];													//vis在蛇身移动时,蛇身的地图块会赋为1
			if (ok && !ma[ttx][tty].in_close) {
				if (!ma[ttx][tty].in_open || ma[ttx][tty].g > ma[tx][ty].g + 1) {
					ma[ttx][tty].x = ttx, ma[ttx][tty].y = tty;
					ma[ttx][tty].g = ma[tx][ty].g + 1;
					ma[ttx][tty].h = abs(ttx - ex) + abs(tty - ey);
					ma[ttx][tty].f = ma[ttx][tty].g + ma[ttx][tty].h;
					ma[ttx][tty].in_open = 1;
					ma[ttx][tty].fax = tx, ma[ttx][tty].fay = ty;
					open_queue.push(ma[ttx][tty]);
				}
			}
		}
	}
	while (!open_queue.empty()) {
		open_queue.pop();
	}
}

六、演示

开始暂停重启调速死亡提示演示在这里插入图片描述
AI贪吃蛇(重调了一下背景,还没设置手动模式和自动模式按键)
在这里插入图片描述

七、总结

这一次通过学习贪吃蛇我学习了一些关于Windows窗口编程的深层原理,对我理解运MFC有很大的帮助。贪吃蛇的界面设置的很low,还可以再丰富游戏性。

八、参考资料

【1】 B站视频 MFC 贪吃蛇小程序(从创建项目开始)
https://www.bilibili.com/video/BV1Q741187fo?from=search&seid=9242306747617284419

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值