在之前一篇《简单数字拼板游戏学习》基础上修改,地址: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 = ;
} 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] = {}; //矩形对象数组,相当于逻辑部分,保存矩形的显示值,坐标
MyRect *myrect[LINELENGTH][LINELENGTH]; //填充画刷,可以控制矩形填充不同的颜色
CBrush *brush; //生成一个新数字,随机一个0-RECTNUM的整数,根据这个整数计算出二维数组的横坐标和竖坐标
// A/LINELENGTH 是横坐标, A%LINELENGTH 是竖坐标, 当生成的位置有值的时候,重新生成
// 初始值为2, 可以再这里加控制生成2,或 4 。
void GenerateNewNum()
{
srand(time());
int A = rand() % RECTNUM;
while (myrect[A/LINELENGTH][A%LINELENGTH]->uValue != )
{
A = rand() % RECTNUM;
}
myrect[A/LINELENGTH][A%LINELENGTH]->uValue = ;
} //判断游戏结束
bool GameOver()
{
//如果有值为0 的矩形,则游戏肯定可以继续,所以直接返回false
for (int i = ; i < LINELENGTH; i++)
for (int j = ; j < LINELENGTH; j++)
{
if ( myrect[i][j]->uValue == )
return false;
}
// 对每一行相邻的两个数,如果有相同的,那么游戏可以继续,返回false
for (int i = ; i < LINELENGTH; i++)
for (int j = ; j < LINELENGTH-; j++)
{
if ( myrect[i][j]->uValue == myrect[i][j+]->uValue )
return false;
} // 对每一列相邻的两个数,如果有相同的,那么游戏可以继续,返回false
for (int j = ; j < LINELENGTH; j++)
for (int i = ; i < LINELENGTH-; i++)
{
if ( myrect[i][j]->uValue == myrect[i+][j]->uValue )
return false;
}
return true;
}

在 CMy2048Dlg::OnInitDialog() 中 , 添加初始化代码,

    // TODO: 在此添加额外的初始化代码
::SetWindowPos(this->m_hWnd, HWND_BOTTOM, , , +LINELENGTH*, +LINELENGTH*, SWP_NOZORDER); //初始化每个矩形的左上角点的坐标
for (int i = ; i < LINELENGTH; i++)
{
for (int j = ; j < LINELENGTH; j++)
{
point[i][j].x = j * + ;
point[i][j].y = i * + ;
}
}
//初始化矩形和填充画刷
for (int i = ; i < LINELENGTH; i++)
{
for (int j = ; j < LINELENGTH; j++)
{
myrect[i][j] = new MyRect(point[i][j].x, point[i][j].y, point[i][j].x+, point[i][j].y+);
myrect[i][j]->uValue = ;
}
} //初始化数字
srand(time());
int A = rand() % RECTNUM;
int B = rand() % RECTNUM;
while ( B == A )
{
B = rand() % RECTNUM;
}
myrect[ A / LINELENGTH][ A % LINELENGTH]->uValue = ;
myrect[ B / LINELENGTH][ B % LINELENGTH]->uValue = ;

在 OnPaint()函数的最后添加绘制代码,

    CFont font;
font.CreateFont(,,,,,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, , RGB(, , ));
//选中字体
dc.SelectObject(pen); for (int i = ; i < LINELENGTH; i++)
{
for (int j = ; 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 == )
{
brush = new CBrush(RGB(0xFC,0xFC,0xFC));
dc.FillRect(rect[i][j], brush);
delete brush;
}
else if (myrect[i][j]->uValue != )
{
switch(myrect[i][j]->uValue)
{
case :brush = new CBrush(RGB(0xFF,0xFF,0xFF));break;
case :brush = new CBrush(RGB(0xFF,0xE4,0xC4));break;
case :brush = new CBrush(RGB(0xFF,0xB6,0xC1));break;
case :brush = new CBrush(RGB(0xFF,0x83,0xFA));break;
case :brush = new CBrush(RGB(0xFF,0xC1,0x25));break;
case :brush = new CBrush(RGB(0xFF,0x6A,0x6A));break;
case :brush = new CBrush(RGB(0xFF,0x14,0x93));break;
case :brush = new CBrush(RGB(0xCD,0x66,0x1D));break;
case :brush = new CBrush(RGB(0x94,0x00,0xD3));break;
case :brush = new CBrush(RGB(0xFF,0xFF,0x00));break;
case :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[] = {''};
itoa(myrect[i][j]->uValue, num, );
dc.DrawText(num, -, rect[i][j], DT_VCENTER|DT_CENTER|DT_SINGLELINE);
}
}
}

然后就是在类向导里添加键盘响应函数,OnKeyUp, 在里面添加以下代码:

        // TODO: 在此添加消息处理程序代码和/或调用默认值
switch(nChar)
{
case VK_LEFT:
//判断是否有动作,用来控制是否生成新数字
bHaveDoneSth = false;
for (int i = ; i < LINELENGTH; i++)
{ //去掉空格
for (int j = ; j < LINELENGTH ; j++)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = ; k < j; k++)
{
if (myrect[i][k]->uValue == )
{
bHaveDoneSth = true;
myrect[i][k]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
} //相加
for (int j = ; j < LINELENGTH- ; j++)
{
if ( myrect[i][j]->uValue != )
{
if ( myrect[i][j+]->uValue == myrect[i][j]->uValue )
{
bHaveDoneSth = true;
myrect[i][j]->uValue += myrect[i][j+]->uValue;
myrect[i][j+]->uValue = ;
}
}
} //去掉空格
for (int j = ; j < LINELENGTH ; j++)
{
if ( myrect[i][j]->uValue != )
{
for (int k = ; k < j; k++)
{
if (myrect[i][k]->uValue == )
{
bHaveDoneSth = true;
myrect[i][k]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
}
}
break;
case VK_UP:
bHaveDoneSth = false;
for (int j = ; j < LINELENGTH; j++)
{
//去掉空格
for (int i = ; i < LINELENGTH ; i++)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = ; k < i; k++)
{
if (myrect[k][j]->uValue == )
{
bHaveDoneSth = true;
myrect[k][j]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
} //相加
for (int i = ; i < LINELENGTH- ; i++)
{
if ( myrect[i][j]->uValue != )
{
if ( myrect[i+][j]->uValue == myrect[i][j]->uValue )
{
bHaveDoneSth = true;
myrect[i][j]->uValue += myrect[i+][j]->uValue;
myrect[i+][j]->uValue = ;
}
}
} //去掉空格
for (int i = ; i < LINELENGTH ; i++)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = ; k < i; k++)
{
if (myrect[k][j]->uValue == )
{
bHaveDoneSth = true;
myrect[k][j]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
}
} break;
case VK_RIGHT:
bHaveDoneSth = false;
for (int i = ; i < LINELENGTH; i++)
{
//去掉空格
for (int j = LINELENGTH - ; j >= ; j--)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = LINELENGTH - ; k >= j; k--)
{
if (myrect[i][k]->uValue == )
{
bHaveDoneSth = true;
myrect[i][k]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
} //相加
for (int j = LINELENGTH - ; j > ; j--)
{
if ( myrect[i][j]->uValue != )
{
if ( myrect[i][j-]->uValue == myrect[i][j]->uValue )
{
bHaveDoneSth = true;
myrect[i][j]->uValue += myrect[i][j-]->uValue;
myrect[i][j-]->uValue = ;
}
}
} //去掉空格
for (int j = LINELENGTH - ; j >= ; j--)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = LINELENGTH - ; k >= j; k--)
{
if (myrect[i][k]->uValue == )
{
bHaveDoneSth = true;
myrect[i][k]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
}
} break;
case VK_DOWN:
bHaveDoneSth = false;
for (int j = LINELENGTH - ; j >= ; j--)
{ //去掉空格
for (int i = LINELENGTH - ; i >= ; i--)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = LINELENGTH - ; k >= i; k--)
{
if (myrect[k][j]->uValue == )
{
bHaveDoneSth = true;
myrect[k][j]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
} //相加
for (int i = LINELENGTH - ; i > ; i--)
{
if ( myrect[i][j]->uValue != )
{
if ( myrect[i-][j]->uValue == myrect[i][j]->uValue )
{
bHaveDoneSth = true;
myrect[i][j]->uValue += myrect[i-][j]->uValue;
myrect[i-][j]->uValue = ;
}
}
} //去掉空格
for (int i = LINELENGTH - ; i >= ; i--)
{
//
if ( myrect[i][j]->uValue != )
{
for (int k = LINELENGTH-; k >= i; k--)
{
if (myrect[k][j]->uValue == )
{
bHaveDoneSth = true;
myrect[k][j]->uValue = myrect[i][j]->uValue;
myrect[i][j]->uValue = ;
break;
}
}
}
}
} break;
default:
break;
} if (bHaveDoneSth)
{
GenerateNewNum();
} Invalidate(FALSE);
if ( GameOver())
{
AfxMessageBox("游戏结束!");
};

(完)

基于MFC对话框的2048游戏的更多相关文章

  1. MFC技术积累——基于MFC对话框类的那些事儿

    1. 创建对话框类 (1)打开VC++6.0环境,点击:文件→新建: (2)在弹出的新建对话框中选择:工程→MFC AppWizard (exe)→输入工程名称(例如:功能调试)→工程保存路径名→确定 ...

  2. MFC技术积累——基于MFC对话框类的那些事儿2

    3. 绘图 3.1 对话框资源编辑 首先通过添加控件的方式来创建一个简单的绘图对话框如图所示,创建步骤为: 第一.在VC++6.0软件环境的灰色空白区域右击,选中Controls,然后会弹出一个控件对 ...

  3. MFC技术积累——基于MFC对话框类的那些事儿3

    3.3.2 创建图形画刷来实现位图加载 1.首先在Resource View中导入一幅位图,位图大小96×96像素: 2.其次在主对话框中添加一个静态文本资源,ID号是IDC_BITMAPAREA,添 ...

  4. MFC技术积累——基于MFC对话框类的那些事儿5

    4. 菜单 4.1 弹出菜单 本节主要讲解如何在主对话框的指定区域内通过鼠标右击来弹出一个菜单选项.最终效果图如图4.1. 如图4.1鼠标只能在指定区域(图中深色区域)内右击时弹出菜单,在指定区域外点 ...

  5. MFC技术积累——基于MFC对话框类的那些事儿4

    3.3.4 借助兼容DC加载DIB位图 创建一个与设备环境相兼容的DC,通过将位图暂时导入至兼容DC,然后利用CDC::BitBlt 或者CDC::StretchBlt函数将位图绘制到设备环境中. 示 ...

  6. c#调用c++制作的基于mfc的ocx控件

    原文:http://blog.csdn.net/yhhyhhyhhyhh/article/details/51286926 原文中有问题部分已修改. c#调用c++制作的基于mfc的ocx控件     ...

  7. powershell字符界面的,powershell加WPF界面的,2048游戏

    ------[序言]------ 1 2048游戏,有段时间很火,我在地铁上看有人玩过.没错,坐地铁很无聊,人家玩我就一直盯着看. 2 我在电脑上找了一个,试玩了以下,没几次格子就满了.我就气呼呼的放 ...

  8. MFC对话框显示BMP图片

    1.MFC对话框显示BMP图片我们先从简单的开始吧.先分一个类: (一) 非动态显示图片(即图片先通过资源管理器载入,有一个固定ID) (二) 动态载入图片(即只需要在程序中指定图片的路径即可载入) ...

  9. MFC对话框中显示BMP,JPG图片

    //************************************ // 方法说明:    显示JPG和GIF.BMP图片 // 参数说明:    CDC * pDC           设 ...

随机推荐

  1. Linux命令:hexdump

    hexdump是Linux下的一个二进制文件查看工具,它可以将二进制文件转换为ASCII.八进制.十进制.十六进制格式进行查看. 指令所在路径:/usr/bin/hexdump 命令语法: hexdu ...

  2. iOS获取应用当前Caches目录路径以及当前日期

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSSt ...

  3. Java csv

    CsvWriter csvWriter = new CsvWriter("data2019052803.csv", ',', Charset.forName("UTF-8 ...

  4. Build Telemetry for Distributed Services之Open Telemetry简介

    官网链接:https://opentelemetry.io/about/ OpenTelemetry is the next major version of the OpenTracing and  ...

  5. Python描述性统计numpy

    import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn import datasets, ...

  6. ASP.NET关于UEditor简单配置和错误修正

    UEditor配置版本为:ueditor1_3_6-utf8-net,放置目录为:/UEditor 一./UEditor/ueditor.config.js文件需要设置: 1.URL修改为:var U ...

  7. 客户端热更新框架之UI热更框架设计(下)

    上一篇笔者介绍了关于什么是热更新,为什么需要热更新的技术文章.本篇就专门针对UI框架的热更新功能实现部分展开讨论,讨论的重点是热更新如何与UI框架进行结合? 现在笔者把设计“UI热更新框架”的整体设计 ...

  8. jq append添加的元素click获取不到的解决方法

    移动端手机ios网页,apppend添加html class名用 on()方法没效果 解决方案:在html里面给需要点击的元素加一个onclick="javascript:void(0)&q ...

  9. linux中文件权限的字母含义

    一.文件系统部分 - 普通文件(文本文件,二进制文件,压缩文件,电影,图片...)d 目录文件(蓝色)b 设备文件(块设备)存储设备硬盘,U盘 /dev/sda, /dev/sda1c 设备文件(字符 ...

  10. 【VS开发】Return与Exit的区别

    1. exit用于结束正在运行的整个程序,它将参数返回给OS,把控制权交给操作系统:而return 是退出当前函数,返回函数值,把控制权交给调用函数. 2. exit是系统调用级别,它表示一个进程的结 ...