C语言三款游戏(贪吃蛇、推箱子、小鸟飞飞)(含gui)(含完整代码)
今天整理blog的时候,在草稿箱里发现写关于第一学期C语言结课大作业的blog,我们是做了三款游戏加上Gui界面,我打算继续将blog写完(含有完整代码)
最近在写Java的游戏——飞机大战,推广一波,嘻嘻
运用C语言知识经行相关主题编程,实现特定功能。选择主题为:游戏。为此计划设计一共三款游戏的编写(贪吃蛇、推箱子、小鸟飞飞),并为了优化界面、增加用户友好度,我自学了Windows程序设计,略懂皮毛,利用Visual C++6.0编写了一个窗口类。
成果展示
这个是这个界面及图标设计
这是打开贪吃蛇;
这是打开推箱子;
这是打开了小鸟飞飞;
当然你不可能永远都赢。
以上是运行结果和运行界面的截图。
中间部分为工程实现过程,会有些冗长,由我和另外几个组员分别完成,衔接处可能会有些生硬。工程源码在文章最后!!!
PBL项目架构
上图为此次PBL工程的大体构架。
主干分为:游戏与窗口两大核心部分。游戏下辖部分含有对贪吃蛇、推箱子、小鸟飞飞三大游戏板块的设计。而在窗口部分含有对窗口类的注册、设计窗口参数、个性化设计、及显示窗口与无线消息接收与处理循环等任务设计。然后要做的就是要将这两大板块经行对接。使其可以相互协调运转。
PBL具体工程
游戏部分
贪吃蛇
①对蛇的状态的定义 利用结构体实现有关蛇的性质:
初始化蛇的长度和速度:
对蛇的坐标的定义 :(蛇在横坐标中占两个单位的长度)
打印蛇头和蛇身:
②按键函数:贪吃蛇的主体是通过上下左右的按键来实现。因此对于↑↓←→用对应的数字表示,通过查资料可得
↑为72,↓为80,←为75,→为77
每一次操作前记录前一个按键的方向:
使用函数kbhit()来检查当前是否有键盘输入值,若有则返回一个非0数,否则返回0。
fflush 函数清空缓冲区,防止scanf的误读:
使用getch返回最终的实际值:
调用两次得到最终返回值。
控制蛇身运动时,要保证不能直接向蛇运动相反的方向直接移动,若玩家想让蛇直接掉头,输出结果是与前一次蛇身运动方向相同。
移动光标,实现蛇转弯,依次打印出蛇头。
其中当蛇身吃到食物与没吃到食物有区别,当蛇身吃到食物时,移动光标,直接打印蛇头,实现移动,当没有吃到食物时,则在打印蛇头的基础上加上清除蛇尾,实现移动。
当吃到食物时changeflag变为1:吃到食物后 蛇的状态会发生一系列的改变:
③判断蛇是否符合规定即
1.判断坐标是否碰到边界
2.判断蛇身是否与蛇头相碰 在函数的开头部分设置宏定义来 运用bool类型来判断输赢
④光标的移动函数:在随机生成食物,打印蛇的头部和身体的移动时需要光标的变化来实现:定义光标的结构体coord 获得标准输出(屏幕)句柄,移动光标。
打印完蛇身后将光标移到屏幕最上方,避免光标在蛇身处一直闪烁。
define定义
运用define来定义蛇的最大长度、游戏边框的长度和宽度。
2.2二维坐标的定义
运用struct定义二维坐标,即食物的坐标(位置)。
2.3游戏边框的绘制
①上下边框
首先,使用for循环语句绘制上下边框。
保持纵坐标不变,在最上方y=0处,输出边框,边框的长度由MAPWIDTH决定。
同上,在y=MAPHEIGHT,即最下方位置y=24的位置,输出边框。
②左右边框
同样使用for循环语句。
保持横坐标不变,在最左侧x=0处,输出边框,边框长度由MAPHEIGHT决定。
同上,在x=MAPWIDTH,即最右侧位置x=78处,输出边框。
③第一个食物的生成
为了实现第一个食物与边框同时生成,在绘制游戏边框的时候,在drawMap函数中就要有随机生成食物的函数:
由于每个在横坐标方向占两个单位,在纵坐标方向占一个单位,上下左右都有边框,而食物不能在边框上生成,所以食物生成时,要在横坐标方向减4,在纵坐标方向减2,而在由长为(MAPWIDTH-4)与宽为(MAPHEIGHT-2)的封闭平面中随机坐标点在长为MAPWIDTH与宽为MAPHEIGHT的封闭平面中由于左边框与上下边框横纵坐标分别占两个单位和一个单位,所以food.x要加二,food.y加一。
用rand()函数产生随机数,为使最终坐标数据在对应平面中定位(产生1-74和1-20的数),使用rand()%来实现。
srand和rand函数:在调用rand()函数之前,可以使用srand()函数设置随机数种子,需要的头文件是<stdlib.h>。
由于食物在横坐标方向占两个单位,边框的长度是偶数,而food.x表示食物左侧的横坐标,为了不使食物在边框上生成,需要food.x为偶数。
食物坐标满足条件后,就可以生成食物了,这里我用了gotoxy函数,实现光标的移动,食物在对应坐标点生成。
2.4随机生成食物
这个if语句用于判断蛇是否吃到食物,当蛇头移动到食物位置时,食物再次生成。
为使食物不在边框及蛇身上,这里我加了一个变量flag,当食物生成在蛇身时,flag改变,食物生成失败,从而避免了食物在蛇身上生成。
代码补充:写到这儿,我想到了一点不足之处,在绘制游戏边框时随机生成食物的时候没有考虑到这个问题,由于小组组员制作贪吃蛇首次出现的位置是确定的(在游戏屏幕中央),因此加了这样一个while语句
使食物在除蛇头之外的点生成。
同样用gotoxy()函数生成食物。
2.5主函数
①通过头文件来调用库功能
这样,主函数中按照头文件中的接口声明来调用库函数,而不必关心接口是怎么实现的。连接器会从库中提取相应的代码,并和用户的程序连接生成可执行文件或者动态连接库文件。
②调用函数
直接使用函数库里面的函数,先调用绘制游戏边框函数:
在生成游戏边框的同时生成第一个食物。
为使游戏持续进行,这里我使用了一个无限的while循环,只有当蛇头碰到时,停止循环,在这样一个while循环中实现方向的改变、食物的随机生成、蛇状态的判断、以及蛇状态的改变。
同样使用gotoxy函数,使得游戏结束时结束语在固定位置:
游戏结束后,为使游戏体验者看到自己的得分,用了Sleep()函数,停顿五秒后,程序自动退出。
- 实验数据及结果分析/Data& Analytics
代码不断完善后,程序便可以正常使用了,我们可以看到游戏运行界面如下:
游戏开始,蛇从屏幕中央出现,并自下而上运动,食物随机生成。
随着蛇不断吃到食物,蛇身边长:
当蛇头撞到时,即撞到墙或蛇身时,游戏结束。
如图:
随着蛇身不断变长,蛇的速度也相应增加,这就使得游戏难度不断增大,游戏更有趣。
容易看到,蛇的初始长度为三,墙的长和宽分别为78和24,均由■组成。蛇的食物由表示,每当蛇吃到一个,蛇长度增加一,蛇身加一个■,得分加十,蛇速度加五。
推箱子
首先要设计出游戏界面,定义一个二维数组规定出各种游戏图案和游戏背景最初所在的位置,也就是记录屏幕上各点的状态,如图:
这是一个九行十一列的数组,其中,0代表着空白的地方,1代表着墙,2代表着玩家,3代表着箱子,4代表着箱子要推至的终点位置。图的变化要靠自己来编写数组,通过数字来进行图的构造。
由于该游戏程序中需要用到一些函数,所以我们先将函数声明一下,之后会对它们一一进行定义:
共三个函数。
接下来编写的是主函数,主函数中进入游戏循环,这个游戏的主循环是等待按键,当接受到上下左右键时执行相关操作。因此,我们运用while语句,编写一个while(1)的无限循环,以保证玩家每次操作后程序正常继续运行。因为要记录人物移动的位置,把移动后的画面呈现在屏幕上,所以我在while循环中编写了system(“cls”);drawmain();tuidong(); 其中drawmain和tuidong分别是之前声明过的打印游戏画面和涉及人物移动功能的两个函数,我在两者之前加了系统命令cls,目的是对屏幕进行清屏,以免在操作过程中重复出现很多游戏界面,影响观感和游戏体验。
2.2.2 函数的功能及定义/Functions and definitions of functions
定义drawmain函数,目的是让程序将游戏界面和各种图形刻画出来。在每次移动操作后均要判断游戏是否结束,所以在drawmain函数一开始,我调用判断输赢的函数,以便玩家赢了游戏之后及时结束游戏并显示提示语。之后运用for语句和switch语句,以此检测数组各单元位置上所应打印出的字符,设置7种情况,分别对应空白区域、墙、人物、箱子、终点位置、人物与终点重叠位置和箱子与终点重叠位置,用易于区分的不同图标标注这些位置。
定义winshu函数,用来确定整个游戏的输赢情况。定义k,表示未处在终点位置上的箱子的数量,初值为0。运用for语句,依次检测数组各个位置上是否有代表箱子没有与终点位置重合的数字,如果有,则k=k+1 。循环结束后,如果k的值仍然为0,那么代表游戏中的箱子全部都被推至终点,玩家赢得游戏胜利,游戏结束,此时,打印“恭喜你,你赢了!”字样在游戏界面上。
2.1确定人的位置
int tuidong()
首先调用开头新定义的函数tuidong(推动),然后对此函数进行定义;
int count,caw;
要想实现对小人的推动,首先需要判断小人此刻所处的位置,才能对小人的状态进行分析,从而判定小人下一步的行动是否能够成功和此行动的结果。所以,我新定义了两个函数:count(行),caw(列), map[count][caw]即为小人目前所处的位置。
for(int i=0;i<9;i++)
for (int j=0;j<11;j++)
根据于雅歌同学的定义,小人的位置用“2”或“6”表示,所以我们需要找到地图中”2”,”6”的位置,定义i,i表示行,i大于等于0小于等于9,表示第0行到第9行,定义j,j表示列,j大于等于0小于等于11,表示第0列到第11列。
if(map[i][j]==2||map[i][j]==6)
count=i;
caw=j;
找到地图中小人的位置,并将此位置的行与列赋予之前定义好的count和caw中,至此,我们就成功地将小人在地图中所处的位置找了出来。
2.2实现推动箱子
2.2.1如何向上推动
int tui=getch();
首先定义一个函数tui等于一个输入值,注意这里使用的是getch(),与getchar()有区别的是:getchar()输入一个字符后需要回车来进行下一个字符的输入,比较麻烦,而getch()则不需要回车就能连续输入多个字符,这样,我们操作时就会方便很多。
switch(tui)
此分支结构表示getch()输入的四种字符“w”,“a”,“s”,“d”
case‘w’
case72
输入w或上键,玩家做出小人向上的操作,之后,我们就对小人的状态进行判定,来看小人是否能够向上移动和向上移动后的结果。
if(map[count-1][caw]==0||map[count-1][caw]==4)
我们知道,如果小人想要向上移动,会有两种情况,第一种情况是小人上方为空白的地方,小人可以直接上去,而另一种情况为小人上方为箱子,小人将箱子推了上去,所以我们首先对第一种情况进行讨论。第一种情况出现的条件为:小人上方的位置为空白的地方,即小人的上一行同列的位置为“0”或“4”(4代表终点位置,实际上也是空白的地方)。
map[count][caw]-=2;
map[count-1][caw]+=2;
移动后的结果为:小人移动前的位置,即count和caw函数目前的位置,变成了空白的地方,由“2”或“6”变成了“0”或“4”,所以使count和caw函数值减2;小人移动后的位置,即count-1和caw的位置,变成了小人,由“0”或“4”变成了“2”或“6”,所以使count-1和caw函数值加2。
else if(map[count-1][caw]==3||map[count-1][caw]==7)
if(map[count-2][caw]==0||map[count-2][caw]==4
第二种情况出现的条件为:小人上方的位置为箱子,并且箱子上方为空白的地方,因为在我们的游戏中,小人是推不动两个箱子的,即小人的上一行同列的位置为“3”,小人上两行同列的位置为“0”或“4”
map[count][caw]-=2;
map[count-1][caw]-=1;
map[count-2][caw]+=3;
移动后的结果为:小人移动前的位置,即count和caw函数目前的位置,变成了空白的地方,由“2”或“6”变成了“0”或“4”,所以使count和caw函数值减2;小人移动后的位置,即count-1和caw的位置,变成了小人,由“3”变成了“2”或“6”,所以使count-1和caw函数值减1。小人移动后的位置的上方,即count-2和caw的位置,变成了箱子,由“0”或“4”变成了“3”,所以使count-2和caw函数值加3。
2.2.2如何向下推动
与向上的操作类似,向下操作与向上有着相似的判定方式,就不再赘述了。
2.2.3如何向左推动
与向上的操作类似,向左操作与向上有着相似的判定方式,就不再赘述了。
2.2.4如何向右推动
小鸟飞飞
2.2.1主体程序
(1)头文件的定义及引用:
首先测试名称是否已被定义,防止重复包含和编译头文件,并定义宏。
引用头文件。
进行windows程序设计时必须引用的头文件。
引用头文件,定义了通过控制台进行数据输入和数据输出时的函数,主要是一些用户通过操控键盘产生的对应操作。
标准输入输出头文件。
引用头文件。
初始化二维数组。
定义地图的高度、宽度,小鸟的位置,通道的y坐标和墙的起始坐标x,记录页面的显示(0代表空白,1代表小鸟的位置,2代表墙,3代表上下界的围墙,4代表左右界的围墙)定义bool类型进行逻辑判断,若判断为真时,说明该点坐标有围墙,(用来判断小鸟过墙时,是否撞墙)。游戏结束时,1代表失败,0代表胜利。记录得分。
定义一个函数startup(),初始化游戏的数值。(定义游戏得分,地图的高度与宽度,小鸟的位置,通道中点纵坐标和墙横坐标的初始状态)
定义一个函数startMap(),首先通过for循环嵌套,通过行的上移和列的右移,向右方和上方打印空白,当列向右移动到width时,为右围墙,停止右移,游戏打开时的初始画面完成。
固定行的值为地图的高度,也就是最下方的一行,列一次向右移动,直到地图的宽度停止,所打印出的即为下边界。
初始化小鸟的位置。
初始化墙的位置。
通道的初始化。(可以自定义通道的大小)。本题定义通道的宽度为5.
定义一个隐藏光标的函数。
HANDLE句柄,表示windows中的对象,GetStdHandle(STD_OUTPUT_HANDLE),取得句柄,coord定义字符在控制台上的坐标,SetConcoleCursorPosition定位光标位置。定义了一个清理部分屏幕的函数。
定义更新无关输入函数,随着游戏的进行,小鸟向右移动,墙向左移动。
情况1:小鸟死亡:当bool类型判断小鸟的坐标为真时,说明小鸟此时的坐标有围墙,也就是小鸟撞墙或者小鸟的高度高于地图的高度,result值为1,游戏失败。
情况2:小鸟成功飞越,墙的高度比小鸟的高度小1,得分加1。
情况2:对于通道的随机生成:当小鸟成功飞越时,(生成的墙的横坐标小于1)重新生成通道,运用srand()和rand()函数,生成随机数。由上述可知,通道的宽度为5,through为通道的中点的纵坐标,在此为随机生成的数对高度取余后所获得的数,为了保证生成的通道距离上边界和下边界的距离最小为1,则需要取余后的得数小于等于3大于等于18,若不满足此条件,则重新生成随机数,对高度取余。
情况2:对于墙的随机生成:当小鸟成功飞跃时,(生成的墙的横坐标小于1),此时,重新生成一堵墙,生成的位置是3/4地图的宽度处。随后运用memset函数,初始化新申请的内容,在此刷新小鸟过墙时需要判断的坐标,再一次进行判断小鸟是否成功过墙。同时,运用Sleep(100)使程序延时,暂停0.1秒,使得游戏与使用者有一个成功的交互。
定义与用户输入有关的更新的函数。运用kbhit函数判断键盘输入,当用户输入空格时,小鸟向上移动两格。
最后定义显示界面的函数,清理一部分屏幕后,打印定义的各种符号,将上述定义的空白,下界,左右边界,小鸟,墙壁进行可视化输出。
最终打印游戏得分情况,和操作提示,并完成头文件中宏的定义。
(2)主函数的编写
调用已定义的函数,判断游戏的输赢,调用系统命令system”cls”,实现清除屏幕的操作。
2.2.2 冲突函数的协调
在编译运行时,gotoxy()函数与贪吃蛇程序中的函数冲突,我们将其改成wenttoxy()函数,
2.2.3 风格设计
在此游戏中,我们仅仅通过一个DOS命令将控制台的背景换成白色。并无其他风格设计。
2.3 窗口链接
先设置窗口中小鸟飞飞的按钮id为112,当程序执行到COMMAND函数时,若操作者通过鼠标点击小鸟飞飞游戏按钮,则执行case 112,打开小鸟飞飞的程序。
Window窗口
创建工程类型
由于WINDOWS设计不同于原来C语言源文件的设计,在这里,我们对该工程创建类型的选择也有所不同。下列描述整个过程
运行VC加加6.0,选择菜单命令"文件/新建",在打开的新建对话框中打开工程选项,单击选择工程类型Win32 Application,然后在右侧工程名称中输入功成名,然后选择该文件位置路径,点击确定。
由于窗口创建,虽然使用C语言,但是其代码编程框架与我们源文件书写相异,在此我们直接选择创建一个简单的win32程序。
在VC++6.0工作空间中,查看该工程的类目录(Class View),然后点开该创建内,然后再点开全局文件,里面就会有一个Winmain文件,Winmain就是相当于我们平时在Win32 console Application工程下的main函数一样是整个工程的主体。点击进去就是这样的一个框架。
在他的注释下就可以了!
2.2.窗口特色化设计
2.2.1窗口的基本设置
1)声明窗口的原型
该处函数原型内设定参数:窗口句柄,消息,消息参数,消息参数
2)窗口的类名的声明
3)注册窗口类
这里要说明一下:WNDCLASS结构包含了RegisterClass函数注册窗口类时的窗口类属性。这个结构在使用RegisterClassEx函数注册窗口类时被WNDCLASSEX结构所取代。我们需要用以注册窗口类的API函数就是RegisterClassEx。
4)利用Wndclass结构指定窗口类风格
我们需要将设计的主窗口的参数放到Wndclass结构里
首先是结构的大小:
这个窗口的大小肯定也不能固定,因为我们是可以通过放缩来调解这个窗口的大小的,所以我们需要设置一个在窗口大小发生改变的时候,就让他能够刷新一下页面重画的功能:
我需要在窗口收到消息时Windows会自动调用函数去通知应用程序,而lpfnWndProc就指定了基于此类的窗口的窗口函数,所以需要设置个窗口函数指针:
对于wndclass结构的cbClsExtra与cbWndExtra是额外的类内存和额外的窗口内存,这个就让他们参数为零,不分配额外内存:
我们要把该程序的实例句柄(WinMain的 一个参数)传给hInstance成员:
然后设计一个光标,我们就是采用了他的预定义的那个光标:
设计图标,外部的圆环是一个“C”寓意着C语言的PBL,这个外观就像一只贪吃蛇的蛇头与食物,内部的食物上写的“pbl”是我们这一次小组项目,象征着我们该次项目三大游戏板块中的贪吃蛇游戏,并寓意象征了我们组可以将这次PBL成功完成。
而在对图标的外源引入的时候需要运用到建立一个资源文件,并且要将图片的从pdf转换ico格式,然后导入到资源文件中,便可调用。
然后调用注意这里资源是ICON1:
背景就用纯色画刷的天蓝色背景这里要创一个刷子用RGB(RED-GREEN-BLUE)调一下色:
在前面的窗口预创建的时候先不用建菜单:
窗口名称就用之前声明过的(OUR PBL-- GAME WORLD):
小图标这里也设为空:
上面这些就把描述主窗口的参数填充到WNDCLASSEX结构了,接下来要注册窗口类i,创建主窗口。
先注册这个窗口类(刚刚那个wndclass,那个RegisterClassEx就是RegisterClass函数从Win16的扩展,“::”这个是调用全局API函数,避免作用域内的函数歧义 ):
我们就可以用注册的窗口内的类名调用CreateWindowEx函数来创建窗口
首先的元素是dwExStyle,扩展样式,填缺省值就行:
接着的是lpClassName,类名:
写个标题:
设置个窗口风格,这个有很多种,我选择的是一个传统较为通用的(含标题,最大化,最小化等):
还需要初始X和Y的坐标:
设置一下宽度与高度:
然后是父窗口句柄、菜单句柄和程序实例句柄:
最后是用户数据:
如果窗口句柄失效就发送消息:
要在桌面上显示窗口Show Window函数,用于设置指定窗口的显示状态,nCmdshow是系统传给WinMain函数的参数:
然后再更新窗口客户区:(如果指定窗口的更新区域不为空的话,UpdateWindow函数通过向这个窗口发一个WM_PAINT消息,更新他的客户区,当窗口显示在屏幕上时,窗口的客户区被在WNDCLASSEX中指定的刷子擦去了,调用函数将促使客户需重画一显示其内容)
再从消息队列中取出消息,交给窗口函数处理,直到GetMessage返回FALSE,结束消息循环:
转化键盘消息:
将消息发送到相应的窗口函数:
当GetMessage返回FALSE时程序结束:
然后绘制子窗口:
插入文本:
设置子窗口的句柄:
现在再来设计当接收到消息时的应答状况;
如果处理了这个消息,则默认消息处理函数不会调用,背景就不会绘制:
我们要通过WM_CREATE创建三个按钮,
在接收到 WM_CREATE 消息时创建一个按钮:
设置按钮上的文本:
在父窗口客户区(400, 100)位置创建一个按钮:
设计的按钮大小为100*50:
输入句柄及其控件ID:
如法炮制另外两个按钮:
然后是控制(WM_command)按钮使其可以触发事件,我们采用的是直接触发运行对应代码的EXE执行文件:
然后就是模块化的重画窗口与销毁窗口的部分,
窗口客户区需要重画:
使无效的客户区变的有效,并取得设备环境句柄:
显示文本的文字:
然后就是最后销毁窗口的处理:
再将我们不处理的消息交给系统做默认处理:
总而言之,创建窗口的主要结构流程就是:
一、注册窗口类(RegisterClassEx)
二、创建窗口(CreateWindowEx)
三、在桌面显示窗口(ShowWindow)
四、更新窗口客户区(UpdateWindow)
五、进入无线的消息获取和处理循环。
首先是获取消息(GetMessage),如果有消息到达,则将消息分派到回调函数进行处理(DispatchMessage),如果消息是WM_QUIT,则GetMessage函数返回FALSE,整个消息循环结束,消息具体的处理过程是在MainWndProc函数中进行的。
工程源码
注意,这是工程代码的总和,需要按照注释分为多文件工程
窗口程序
// trying1.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>//windows编程头文件
#include <time.h>
#include <conio.h>//控制台输入输出头文件
#include"pblsnake.h"
#include"pblbird.h"
#include"resource.h"
//窗口函数的函数模型
LRESULT CALLBACK MainWndProc(HWND,UINT,WPARAM,LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
char szClassName[] = "OUR PBL-- GAME WORLD";
WNDCLASSEX wndclass;
//用描述主窗口的参数填充WNDCLASSEX结构
wndclass.cbSize=sizeof(wndclass);//结构的大小
wndclass.style=CS_HREDRAW|CS_VREDRAW;//指定如果大小改变就重画
wndclass.lpfnWndProc=MainWndProc;//窗口函数指针
wndclass.cbClsExtra=0;//没有额外的类内存
wndclass.cbWndExtra=0;//没有额外的窗口内存
wndclass.hInstance=hInstance;//实例句柄
/*wndclass.hIcon=::LoadIcon(NULL, IDI_APPLICATION);//使用预定义图标*/
wndclass.hIcon = (HICON)LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON2));
wndclass.hCursor=::LoadCursor(NULL, IDC_ARROW);//使用预定义的光标
wndclass.hbrBackground=(HBRUSH)::GetStockObject(WHITE_BRUSH);//使用白色背景画刷
wndclass.lpszMenuName=NULL;//不指定菜单
wndclass.lpszClassName=szClassName;//窗口类的名称
wndclass.hIconSm=NULL;//没有类的小图标
//注册这个窗口类
::RegisterClassEx(&wndclass);
//创建主窗口
HWND hwnd=::CreateWindowEx(
0,// dwExStyle,扩展样式
szClassName,// lpClassName,类名
"OUR PBL-- GAME WORLD!", // lpWindowName,标题
WS_OVERLAPPEDWINDOW, // dwStyle,窗口风格
CW_USEDEFAULT,//X,初始X坐标
CW_USEDEFAULT,//Y,初始Y坐标
CW_USEDEFAULT,//nWidth,宽度zhe
CW_USEDEFAULT,//nHeight,高度
NULL,//hWndParent,父窗口句柄
NULL,//1I hMenu,菜单句柄
hInstance,// hlInstance,程序实例句柄
NULL);// IpParam,用户数据
if(hwnd==NULL)
{
::MessageBox(NULL, "创建窗口出错! ","error", MB_OK);
return-1;
}
//显示窗口,刷新窗口客户区
::ShowWindow(hwnd,nCmdShow);
::UpdateWindow(hwnd);
//从消息队列中取出消息,交给窗口函数处理,直到GetMessage返回FALSE,结束消息循环
MSG msg;
while(::GetMessage(&msg, NULL, 0, 0))
{
//转化键盘消息
::TranslateMessage(&msg);
//将消息发送到相应的窗口函数
::DispatchMessage(&msg);
}
//当GetMessage返回FALSE时程序结束
return msg.wParam;
}
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM IParam)
{
char szText[]= "欢迎来到PBL之游戏世界! ";/*
switch (message)
{
case WM_PAINT://窗口客户区需要重画
{
HDC hdc;
PAINTSTRUCT ps;
//使无效的客户区变的有效,并取得设备环境句柄
hdc=::BeginPaint(hwnd,&ps);
//显示文字
::TextOut(hdc,10,10,szText,strlen(szText));
::EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY://正在销毁窗口
//向消息队列投递一个WM QUIT消息,促使GetMessage函数返回0, 结束消息循环
::PostQuitMessage(0);
return 0 ;*/
static HWND btnHwnd ; //子窗口句柄
switch(message)
{
case WM_ERASEBKGND: //如果处理了这个消息,则默认消息处理函数不会调用,背景就不会绘制
{
break;
}
case WM_CREATE: //在接收到 WM_CREATE 消息时创建一个按钮
btnHwnd = CreateWindow( TEXT("button"), TEXT("贪吃蛇"),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
400, 100, //在父窗口客户区(10, 10)位置创建一个按钮
100, 50, //按钮的大小为100x30
hwnd, //父窗口句柄
(HMENU)110, //按钮控件ID
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE ),
NULL ) ;
/* case WM_DESTROY:
PostQuitMessage( 0 ) ;
static HWND btnHwnd ; */ //子窗口句柄
//在接收到 WM_CREATE 消息时创建一个按钮
btnHwnd = CreateWindow( TEXT("button"), TEXT("推箱子"),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
400, 170, //在父窗口客户区(10, 10)位置创建一个按钮
100, 50, //按钮的大小为100x30
hwnd, //父窗口句柄
(HMENU)111, //按钮控件ID
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE ),
NULL ) ;
btnHwnd = CreateWindow( TEXT("button"), TEXT("小鸟飞飞"),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
400, 240, //在父窗口客户区(10, 10)位置创建一个按钮
100, 50, //按钮的大小为100x30
hwnd, //父窗口句柄
(HMENU)112, //按钮控件ID
(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE ),
NULL ) ;
return 0;
case WM_PAINT://窗口客户区需要重画
{
HDC hdc;
PAINTSTRUCT ps;
//使无效的客户区变的有效,并取得设备环境句柄
hdc=::BeginPaint(hwnd,&ps);
//显示文字
::TextOut(hdc,360,50,szText,strlen(szText));
::EndPaint(hwnd, &ps);
return 0;
}
case WM_COMMAND:
switch(LOWORD(wParam))
{
case 110: system("贪吃蛇.exe");break;
case 111: system("推箱子.exe");break;
case 112: system("小鸟飞飞.exe");break;
}
case WM_DESTROY:
PostQuitMessage( 0 ) ;
return 0 ;
}
//将我们不处理的消息交给系统做默认处理
return::DefWindowProc(hwnd,message,wParam,IParam);}
StdAfx.h文件(VC++6.0自主生成)
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#if !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
#define AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)
Pblsnake.h文件
#ifndef _PBLSNAKE_H_
#define _PBLSNAKE_H_
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>//windows编程头文件
#include <time.h>
#include <conio.h>//控制台输入输出头文件
#ifndef __cplusplus
typedef char bool;
#define false 0
#define true 1
#endif
//将光标移动到控制台的(x,y)坐标点处
void gotoxy(int x, int y)
{
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
#define SNAKESIZE 100//蛇的身体最大节数
#define MAPWIDTH 78//宽度
#define MAPHEIGHT 24//高度
//食物的坐标
struct {
int x;
int y;
}food;
//蛇的相关属性
struct {
int speed;//蛇移动的速度
int len;//蛇的长度
int x[SNAKESIZE];//组成蛇身的每一个小方块中x的坐标
int y[SNAKESIZE];//组成蛇身的每一个小方块中y的坐标
}snake;
//绘制游戏边框
void drawMap();
//随机生成食物
void createFood();
//按键操作
void keyDown();
//蛇的状态
bool snakeStatus();
//从控制台移动光标
void gotoxy(int x, int y);
int key = 72;//表示蛇移动的方向,72为按下“↑”所代表的数字
//用来判断蛇是否吃掉了食物,这一步很重要,涉及到是否会有蛇身移动的效果以及蛇身增长的效果
int changeFlag = 0;
int sorce = 0;//记录玩家的得分
int i;
void drawMap()
{
//打印上下边框
for (i = 0; i <= MAPWIDTH; i += 2)//i+=2是因为横向占用的是两个位置
{
//将光标移动依次到(i,0)处打印上边框
gotoxy(i, 0);
printf("■");
//将光标移动依次到(i,MAPHEIGHT)处打印下边框
gotoxy(i, MAPHEIGHT);
printf("■");
}
//打印左右边框
for (i = 1; i < MAPHEIGHT; i++)
{
//将光标移动依次到(0,i)处打印左边框
gotoxy(0, i);
printf("■");
//将光标移动依次到(MAPWIDTH, i)处打印左边框
gotoxy(MAPWIDTH, i);
printf("■");
}
//随机生成初试食物
while (1)
{
srand((unsigned int)time(NULL));
food.x = rand() % (MAPWIDTH - 4) + 2;
food.y = rand() % (MAPHEIGHT - 2) + 1;
//生成的食物横坐标的奇偶必须和初试时蛇头所在坐标的奇偶一致,因为一个字符占两个字节位置,若不一致
//会导致吃食物的时候只吃到一半
if (food.x % 2 == 0)
break;
}
//将光标移到食物的坐标处打印食物
gotoxy(food.x, food.y);
printf("¥");
//初始化蛇的属性
snake.len = 3;
snake.speed = 200;
//在屏幕中间生成蛇头
snake.x[0] = MAPWIDTH / 2 + 1;//x坐标为偶数
snake.y[0] = MAPHEIGHT / 2;
//打印蛇头
gotoxy(snake.x[0], snake.y[0]);
printf("■");
//生成初试的蛇身
for (i = 1; i < snake.len; i++)
{
//蛇身的打印,纵坐标不变,横坐标为上一节蛇身的坐标值+2
snake.x[i] = snake.x[i - 1] + 2;
snake.y[i] = snake.y[i - 1];
gotoxy(snake.x[i], snake.y[i]);
printf("■");
}
//打印完蛇身后将光标移到屏幕最上方,避免光标在蛇身处一直闪烁
gotoxy(MAPWIDTH - 2, 0);
return;
}
void keyDown()
{
int pre_key = key;//记录前一个按键的方向
if (_kbhit())//如果用户按下了键盘中的某个键
{
fflush(stdin);//清空缓冲区的字符
//getch()读取方向键的时候,会返回两次,第一次调用返回0或者224,第二次调用返回的才是实际值
key = _getch();//第一次调用返回的不是实际值
key = _getch();//第二次调用返回实际值
}
/*
*蛇移动时候先擦去蛇尾的一节
*changeFlag为0表明此时没有吃到食物,因此每走一步就要擦除掉蛇尾,以此营造一个移动的效果
*为1表明吃到了食物,就不需要擦除蛇尾,以此营造一个蛇身增长的效果
*/
if (changeFlag == 0)
{
gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);
printf(" ");//在蛇尾处输出空格即擦去蛇尾
}
//将蛇的每一节依次向前移动一节(蛇头除外)
for (i = snake.len - 1; i > 0; i--)
{
snake.x[i] = snake.x[i - 1];
snake.y[i] = snake.y[i - 1];
}
//蛇当前移动的方向不能和前一次的方向相反,比如蛇往左走的时候不能直接按右键往右走
//如果当前移动方向和前一次方向相反的话,把当前移动的方向改为前一次的方向
if (pre_key == 72 && key == 80)
key = 72;
if (pre_key == 80 && key == 72)
key = 80;
if (pre_key == 75 && key == 77)
key = 75;
if (pre_key == 77 && key == 75)
key = 77;
/**
*控制台按键所代表的数字
*“↑”:72
*“↓”:80
*“←”:75
*“→”:77
*/
//判断蛇头应该往哪个方向移动
switch (key)
{
case 75:
snake.x[0] -= 2;//往左
break;
case 77:
snake.x[0] += 2;//往右
break;
case 72:
snake.y[0]--;//往上
break;
case 80:
snake.y[0]++;//往下
break;
}
//打印出蛇头
gotoxy(snake.x[0], snake.y[0]);
printf("■");
gotoxy(MAPWIDTH - 2, 0);
//由于目前没有吃到食物,changFlag值为0
changeFlag = 0;
return;
}
void createFood()
{
if (snake.x[0] == food.x && snake.y[0] == food.y)//蛇头碰到食物
{
//蛇头碰到食物即为要吃掉这个食物了,因此需要再次生成一个食物
while (1)
{
int flag = 1;
srand((unsigned int)time(NULL));
food.x = rand() % (MAPWIDTH - 4) + 2;
food.y = rand() % (MAPHEIGHT - 2) + 1;
//随机生成的食物不能在蛇的身体上
for (i = 0; i < snake.len; i++)
{
if (snake.x[i] == food.x && snake.y[i] == food.y)
{
flag = 0;
break;
}
}
//随机生成的食物不能横坐标为奇数,也不能在蛇身,否则重新生成
if (flag && food.x % 2 == 0)
break;
}
//绘制食物
gotoxy(food.x, food.y);
printf("¥");
snake.len++;//吃到食物,蛇身长度加1
sorce += 10;//每个食物得10分
snake.speed -= 5;//随着吃的食物越来越多,速度会越来越快
changeFlag = 1;//很重要,因为吃到了食物,就不用再擦除蛇尾的那一节,以此来造成蛇身体增长的效果
}
return;
}
bool snakeStatus()
{
//蛇头碰到上下边界,游戏结束
if (snake.y[0] == 0 || snake.y[0] == MAPHEIGHT)
return false;
//蛇头碰到左右边界,游戏结束
if (snake.x[0] == 0 || snake.x[0] == MAPWIDTH)
return false;
//蛇头碰到蛇身,游戏结束
for (i = 1; i < snake.len; i++)
{
if (snake.x[i] == snake.x[0] && snake.y[i] == snake.y[0])
return false;
}
return true;
}
#endif
贪吃蛇.cpp
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>//windows编程头文件
#include <time.h>
#include <conio.h>//控制台输入输出头文件
#include"pblsnake.h"
int main(){
system("color F0");
int x;
drawMap();
while (1)
{
keyDown();
if (!snakeStatus())
break;
createFood();
Sleep(snake.speed);
}
gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2);
printf("Game Over!\n");
gotoxy(MAPWIDTH / 2, MAPHEIGHT / 2 + 1);
printf("本次游戏得分为:%d\n", sorce);
Sleep(5000);
return 0;
}
推箱子.cpp
#include<stdio.h>
#include<conio.h>
#include<windows.h>
int map[9][11]={
{0,1,1,1,1,1,1,1,1,1,0},
{0,1,0,0,0,1,0,0,0,1,0},
{0,1,0,3,3,3,3,3,0,1,0},
{0,1,0,3,0,3,0,3,0,1,1},
{0,1,0,0,0,2,0,0,3,0,1},
{1,1,0,1,1,1,1,0,3,0,1},
{1,0,4,4,4,4,4,1,0,0,1},
{1,0,4,4,4,4,4,0,0,1,1},
{1,1,1,1,1,1,1,1,1,1,0}
};//原始的图表,五行六列,其中 0 代表着空白的地方; 1 代表着墙;2 代表着人;
//3 代表着箱子;4 代表着箱子的中点位置。
//图的变化要靠自己来编写数组,通过数字来进行图的构造。
int drawmain();
int tuidong();
int winshu();
int main()//主函数
{
system("color F0");
while(1)
{
system("cls");//对其进行清屏
drawmain();
tuidong();
}
printf("shuchu \n");
return 0;
}
//把图形刻画出来
int drawmain()
{
int i,j;
winshu();//调用输赢的函数
for(i=0;i<9;i++)
{
for(j=0;j<11;j++)
{
switch(map[i][j])
{
case 0:
printf(" "); //空白的地方
break;
case 1:
printf("■"); //墙
break;
case 2:
printf("♀"); //人
break;
case 3:
printf("☆"); //箱子
break;
case 4:
printf("◎"); //终点地方
break;
case 6:
printf("♂");//人加终点位置
break;
case 7:
printf("★") ;//箱子加终点位置
break;
}
}
printf("\n");
}
}
//进行小人的移动,整个移动的过程就是数组变化的过程
int tuidong()
{
int count,caw;//行和列
for(int i=0;i<9;i++)//确定人的位置
{
for (int j=0;j<11;j++)
{
if(map[i][j]==2||map[i][j]==6)
{
count=i;
caw=j;
}
}
}
int tui=getch();//与getchar()有区别的是:getchar()输入一个字符后需要回车来进行下一个字符的输入,
//比较麻烦 ,getch()则不需要回车就能连续输入多个字符。
switch(tui)
{//上
case 'W':
case 72:
// 1.人的前面是空地;
// 2.人的前面是终点位置;
// 3.人的前面是箱子
//3.1.箱子的前面是空地;
//3.2.箱子的前面是终点位置。
if(map[count-1][caw]==0||map[count-1][caw]==4)
{
map[count][caw]-=2;
map[count-1][caw]+=2;
}
else if(map[count-1][caw]==3||map[count-1][caw]==7)
{
if(map[count-2][caw]==0||map[count-2][caw]==4)
{
map[count][caw]-=2;
map[count-1][caw]-=1;
map[count-2][caw]+=3;
}
}
break;
/* 移动的情况:
位置:
人 map[count][caw]
人的前面是空地 map[count-1][caw]
人的前面是终点位置 map[count-1][caw]
箱子的前面是空地或终点位置 map[count-2][caw]*/
//下
case 'S':
case 80://键值
if(map[count+1][caw]==0||map[count+1][caw]==4)
{
map[count][caw]-=2;
map[count+1][caw]+=2;
}
else if(map[count+2][caw]==0||map[count+2][caw]==4)
{
if(map[count+1][caw]==3||map[count+1][caw]==7)
{
map[count][caw]-=2;
map[count+1][caw]-=1;
map[count+2][caw]+=3;
}
}
break;
//左
case 'A':
case 75:
if(map[count][caw-1]==0||map[count][caw-1]==4)
{
map[count][caw]-=2;
map[count][caw-1]+=2;
}
else if(map[count][caw-2]==0||map[count][caw-2]==4)
{
if(map[count][caw-1]==3||map[count][caw-1]==7)
{
map[count][caw]-=2;
map[count][caw-1]-=1;
map[count][caw-2]+=3;
}
}
break;
//右
case 'D':
case 77:
if(map[count][caw+1]==0||map[count][caw+1]==4)
{
map[count][caw]-=2;
map[count][caw+1]+=2;
}
else if(map[count][caw+2]==0||map[count][caw+2]==4)
{
if(map[count][caw+1]==3||map[count][caw+1]==7)
{
map[count][caw]-=2;
map[count][caw+1]-=1;
map[count][caw+2]+=3;
}
}
break;
}
/*进行小人的上下左右的移动
移动的情况:
1.人的前面是空地;
2.人的前面是终点位置;
3.人的前面是箱子
3.1.箱子的前面是空地;
3.2.箱子的前面是终点位置。
不移动的情况:
1.人的前面是墙;
2.人的前面是箱子;
2.1.箱子的前面是墙 ;
2.2.箱子的前面是箱子;
*/
//分析后,要进行确定人的位置以及胜利的条件。
}
//整个游戏的输赢
int winshu()
{
int k=0;
for(int i=0;i<9;i++)
{
for (int j=0;j<11;j++)
{
if(map[i][j]==3)
k++;
}
}
if(k==0)
printf("恭喜你,你赢了!\n");
}
Pblbird.h文件
#ifndef _PBLBIRD_H_
#define _PBLBIRD_H_
#include<conio.h>
#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
#include<time.h>
#define MAX 100
int high,width;//地图高度与宽度
int bird_x,bird_y;//小鸟的位置
int through,wall;//通道的x坐标和墙的起始坐标y
int map[MAX][MAX];//记录页面的显示
/*0代表空白,1代表小鸟的位置,2代表墙
3代表上下围墙,4代表左右围墙*/
bool book[MAX][MAX];//代表改点有围墙
int score;//记录得分
bool result = 0;//游戏结果1代表失败,0代表胜利,不过永远赢不了~~
void startup()
{
score = 0;
high = 20;
width = 50;
bird_x = high/2;
bird_y = width/4;
through = high/2;
wall = width/4*3;
}
void startMap()
{
int i,j;
for(i=1;i<=high-1;i++)
{
map[i][1] = 4;
for(j=2;j<=width-1;j++)
map[i][j] = 0;
map[i][width] = 4;
}
//下方围墙的初始化
i = high;
for(j=1;j<=width;j++)
map[i][j] = 3;
//小鸟位置的初始化
map[bird_x][bird_y] = 1;
//墙的初始化
for(i=1;i<=high-1;i++)
{
map[i][wall] = 2;
book[i][wall] = 1;
}
//通道的初始化
for(i=through-2;i<=through+2;i++)//通道的大小可以自定义.
{
map[i][wall] = 0;
book[i][wall] = 0;
}
}
void HideCursor()//隐藏光标
{
CONSOLE_CURSOR_INFO cursor_info = {1, 0};
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void wenttoxy(int x,int y)//清理一部分屏幕
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle,pos);
}
void UPdatewithOutinput()//与输入无关的更新
{
bird_x++;
wall--;
if(book[bird_x][bird_y] == 1 || bird_x > high)//当小鸟死亡时
{
result = 1;
}
if(wall == bird_y-1)//当小鸟走过墙时得一分
score++;
if(wall < 1)
{
srand(time(NULL));
through = rand() % high;
while(through <= 3 || through >= high-2)
{
through = rand() % high;
}
}
if(wall < 1)
{
wall = width/4*3;
}
memset(book,0,sizeof(book));
Sleep(100);
}
void UPdatewithinput()
{
char input;
if(kbhit())//判断是否有键盘输入
{
input = getch();
if(input == ' ')
bird_x -= 2;//小鸟向上蹦两格
}
}
void show()
{
wenttoxy(0,0);
int i,j;
for(i=1;i<=high;i++)
{
for(j=1;j<=width;j++)
{
switch(map[i][j])
{
case 0:
printf(" ");
break;
case 1:
printf("@");
break;
case 2:
printf("*");
break;
case 3:
printf("~");
break;
case 4:
printf("|");
break;
}
}
printf("\n");
}
printf("你的分数是:%d\n\n",score);
printf("操作说明:空格键向上移动\n");
}
#endif
小鸟飞飞.cpp
#include<stdio.h>
#include<string.h>
#include"pblbird.h"
int main(void)
{
system("color F0");
startup();
while(1)
{
HideCursor();
startMap();
show();
UPdatewithOutinput();
if(result == 1)//is the bird die?
break;
UPdatewithinput();
}
system("cls");
printf("你输了");
getchar();
getchar();
return 0;
}