C++/C小游戏 双人贪吃蛇——金蛇狂舞 课程设计作业分享
源码见文末
一、问题描述
把数据结构中的基本知识点,如栈、队列、线性表、图、树等运用到实际的问题当中,以小游戏或小工具等程序方式呈现,要求理解基本结构并能拓展,最后达到应用的目的。
二、准备工作
将背景、贴图、头部、身体、食物照片提前转为bmp格式并调好像素转入目标文件夹
三、部分代码分析与实现(详细代码见附件)
拓展部分:将背景墙样式重新设计为具有中国红元素的喜庆风格,洋溢春节喜庆色彩。
实现方式:
分析:此设计目的在于将背景打造出新年广场舞狮的感觉,个人认为中国红元素也是红色元素,喜庆的色彩使得整个游戏民族文化的氛围更加浓郁。
拓展部分:玩家一蛇身为橙色龙鳞,头部为舞狮画像,代表舞狮,玩家二蛇身为蓝色龙鳞,头部为年兽画像,代表年兽,食物使用红色灯笼图片,并且取名玩家一为舞狮,玩家二为年兽,游戏为金蛇狂舞,并贴入主题图片,在右侧显示相关信息,介绍操作方式与身体长度。
实现方式:
分析:本游戏舞狮与年兽在新年广场(指背景版)争夺灯笼的比赛设计灵感来自于舞狮与年兽的传说,添加主题图片“金蛇狂舞”对应双人贪吃蛇对战,所有的中国风红色喜庆元素的加入旨在烘托出新年喜庆活动舞狮子的春节氛围,右侧提示栏方便玩家理解游戏操作与分数统计。
拓展部分:本游戏扩展为双人对战,双方均可通过吞噬食物增长体型。主要玩法为保护自身的头部,吞噬食物同时利用身体长度优势对另一方进行围堵,若一方的头部撞到另一方的身体,则另一方获胜,反之,则以。
实现方式:
分析:在原有的基础上添加了另一个玩家的身体参数记录操作控制、食物吞噬、撞墙死亡判定,算法原理不变,在此之外还添加了双方对战的死亡判定,若一方头部碰到另一方身体,则判定前者死亡,反之则以。增加了游戏的可玩性与操作性,但也出现了很多新的问题,例如身体与背景的叠加打印会导致快速闪烁,引起视觉的不舒服,在后面扩展中会一一解决。
拓展部分:随机生成多个食物供两个玩家吞噬
实现方式:
分析:贪吃蛇双人对战一个食物肯定会影响游戏的体验,所以出于这个考虑将食物设定为可调节的多个食物,在游戏初次运行或重新开始时生成多个新的随机食物,当一位玩家吃掉一个食物时身体长度会加一,同时会生成一个新的随机食物,但也出现了新的问题,例如多个食物生成会重叠,新的食物生成在旧的食物身上或者在蛇身上,食物生成多个的同时还要考虑生成食物的代价,与判定食物吞噬的代价,食物与背景重叠打印会闪烁,这些问题在后面的优化扩展中会给予解答。
拓展部分:建立判定身体食物位置状态的数组,解决重叠打印带来的身体食物闪烁问题,优化结构减少程序代价。
实现方式:
分析:游戏初次运行或重新开始时将身体判定数组与食物判定数组初始化为零,在初次生成多个食物时将食物判定数组中各食物位置权值置1,在游戏进行的同时每次打印前将身体位置判定数组初始化权值置为0,并将此时的身体位置所在权值更新为1;当食物被吞噬时,将该位置食物判定数组权值重新置0,并生成新食物;在新食物生成时使用do-while语句先生成随机位置再判定是否此位置已有食物或者此位置为玩家身体,若不符合要求则重新生成,若符合要求则将此位置的食物判定数组权值置为1;在每次打印背景板时加入该位置的身体与食物的判定,若此处食物判定数组权值为1或身体判定数组权值为1则不打印背景板,若此处两判定权值均为0则打印背景板;以上操作有效解决了多个食物产生的错误位置打印问题,与背景板重叠打印在身体和食物上产生的闪烁问题。
拓展部分:在游戏开始时自动播放喜庆主题背景音乐“金蛇狂舞”
实现方式:
分析:插入提前通过Audacity编辑好的BGM“金蛇狂舞”呼应主题金蛇狂舞,一同作用,共同渲染了中国红的新年喜庆舞狮氛围,并且激烈喜庆的音乐也为游戏的体验添加了几份别样风采。
拓展部分:在初次打开游戏时在窗口中央显示主题图片,并在游戏开始后消失
实现方式:
分析:通过设置全局变量kk控制游戏在初次开始时进入开场图片打印函数start(),该图片在呼应主题的同时,使得玩家在初次打开游戏时的视觉上得到满足,提升游戏的整体观赏档次。
拓展部分: 空格控制游戏初次开始与快速重新开始
实现方式:
分析:初次进入游戏按下空格,主题图片消失,游戏进行时按下空格,快速重新开始,丰富了玩家的操作内容与游戏体验。
四、游戏运行展示
五、总结
在原有的游戏基础上进行了彻头彻尾的修改与添加,将游戏单独的贪吃蛇游戏模式改为了双人贪吃蛇对战模式,双方均可通过吞噬食物增长体型。主要玩法为保护自身的头部,吞噬食物同时利用身体长度优势对另一方进行围堵,若一方的头部撞到另一方的身体,则另一方获胜,反之,则以。多项修改大幅增加了游戏的可玩性,同时针对很多出现的bug与问题进行了比较完善的解决
感想(纯属应付作业 可借鉴):
本次游戏设计使我收获良多,从最早的毫无头绪,拿到基础代码不知从何下手,一改就是很多bug,到后来的的井井有条,在原来的基础上可以灵活的进行删改与创新,我不仅掌握了编写游戏的能力,更加掌握了优化结构,创造游戏的能力,也在一定程度上提升了自身的审美设计能力。一开始我的目标只是制作一个双人贪吃蛇游戏,像前一阵子很火的一款手游(蛇蛇大作战),即两只蛇进行pk,一方头部碰到另一方的身体即为死亡,反之则以,两只蛇的操作与死亡判定的代码实现过程很是顺利,在进行的过程中遇到了其他的麻烦,比如两条蛇争夺一个食物,虽变成了双人游戏,但游戏的可玩性却大幅下降,在认识到这个问题后,便开始尝试生成多个食物,而生成多个食物最早的想法只是单纯的生成多个食物,但陆续发现多个食物生成时会出现重叠打印导致食物越来越少,或者一个食物生长长度不为一的情况,针对这个问题在每次生成食物时添加了for循环的判定原则,用于判定此位置是否为蛇身或者已有食物生成,但在运行起来之后发现使用这种多层循环的结构程序运行代价过高,在食物数量设定为十个以上时,程序会经常挂掉,之后参考了陈伟斌老师算法设计这门课程中讲到的众多实例,最终设立全局身体与食物位置的判定数组,通过这种方式使得在每次食物更新时只需一个do-while语句便可完成更新,大大减小了计算复杂度,在此基础上也通过该判定数组的设立于打印时添加条件,解决了背景块与食物重复打印导致的闪烁问题,以上的操作过程使得整个游戏得以基本成型,之后便是美化环节,但考虑到融入五星红旗元素过于僵硬,甚至导致起到反效果,正逢临近新年,于是思考之下决定将红色元素的范畴扩大到中国红,民族红,一开始想到的便是与两蛇很接近的新年喜庆音乐“金蛇狂舞”,但两只蛇争抢食物的设定过于单薄,后来查阅了相关资料,将游戏环境定义为新年广场的舞狮表演,背景颜色选择中国红与金黄色,寓意红红火火,吉祥如意,玩家一的头部改为舞狮头部画像,身躯选用橙色龙鳞,狮是凶猛的代表,吉祥的象征,他的舞动对应中华民族传统文化“舞狮子”,并将食物改为灯笼样式,寓意新年新气象,争夺灯笼便是代表着百姓对新一年的憧憬,而玩家二则选用年兽头部画像作为头部,选择蓝色龙鳞作为身躯,该设计源自于佛山起源说——年兽与舞狮的故事,简要内容为百姓用舞狮的舞动配合敲响乐(金蛇狂舞便是其中一种)吓走了想要破坏村庄的年兽(除夕),随后便用舞狮的方式庆祝佳节,玩家一与玩家二的对战,便对应着舞狮与年兽的对战,象征着佳节将至,营造了中华民族传统节日的氛围,及中国红元素。本次游戏设计,我个人认为自己设计的很满意,在本来的基础上进行了多次的蜕变,从基础的贪吃蛇,到双人贪吃蛇对战,到多食物的双人对战,再到最后的金蛇狂舞——舞狮与年兽的宿命之战,新年气运灯笼的抢夺,其中不仅加入了很多自己的设计创意,很多中国红元素,更是解决了程序运行代价大、背景块与身体食物重叠打印的闪烁问题,并且在最终成品上竟然真的营造出了一种“年味儿”,这也是我本次游戏设计最大的收获。
源码:
// MySnake.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "stdio.h"
#include "resource.h"
#include "time.h"
#include
#include
#pragma comment(lib,"winmm.lib")
#define MAX_LOADSTRING 100
HINSTANCE hInst;
TCHAR szTitle[MAX_LOADSTRING];
TCHAR szWindowClass[MAX_LOADSTRING];
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HACCEL hAccelTable;
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_MYSNAKE, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MYSNAKE);
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_MYSNAKE);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCSTR)IDC_MYSNAKE;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
return RegisterClassEx(&wcex);
}
//框架系数
HDC hdc,mdc,bufdc;
int row=20;
int col=25;
int kk = 0;
int heightT = 200;
HBITMAP bg,bg2,pi1,pi2, food,pn[2];//贴图
#define WIDTH 30
#define HEIGHT 30
#define MAXSIZE 50
//身体 数据结构
struct Snake
{
POINT m_pos[MAXSIZE];
int m_direction;
int len;
};
Snake m_snake,m_snake2;//舞狮 年兽
#define NUMBER 15 //食物数量
POINT m_food[NUMBER];//食物生成
int foods[30][25],body[30][25];//防止重复打印
//食物初始化
void foodbegin() {
for (int i = 0; i < NUMBER; i++) {
m_food[i].x = rand() %(col-1)+1;
m_food[i].y = rand() %(row-1)+1;
foods[m_food[i].x][m_food[i].y] = 1;
}
}
//食物更新
void generatefood(int i)
{
srand((unsigned)time(NULL));
do {
m_food[i].x = rand() % (col - 1) + 1;
m_food[i].y = rand() % (row - 1) + 1;
} while (foods[m_food[i].x][m_food[i].y] == 1); //避免食物重复生成
foods[m_food[i].x][m_food[i].y] = 1;
}
//身体系数初始化
void init_snake()
{
//舞狮 身体初始化
m_snake.len=3;
m_snake.m_pos[0].x=1;
m_snake.m_pos[0].y=5;
m_snake.m_direction=1;
for(int n=1;n -1; ii--)
body[m_snake.m_pos->x][m_snake.m_pos->y] = 1;
for (int ii = m_snake2.len; ii > -1; ii--)
body[m_snake2.m_pos->x][m_snake2.m_pos->y] = 1;
//背景板生成
HBRUSH hbr=CreateSolidBrush(RGB(220, 205, 50));
SelectObject(hdc,hbr);
for(int y=1;y=1;n--)
m_snake.m_pos[n]=m_snake.m_pos[n-1];
if(0==m_snake.m_direction)
m_snake.m_pos[0].y=m_snake.m_pos[0].y-1;
if(1==m_snake.m_direction)
m_snake.m_pos[0].x=m_snake.m_pos[0].x+1;
if(2==m_snake.m_direction)
m_snake.m_pos[0].y=m_snake.m_pos[0].y+1;
if( 3==m_snake.m_direction)
m_snake.m_pos[0].x=m_snake.m_pos[0].x-1;
//年兽前进
for (n = m_snake2.len - 1; n >= 1; n--)
m_snake2.m_pos[n] = m_snake2.m_pos[n - 1];
if (0 == m_snake2.m_direction)
m_snake2.m_pos[0].y = m_snake2.m_pos[0].y - 1;
if (1 == m_snake2.m_direction)
m_snake2.m_pos[0].x = m_snake2.m_pos[0].x + 1;
if (2 == m_snake2.m_direction)
m_snake2.m_pos[0].y = m_snake2.m_pos[0].y + 1;
if (3 == m_snake2.m_direction)
m_snake2.m_pos[0].x = m_snake2.m_pos[0].x - 1;
//捕获食物判定
for (int i = 0; i < NUMBER; i++) {
if(m_snake.m_pos[0].x==m_food[i].x&&m_snake.m_pos[0].y==m_food[i].y)
{
m_snake.len++; //身体增长
foods[m_food[i].x][m_food[i].y] = 0; //防止食物重复打印系数 归零
generatefood(i); //食物更新
}
if (m_snake2.m_pos[0].x == m_food[i].x && m_snake2.m_pos[0].y == m_food[i].y)
{
m_snake2.len++;
foods[m_food[i].x][m_food[i].y] = 0;
generatefood(i);
}
}
//舞狮 死亡判定
for (int i = 1; i < m_snake2.len; i++)
{
if (m_snake.m_pos[0].x == m_snake2.m_pos[i].x && m_snake.m_pos[0].y == m_snake2.m_pos[i].y)
{
KillTimer(hWnd, 1);
if (IDYES == MessageBox(hWnd, "年兽夺魁", "提示", MB_YESNO))
OnStart(hWnd);
else
PostQuitMessage(0);
}
}
if(m_snake.m_pos[0].x>col||m_snake.m_pos[0].y>row||m_snake.m_pos[0].x<0||m_snake.m_pos[0].y<0)
{
KillTimer(hWnd,1);
if(IDYES == MessageBox(hWnd, "年兽夺魁", "提示",MB_YESNO))
OnStart(hWnd);
else
PostQuitMessage(0);
}
//年兽 死亡判定
for (int i = 1; i < m_snake.len; i++)
{
if (m_snake2.m_pos[0].x == m_snake.m_pos[i].x && m_snake2.m_pos[0].y == m_snake.m_pos[i].y)
{
KillTimer(hWnd, 1);
if (IDYES == MessageBox(hWnd, "舞狮夺魁", "提示", MB_YESNO))
OnStart(hWnd);
else
PostQuitMessage(0);
}
}
if (m_snake2.m_pos[0].x > col || m_snake2.m_pos[0].y > row || m_snake2.m_pos[0].x < 0 || m_snake2.m_pos[0].y < 0)
{
KillTimer(hWnd,1);
if (IDYES == MessageBox(hWnd, "舞狮夺魁", "提示", MB_YESNO))
OnStart(hWnd);
else
PostQuitMessage(0);
}
//画面刷新
hdc=GetDC(hWnd);
MyPaint(hdc);
break;
case WM_PAINT:
//画面产生
hdc = BeginPaint(hWnd, &ps);
MyPaint(hdc);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}