CodeBus

分享代码,一起进步~

墨攻棋阵 - 黑白棋中的 AI

到了考试周了佯,可是偏偏这个时候迎来了很多很多的课程设计,幸好教授把C语言的课程设计提前发出了,不然都在最后几周,加上数据结构的课程设计就没有时间做这个啦~ 刚开始打算做成UWP应用的,可是网上的教程都是C#,并且用C++做的话某些功能和C#不一样,所以就这样拖了好多周,省赛前一点儿也没有开始做,等到省赛结束之后,别人都差不多完成啦!而我才开始准备查找资料…… 然而一周过去了,进度还是0%。噫,1%吧! 眼看就要开始验收了,算了,还是用最简单的 EasyX 做吧!以后的 C# 课程设计再考虑 UWP。 周一开始敲代码,整整一周的课余时间,都在努力做这个,现在想起来,那个时候真的好累唉,居然没有感觉到~ 最初做这个游戏是因为想起来 秦时明月 中的 墨攻棋阵 ,也就是黑白棋,努力还原动漫中的场景,周末的时候终于完成了。 先附图:     怎么说千千也都是新手呢!感觉做的还算满意吧! 人机对战中有三种模式哦!简单、中等、困难 那么,接下来,我们一起来看看黑白棋中的AI是如何实现的。 对于我们来说,下棋的时候总是想着如何才能对自己最有利,当前最优?还是全局最优? 如果我们往后几步考虑的话,那便是全局最优啦!那当我们只看眼下哪一个位置的落子对自己最有利,这样便是当前最优,也是局部最优。 在黑白棋中,我们同样可以采用这样的思想。 首先来看看简单AI,因为简单呗,所以它返回的仅仅只是当前的最优解,再怎么说也不能让它随机返回坐标对吧! 那局部最优解又是以什么为评测标准的呢? 嗯,我们采用的是能够转换对方棋子最多的位置,这个可不是行动力哦! POINT2 Easy() //人机对战简单AI { POINT2 MAX; //定义以及初始化最优解 MAX.INIT(0, 0); int maxx = 0; for (int i = 0; i < SIZE; ++i) for (int j = 0; j < SIZE; ++j) { if (expect[i][j] >= maxx) //寻找可以转化棋子最多的点作为最优解 { maxx = expect[i][j]; MAX.INIT(i, j); } } if (ESCEXIT)gameStart(); Sleep(800); //间歇 return MAX; //返回最优解 } 呐,expect中便是每个点可以转换对方棋子的个数,这个 AI 简单吧! 其他难度 AI: 既然是简单以上的难度,就不能再像那样简单啦!不然一个中等AI被简单AI击败多没意思,O(∩_∩)O哈哈~ 首先,我们应该知道一个估值表的问题,在黑白棋中,不同位置都有不同位置的估值,虽然这样的估值表的用处并不是很大,但却在某些细节中表现出至关重要的作用。 黑白棋的棋盘默认是8*8的,总共64格。 从游戏规则我们可以看出来,角上的子很重要,因为这里不会被对方转换,角边上的点很危险,它给了对方直接进角的机会。 边上中间的四个点比较重要,只能从一个方向被翻转……等等。 根据这样的经验,我们大致可以得到以下的估值表: A B C D E F G H 1 90 -60 10 10 10 10 -60 90 2 -60 -80 5 5 5 5 -80 -60 3 10 5 1 1 1 1 5 10 4 10 5 1 1 1 1 5 10 5 10 5 1 1 1 1 5 10 6 10 5 1 1 1 1 5 10 7 -60 -80 5 5 5 5 -80 -60 8 90 -60 10 10 10 10 -60 90 有了这张表,AI 进行估值的时候就很简单了,不过仅凭这一点可不行哦! 黑白棋的AI中,我们要考虑的除了估值表,还有稳定子。 稳定子,即不能被转换的棋子,稳定子的数量在游戏中是变化的,比如,一方占据整整一条边,那么这一条边上的所有棋子都是稳定子。 行动力,某方当前可走位置的个数,因为在黑白棋的游戏规则中,每一步的走棋都要形成转换,否则不能走棋,既然这样的话,我们在AI中便要尽可能让自己的行动力最大,而对方行动力最小,也就是尽可能让双方行动力差最大,这样的话,很容易AI便可以把玩家逼上绝路,玩笑而已…… 除了这两个还有余裕手和潜在行动力,虽然并不懂~ 对电脑AI设定中,我们的原则是能走角就走角,不到万不得已的情况下不要走邻角点。 对其他情况下采用极大极小博弈树搜索: 这里假设AI的对手都是最聪明的,会选择最优解,即会选择对AI最不利的选择。 搜出来的结果集是AI方的结果,那么要选择最终得分最高的那个位置 搜出来的结果集是玩家方的结果,那么要选择最终得分最低的那个位置。 如下图: 假设圆形代表的是AI节点,方形代表玩家节点。 对于A2和A3这两种选择,AI显然是选择A2得10分。对于A4和A4这两种选择,AI显然是选择A4得20分。 但是对于B1,B2来说,玩家如果下B2,使得AI可以得20分,下B1,使得AI只能得10分,那么玩家显然是下B1。 所以最终A1这一步,AI只能得10分。这就是极大极小算法。 然后就是α-β剪枝: 现在A2,A3已经选出最大值10,B1的得分是10分。 而对于B1,B2来说是要选最小值,既然B1的得分是10分,则B1,B2之间的最终结果是<=10的。 而A4的得分是20分,对于A4,A5来说是选择最大值得,即A4,A5之间的最终结果是>=20的,说明B2的最终结果是>=20的。 那么这种情况下肯定是选B1了,对于还没有搜索的A5节点来说,已经影响不到最终的选择结果了,所以就可以不用考虑了。 然后得分的计算: 这里每一步的得分,都是相对于AI来说的得分。 AI自己落子某一个位置,得一个正分,之后对手落子某一个位置,所得的分数对于AI来说就是一个负分(即玩家取得的优势,对于AI来说就是劣势)。 对于已经搜到最大深度的节点来说,它的得分就是这个位置的本身得分(因为后面已经不搜了)。 而对于中途节点来说,它的得分应该是这个位置的本身得分,加上下一步对方的选择结果的得分。这里不能只以最后一步的结果逆推的。 举个例子: 如上图的左右两种情况。 假设圆形代表的是AI节点,方形代表玩家节点。 其中分值表示的是节点自身落子该位置所获得的在估值表中的得分,玩家节点取负分。 如果只是用最深层的节点的得分,来计算最上层的节点的得分,那么按照上面极大极小算法,AI最后的得分:左边是10分,右边是5分。那么AI选择左边的10分这种情况。 但是却造成了中间过程中,玩家可以得到50分的这样一个相对来说是比较好的分值。 而AI应该不让玩家取得这样一个比较好的优势。 所以要综合前后对方的落子位置以及得分来考虑最终得分: AI最后的得分:左边是-30分,右边是-15分。最终选择为右边,而不是左边。 极大极小搜索就是这样了,难度的抉择取决于搜索的深度,不过要保证的是不要超时哦! 接下来附上我的 墨攻棋阵 中的AI算法,估计只有一点点的沾边吧! 还有很多需要优化的地方惹 int difai(int x,int y,int mapnow[SIZE][SIZE],int expectnow[SIZE][SIZE],int depin,int depinmax) //极大极小搜索 { if (depin >= depinmax)return 0; //递归出口 int maxx = -10005; //最大权值 POINT2 NOW; int expectnow2[SIZE][SIZE] , mapnow2[SIZE][SIZE],mapnext[SIZE][SIZE],expectlast[SIZE][SIZE]; //定义临时数组 copymap(mapnow2, mapnow); //复制当前棋盘 mapnow2[x][y] = NOWCOLOR ? 1 : -1; //模拟在当前棋盘上下棋 int ME = MAPPOINTCOUNT[x][y] + expectnow[x][y]; //当前棋子权 NOW.INIT(x,y); Change(NOW, mapnow2, false); //改变棋盘AI结束 int MAXEXPECT = 0, LINEEXPECT = 0, COUNT = 0; for (int i = 0; i < SIZE; ++i) for (int j = 0; j < SIZE; ++j) { expectnow2[i][j] = Judge(i, j, !NOWCOLOR, mapnow2);//预判对方是否可以走棋 if (expectnow2[i][j]) { ++MAXEXPECT; if ((i == 0 && j == 0) || (i == 0 && j == SIZE - 1) || (i == SIZE - 1 && j == SIZE - 1) || (i == SIZE - 1 && j == 0))return -1800; //如果对方有占角的可能 if ((i < 2 && j < 2) || (i < 2 && SIZE - j - 1 < 2) || (SIZE - 1 - i < 2 && j < 2) || (SIZE - 1 - i < 2 && SIZE - 1 - j < 2))++LINEEXPECT; } } if (LINEEXPECT * 10 > MAXEXPECT * 7)return 1400;//如果对方走到坏点状态较多 剪枝 for (int i = 0; i < SIZE; i++) for (int j = 0; j < SIZE; j++) if (expectnow2[i][j]) //如果对方可以走棋 { int YOU = MAPPOINTCOUNT[i][j] + expectnow2[i][j];//当前权值 copymap(mapnext, mapnow2); //拷贝地图 mapnext[i][j] = (!NOWCOLOR) ? 1 : -1; //模拟对方走棋 NOW.INIT(i, j); Change(NOW, mapnext, false); //改变棋盘 for (int k = 0; k < SIZE; k++) for (int l = 0; l < SIZE; l++) expectlast[k][l] = Judge(k, l, NOWCOLOR, mapnext); //寻找AI可行点 for (int k = 0; k < SIZE; k++) for (int l = 0; l < SIZE;l++) if (expectlast[k][l]) { int nowm = ME - YOU + difai(k, l, mapnext, expectlast, depin + 1, depinmax); maxx = maxx < nowm ? nowm : maxx; } } return maxx; } 有关黑白棋AI极大极小搜索的算法也就这些了,希望本文能对你有一点帮助。 另外,墨攻棋阵的项目源码在我的 GitHub 里面,欢迎大家 Fork ,发现 Bug 后不要忘记给我留言哦! 项目完整源码【请点击这里下载】。 作者:千千QQ:1335661317主页:www.dreamwings.cn编程语言:C++编译环境:VS2015 + EasyX编译平台:Windows

2D 横向对抗射击游戏

花了一天时间,用最新版 easyx 做了一个小游戏,程序中所有的类函数都是内联函数,大约 300 行,开发环境是 Visual Studio 2015 Community,其他的编译器能不能通过编译不确定。 游戏运行截图如下: 代码说明:关于无阻塞延时,首先,先要 ctime 创建一个 clock_t 变量 a,初始化为 clock(),貌似是自从 1970 年到现在的毫秒数。我们要每隔 0.5 秒执行函数 func() 一次。那么创建主循环 while(1)调用前用 clock() - a;如果 clock() - a > 500,那么执行 func(),并把 a 重新赋值为 clock()。如果使用 Sleep(500) 的话,这个循环就只能执行 func 函数了,在此期间什么也做不了。 完整的游戏源代码如下: /* * 作者:674729261 * QQ:2292683261 * 图像引擎:EasyX(http://www.easyx.cn) * 编译环境:Visual Studio 2015 Community * 这只是个小游戏,所以全部内联 * 编译需要先安装 EasyX 2015(beta) */ #include <easyx.h> #include <time.h> #include <conio.h> class Bullet; class Tank; class E_Bullet; class Boss; bool dead = false; bool wined = false; struct pos//坐标类 { int a; int b; }; class E_Bullet//敌人打出的子弹 { public: clock_t d; int x; int y; bool on = false; pos show()//画出新的位置 { setfillcolor(RGB(255, 180, 20)); fillrectangle(x - 5, y - 5, x + 5, y + 5); return pos{ x,y }; } pos del()//覆盖原来的位置 { setfillcolor(0); setlinecolor(0); fillrectangle(x - 5, y - 5, x + 5, y + 5); rectangle(x - 5, y - 5, x + 5, y + 5); return pos{ x,y }; } pos move()//左移 { x -= 3; return pos{ x,y }; } }; class Bullet//玩家打出的子弹,同上 { public: clock_t d; int x; int y; bool on = false; pos show() { setfillcolor(RGB(150, 180, 210)); fillrectangle(x - 5, y - 5, x + 5, y + 5); return pos{ x,y }; } pos del() { setfillcolor(0); setlinecolor(0); fillrectangle(x - 5, y - 5, x + 5, y + 5); rectangle(x - 5, y - 5, x + 5, y + 5); return pos{ x,y }; } pos move()//右移 { x += 3; return pos{ x,y }; } }; class Boss//敌人 { public: bool hurting = false; clock_t d_hurt; COLORREF clr = RGB(0, 130, 125); int x; int y; int hp = 100;//生命 clock_t d;//无阻塞延时,其实不是延时,只是判断举例上一次执行某一函数过了多久 clock_t att_d; bool angle = false;//方向 pos show() { setfillcolor(clr); fillrectangle(x - 20, y - 40, x + 20, y + 40); return pos{ x,y }; } pos del() { setfillcolor(0); setlinecolor(0); rectangle(x - 20, y - 40, x + 20, y + 40); fillrectangle(x - 20, y - 40, x + 20, y + 40); return pos{ x,y }; } void fire(E_Bullet& but)//攻击 { but.on = true;//放置一个子弹 but.x = x -20; but.y = y; but.d = clock(); } void move()//上上下下得移动 { if (angle == true) y -= 5; if (angle == false) y += 5; if (y >= 440) angle = true; if (y <= 40) angle = false; } void hurt()//受伤 { hp -= 4; d_hurt = clock(); setfillcolor(0); setlinecolor(WHITE); fillrectangle(160, 485, 560, 510);//更新血条 rectangle(160, 485, 160+hp*4, 510); setfillcolor(RGB(230, 0, 1)); setlinecolor(RGB(255, 255, 255)); fillrectangle(160, 485,160+hp*4, 510); rectangle(160, 485, 160 + hp * 4, 510); hurting = true; if (hp <= 0)//挂屁 { wined = true; } } }; class Tank//玩家类,同上 { public: bool hurting = false; int hp = 100; int x; COLORREF clr = RGB(150, 180, 210); int y; clock_t d_hurt; Tank(){} Tank(int _x, int _y) { x = _x; y = _y; } Tank operator=(pos p) { x = p.a; y = p.a; } pos show() { setfillcolor(clr); fillrectangle(x - 25, y - 25, x + 25, y + 25); setfillcolor(RGB(100, 200, 180)); fillrectangle(x, y + 5, x + 40, y - 5); return pos{ x,y }; } pos del() { setfillcolor(0); setlinecolor(0); fillrectangle(x - 25, y - 25, x + 25, y + 25); rectangle(x - 25, y - 25, x + 25, y + 25); fillrectangle(x, y + 5, x + 40, y - 5); rectangle(x, y + 5, x + 40, y - 5); return pos{ x,y }; } void fire(Bullet& but) { but.on = true; but.x = x + 45; but.y = y; but.d = clock(); but.show(); } void hurt() { hp -= 2; d_hurt = clock(); setfillcolor(0); setlinecolor(WHITE); fillrectangle(160, 515, 560, 540); rectangle(160, 515, 560, 540); rectangle(160, 515, 160 + hp * 4, 540); setfillcolor(RGB(0, 255, 1)); setlinecolor(RGB(255, 255, 255)); fillrectangle(160, 515, 160 + hp * 4, 540); rectangle(160, 515, 160 + hp * 4, 540); hurting = true; if (hp <= 0) dead = true; } }; #define BT_MAX 8 int main() { initgraph(640, 550, 4);//初始化屏幕 settextcolor(RGB(0, 254, 0)); settextstyle(35, 0, "黑体"); outtextxy(150, 200, "W,S移动,K攻击"); Sleep(3000); setlinecolor(0); setfillcolor(0); rectangle(0, 0, 640, 550); fillrectangle(0, 0, 640, 550); setlinecolor(RGB(255, 255, 255)); setfillcolor(RGB(255, 255, 255)); clock_t delay = clock();//玩家移动的延时 clock_t d_f = clock();//玩家开火的延时 line(0, 481, 640, 481);//分割画面与血条 Bullet bt[BT_MAX];//玩家的子弹 Tank tk(30, 30);//玩家 Boss bo;//敌人 bo.x = 580; bo.y = 240; E_Bullet ebt[BT_MAX];//敌人的子弹 bo.d = clock();//初始化延时 bo.att_d = clock(); tk.show(); settextstyle(20, 0, "黑体"); outtextxy(10,485,"BOSS的生命值:"); setfillcolor(RGB(230, 0, 1)); fillrectangle(160, 485, 560, 510);//敌人血条 outtextxy(10, 520, "玩家的生命值:"); setfillcolor(RGB(0, 255, 1)); fillrectangle(160, 515, 560, 540);//玩家血条 while (1)//主循环 { if (wined||dead)//玩家死了或者敌人死了 break; if (GetAsyncKeyState('W') & 0x8000)//玩家移动 { if (tk.y > 28 && (clock() - delay) >= 40) { tk.del(); tk.y -= 3; tk.show(); delay = clock(); } } if (GetAsyncKeyState('w') & 0x8000)//玩家移动 { if (tk.y > 28 && (clock() - delay) >= 40) { tk.del(); tk.y -= 3; tk.show(); delay = clock(); } } if (GetAsyncKeyState('k') & 0x8000)//玩家开火 { for (int i = 0; i < BT_MAX; i++) { if (bt[i].on == false && (clock() - d_f) > 800) { bt[i].on = true; tk.fire(bt[i]); d_f = clock(); break; } } } if (GetAsyncKeyState('K') & 0x8000)//玩家开火 { for (int i = 0; i < BT_MAX; i++) { if (bt[i].on == false && (clock() - d_f) > 800) { tk.fire(bt[i]); d_f = clock(); break; } } } if (GetAsyncKeyState('S') & 0x8000)//玩家移动 { if (tk.y < 452 && (clock() - delay) >= 40) { tk.del(); tk.y += 3; tk.show(); delay = clock(); } } if (GetAsyncKeyState('s') & 0x8000)//玩家移动 if (tk.y < 452 && (clock() - delay) >= 40) { tk.del(); tk.y += 3; tk.show(); delay = clock(); } for (int i = 0; i < BT_MAX; i++)//遍历子弹,使子弹刷新 { if (bt[i].on == true && (clock() - bt[i].d) > 20) { bt[i].del(); bt[i].move(); bt[i].show(); bt[i].d = clock(); if (bt[i].x >= 635) bt[i].on = false, bt[i].del();//到达了屏幕最右端 if ((bt[i].x + 5 >= bo.x - 20 && bt[i].x - 5 <= bo.x + 20) && (bt[i].y - 5 < bo.y + 40 && bt[i].y + 5 > bo.y - 40)) //击中敌人 bt[i].on = false, bo.hurt(),bt[i].del(); } } if (clock() - bo.att_d > 700)//敌人自动开火 { for (int i = 0; i < BT_MAX; i++) { if (ebt[i].on == false) { bo.fire(ebt[i]); break; } } bo.att_d = clock(); } for (int i = 0; i < BT_MAX; i++)//敌人子弹刷新,同上 { if (ebt[i].on == true && (clock() - ebt[i].d > 20)) { ebt[i].del(); ebt[i].move(); ebt[i].show(); ebt[i].d = clock(); if (ebt[i].x < 5) ebt[i].del(),ebt[i].on = false; if (ebt[i].x - 5 < tk.x + 25 && ebt[i].x + 5 > tk.x - 25 && ebt[i].y - 5 < tk.y + 25 && ebt[i].y + 5 > tk.y - 25) { ebt[i].on = false, tk.hurt(), ebt[i].del(); } } } if (tk.hurting == true)//玩家受伤闪烁0.1秒 if (clock() - tk.d_hurt > 100) { tk.clr = RGB(150, 180, 210), tk.show(), tk.hurting = false; } else tk.clr = RGB(255, 0, 0), tk.show(); if (bo.hurting == true)//敌人受伤闪烁0.1秒 if (clock() - bo.d_hurt > 100) { bo.clr = RGB(0, 130, 125), bo.show(), bo.hurting = false; } else bo.clr = RGB(0, 255, 0), bo.show(); if (clock() - bo.d > 50)//敌人移动延时; bo.del(), bo.move(), bo.show(), bo.d = clock(); } if (wined)//胜负已分 { settextcolor(RGB(0, 254, 0)); settextstyle(35, 0, "黑体"); outtextxy(150, 200, "你打败了boss!你赢了!!"); } else { settextcolor(RGB(254, 0, 0)); settextstyle(35, 0, "黑体"); outtextxy(140, 200, "你被boss打败了!"); } Sleep(5000); closegraph();//关闭画布 return 0; } 作者:674729261QQ:2292683261

2D 射击游戏:大战黑帮

这是一款经典的2D射击游戏,ASDW控制,鼠标左键射击,空格键放技能,P键暂停 你正以一己之力撼动整个社区最大的黑帮!杀死敌人!越多越好! 游戏运行截图如下: 完整源代码、图片及编译后的可执行文件请【点击这里下载】。 作者:Starsky作者邮箱:lcj20000101@163.com

华容道

华容道游戏说明: 操作方式:鼠标左键移动棋子,将曹操移动到出口处,过关。 游戏运行截图如下: 完整源代码、图片及编译后的可执行文件请【点击这里下载】。 作者:左慈QQ:2382036672邮箱:qwj2222@126.com编译环境:Visual C++ 6.0,EasyX 2014冬至版最后修改:2015-3-14

防空车打飞机

游戏说明: 在游戏中,你操控你的防空车尽可能的多击落飞机,躲避飞机扔的炸弹。 游戏结束后,你可以选择重新开始游戏。 感谢所有帮助过我的人。 游戏运行截图如下: 完整源代码、图片及编译后的可执行文件请【点击这里下载】。 作者:左慈 QQ:2382036672邮箱:qwj2222@126.com编译环境:Visual C++ 6.0,EasyX 2014冬至版最后修改:2014-12-27

别踩白块儿(双人版) VC 源代码

这就是当前十分流行的游戏“别踩白块儿”的双人版,两个人可以同时进行游戏。 默认情况下,喜羊羊的四个按键是 asdf,灰太狼的四个按键是 jkl;,可以在代码中修改。 以下是游戏截图: 以下是完整的游戏源代码,在 VC6.0 和 VC2013 下面测试通过: /////////////////////////////////////////////////// // 程序名称:别踩白块儿(双人版) VC 源代码 // 编译环境:Visual C++ 6.0、2013,EasyX 20140321(beta) // 作  者:yangw80 <yw80@qq.com> // 最后修改:2014-5-26 // #undef UNICODE #undef _UNICODE #include <graphics.h> #include <conio.h> #include <stdio.h> #include <time.h> #define MAXTASK 50 // 定义游戏需要完成的黑块数量 // 定义宏 __sprintf 自适应 vc6 与 vc2013 #if _MSC_VER > 1200 #define __sprintf(...) sprintf_s(__VA_ARGS__) #else #define __sprintf sprintf #endif // 精确延时函数(可以精确到 1ms,精度 ±1ms) // 摘自 www.easyx.cn void HpSleep(int ms) { static clock_t oldclock = clock(); // 静态变量,记录上一次 tick oldclock += ms * CLOCKS_PER_SEC / 1000; // 更新 tick if (clock() > oldclock) // 如果已经超时,无需延时 oldclock = clock(); else while (clock() < oldclock) // 延时 Sleep(1); // 释放 CPU 控制权,降低 CPU 占用率 } // 游戏状态常量 enum STATUS{BEGIN, // 游戏开始 RUNNING, // 游戏运行中 PASSANI, // 游戏通过的动画 PASS, // 游戏通过 FAILANI, // 游戏失败的动画 FAIL }; // 游戏失败 // 游戏者类(每个游戏者都有一个独立的游戏区域) class PLAYER { private: STATUS m_status; // 游戏状态 char* m_strName; // 游戏者名称 POINT m_offset; // 界面的偏移量 char* m_keys; // 按键 // 任务 byte m_Task[MAXTASK]; // 任务列表 byte m_iTask; // 当前需要执行的任务 ID int m_nextTaskY; // 界面中下一个任务的 Y 坐标 // 时钟和游戏记录 clock_t m_beginClock; // 游戏开始的时钟计数 float m_bestTime; // 最佳纪录的完成时间 float m_lastTime; // 最后一次的完成时间 // 控制失败动画的变量 byte m_failErrorKey; // 按错的键的序号(值为 0、1、2、3) RECT m_failRect; // 按错的键的区域 int m_failFrame; // 失败后的动画的帧计数 public: PLAYER(char* name, char* keys, int offsetx, int offsety); // 构造函数 void Hit(char key); // 处理游戏者按键 void Draw(); // 绘制该游戏者的游戏界面 private: void Init(); // 初始化当前游戏者的游戏信息 void DrawFrame(); // 绘制游戏界面的外框 void DrawRow(int baseY, int iTask); // 绘制游戏界面中的一行任务 void DrawPass(); // 绘制通过游戏后的界面 void DrawFail(); // 绘制游戏失败后的界面 // 进行偏移量计算的绘图函数 void OutTextXY(int x, int y, LPCTSTR s) // 在指定位置输出字符串 { outtextxy(m_offset.x + x, m_offset.y + y, s); } void OutTextXY(int x, int y, char c) // 在指定位置输出字符 { outtextxy(m_offset.x + x, m_offset.y + y, c); } void Rectangle(int x1, int y1, int x2, int y2) // 绘制矩形 { rectangle(m_offset.x + x1, m_offset.y + y1, m_offset.x + x2, m_offset.y + y2); } void FillRectangle(int x1, int y1, int x2, int y2) // 绘制有边框填充矩形 { fillrectangle(m_offset.x + x1, m_offset.y + y1, m_offset.x + x2, m_offset.y + y2); } void SolidRectangle(int x1, int y1, int x2, int y2) // 绘制无边框填充矩形 { solidrectangle(m_offset.x + x1, m_offset.y + y1, m_offset.x + x2, m_offset.y + y2); } }; // 构造函数 // 参数: // name: 游戏者名称 // keys: 游戏者所用按键(指向长度为 4 的字符串) // offsetx, offsety: 游戏者对应的游戏区域在主窗口中的偏移量 PLAYER::PLAYER(char* name, char* keys, int offsetx, int offsety) { m_strName = name; m_keys = keys; m_offset.x = offsetx; m_offset.y = offsety; m_bestTime = 99; // 设置最佳成绩 Init(); // 初始化游戏者 } // 初始化当前游戏者的游戏信息 void PLAYER::Init() { // 初始化任务 for (int i = 0; i < MAXTASK; i++) m_Task[i] = rand() % 4; m_iTask = 0; // 从第一个任务开始 m_nextTaskY = 200; // 设定下一行任务的 Y 坐标,100 是基准,200 表示开始会有下落的动画 m_status = BEGIN; // 设置游戏初始状态 m_failFrame = 0; // 重置失败后的动画的帧计数 // 初始化游戏界面 DrawFrame(); } // 绘制该游戏者的游戏界面 void PLAYER::Draw() { switch (m_status) { case PASSANI: // 游戏成功后的动画 if (m_nextTaskY == 100) { m_status = PASS; DrawPass(); break; } case BEGIN: // 游戏初次开始 case RUNNING: // 游戏运行中 { // 如果画面处于静止,直接返回不再重绘 if (m_nextTaskY == 100) return; m_nextTaskY -= (m_nextTaskY - 100 + 9) / 10; // 绘制完成的任务区 int rowy = m_nextTaskY; int itask = m_iTask; do { rowy -= 100; itask--; DrawRow(rowy, itask); } while (rowy > 0); // 绘制未完成的任务区 rowy = m_nextTaskY; itask = m_iTask; do { DrawRow(rowy, itask); rowy += 100; itask++; } while (rowy < 400); break; } case FAILANI: // 游戏失败后的动画 DrawFail(); break; case PASS: // 游戏通过后的成绩显示 case FAIL: // 游戏失败后的成绩显示 break; } } // 绘制游戏界面的外框 void PLAYER::DrawFrame() { // 画外框 setlinecolor(0xfb9700); Rectangle(0, 0, 243, 464); setfillcolor(0xeca549); settextcolor(BLACK); settextstyle(16, 0, "Verdana"); setbkmode(TRANSPARENT); // 画姓名区 SolidRectangle(2, 2, 241, 21); int w = textwidth(m_strName); OutTextXY((244 - w) / 2, 4, m_strName); // 画成绩区 SolidRectangle(2, 23, 241, 42); char tmp[50]; __sprintf(tmp, "最好记录:%.3f 秒", m_bestTime); OutTextXY(10, 26, tmp); // 2 <= x <= 241, 44 <= y <= 443 为游戏区 // 画控制区 SolidRectangle(2, 445, 241, 462); for (int i = 0; i < 4; i++) OutTextXY(2 + i * 60 + 26, 446, m_keys[i]); } // 绘制游戏界面中的一行任务 void PLAYER::DrawRow(int baseY, int iTask) { int fromY = baseY; // 任务行的起始 y 坐标 int toY = baseY + 99; // 任务行的终止 y 坐标 // 如果 y 坐标超出显示范围,做调整 if (fromY < 0) fromY = 0; if (toY > 399) toY = 399; COLORREF c[4]; // 任务行四个方块的颜色 if (iTask < 0) { for (int i = 0; i < 4; i++) c[i] = YELLOW; } else if (iTask >= MAXTASK) { for (int i = 0; i < 4; i++) c[i] = GREEN; } else { for (int i = 0; i < 4; i++) c[i] = WHITE; c[m_Task[iTask]] = (iTask < m_iTask)? LIGHTGRAY : BLACK; } // 画任务行的四个方块 setlinecolor(0xe9dbd6); for (int i = 0; i < 4; i++) { setfillcolor(c[i]); FillRectangle(2 + i * 60, 44 + 399 - fromY, 2 + i * 60 + 59, 44 + 399 - toY); } // 如果是第一行,在方块儿上写“开始”两个字 if (iTask == 0 && m_iTask == 0) { int w = textwidth("开始"); int h = textheight("开始"); int x = 2 + m_Task[iTask] * 60 + (60 - w) / 2; int y = 44 + 399 - 99 - fromY + (100 - h) / 2; settextcolor(WHITE); settextstyle(16, 0, "Verdana"); OutTextXY(x, y, "开始"); } } // 绘制通过游戏后的界面 void PLAYER::DrawPass() { // 绘制成功的背景 setfillcolor(GREEN); SolidRectangle(2, 44, 241, 443); // 输出"成功" settextcolor(WHITE); settextstyle(60, 0, "Verdana"); int w = textwidth("成功"); OutTextXY((244 - w) / 2, 100, "成功"); // 输出成绩 char tmp[100]; settextstyle(32, 0, "Verdana"); __sprintf(tmp, "成绩:%.3f 秒", m_lastTime); w = textwidth(tmp); OutTextXY((244 - w) / 2, 200, tmp); __sprintf(tmp, "速度:%.3f/s", MAXTASK / m_lastTime); OutTextXY((244 - w) / 2, 240, tmp); // 输出重新开始的提示 settextstyle(16, 0, "Verdana"); w = textwidth("按任意控制键重新开始"); OutTextXY((244 - w) / 2, 400, "按任意控制键重新开始"); } // 绘制游戏失败后的界面 void PLAYER::DrawFail() { if (m_failFrame == 0) { // 初始化,计算闪烁效果的区域 m_failRect.left = 3 + m_failErrorKey * 60; m_failRect.right = m_failRect.left + 57; m_failRect.bottom = m_nextTaskY + 1; m_failRect.top = m_nextTaskY + 98; if (m_failRect.top > 398) m_failRect.top = 398; m_failRect.bottom = 44 + 399 - m_failRect.bottom; m_failRect.top = 44 + 399 - m_failRect.top; } if (m_failFrame < 60) { // 实现闪烁效果 setfillcolor(((m_failFrame / 6) % 2 == 0) ? RED : LIGHTRED); SolidRectangle(m_failRect.left, m_failRect.bottom, m_failRect.right, m_failRect.top); m_failFrame++; } else { // 改变游戏状态 m_status = FAIL; // 绘制失败的背景 setfillcolor(RED); SolidRectangle(2, 44, 241, 443); // 输出"失败" settextcolor(WHITE); settextstyle(60, 0, "Verdana"); int w = textwidth("失败"); OutTextXY((244 - w) / 2, 100, "失败"); // 输出历史成绩 settextstyle(20, 0, "Verdana"); char tmp[100]; __sprintf(tmp, "历史最好成绩:%.3f 秒", m_bestTime); w = textwidth(tmp); OutTextXY((244 - w) / 2, 200, tmp); // 输出重新开始的提示 settextstyle(16, 0, "Verdana"); w = textwidth("按任意控制键重新开始"); OutTextXY((244 - w) / 2, 400, "按任意控制键重新开始"); } } // 处理游戏者按键 void PLAYER::Hit(char key) { switch (m_status) { case BEGIN: // 游戏初次开始 if (strchr(m_keys, key) != NULL) { m_beginClock = clock(); // 记录游戏开始时的时钟 m_status = RUNNING; // 改变游戏状态 } case RUNNING: // 游戏运行中 { char* pdest = strchr(m_keys, key); byte pos; if (pdest != NULL) // 判断是否是当前游戏者按键 { pos = pdest - m_keys; // 计算按键对应的位置 if (pos == m_Task[m_iTask]) // 判断按键是否正确 { // 按键正确 m_iTask++; m_nextTaskY += 100; if (m_iTask == MAXTASK) // 如果完成了全部任务 { // 计算完成时间 clock_t t = clock(); m_lastTime = ((float)(clock() - m_beginClock)) / CLOCKS_PER_SEC; // 更新最好记录 if (m_lastTime < m_bestTime) m_bestTime = m_lastTime; // 将最后一条任务滚动出屏幕 m_iTask++; m_nextTaskY += 100; m_status = PASSANI; } } else { // 按键失败 m_failErrorKey = pos; m_status = FAILANI; } } break; } case PASSANI: // 游戏成功后的动画 case FAILANI: // 游戏失败后的动画 break; case PASS: // 游戏通过后的成绩显示 case FAIL: // 游戏失败后的成绩显示 if (strchr(m_keys, key) != NULL) Init(); break; } } // 程序入口主函数 void main() { initgraph(640, 480); // 创建绘图窗口 srand((unsigned)time(NULL)); // 设置随机函数种子 setbkcolor(0x01bbfb); cleardevice(); PLAYER p1("喜羊羊", "asdf", 38, 8); // 创建游戏者 喜羊羊 PLAYER p2("灰太狼", "jkl;", 358, 8); // 创建游戏者 灰太狼 char c = 0; while (c != 27) { while (_kbhit()) // 判断是否有按键 { // 按键处理 c = _getch(); p1.Hit(c); p2.Hit(c); } // 绘制游戏场景 p1.Draw(); p2.Draw(); // 延时 HpSleep(16); } // 结束游戏 closegraph(); // 关闭绘图窗口 }

贪吃蛇

一个比较新颖的贪吃蛇程序。 用左右表示蛇向左转或向右转,上用来加速。 源代码如下: /////////////////////////////////// // 程序名称:贪吃蛇 // 编译环境:Visual C++ 6.0 / 2008,EasyX 2013冬至版 // 作者:王垣恒 QQ:646361765 // 最后修改:2014-5-20 // 操作方式:以蛇为第一视角,左 左转;右 右转;下 暂停;上 快进。 // 状态: 黄色 蛇头;红色 蛇身;绿色 食物。 #include <graphics.h> #include <string.h> #include <time.h> #define NUM_R 10 //半径 #define NUM_X 25 //横向个数 #define NUM_Y 25 //纵向个数 #define NUM 30 //所需节点个数 void exe(int x,int y,int f); int GetCommand(); void eat(int x,int y); void clear(); void set(); void flush(); void over(bool a); struct pos //建立链表储存每个关节的位置 { int x; int y; struct pos*next; }; struct pos*head=(pos*)malloc(sizeof(pos)); //建立头指针 int n=0; //记录节点个数 void main() //初始化游戏 { int x,y,f; //储存初始化点的位置方向 srand((unsigned) time(NULL)); //初始化随机库 do { x=rand()%NUM_X*NUM_R*2+NUM_R; y=rand()%NUM_Y*NUM_R*2+NUM_R; } while(x<4*NUM_R || y<4*NUM_R || 2*NUM_R*(NUM_X-2)<x || 2*NUM_R*(NUM_Y-2)<y); //产生不在矩形边缘的初始点 f=rand()%4; //随机方向 struct pos*a=(pos*)malloc(sizeof(pos)),*p=head; //建立链表第一个节点 a->x=x; //指针a储存第一个点数据 a->y=y; head->next=a; //接链 a->next=NULL; //结尾 initgraph(2*NUM_R*NUM_X,2*NUM_R*NUM_Y+50); //初始绘图窗口 setcolor(WHITE); line(0,2*NUM_R*NUM_Y+1,2*NUM_R*NUM_X,2*NUM_R*NUM_Y+1); setcolor(getbkcolor()); //取消圆的边缘 setfillcolor(YELLOW); //设置填充颜色 fillcircle(x,y,NUM_R); //绘出初始点 set(); //产生食物 exe(x,y,f); //进入控制函数 } void exe(int x,int y,int f) //操作游戏 { int xf,yf,c,i; while(1) //进入循环 { c=0; //初始化方向 for(i=0;i<5;i++) //循环5次获取命令 { Sleep(100-50*n/NUM); //等待 if(c==0) //若没获取到命令就进行获取 { c=GetCommand(); if(c==4) //返回4时退出循环等待 break; } } f=f+c; //改变方向 if(f>3) //溢出处理 f=f-4; xf=yf=0; //初始化方向参数 switch(f) { case 0:xf=1;break; //方向向右时 x坐标增加 case 1:yf=1;break; //方向向右时 y坐标增加 case 2:xf=-1;break; //方向向右时 x坐标减少 case 3:yf=-1;break; //方向向右时 y坐标减少 } x=x+2*NUM_R*xf; //x坐标变化 y=y+2*NUM_R*yf; //y坐标变化 if(getpixel(x,y)==RED || x<0 || y<0 || 2*NUM_X*NUM_R<x || 2*NUM_Y*NUM_R<y) //判断是否遇到自身或碰到边界 over(0); //结束游戏 else //不结束进行下步运算 { if(getpixel(x,y)==GREEN) //判断前方是否为食物 set(); //产生新食物 else clear(); //清除尾结点 eat(x,y); //在前方生成新结点 if(n>NUM-1) //判断胜利条件 over(1); //结束游戏 } } } int GetCommand() //获取方向 { int c=0; //初始化方向变量 if(GetAsyncKeyState(VK_RIGHT) & 0x8000) c = 1; //右转为1 if(GetAsyncKeyState(VK_LEFT) & 0x8000) c = 3; //左转为3 if(GetAsyncKeyState(VK_UP) & 0x8000) c = 4; //按上为4 快进 if(GetAsyncKeyState(VK_DOWN) & 0x8000) system("pause"); //按下则暂停 return c; } void eat(int x,int y) //增加新结点 { struct pos*a=(pos*)malloc(sizeof(pos)),*p=head; //声明指针变量 while(p->next!=NULL) //寻找链表尾节点 p=p->next; a->x=x; //把数据储存到结点 a->y=y; p->next=a; //指针a接到尾节点后 a->next=NULL; //结尾 setcolor(getbkcolor()); //取消圆的边缘 setfillcolor(RED); //设置填充颜色 fillcircle(p->x,p->y,NUM_R); //绘制新结点 setfillcolor(YELLOW); //设置填充颜色 fillcircle(x,y,NUM_R); //绘制新结点 } void clear() //清除尾结点 { setcolor(getbkcolor()); //取消圆的边缘 setfillcolor(getbkcolor()); //设置填充颜色 fillcircle(head->next->x,head->next->y,NUM_R); //擦除结点 head->next=head->next->next; //删除节点数据 } void set() //产生食物和胜利判断 { flush(); int x,y; //声明变量 do { x=rand()%NUM_X*NUM_R*2+NUM_R; y=rand()%NUM_Y*NUM_R*2+NUM_R; } while (getpixel(x,y)==RED); //随机产生食物在非蛇的位置 setcolor(getbkcolor()); setfillcolor(GREEN); //设置填充颜色 fillcircle(x,y,NUM_R); //产生食物 } void flush() { n++; //节点计数累加 char strnum[20],string[10]="进度:"; itoa(n,strnum,10); //转换 strcat(string,strnum); //链接 strcpy(strnum,"/"); //赋值 strcat(string,strnum); //连接 itoa(NUM,strnum,10); strcat(string,strnum); setcolor(WHITE); settextstyle(32,0,_T("宋体")); //设置字体类型 outtextxy(20,2*NUM_R*NUM_Y+2," "); outtextxy(20,2*NUM_R*NUM_Y+2,string); } void over(bool a) //结束游戏 { setcolor(WHITE); //设置字体颜色 settextstyle(48,0,_T("宋体")); //设置字体类型 if(a) //判断条件 outtextxy(NUM_X*NUM_R-20,NUM_Y*NUM_R-20,"胜利"); //输出结果 else outtextxy(NUM_X*NUM_R-20,NUM_Y*NUM_R-20,"失败"); //输出结果 Sleep(2000); system("pause"); exit(0); } 作者:王垣恒QQ:646361765

一个简单的小游戏:盒子接球

用VC写了一个小小的游戏,在界面右侧有运行时间,接到的小球个数等信息,有 10 个小球下落,玩家可以控制一个盒子左右移动(方向键),来接小球,按 Esc 键退出,最后会显示接到的小球的数目。就是这么简单。 游戏运行截图如下: 以下是游戏的全部源代码: /////////////////////////////////////////////////// // 程序名称:盒子接球(一个简单的小游戏) // 编译环境:Visual C++ 6.0,EasyX 2013霜降版 // 作  者:圣石 <2464847121@qq.com> // 最后修改:2013-12-1 // 玩法:按方向键控制盒子移动接住小球,总共有 20s 的游戏时间,倒计时为 0 时游戏结束 // #include <graphics.h> #include <conio.h> #include <time.h> #include <stdio.h> // 定义常量 #define NUM 10 #define CMD_LEFT 1 #define CMD_RIGHT 2 #define CMD_QUIT 4 int box_x = 10; int box_y = 420; // 定义球的结构体 struct Ball { int x, y, v; }; // 获取用户控制 int GetCommand() { int c = 0; if (GetAsyncKeyState(VK_LEFT) & 0x8000) c |= CMD_LEFT; if (GetAsyncKeyState(VK_RIGHT) & 0x8000) c |= CMD_RIGHT; if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) c |= CMD_QUIT; return c; } // 倒计时 int Time(int t) { char strsec[10]; int sec = 20 - (GetTickCount() - t) / 1000; itoa(sec, strsec, 10); outtextxy(570, 110, " "); outtextxy(570, 110, strcat(strsec, "s")); return sec; } // 介绍 void menu() { line(449, 0, 449, 480); char runTime[] = "游戏倒计时 : ", receiveBallNum[] = "接到的球的数量:", copyRight[] = "版权所有:圣石", finishWorkDate[] = "完成日期:2012年12月1日", introductiona[] = "按方向键控制盒子移动接住", introductionb[] = "小球,倒计时为0时游戏结束"; settextcolor(GREEN); outtextxy(450, 10, introductiona); outtextxy(450, 30, introductionb); outtextxy(450, 110, runTime); outtextxy(450, 210, receiveBallNum); outtextxy(450, 310, copyRight); outtextxy(450, 410, finishWorkDate); } // 产生随机球 void ballRandom(Ball ball[], int i) { ball[i].x = 16 + 45 * i; ball[i].y = 8 + rand() % 32; ball[i].v = 1 + rand() % 5; } // 画球,并计算得分 void calculateScore(Ball ball[], int &score) { for(int i = 0; i < NUM; i++) { fillcircle(ball[i].x, ball[i].y, 8); if(ball[i].y >= 472) { ballRandom(ball, i); continue; } if(box_x + 8 <= ball[i].x && ball[i].x <= box_x + 72 && ball[i].y >= 412) { score++; ballRandom(ball, i); } } } // 主函数 int main() { // 初始化 initgraph(640, 480); srand(time(NULL)); BeginBatchDraw(); setlinecolor(GREEN); setfillcolor(WHITE); menu(); Ball ball[NUM]; int dx, i, c, score = 0; bool flag = true; for(i=0; i<NUM; i++) { ballRandom(ball, i); } int t = GetTickCount(); char strScore[10], str[] = "your score:"; // 游戏主循环 while(flag) { dx = 0; // 显示得分 char strScore[10]; itoa(score, strScore, 10); outtextxy(570, 210, strScore); // 画球,并计算得分 calculateScore(ball, score); // 画盒子 fillrectangle(box_x, box_y, box_x+80, box_y+60); FlushBatchDraw(); // 获取用户控制命令 c = GetCommand(); if (c & CMD_LEFT) dx = -10; if (c & CMD_RIGHT) dx = 10; if (c & CMD_QUIT) flag = false; if (!Time(t)) flag = false; // 延时 Sleep(25); // 擦除游戏区 clearrectangle(0, 0, 448, 480); // 计算球的新坐标 for(i = 0; i < NUM; i++) { ball[i].y += ball[i].v; } // 移动盒子 box_x += dx; if(box_x < 0) box_x = 0; if(box_x > 368) box_x = 368; } // 清空键盘缓冲区 FlushConsoleInputBuffer(GetStdHandle(STD_INPUT_HANDLE)); // 输出游戏结果 itoa(score, strScore, 10); outtextxy(222, 240, strcat(str, strScore)); outtextxy(220, 300, "按任意键退出"); EndBatchDraw(); // 按任意键退出 getch(); closegraph(); return 0; } 作者:圣石邮箱:2464847121@qq.com

游戏:矿井逃生

游戏说明: 矿井里的电路又出问题了。迅速借助你的头灯,在漆黑的矿井里找到出口逃出去吧。 控制说明: 方向键:移动A/S/D/W:移动鼠标:控制照射方向F2:重来一局ESC:退出游戏就不放抓图了,有兴趣的编译运行一下代码看看效果吧。 以下是全部源代码: /////////////////////////////////////////////////// // 程序名称:矿井逃生 // 编译环境:Visual C++ 6.0 / 2010,EasyX 2013霜降版 // 作  者:yangw80 <yw80@qq.com> // 最后修改:2013-11-15 // #include <easyx.h> #include <time.h> #include <math.h> #include <stdio.h> // 定义常量 #define PI 3.141592653589 // 圆周率 #define UNIT_GROUND 0 // 表示地面 #define UNIT_WALL 1 // 表示墙 #define LIGHT_A PI / 3 // 灯光的角度范围 #define LIGHT_R 120 // 灯光的照射距离 #define WIDTH 480 // 矿井的宽度 #define HEIGHT 480 // 矿井的高度 #define SCREENWIDTH 640 // 屏幕宽度 #define SCREENHEIGHT 480 // 屏幕高度 #define UNIT 20 // 每个墙壁单位的大小 #define PLAYER_R 5 // 游戏者的半径 // 定义常量 const SIZE g_utMap = {23, 23}; // 矿井地图的尺寸(基于 UNIT 单位) const POINT g_utPlayer = {1, 1}; // 游戏者的位置(基于 UNIT 单位) const POINT g_utExit = {21, 22}; // 出口位置(基于 UNIT 单位) const POINT g_ptOffset = {10, 10}; // 矿井显示在屏幕上的偏移量 ////////////////////////////////////////////////////// // 定义全局变量 // POINT g_ptPlayer; // 游戏者的位置 POINT g_ptMouse; // 鼠标位置 IMAGE g_imgMap(WIDTH, HEIGHT); // 矿井平面图 DWORD* g_bufMap; // 矿井平面图的显存指针 IMAGE g_imgRender(WIDTH, HEIGHT); // 渲染 DWORD* g_bufRender; // 渲染的显存指针 DWORD* g_bufScreen; // 屏幕的显存指针 // 枚举用户的控制命令 enum CMD { CMD_QUIT = 1, CMD_UP = 2, CMD_DOWN = 4, CMD_LEFT = 8, CMD_RIGHT = 16, CMD_RESTART = 32 }; ////////////////////////////////////////////////////// // 函数声明 // // 初始化 void Welcome(); // 绘制游戏界面 void ReadyGo(); // 准备开始游戏 void InitGame(); // 初始化游戏数据 // 矿井生成 void MakeMaze(int width, int height); // 初始化(注:宽高必须是奇数) void TravelMaze(int x, int y, BYTE** aryMap); // 遍历 (x, y) 四周 void DrawWall(int x, int y, bool left, bool top, bool right, bool bottom); // 画一面墙 // 绘制 void Paint(); // 绘制视野范围内的矿井 void Lighting(int _x, int _y, double _a); // 在指定位置和角度“照明” void DrawPlayer(); // 绘制游戏者 void DrawExit(); // 绘制出口 // 处理用户控制 int GetCmd(); // 获取用户输入的命令 void OnUp(); // 向上移动 void OnLeft(); // 向左移动 void OnRight(); // 向右移动 void OnDown(); // 向下移动 bool CheckWin(); // 检查是否到出口 ////////////////////////////////////////////////////// // 函数定义 // // 主程序 void main() { // 初始化 initgraph(SCREENWIDTH, SCREENHEIGHT); // 创建绘图窗口 srand((unsigned)time(NULL)); // 设置随机种子 // 显示主界面 Welcome(); // 游戏过程 int c; do { ReadyGo(); while(true) { // 获得用户输入 c = GetCmd(); // 处理用户输入 if (c & CMD_UP) OnUp(); if (c & CMD_DOWN) OnDown(); if (c & CMD_LEFT) OnLeft(); if (c & CMD_RIGHT) OnRight(); if (c & CMD_RESTART) { if (MessageBox(GetHWnd(), _T("您要重来一局吗?"), _T("询问"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK) break; } if (c & CMD_QUIT) { if (MessageBox(GetHWnd(), _T("您确定要退出游戏吗?"), _T("询问"), MB_OKCANCEL | MB_ICONQUESTION) == IDOK) break; } // 绘制场景 Paint(); // 判断是否走出矿井 if (CheckWin()) { // 是否再来一局 HWND hwnd = GetHWnd(); if (MessageBox(hwnd, _T("恭喜你走出来了!\n您想再来一局吗?"), _T("恭喜"), MB_YESNO | MB_ICONQUESTION) != IDYES) c = CMD_QUIT; break; } // 延时 Sleep(16); } } while(!(c & CMD_QUIT)); // 关闭图形模式 closegraph(); } // 准备开始游戏 void ReadyGo() { // 初始化 InitGame(); // 停电前兆 int time[7] = {1000, 50, 500, 50, 50, 50, 50}; int i, x, y; for (i = 0; i < 7; i++) { if (i % 2 == 0) { putimage(0, 0, &g_imgMap); DrawPlayer(); DrawExit(); } else clearrectangle(0, 0, WIDTH - 1, HEIGHT - 1); Sleep(time[i]); } // 电力缓慢中断 for (i = 255; i >= 0; i -= 5) { for (y = (HEIGHT - 1) * SCREENWIDTH; y >= 0; y -= SCREENWIDTH) for (x = 0; x < WIDTH; x++) if (g_bufScreen[y + x] != 0) g_bufScreen[y + x] = g_bufScreen[y + x] - 0x050505; FlushBatchDraw(); DrawPlayer(); DrawExit(); Sleep(50); } // 绘制游戏区 Paint(); } // 绘制游戏界面 void Welcome() { setfillcolor(DARKGRAY); solidrectangle(WIDTH, 0, SCREENWIDTH - 1, SCREENHEIGHT - 1); // 设置字体样式 settextcolor(WHITE); setbkmode(TRANSPARENT); // 绘制标题 settextstyle(24, 0, _T("宋体")); outtextxy(512, 40, _T("矿井逃生")); // 绘制操作说明 RECT r = {488, 100, 632, 470}; settextstyle(12, 0, _T("宋体")); drawtext(_T("[游戏说明]\n  矿井里的电路又出问题了。迅速借助你的头灯,在漆黑的矿井里\ 找到出口逃出去吧。\n\n[控制说明]\n方向键: 移动\nA/S/D/W:移动\n鼠标:  控制照射\ 方向\nF2:   重来一局\nESC:  退出游戏"), &r, DT_WORDBREAK); outtextxy(495, 465, _T("Powered by yw80@qq.com")); } // 初始化游戏数据 void InitGame() { // 获得窗口显存指针 g_bufRender = GetImageBuffer(&g_imgRender); g_bufMap = GetImageBuffer(&g_imgMap); g_bufScreen = GetImageBuffer(NULL); // 设置 Render 环境 SetWorkingImage(&g_imgRender); setbkmode(TRANSPARENT); SetWorkingImage(NULL); // 创建矿井 MakeMaze(g_utMap.cx, g_utMap.cy); // 设置游戏者位置 g_ptPlayer.x = g_utPlayer.x * UNIT + UNIT / 2 + g_ptOffset.x; g_ptPlayer.y = g_utPlayer.y * UNIT + UNIT / 2 + g_ptOffset.y; } // 生成矿井:初始化(注:宽高必须是奇数) void MakeMaze(int width, int height) { if (width % 2 != 1 || height % 2 != 1) return; int x, y; // 定义矿井二维数组,并初始化全部为墙壁 // 宽高比实际多 2,是因为两端各有一个“哨兵”,用于方便处理数据 BYTE** aryMap = new BYTE*[width + 2]; for(x = 0; x < width + 2; x++) { aryMap[x] = new BYTE[height + 2]; memset(aryMap[x], UNIT_WALL, height + 2); } // 定义边界(哨兵功能) for (x = 0; x <= width + 1; x++) aryMap[x][0] = aryMap[x][height + 1] = UNIT_GROUND; for (y = 1; y <= height; y++) aryMap[0][y] = aryMap[width + 1][y] = UNIT_GROUND; // 从任意点开始遍历生成矿井 TravelMaze(((rand() % (width - 1)) & 0xfffe) + 2, ((rand() % (height - 1)) & 0xfffe) + 2, aryMap); // 设置出口 aryMap[g_utExit.x + 1][g_utExit.y + 1] = UNIT_GROUND; // 将矿井绘制在 IMAGE 对象上 SetWorkingImage(&g_imgMap); cleardevice(); for (y = 1; y <= height; y++) for (x = 1; x <= width; x++) if (aryMap[x][y] == UNIT_WALL) DrawWall(x, y, aryMap[x - 1][y] == UNIT_WALL, aryMap[x][y - 1] == UNIT_WALL, aryMap[x + 1][y] == UNIT_WALL, aryMap[x][y + 1] == UNIT_WALL); SetWorkingImage(NULL); } // 生成矿井:遍历 (x, y) 四周 void TravelMaze(int x, int y, BYTE** aryMap) { // 定义遍历方向 int d[4][2] = {0, 1, 1, 0, 0, -1, -1, 0}; // 将遍历方向乱序 int n, t, i; for(i = 0; i < 4; i++) { n = rand() % 4; t = d[i][0], d[i][0] = d[n][0], d[n][0] = t; t = d[i][1], d[i][1] = d[n][1], d[n][1] = t; } // 尝试周围四个方向 aryMap[x][y] = UNIT_GROUND; for(i = 0; i < 4; i++) if (aryMap[x + 2 * d[i][0]][y + 2 * d[i][1]] == UNIT_WALL) { aryMap[x + d[i][0]][y + d[i][1]] = UNIT_GROUND; TravelMaze(x + d[i][0] * 2, y + d[i][1] * 2, aryMap); // 递归 } } // 生成矿井:画一面墙 // 参数:left/top/right/bottom 表示墙壁是否与旁边连接 void DrawWall(int x, int y, bool left, bool top, bool right, bool bottom) { // 墙壁厚 4 pixel int cx, cy; cx = x * UNIT - UNIT / 2 - 2 + 10; cy = y * UNIT - UNIT / 2 - 2 + 10; if (left) solidrectangle(x * UNIT - UNIT + 10, cy, cx + 4, cy + 4); if (top) solidrectangle(cx, y * UNIT - UNIT + 10, cx + 4, cy + 4); if (right) solidrectangle(cx, cy, x * UNIT + 9, cy + 4); if (bottom) solidrectangle(cx, cy, cx + 4, y * UNIT + 9); } // 绘制视野范围内的矿井 void Paint() { // 设置绘图目标为 Render 对象 SetWorkingImage(&g_imgRender); // 清空 Render 对象 cleardevice(); // 计算视野角度 double dx, dy, a; dx = g_ptMouse.x - g_ptPlayer.x; dy = g_ptMouse.y - g_ptPlayer.y; if (dx == 0 && dy == 0) a = 0; else if (dx != 0 && dy != 0) a = atan(dy / dx); else if (dx == 0) a = (dy > 0) ? PI / 2 : PI * 3 / 2; else a = 0; if (dx < 0) a += PI; if (a < 0) a += PI * 2; // 绘制灯光 Lighting(g_ptPlayer.x, g_ptPlayer.y, a); // 画游戏者 DrawPlayer(); // 画出口 DrawExit(); // 设置绘图目标为窗口 SetWorkingImage(NULL); // 显示到窗口上 putimage(0, 0, &g_imgRender); } // 在指定位置和角度“照明” void Lighting(int _x, int _y, double _a) { int i; // 定义循环变量 int x, y; // 定义临时坐标 double a; // 定义临时角度 // 计算灯光照亮的角度区域 double a1 = _a - LIGHT_A / 2; double a2 = _a + LIGHT_A / 2; for(a = a1; a < a2; a += PI / 360) // 扇形循环 { for(int r = 0; r < LIGHT_R; r++) // 半径循环 { // 计算照射到的位置 x = (int)(_x + cos(a) * r); y = (int)(_y + sin(a) * r); // 光线超出屏幕范围,终止 // (为了简化全凭模糊运算,不处理最上和最下一行) if (x < 0 || x >= WIDTH || y <= 0 || y >= HEIGHT - 1) break; // 光线碰到建筑物,终止 if(g_bufMap[y * WIDTH + x]) break; // 光线叠加 g_bufRender[y * WIDTH + x] += 0x202000; // 0x202000 是很淡的黄色 } } // 计算光照扇形区域的最小包围矩形 // 方法:获得 7 个点的最值:圆心、圆弧两端、圆与 xy 轴的 4 个交点 // 第一步:初始化 7 个点 POINT pt[7]; pt[0].x = _x; pt[0].y = _y; pt[1].x = int(_x + LIGHT_R * cos(a1) + 0.5); pt[1].y = int(_y + LIGHT_R * sin(a1) + 0.5); pt[2].x = int(_x + LIGHT_R * cos(a2) + 0.5); pt[2].y = int(_y + LIGHT_R * sin(a2) + 0.5); for (a = ceil(a1 * 4 / (2 * PI)) * (PI / 2), i = 3; a < a2; a += PI / 2, i++) { pt[i].x = int(_x + LIGHT_R * cos(a) + 0.5); pt[i].y = int(_y + LIGHT_R * sin(a) + 0.5); } // 第二步:获取 7 个点的最大最小值,得到最小包围矩形 i--; RECT r = {pt[i].x, pt[i].y, pt[i].x, pt[i].y}; for (--i; i >= 0; i--) { if (pt[i].x < r.left) r.left = pt[i].x; if (pt[i].x > r.right) r.right = pt[i].x; if (pt[i].y < r.top) r.top = pt[i].y; if (pt[i].y > r.bottom) r.bottom = pt[i].y; } // 调整矩形范围 if (r.left < 0) r.left = 0; if (r.top < 1) r.top = 1; if (r.right >= WIDTH) r.right = WIDTH - 1; if (r.bottom >= HEIGHT - 1) r.bottom = HEIGHT - 2; // 修正曝光过度的点 for (y = r.top; y <= r.bottom; y++) for (x = r.left; x <= r.right; x++) { i = y * WIDTH + x; if (g_bufRender[i] > 0xffff00) g_bufRender[i] = 0xffff00; } // 将光线模糊处理(避开建筑物) for (y = r.top; y <= r.bottom; y++) for (x = r.left; x <= r.right; x++) { i = y * WIDTH + x; if (!g_bufMap[i]) g_bufRender[i] = RGB( (GetRValue(g_bufRender[i - WIDTH]) + GetRValue(g_bufRender[i - 1]) + GetRValue(g_bufRender[i]) + GetRValue(g_bufRender[i + 1]) + GetRValue(g_bufRender[i + WIDTH])) / 5, (GetGValue(g_bufRender[i - WIDTH]) + GetGValue(g_bufRender[i - 1]) + GetGValue(g_bufRender[i]) + GetGValue(g_bufRender[i + 1]) + GetGValue(g_bufRender[i + WIDTH])) / 5, (GetBValue(g_bufRender[i - WIDTH]) + GetBValue(g_bufRender[i - 1]) + GetBValue(g_bufRender[i]) + GetBValue(g_bufRender[i + 1]) + GetBValue(g_bufRender[i + WIDTH])) / 5); } } // 绘制游戏者 void DrawPlayer() { // 画安全帽 setlinecolor(DARKGRAY); circle(g_ptPlayer.x, g_ptPlayer.y, 5); } // 绘制出口 void DrawExit() { settextstyle(12, 0, _T("宋体")); outtextxy(g_utExit.x * UNIT + g_ptOffset.x, g_utExit.y * UNIT + g_ptOffset.y + 8, _T("出口")); } // 获取用户输入的命令 int GetCmd() { int c = 0; if ((GetAsyncKeyState(VK_LEFT) & 0x8000) || (GetAsyncKeyState('A') & 0x8000)) c |= CMD_LEFT; if ((GetAsyncKeyState(VK_RIGHT) & 0x8000) || (GetAsyncKeyState('D') & 0x8000)) c |= CMD_RIGHT; if ((GetAsyncKeyState(VK_UP) & 0x8000) || (GetAsyncKeyState('W') & 0x8000)) c |= CMD_UP; if ((GetAsyncKeyState(VK_DOWN) & 0x8000) || (GetAsyncKeyState('S') & 0x8000)) c |= CMD_DOWN; if (GetAsyncKeyState(VK_F2) & 0x8000) c |= CMD_RESTART; if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) c |= CMD_QUIT; MOUSEMSG m; while(MouseHit()) { m = GetMouseMsg(); g_ptMouse.x = m.x; g_ptMouse.y = m.y; } return c; } // 向上移动 void OnUp() { int i = (g_ptPlayer.y - 6) * WIDTH + (g_ptPlayer.x - 5) + 1; int j; for (j = 0; j < 5; j++, i += 2) if (g_bufMap[i]) break; if (j == 5) g_ptPlayer.y--; } // 向左移动 void OnLeft() { int i = (g_ptPlayer.y - 5) * WIDTH + (g_ptPlayer.x - 5); int j; for (j = 0; j < 5; j++, i += WIDTH) if (g_bufMap[i]) break; if (j == 5) g_ptPlayer.x--; } // 向右移动 void OnRight() { int i = (g_ptPlayer.y - 5) * WIDTH + (g_ptPlayer.x + 5) + 1; int j; for (j = 0; j < 5; j++, i += WIDTH) if (g_bufMap[i]) break; if (j == 5) g_ptPlayer.x++; } // 向下移动 void OnDown() { int i = (g_ptPlayer.y + 5) * WIDTH + (g_ptPlayer.x - 5) + 1; int j; for (j = 0; j < 5; j++, i += 2) if (g_bufMap[i]) break; if (j == 5) g_ptPlayer.y++; } // 检查是否到出口 bool CheckWin() { return (g_ptPlayer.y >= g_utExit.y * UNIT + UNIT / 2 + g_ptOffset.y); }

游戏:AyaCrossX(十字消除游戏)

十字消除是一款经典消除类小游戏。玩法如下: 点击空白处,所在横纵十字线上同色方块会被消除。 消除方块会累加得分,并恢复一定量的时间。 方块数量少于某数值会出现新方块并回复时间。 时间耗尽时则游戏结束。 游戏运行截图如下: 游戏的源码编译环境:Visual C++ 6.0 / 2008 / 2010 / 2012,EasyX 2013霜降版 游戏的源代码如下: ///////////////////////////////////////////////////////// // 程序名称:AyaCrossX(十字消除游戏) // 编译环境:Visual C++ 6.0 / 2012,EasyX 2013霜降版 // 作 者:ls9512 <http://www.baidu.com/p/ls9512> // 最后修改:2013-11-2 // //系统函数库 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <conio.h> #include <math.h> #include <time.h> //EasyX图形库 #include <graphics.h> //////////////////////////////以下是 宏定义参数 ////////////////////////////// #define WIN_WIDTH 600 //屏幕宽度 #define WIN_HEIGHT 430 //屏幕高度 #define WIN_BLANKTOP 45 //屏幕顶部空边 #define WIN_BLANK 25 //屏幕边缘留空 #define G_BN 20 //方块横向数量 #define G_BM 12 //方块纵向数量 #define G_BW 20 //方块宽度 #define G_BS 8 //方块间隔宽度 #define G_BSELECT 3 //方块选中大小 #define G_FLOATW 4 //选中浮动大小 #define G_BDWO 1 //消失初始大小 #define G_BDW 6 //消失大小 #define G_CN 10 //颜色数量 #define G_BUL 25 //方块增量程度 #define C_BLOCKPANEL RGB(255, 255, 221) //底板颜色 #define C_HPBAR RGB(255, 170, 255) //HP条颜色 //////////////////////////////以下是数据结构定义////////////////////////////// //方块结构 struct Block{ COLORREF color; //颜色 float FLOAT; //浮动大小 float DIS; //消除延迟 bool isDIS; //是否消失中 bool isClick; //是否被点击 }; //点结构 struct Point{ int x; int y; }; //矩形结构 struct Rect{ int x; int y; int w; int h; }; //////////////////////////////以下是 全局变量 ////////////////////////////// int GamePhase = 0; //游戏阶段 0 准备 1 进行 2 结束 3 帮助界面 int isShowFPS = 0; //是否显示帧数 int mouseX; //鼠标位置坐标X int mouseY; //鼠标位置坐标Y int time_max; //总游戏时间 int time_now; //当前剩余时间 int level; //等级 int score; //分数 bool isMouseDown; //鼠标按下 Block block[G_BM][G_BN]; //方块数组 COLORREF Defcolor[G_CN]; //候选颜色数组 //////////////////////////////以下是 函数声明 ////////////////////////////// float GetFPS(); //获取帧数 void HpSleep(DWORD ms); //绝对延时 Point GetMouseLocal(int x, int y); //获取鼠标所在坐标 void AddBlock(int n); //添加指定个数的未选中方块 void Init(); //初始化 void DisCheck(int x, int y); //检测指定位置的消除 int CountBlock(); //统计方块个数 void Manager(); //逻辑处理 void Draw(); //绘图处理 void StartDraw(); //游戏开始绘图 void GamingDraw(); //游戏进行中绘图 void EndDraw(); //游戏结束绘图 void HelpDraw(); //游戏帮助绘图 void EndFrame(); //帧结束处理 bool IsInRect(int x, int y, Rect r); //是否在矩形内 //////////////////////////////以下是辅助功能函数////////////////////////////// //计算画面FPS(每秒帧数) float GetFPS() { #define FPS_COUNT 8 static int i = 0; static DWORD oldTime = GetTickCount(); static float fps; if (i > FPS_COUNT) { i = 0; int newTime = GetTickCount(); int elapsedTime = newTime - oldTime; fps = FPS_COUNT / (elapsedTime / 1000.0f); oldTime = newTime; } i++; return fps; } //绝对延时 void HpSleep(DWORD ms) { static clock_t oldclock = clock(); // 静态变量,记录上一次 tick oldclock += ms * CLOCKS_PER_SEC / 1000; // 更新 tick if (clock() > oldclock) // 如果已经超时,无需延时 oldclock = clock(); else while(clock() < oldclock) // 延时 Sleep(1); // 释放 CPU 控制权,降低 CPU 占用率 } //获取鼠标选中点 Point GetMouseLocal(int x, int y) { Point p; x = (x - WIN_BLANK) / (G_BW + G_BS); y = (y - WIN_BLANK - WIN_BLANKTOP) / (G_BW + G_BS); if (x < 0 || x >= G_BN) x = -1; if (y < 0 || y >= G_BM) y = -1; p.x = x; p.y = y; return p; } //是否在矩形内 bool IsInRect(int x, int y, Rect r) { return ((x >= r.x && x <= r.w) && (y >= r.y && y <= r.h)); } //添加制定个数的未选中方块 void AddBlock(int n) { //随机生成方块 int num = 0, x1, y1, x2, y2; int num_max = n; while(num < num_max) { x1 = rand() % G_BN; y1 = rand() % G_BM; x2 = rand() % G_BN; y2 = rand() % G_BM; if(block[y1][x1].isClick && block[y2][x2].isClick) { COLORREF color = Defcolor[rand() % G_CN]; block[y1][x1].color = color; block[y1][x1].isClick = false; block[y1][x1].FLOAT = 0; block[y1][x1].DIS = 0; block[y1][x1].isDIS = false; block[y2][x2].color = color; block[y2][x2].isClick = false; block[y2][x2].FLOAT = 0; block[y2][x2].DIS = 0; block[y2][x2].isDIS = false; num += 2; } } } //初始化 void Init() { //预置颜色 Defcolor[0] = RGB(255, 153, 0); Defcolor[1] = RGB(204, 102, 0); Defcolor[2] = RGB(27, 118, 255); Defcolor[3] = RGB(255, 136, 255); Defcolor[4] = RGB(204, 204, 102); Defcolor[5] = RGB(104, 204, 204); Defcolor[6] = RGB(255, 125, 125); Defcolor[7] = RGB(204, 104, 204); Defcolor[8] = RGB(190, 190, 190); Defcolor[9] = RGB(0, 204, 0); //初始化时间 time_max = 10000; time_now = 10000; //初始化分数 score = 0; //置全选中 for(int i = 0; i < G_BM; i++) { for(int j = 0; j < G_BN; j++) { block[i][j].isClick = true; block[i][j].FLOAT = 0; block[i][j].DIS = 0; } } //添加方块 AddBlock(G_BN * G_BM * 2 / 3); //清除鼠标消息队列 FlushMouseMsgBuffer(); } //统计方块个数 int CountBlock() { int n = 0; for(int i = 0; i < G_BN; i++) { for(int j = 0; j < G_BM; j++) { if(!block[j][i].isClick) n++; } } return n; } //检测指定位置消除 void DisCheck(int x, int y) { //记录待消除的方块 Block* b[4]; int i, j, n = 0; //向左 for(i = x; i >= 0; i--) { if(!block[y][i].isClick) { b[n] = &block[y][i]; n++; break; } } //向右 for(i = x; i < G_BN; i++) { if(!block[y][i].isClick) { b[n] = &block[y][i]; n++; break; } } //向上 for(j = y; j >= 0; j--) { if(!block[j][x].isClick) { b[n] = &block[j][x]; n++; break; } } //向下 for(j = y; j < G_BM; j++) { if(!block[j][x].isClick) { b[n] = &block[j][x]; n++; break; } } //消除 for(i = 0; i < n; i++) { for(j = i + 1; j < n; j++) { if(b[i]->color == b[j]->color) { if(!b[i]->isDIS){ b[i]->DIS = G_BDWO; b[i]->isDIS = true; } if(!b[j]->isDIS){ b[j]->DIS = G_BDWO; b[j]->isDIS = true; } } } } isMouseDown = false; } //逻辑处理 void Manager() { //如果在游戏中 if(GamePhase == 1) { Point p = GetMouseLocal(mouseX, mouseY); int x = p.x, y = p.y; //如果选中 if(p.x != -1 && p.y != -1) { //如果单击 if(isMouseDown && block[y][x].isClick) { //触发消除检测 DisCheck(x, y); } } //添加新方块添加随机个数新方块 if(CountBlock() < G_BN * G_BM / 3) { AddBlock((rand() % 15 + 15) * 2); //时间恢复 time_now += 500; if(time_now > time_max) time_now = time_max; } } } //开始绘图 void StartDraw() { settextstyle(52, 30, _T("Impact")); //标题 settextcolor(RGB(255, 17, 102)); outtextxy(143, 93, _T("AyaCrossX")); settextcolor(RGB(255, 153, 238)); outtextxy(140, 90, _T("AyaCrossX")); //版本 settextstyle(22, 10, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(340, 153, _T("v0.91 By:ls9512")); //开始游戏按钮 Rect r; r.x = 240; r.y = 220; r.w = r.x + 125; r.h = r.y + 35; setlinecolor(RGB(255, 17, 102)); if(IsInRect(mouseX, mouseY, r)) { setfillcolor(RGB(255, 187, 119)); //开始游戏按键响应 if(isMouseDown) { Init(); GamePhase = 1; isMouseDown = false; } } else setfillcolor(RGB(255, 255, 204)); fillrectangle(r.x, r.y, r.w, r.h); settextstyle(25, 10, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(r.x + 5, r.y + 5, _T("PLAY GAME")); //帮助游戏按钮 r.x = 240; r.y = 270; r.w = r.x + 125; r.h = r.y + 35; setlinecolor(RGB(255, 17, 102)); if(IsInRect(mouseX, mouseY, r)) { setfillcolor(RGB(255, 187, 119)); //帮助按键响应 if(isMouseDown) GamePhase = 3; } else setfillcolor(RGB(255, 255, 204)); fillrectangle(r.x, r.y, r.w, r.h); settextstyle(25, 10, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(r.x + 5, r.y + 5, _T("HELP INFO")); //退出游戏按钮 r.x = 240; r.y = 320; r.w = r.x + 125; r.h = r.y + 35; setlinecolor(RGB(255, 17, 102)); if(IsInRect(mouseX, mouseY, r)) { setfillcolor(RGB(255, 187, 119)); //退出按键响应 if(isMouseDown) exit(0); } else setfillcolor(RGB(255, 255, 204)); fillrectangle(r.x, r.y, r.w, r.h); settextstyle(25, 10, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(r.x + 5, r.y + 5, _T("EXIT GAME")); //说明 settextstyle(16, 5, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(175, 400, _T("Programing By VC++ & EasyX Date:2013.11.01")); } //游戏中绘图 void GameingDraw() { int x, y; COLORREF c, c2; //时间条 x = WIN_BLANK; y = WIN_BLANK; int HPBARW = (G_BW + G_BS) * G_BN - 200; setlinecolor(RGB(255, 17, 255)); setfillcolor(WHITE); fillrectangle(x, y, x + HPBARW, y + 10); setfillcolor(C_HPBAR); fillrectangle(x, y, x + (int)(HPBARW * (1.0 * time_now / time_max)), y + 10); //底板 setlinecolor(RGB(255, 25, 22)); setfillcolor(C_BLOCKPANEL); x = WIN_BLANK - G_BS; y = WIN_BLANK + WIN_BLANKTOP - G_BS; fillrectangle(x, y, x + (G_BW + G_BS) * G_BN + G_BS, y + (G_BW + G_BS) * G_BM + G_BS); //画方块 for(int i = 0; i < G_BM; i++) { for(int j = 0; j < G_BN; j++) { if(!block[i][j].isClick) { c = block[i][j].color; c = RGB(GetRValue(c) - (BYTE)G_BUL, GetGValue(c) - (BYTE)G_BUL, GetBValue(c) - (BYTE)G_BUL); int fw = (int)block[i][j].FLOAT; int fd = (int)block[i][j].DIS; setlinecolor(c); setfillcolor(block[i][j].color); x = j * (G_BW + G_BS) + WIN_BLANK - fw - fd; y = i * (G_BW + G_BS) + WIN_BLANK + WIN_BLANKTOP - fw - fd; fillrectangle(x, y, x + G_BW + 2 * (fw + fd), y + G_BW + 2 * (fw + fd)); //浮动缩减 if(block[i][j].FLOAT > 0) block[i][j].FLOAT -= 0.4f; //消失延迟 if(block[i][j].isDIS) { if(block[i][j].DIS < G_BDW) block[i][j].DIS += 0.4f; else { //消除 block[i][j].isClick = true; //加分 score += 100; time_now += 25; if (time_now > time_max) time_now = time_max; } } } } } //获取鼠标位置 Point p = GetMouseLocal(mouseX, mouseY); //画出鼠标光标 if(p.x != -1 && p.y != -1) { c = RED; x = p.x, y = p.y; int fw; if(block[y][x].isClick) fw = 0; else fw = (int)block[y][x].FLOAT; x = x * (G_BW + G_BS) + WIN_BLANK - G_BSELECT - fw; y = y * (G_BW + G_BS) + WIN_BLANK + WIN_BLANKTOP - G_BSELECT -fw; //选中方块加亮 if(!block[p.y][p.x].isClick) { //置浮动大小 block[p.y][p.x].FLOAT = G_FLOATW; c2 = block[p.y][p.x].color; } else { c2 = C_BLOCKPANEL; } setlinecolor(c); setfillcolor(c2); fillrectangle(x, y, x + G_BW + 2 * (G_BSELECT + fw), y + G_BW + 2 * (G_BSELECT + fw)); } //画出分数 settextstyle(25, 10, _T("Verdana")); settextcolor(RGB(155, 51, 68)); TCHAR b[10]; #if _MSC_VER > 1200 _stprintf_s(b, _T("%d"), score); #else _stprintf(b, _T("%d"), score); #endif outtextxy(390, 15, _T("分数:")); settextcolor(RGB(255, 51, 68)); outtextxy(440, 16, b); } //游戏结束绘图 void EndDraw() { //标题 settextstyle(52, 30, _T("Impact")); settextcolor(RGB(255, 17, 102)); outtextxy(143, 103, _T("GAME OVER")); settextcolor(RGB(215, 193, 238)); outtextxy(140, 100, _T("GAME OVER")); //版本 settextstyle(34, 12, _T("Verdana")); settextcolor(RGB(255, 51, 68)); TCHAR b[10]; #if _MSC_VER > 1200 _stprintf_s(b, _T("%d"), score); #else _stprintf(b, _T("%d"), score); #endif outtextxy(232, 173, _T("分数:")); outtextxy(290, 173, b); //重新开始游戏按钮 Rect r; r.x = 240; r.y = 240; r.w = r.x + 125; r.h = r.y + 35; setlinecolor(RGB(255, 17, 102)); if(IsInRect(mouseX, mouseY, r)) { setfillcolor(RGB(255, 187, 119)); //开始游戏按键响应 if(isMouseDown) { Init(); GamePhase = 1; } } else setfillcolor(RGB(255, 255, 204)); fillrectangle(r.x, r.y, r.w, r.h); settextstyle(25, 10, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(r.x + 5, r.y + 5, _T(" REPLAY")); //退出 r.x = 240; r.y = 290; r.w = r.x + 125; r.h = r.y + 35; setlinecolor(RGB(255, 17, 102)); if(IsInRect(mouseX, mouseY, r)) { setfillcolor(RGB(255, 187, 119)); //退出按键响应 if(isMouseDown) exit(0); } else setfillcolor(RGB(255, 255, 204)); fillrectangle(r.x, r.y, r.w, r.h); settextstyle(25, 10, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(r.x + 5, r.y + 5, _T("EXIT GAME")); } //游戏帮助绘图 void HelpDraw() { //标题 settextstyle(52, 30, _T("Impact")); settextcolor(RGB(255, 17, 152)); outtextxy(233, 33, _T("HELP")); settextcolor(RGB(215, 193, 238)); outtextxy(230, 30, _T("HELP")); //底板 setlinecolor(RGB(255, 17, 204)); setfillcolor(RGB(255, 221, 255)); fillrectangle(60, 100, 550, 280); //版本 settextstyle(18, 10, _T("黑体")); settextcolor(RGB(245, 126, 92)); outtextxy(70, 110, _T("十字消除是一款经典消除类小游戏。")); outtextxy(70, 145, _T("【玩法】")); outtextxy(80, 170, _T("1.点击空白处,所在横纵十字线上同色方块会被消除。")); outtextxy(80, 195, _T("2.消除方块会累加得分,并恢复一定量的时间.")); outtextxy(80, 220, _T("3.方块数量少于某数值会出现新方块并回复时间.")); outtextxy(80, 245, _T("4.时间耗尽时则游戏结束.")); //返回 Rect r; r.x = 240; r.y = 360; r.w = r.x + 125; r.h = r.y + 35; setlinecolor(RGB(255, 17, 102)); if(IsInRect(mouseX, mouseY, r)) { setfillcolor(RGB(255, 187, 119)); //返回按键响应 if(isMouseDown) GamePhase = 0; } else setfillcolor(RGB(255, 255, 204)); fillrectangle(r.x, r.y, r.w, r.h); settextstyle(25, 9, _T("Verdana")); settextcolor(RGB(255, 51, 68)); outtextxy(r.x + 5, r.y + 5, _T("BACK MENU")); } //输出帧数 void DrawFPS() { //输出帧数 if(isShowFPS) { settextcolor(RED); settextstyle(14, 0, _T("宋体")); TCHAR s[5]; #if _MSC_VER > 1200 _stprintf_s(s, _T("%.1f"), GetFPS()); #else _stprintf(s, _T("%.1f"), GetFPS()); #endif outtextxy(0, 0, s); } } //绘图 void Draw() { //清屏 setlinecolor(WHITE); setfillcolor(WHITE); fillrectangle(0, 0, WIN_WIDTH, WIN_HEIGHT); switch(GamePhase) { case 0: StartDraw(); break; case 1: GameingDraw(); break; case 2: EndDraw(); break; case 3: HelpDraw(); break; } DrawFPS(); } //帧结束处理 void EndFrame() { time_now -= 2; //游戏结束 if(time_now <= 0 && GamePhase == 1) { GamePhase = 2; } } //主入口函数 int main() { //置随机数种子 srand((unsigned)time(NULL)); //初始化设备,加载图片 initgraph(WIN_WIDTH, WIN_HEIGHT); //设置窗口标题 SetWindowText(GetHWnd(), _T("AyaCrossX v0.91 By:ls9512")); cleardevice(); //设置黑色背景 setbkmode(TRANSPARENT); settextcolor(BLACK); //开启双缓冲,防止闪屏 BeginBatchDraw(); // 鼠标消息变量 MOUSEMSG mmsg; //初始化 Init(); while(true) { //处理鼠标消息 while(MouseHit()) { mmsg = GetMouseMsg(); switch(mmsg.uMsg) { case WM_MOUSEMOVE: mouseX = mmsg.x; mouseY = mmsg.y; break; case WM_LBUTTONDOWN: isMouseDown = true; break; case WM_LBUTTONUP: isMouseDown = false; break; } } //逻辑处理 Manager(); //绘图处理 Draw(); //显示缓存的绘制内容 FlushBatchDraw(); //帧结束处理 EndFrame(); //延迟,帧数控制 HpSleep(18); } // 关闭 EndBatchDraw(); closegraph(); return 0; } 作者:ls9512博客:http://www.baidu.com/p/ls9512