在之前一篇《简单数字拼板游戏学习》基础上修改,地址:http://www.cnblogs.com/fwst/p/3706483.html
开发环境:Windows 7/ Visual Studio 2010 / MFC对话框用 / 字符集:使用多字节字符集
运行效果:
(4 X 4)
(7 X 7)
(1)已完成 2048 游戏基本功能,需要解决的几个关键问题是
a. 首先是数据结构。先定义矩形类,然后定义矩形类对象的二维数组,长度由宏定义,可修改,即可自定义成N*N的游戏。这样游戏就是由N*N个矩形对象组成。
b. 然后是游戏逻辑处理,也是最重要的一部分。方向键的响应。键盘上下左右四个方向键的逻辑一样,代码部分只是稍微修改一下。这部分逻辑有点纠结,应该有多种方法,这里介绍我的处理,有不同方法欢迎分享。以左键为例,这部分逻辑对每一行的处理步骤如下:
I. 清空空格, 并将所有数字按次序移到最左边。每个矩形有一个值,当值为0时,不显示,这些矩形就是空格。如一开始是0 2 0 2,那么经这一步处理后就应该是
2 2 0 0;
II. 从左边开始,依次将与右边相邻值相等的矩形的值加倍,并将该相邻值置为0。如 2 2 0 0,处理后应该是 4 0 0 0; 再如 2 2 2 2 处理后应该是 4 0 4 0 ;再如 4 2 2 8处理后是 4 4 0 8;
III. 再做一次第一步。这一步是为了处理做完第二步后新出现的空格。比如2 2 2 2做完第二步是4 0 4 0, 再经过这一步后就变成最终的4 4 0 0。
c. 生成新数字。每做完一个动作后需要生成一个新数字,本来原来游戏中新生成的为2或者4, 我这里就直接全都用2了,相对于原来游戏算是降低了难度,要生成4也很简单,加个概率随机生成就行。新生成数字的位置用一个循环,当生成的位置的值不为0 的时候就再次重新随机生成,直到随机到的位置的值为0。
另外,生成之前必须要加一个判断,就是如果最近的按键没有引起游戏面盘上的变化,则不能生成新数字。
d. 游戏结束的判断。 用一个全局函数,游戏结束的条件是游戏面盘上有空格,或者没有空格且任意两个相邻的数字的值都不相同,这里的相邻是指行和列两个方向上的相邻。这里用三个循环,第一个循环判断是否有空格,如果有空格,那么游戏肯定没有结束,函数直接返回false。第二个循环是从行的方向上,依次判断相邻的两个值。同理第三个循环是从列的方向上判断。游戏结束判断为true后用messagebox弹出对话框。
(2) 还没有做的事
a. 可以用圆角矩形。
b. 界面色彩太花,伤眼,可以弄成原作那样数字从小到大,颜色由浅到深。
c. 没有记分功能。
d. 没做重新开始。没做回一步。
e. 界面框框大小不固定,可以拖动。。。(在对话框的属性里将Border由Resizing改为Dialog Frame就可以了)
f. 这里没有加2048就胜利的判断,相当于Endless模式。
今天修改的效果图:
将每个矩形的大小缩小了一点,然后给不同的数字配上了不同的颜色,由浅到深。
最终代码如下:
MyRect.h
#include "stdafx.h" class MyRect { public: MyRect(UINT x1, UINT y1, UINT x2, UINT y2); ~MyRect(); public: //矩形框的当前值 UINT uValue;//矩形顶点坐标 UINT x1; UINT y1; UINT x2; UINT y2; };
MyRect.cpp
#include "stdafx.h" #include "MyRect.h" MyRect::MyRect(UINT x1, UINT y1, UINT x2, UINT y2) { this->x1 = x1; this->y1 = y1; this->x2 = x2; this->y2 = y2; uValue = 0; } MyRect::~MyRect() { }
在 2048Dlg.cpp中,首先添加头文件,
#include "MyRect.h"
然后是全局变量和函数部分, 即在头文件和宏定义之后添加,
//大矩形为 LINELENGTH * LINELENGTH #define LINELENGTH 4 #define RECTNUM (LINELENGTH*LINELENGTH) struct MyPoint{ int x; int y; }; //实际矩形数组,面板上显示的每个矩形都是CRect类型,声明在这里 CRect *rect[LINELENGTH][LINELENGTH]; //控制是否生成新数字,为true的时候说明有动作,就会生成新数字 bool bHaveDoneSth; //端点位置 MyPoint point[LINELENGTH][LINELENGTH] = {0}; //矩形对象数组,相当于逻辑部分,保存矩形的显示值,坐标 MyRect *myrect[LINELENGTH][LINELENGTH]; //填充画刷,可以控制矩形填充不同的颜色 CBrush *brush; //生成一个新数字,随机一个0-RECTNUM的整数,根据这个整数计算出二维数组的横坐标和竖坐标 // A/LINELENGTH 是横坐标, A%LINELENGTH 是竖坐标, 当生成的位置有值的时候,重新生成 // 初始值为2, 可以再这里加控制生成2,或 4 。 void GenerateNewNum() { srand(time(0)); int A = rand() % RECTNUM; while (myrect[A/LINELENGTH][A%LINELENGTH]->uValue != 0) { A = rand() % RECTNUM; } myrect[A/LINELENGTH][A%LINELENGTH]->uValue = 2; } //判断游戏结束 bool GameOver() { //如果有值为0 的矩形,则游戏肯定可以继续,所以直接返回false for (int i = 0; i < LINELENGTH; i++) for (int j = 0; j < LINELENGTH; j++) { if ( myrect[i][j]->uValue == 0 ) return false; } // 对每一行相邻的两个数,如果有相同的,那么游戏可以继续,返回false for (int i = 0; i < LINELENGTH; i++) for (int j = 0; j < LINELENGTH-1; j++) { if ( myrect[i][j]->uValue == myrect[i][j+1]->uValue ) return false; } // 对每一列相邻的两个数,如果有相同的,那么游戏可以继续,返回false for (int j = 0; j < LINELENGTH; j++) for (int i = 0; i < LINELENGTH-1; i++) { if ( myrect[i][j]->uValue == myrect[i+1][j]->uValue ) return false; } return true; }
在 CMy2048Dlg::OnInitDialog() 中 , 添加初始化代码,
// TODO: 在此添加额外的初始化代码 ::SetWindowPos(this->m_hWnd, HWND_BOTTOM, 0, 0, 25+LINELENGTH*100, 48+LINELENGTH*100, SWP_NOZORDER); //初始化每个矩形的左上角点的坐标 for (int i = 0; i < LINELENGTH; i++) { for (int j = 0; j < LINELENGTH; j++) { point[i][j].x = j * 100 + 10; point[i][j].y = i * 100 + 10; } } //初始化矩形和填充画刷 for (int i = 0; i < LINELENGTH; i++) { for (int j = 0; j < LINELENGTH; j++) { myrect[i][j] = new MyRect(point[i][j].x, point[i][j].y, point[i][j].x+90, point[i][j].y+90); myrect[i][j]->uValue = 0; } } //初始化数字 srand(time(0)); int A = rand() % RECTNUM; int B = rand() % RECTNUM; while ( B == A ) { B = rand() % RECTNUM; } myrect[ A / LINELENGTH][ A % LINELENGTH]->uValue = 2; myrect[ B / LINELENGTH][ B % LINELENGTH]->uValue = 2;
在 OnPaint()函数的最后添加绘制代码,
CFont font; font.CreateFont(25,25,0,0,700,false,false,false, CHINESEBIG5_CHARSET,OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,DEFAULT_QUALITY, FF_MODERN,TEXT("宋体")); //客户区设备环境 CClientDC dc(this); //新建画笔 CPen pen; pen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0)); //选中字体 dc.SelectObject(pen); for (int i = 0; i < LINELENGTH; i++) { for (int j = 0; j < LINELENGTH; j++) { //画矩形 //dc.RoundRect(myrect[i][j]->getRect(), 4, 4); dc.Rectangle(myrect[i][j]->x1, myrect[i][j]->y1,myrect[i][j]->x2, myrect[i][j]->y2); //填充矩形 rect[i][j] = new CRect(myrect[i][j]->x1, myrect[i][j]->y1,myrect[i][j]->x2, myrect[i][j]->y2); //设置文字背景透明 dc.SetBkMode(TRANSPARENT); //选中字体 dc.SelectObject(font); //写数字 if (myrect[i][j]->uValue == 0) { brush = new CBrush(RGB(0xFC,0xFC,0xFC)); dc.FillRect(rect[i][j], brush); delete brush; } else if (myrect[i][j]->uValue != 0) { switch(myrect[i][j]->uValue) { case 2:brush = new CBrush(RGB(0xFF,0xFF,0xFF));break; case 4:brush = new CBrush(RGB(0xFF,0xE4,0xC4));break; case 8:brush = new CBrush(RGB(0xFF,0xB6,0xC1));break; case 16:brush = new CBrush(RGB(0xFF,0x83,0xFA));break; case 32:brush = new CBrush(RGB(0xFF,0xC1,0x25));break; case 64:brush = new CBrush(RGB(0xFF,0x6A,0x6A));break; case 128:brush = new CBrush(RGB(0xFF,0x14,0x93));break; case 256:brush = new CBrush(RGB(0xCD,0x66,0x1D));break; case 512:brush = new CBrush(RGB(0x94,0x00,0xD3));break; case 1024:brush = new CBrush(RGB(0xFF,0xFF,0x00));break; case 2048:brush = new CBrush(RGB(0xFF,0x00,0x00));break; default:brush = new CBrush(RGB(0xFF,0x00,0x00));break; } dc.FillRect(rect[i][j], brush); delete brush; char num[10] = {'0'}; itoa(myrect[i][j]->uValue, num, 10); dc.DrawText(num, -1, rect[i][j], DT_VCENTER|DT_CENTER|DT_SINGLELINE); } } }
然后就是在类向导里添加键盘响应函数,OnKeyUp, 在里面添加以下代码:
// TODO: 在此添加消息处理程序代码和/或调用默认值 switch(nChar) { case VK_LEFT: //判断是否有动作,用来控制是否生成新数字 bHaveDoneSth = false; for (int i = 0; i < LINELENGTH; i++) { //去掉空格 for (int j = 0; j < LINELENGTH ; j++) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = 0; k < j; k++) { if (myrect[i][k]->uValue == 0) { bHaveDoneSth = true; myrect[i][k]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } //相加 for (int j = 0; j < LINELENGTH-1 ; j++) { if ( myrect[i][j]->uValue != 0 ) { if ( myrect[i][j+1]->uValue == myrect[i][j]->uValue ) { bHaveDoneSth = true; myrect[i][j]->uValue += myrect[i][j+1]->uValue; myrect[i][j+1]->uValue = 0; } } } //去掉空格 for (int j = 0; j < LINELENGTH ; j++) { if ( myrect[i][j]->uValue != 0 ) { for (int k = 0; k < j; k++) { if (myrect[i][k]->uValue == 0) { bHaveDoneSth = true; myrect[i][k]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } } break; case VK_UP: bHaveDoneSth = false; for (int j = 0; j < LINELENGTH; j++) { //去掉空格 for (int i = 0; i < LINELENGTH ; i++) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = 0; k < i; k++) { if (myrect[k][j]->uValue == 0) { bHaveDoneSth = true; myrect[k][j]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } //相加 for (int i = 0; i < LINELENGTH-1 ; i++) { if ( myrect[i][j]->uValue != 0 ) { if ( myrect[i+1][j]->uValue == myrect[i][j]->uValue ) { bHaveDoneSth = true; myrect[i][j]->uValue += myrect[i+1][j]->uValue; myrect[i+1][j]->uValue = 0; } } } //去掉空格 for (int i = 0; i < LINELENGTH ; i++) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = 0; k < i; k++) { if (myrect[k][j]->uValue == 0) { bHaveDoneSth = true; myrect[k][j]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } } break; case VK_RIGHT: bHaveDoneSth = false; for (int i = 0; i < LINELENGTH; i++) { //去掉空格 for (int j = LINELENGTH - 1; j >= 0 ; j--) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = LINELENGTH - 1; k >= j; k--) { if (myrect[i][k]->uValue == 0) { bHaveDoneSth = true; myrect[i][k]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } //相加 for (int j = LINELENGTH - 1; j > 0 ; j--) { if ( myrect[i][j]->uValue != 0 ) { if ( myrect[i][j-1]->uValue == myrect[i][j]->uValue ) { bHaveDoneSth = true; myrect[i][j]->uValue += myrect[i][j-1]->uValue; myrect[i][j-1]->uValue = 0; } } } //去掉空格 for (int j = LINELENGTH - 1; j >= 0 ; j--) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = LINELENGTH - 1; k >= j; k--) { if (myrect[i][k]->uValue == 0) { bHaveDoneSth = true; myrect[i][k]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } } break; case VK_DOWN: bHaveDoneSth = false; for (int j = LINELENGTH - 1; j >= 0; j--) { //去掉空格 for (int i = LINELENGTH -1 ; i >= 0; i--) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = LINELENGTH - 1; k >= i; k--) { if (myrect[k][j]->uValue == 0) { bHaveDoneSth = true; myrect[k][j]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } //相加 for (int i = LINELENGTH - 1; i > 0 ; i--) { if ( myrect[i][j]->uValue != 0 ) { if ( myrect[i-1][j]->uValue == myrect[i][j]->uValue ) { bHaveDoneSth = true; myrect[i][j]->uValue += myrect[i-1][j]->uValue; myrect[i-1][j]->uValue = 0; } } } //去掉空格 for (int i = LINELENGTH -1 ; i >= 0; i--) { // if ( myrect[i][j]->uValue != 0 ) { for (int k = LINELENGTH-1; k >= i; k--) { if (myrect[k][j]->uValue == 0) { bHaveDoneSth = true; myrect[k][j]->uValue = myrect[i][j]->uValue; myrect[i][j]->uValue = 0; break; } } } } } break; default: break; } if (bHaveDoneSth) { GenerateNewNum(); } Invalidate(FALSE); if ( GameOver()) { AfxMessageBox("游戏结束!"); };
(完)