第一次写技术性的博客啊,本人菜鸟,如果哪里说的不够准确完善,欢迎大神前来点拨呀~
先放几张效果图吧~
这是我最近做的一个三阶魔方还原的小程序。
用的环境是VC++6.0,其中也大量运用了easyx库进行图形绘制等。
这次写程序深刻感受到了算法是程序的灵魂。当时写魔方算法的时候,准备了两种算法,:第一种,储存每一步的步骤,再逆序还原,这方法实现起来到不太难;第二种,想用传统层先法还原,这倒是真把我难倒了。本来想着魔方还原都是有公式的,还原起来用这现成公式的算法不就好了吗?但是,写起来才发现没那么简单,因为人脑进行判断时可以迅速处理所看到的色块信息,灵活的进行公式变换,而电脑却不像人那样思考。可能对人来说,层先法第一步,底层十字还原,不难做到,因为实现的方法多种多样,但正是这多种多样,让电脑判断时也需逐条分析。我当初一直想寻找复原十字通法而无果(本人其实是魔方菜鸟......),后来在某魔方大神的指导下依次判断每个棱块进行检测,遍历96种情况,或许方法不是最简捷的,但终于完成了底层十字的复原,这一步的完成可谓是历史性的啊,正所谓万事开头难嘛,完成了十字,之后依次是底层角块、侧棱、顶层棱块色相、顶层角块色相、顶层角块位置、顶层棱块位置,这些步骤结合魔方公式到比十字得心应手得多了。运用魔方公式的算法具体实现方法就不赘述了,就是根据魔方公式与程序有机结合啦。
首先,魔方的面是用的二维数组,并且运用了C++的类。
class Face
{
public:
Face();
COLORREF color;
void coordinate(int x1, int y1, int x2, int y2);
void coloring();
private:
int iX1, iY1; //立方体小块涂色坐标
int iX2, iY2; //展开图小块涂色坐标
};
Face::Face()
{
color = RGB(0, 250, 0); //初始化
}
void Face::coordinate(int x1, int y1, int x2, int y2)
{
iX1 = x1;
iY1 = y1;
iX2 = x2;
iY2 = y2;
}
void Face::coloring()
{
setfillstyle(0);
setfillcolor(color);
floodfill(iX1, iY1, RGB(233, 233, 233));
floodfill(iX2, iY2, RGB(233, 233, 233));
}
Face F[3][3];
Face U[3][3];
Face R[3][3];
Face L[3][3];
Face D[3][3];
Face B[3][3];
接下来说说图形界面。首先是一个发光魔方的欢迎界面,点击“Rubik”(魔方)后即可进入,进入后分为图形显示界面和步骤显示操作界面。图形显示界面左下角有操作说明:
“你可以摁字母控制魔方转动:大写代表顺时针,小写代表逆时针。如:R r U u L l D d F f B b M m X x Y y Z z 按数字0可退出,按*逆序还原,按+传统还原”。
操作说明框具体实现代码如下:
//提示框
setcolor(RGB(255, 174, 201));
setbkmode(TRANSPARENT); //字体透明背景
setlinestyle(PS_SOLID, 10);
roundrect(50, 300, 450, 480, 50, 50); //圆角矩形
setcolor(RGB(192, 45, 204));
LOGFONT f;
gettextstyle(&f); // 获取当前字体设置
f.lfHeight = 30; // 设置字体高度为 30
_tcscpy(f.lfFaceName, _T("Ravie")); // 设置字体为“Ravie”
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
settextstyle(&f); // 设置字体样式
outtextxy(140, 310, "Rubik's Cube");
//提示框说明
_tcscpy(f.lfFaceName, _T("华文行楷"));
setcolor(RGB(184, 39, 254));
f.lfHeight = 20;
settextstyle(&f);
outtextxy(60, 340, "你可以摁字母控制魔方转动:");
outtextxy(60, 390, "大写代表顺时针,小写代表逆时针。");
//绿字提示
_tcscpy(f.lfFaceName, _T("jokerman"));
setcolor(GREEN);
f.lfHeight = 30;
settextstyle(&f);
outtextxy(60, 360, "如:R r U u L l D d F f B b M m X x Y y Z z");
outtextxy(60, 410, "按数字0可退出,按*逆序还原,按+传统还原");
coloring();
_tcscpy(f.lfFaceName, _T("jokerman"));
setcolor(RGB(253, 2, 46));
settextstyle(&f);
那些漂亮的字体,事实上windows已经为我们准备了很多漂亮的字体了哦!你只要打开“C:\Windows\Fonts”就能找到了哦~(*^__^*) 嘻嘻~
图形显示界面的背景是彩虹渐变色的背景,所用的方法是不断地画横线,从上到下填满整个屏幕,而渐变效果的秘诀就在于线条颜色参数的变化。代码如下:
// 画渐变的背景//彩虹背景色
float h = 0.0; // 色相
float s = 1; // 饱和度
float l = 0.7f; // 亮度
for(int y = 0; y < 500; y++)
{
h += 0.72;
l += 0.0005f;
setlinecolor( HSLtoRGB(h, s, l) );//画线
line(0, y, 1149, y);
}
HSLtoRGB(h, s, l)
就是用来画线色彩变化的,循环的画线,同时不断调整颜色参数:色相、饱和度、亮度。所画的每根线都不一样,一起显示出来的效果自然就是渐变啦~
在这里总结一下调色的三种方法:
方法一:用预定义颜色常量
常量 | 值 | 颜色 | 常量 | 值 | 颜色 | |
---|---|---|---|---|---|---|
BLACK | 0 | 黑 | DARKGRAY | 0x555555 | 深灰 | |
BLUE | 0xAA0000 | 蓝 | LIGHTBLUE | 0xFF5555 | 亮蓝 | |
GREEN | 0x00AA00 | 绿 | LIGHTGREEN | 0x55FF55 | 亮绿 | |
CYAN | 0xAAAA00 | 青 | LIGHTCYAN | 0xFFFF55 | 亮青 | |
RED | 0x0000AA | 红 | LIGHTRED | 0x5555FF | 亮红 | |
MAGENTA | 0xAA00AA | 紫 | LIGHTMAGENTA | 0xFF55FF | 亮紫 | |
BROWN | 0x0055AA | 棕 | YELLOW | 0x55FFFF | 黄 | |
LIGHTGRAY | 0xAAAAAA | 浅灰 | WHITE | 0xFFFFFF | 白 |
方法二: 用 16 进制的颜色表示,形式为:0xbbggrr (bb=蓝,gg=绿,rr=红),具体参数如上图所示。
方法三:用 RGB 宏合成颜色。这里颜色范围是0~255,比如你可以写setcolor(255, 0, 0);这就是设置成纯红的啦~
方法四:用 HSLtoRGB、HSVtoRGB 转换其他色彩模型到 RGB 颜色。
我这里用的就是HSLtoRGB。
该函数用于转换 HSL 颜色为 RGB 颜色。
COLORREF HSLtoRGB( float H, float S, float L );
参数:
H
原 HSL 颜色模型的 Hue(色相) 分量,0 <= H < 360。
S
原 HSL 颜色模型的 Saturation(饱和度) 分量,0 <= S <= 1。
L
原 HSL 颜色模型的 Lightness(亮度) 分量,0 <= L <= 1。
返回值:
对应的 RGB 颜色。
说明:
HSL 又称 HLS。
H 是英文 Hue 的首字母,表示色相,即组成可见光谱的单色。红色在 0 度,绿色在 120 度,蓝色在 240 度,以此方向过渡。
S 是英文 Saturation 的首字母,表示饱和度,等于 0 时为灰色。在最大饱和度 1 时,具有最纯的色光。
L 是英文 Lightness 的首字母,表示亮度,等于 0 时为黑色,等于 0.5 时是色彩最鲜明的状态,等于 1 时为白色。
然后再说说成功复原时界面吧~
看到那句"Congratulations!You win~(碰我重新开始哦^_^)"了吗?在成功复原的时候句子就会悠悠的飘~出来,遇到边框还会反弹哦~当鼠标光标触碰字体时就可以重新开始操作。
当时为了实现这个功能,到也调试了挺久的(我是菜鸟,很菜的菜鸟......),先看代码吧:
int word(char word[]) //运动的字 /*如果用string word(char word[])会有error*/
{
void picture();
//定义字符串
// string s(word); //使用TCHAR s[]=_T(word);会出现 error C2440: 'initializing' : cannot convert from 'char []' to 'char []'
FlushMouseMsgBuffer(); //清除鼠标消息缓冲区必须有,不然多次胜利后可能无法接收到"光标触字"指令!
//定义字符串初始位置
int x = 10;
int y = 10;
//获取字符串高度、宽度
int w = textwidth(word);
int h = textheight(word);
//判断移动方向
bool isLeft = false;
bool isUp = false;
BeginBatchDraw(); //开始批量绘图
bool flag = true;
//绘制移动文字
while(flag)
{
FlushBatchDraw();
picture(); //绘制背景
//实现用鼠标达到退出动画的效果
while(MouseHit())
{
MOUSEMSG m = GetMouseMsg();
if((m.x>x)&(m.x<(x+w))&(m.y>y)&(m.y<(y+h)))
{
BeginBatchDraw(); //开始批量绘图(必须有,不然图像显示会出问题)
picture(); //picture()函数必须放word()函数内调用,防止光标触碰后字体无法及时清除的问题
FlushBatchDraw(); //批量绘图显示picture
EndBatchDraw(); //结束批量绘图
flag = false;
return 0;
}
}
if( x - 10 <= 0 ) isLeft = false;
if( x + w + 10 >= 1150 ) isLeft = true;
if( y - 10 <= 0 ) isUp = false;
if( y + h + 10 >= 500) isUp = true;
if( isLeft )
x -= 10;
else
x += 10;
if( isUp )
y -= 10;
else
y += 10;
outtextxy(x, y, word);
Sleep(100);
}
//退出
EndBatchDraw();
}
其中char和string貌似有点不同
int word(char word[])
如果用string word(char word[])会有error,本来定义所飘动的字符串的时候想用string s(word);用来接收传入的字符串,然而会报错......char和string貌似有点不同?
使用TCHAR s[]=_T(word);会出现 error C2440: 'initializing' : cannot convert from 'char []' to 'char []',不知道是什么原因?
值得一提的是
FlushMouseMsgBuffer();
这个函数用于清除鼠标缓存区,因为程序是不断地获取鼠标位置来判断是否跳出函数,当时我没使用该函数时,由于之前也可能收到了鼠标消息,所以会导致消息滞留,鼠标光标触碰字体的时候的消息很可能没有被及时接受处理,导致消息处理延时失灵。我一开始还以为电脑不灵敏呢,现在看来,一条感悟:自己写的代码运行后没有达到想要的结果时,99%是自己的原因,电脑出错的概率很小!
然后一定要说说批量绘图函数!之前我写一些小的图像觉得电脑运行速度挺快的呀,批量绘图用不用好像没区别吧,然而,这次写转魔方的变幻时,却出现了频闪,这是有原因的,浮动字符串浮动所用的方法是不断调用绘制整个界面的函数用以覆盖之前的界面,从而达到“清除”效果,就好像是图形的绘制是只能加不能减的感觉吧,要用“覆盖”来模拟“擦除”。之前说到彩虹渐变的背景是通过循环改变颜色参数并重复画线直到覆盖整个窗口来实现的,而且我所用的又是整体重绘,所以工程量自然就比简单的小图形大得多啦!所以批量绘图就至关重要啦~
所以说:
BeginBatchDraw(); //开始批量绘图(必须有,不然图像显示会出问题)
picture(); //背景及魔方的显示
EndBatchDraw(); //结束批量绘图
这段代码很关键呀!
然后以下是字符串撞击屏幕反弹的效果:
if( x - 10 <= 0 ) isLeft = false;
if( x + w + 10 >= 1150 ) isLeft = true;
if( y - 10 <= 0 ) isUp = false;
if( y + h + 10 >= 500) isUp = true;
if( isLeft )
x -= 10;
else
x += 10;
if( isUp )
y -= 10;
else
y += 10;
outtextxy(x, y, word);
if( x + w + 10 >= 1150 ) isLeft = true;
和
if( y + h + 10 >= 500) isUp = true;
判断距离字符串边缘10像素的地方是否碰到窗口边框。用了变量w和h分别存字符串的宽和高:
//获取字符串高度、宽度
int w = textwidth(word);
int h = textheight(word);
转魔方函数定义如下:
void turn() //转魔方
{
//void Do(char turn[]);char xx[2]; //不能用此法,不然输入会有延迟效果
void computer();
void coloring();
char turn[100] = "";
bool ifwin();
int word(char word[]);
system("color BD"); //设置控制台前景色和背景色
for(int i = 0; i<100; i++)
{
turn[i] = '1';
}
i = 0;
turn[i] = getch(); //不能用getchar();不然每次都要输入回车
cout<
对了,还要说一下背景音乐。
首先要在程序顶部写一下
#pragma comment(lib,"Winmm.lib")
然后
mciSendString("open \".\\资源\\background.mp3\" alias mymusic", NULL, 0, NULL); // 打开背景音乐
mciSendString("play mymusic repeat", NULL, 0, NULL); // 播放音乐
其中“repeat”用于循环播放。
播放完音效后,记得停止并关闭音乐哦~ // 停止播放并关闭音乐
mciSendString("stop mymusic", NULL, 0, NULL);
mciSendString("close mymusic", NULL, 0, NULL);
因为可以灵活控制音乐的开始、结束、循环等功能,所以用在游戏音效里还是不错的哦~
--------------------------------------------------------------------------------
最近看到有这么多人关注我的这篇文章,心里很感动,谢谢大家的支持,所以决定进一步分享一下魔方还原的算法设计。介于我自己魔方水平有限,是照着魔方还原说明书设计的算法,所以大神们若发现有哪个地方设计的不妥当,欢迎分享更优质的方案,请多指教(。ゝω・。)☆
魔方还原参考的说明书:
整体设计思路
层先法程序流程1——还原底层十字
层先法程序流程2——还原底层四角
层先法程序流程3——还原中层棱块
层先法程序流程4——还原顶层棱块色向
层先法程序流程5——还原顶层角块色向
层先法程序流程6——还原顶层角块位置
层先法程序流程7——还原顶层棱块位置
OK~最后附上下载资源的地址(但其中用到了easyx库用于渲染图形界面,需要自己在VC++6.0上配置一下哦):
魔方资源 (该资源比上面介绍的界面进一步做了一丢丢美化哦~)
本人C币较少,借此资源赚点积分(*/ω\*)
最后,谢谢大家的支持啦!