前阵子,我用C#在Unity写了一个魔方小游戏,写到了后期我想写魔方的还原算法,但一开始就遇到难题,要想还原魔方首先就要知道魔方的状态,人是很容易识别魔方的颜色从而确定魔方的状态的,但要想机器也能看懂魔方的状态是件难事。
下面的是麻烦的推理,若不想看,可直接看:魔方状态2
看了很多贴,想借鉴一下前辈们的经验,但要么对于我说来很难,比如用机器直接识别魔方的颜色,用魔方的颜色反推魔方的状态,要么只有理论,没有一个确切的实现理论依据,思来想去决定自己写。
下面由我来描述一下我原创的魔方状态表示法:
最开始时,我首先想到的是用魔方块的所处位置(坐标)和魔方块在该位置时的旋转状态(旋转角度)来确定一个魔方块的唯一状态,理论可行我编写了魔方的一键打乱和状态保存及状态复原功能,以上三个功能全部实现,实践也可行。
接下来我就想,能不能反推,用魔方的坐标和魔方在该坐标的所处旋转状态,用确定魔方的唯一状态,最终在今天早上成功实现了,证明反推它也是可行的,只是整个过程有点艰辛。
开始的时候用的是四元数来保存旋转角度,那简直是一头雾水,根本找不到规律可言,但我还是找到了一条规律:一个欧拉角可用两个四元数来表示,然后就项目就推不下去了,我就去问了我的指导老师(当时我将这项目报了比赛,遗憾没选上),指导老师说不一定要用四元数,后来我改用了欧拉角。
果然很快发现了一个规律:
当要检测某个魔方块是否被还原,也就是检测魔方块的坐标和旋转角度是否正确。
棱块坐标还原检测:
如:要检测红白棱块,可以将红色魔方块的中心块的坐标加上白色魔方块的中心块的坐标,得到红白棱块坐标。
角块坐标还原检测:
如:要检测红白绿棱块,可以将红色魔方块的中心块的坐标加上白色魔方块的中心块的坐标加上绿色魔方块的中心块的坐标,得到红白绿棱块坐标。
棱块、角块旋转角度还原检测:
只要棱块或角块的旋转角度跟魔方最中心的魔方块旋转角度一样即为已还原。
当上述两个检测通过则表示某个魔方块成功被还原,那样能实时检测20个魔方块的实时还原情况。
但还是没有完成我要检测魔方状态的功能,而接下要讲的较为复杂:
由于我不知道如何反推,我采用了最笨的方法:穷尽法。
我先利用我写的一键打乱魔方功能,每次将魔方打乱12000次,利用unity的日志功能,记录每个魔方块旋转状态共有多少种,27个魔方块我都测试了,得出了一个结论:27个魔方块共有24种旋转状态
24是个神奇的数字,为什么每个魔方块都只有24种旋转状态呢?
后来我又想到棱块只能转到棱块位置,角块只能转到角块位置,则棱块有12种位置,角块有8种位置,棱块有两个面,角块有三个面,由是我有下面的猜想:
棱块12种位置*棱块两个面=24
角块8种位置*角块三个面=24
我罗列了48条记录,发现真的可以分类,且发现了个规律:
一个棱块在某个位置,有着两种旋转状态.
一个角块在某个位置,有着三种旋转状态.
后面实践证明每个棱块和角块都有这种特性。
刚开始我就在想,为什么会有这种情况的发生,经过反复猜想和证实,明白了:
棱块有:白红、白绿、白蓝、白橙、黄红、黄绿、黄蓝、黄橙、红绿、绿橙、蓝红、橙蓝
角块有:白绿橙、白橙蓝、白蓝红、白红绿、黄绿橙、黄橙蓝、黄蓝红、黄红绿
棱块的位置有:UF、UR、UB、UL、DF、DR、DB、DL、FR、FL、BR、BL
角块的位置有:UFR、URB、UBL、ULF、DRF、DBR、DLB、DFL
以白色做为还原底,则白色中心块作为底面(D),黄色中心块为顶面(U),绿色中心块为前面(F),则:
棱块的正方向定义为:U、D、F、B
角块的正方向定义为:U、D
这样就可以定义一个魔方块的状态了,注意以上顺序不能随意对换,这样会导致最后反推结果是错误的。
如白红魔方块:
当它位置处于顶面时,若白色面处于U则为旋转状态为0,红色面处于U则为旋转状态为1;
当它位置处于底面时,若白色面处于D则为旋转状态为0,红色面处于D则为旋转状态为1;
当它位置处于前面时,若白色面处于F则为旋转状态为0,红色面处于F则为旋转状态为1;
当它位置处于后面时,若白色面处于B则为旋转状态为0,红色面处于B则为旋转状态为1;
如白绿红魔方块:
当它位置处于顶面时,若白色面处于U则为旋转状态为0,绿色面处于U则为旋转状态为-1,
红色面处于U则为旋转状态为1;
当它位置处于底面时,若白色面处于D则为旋转状态为0,绿色面处于D则为旋转状态为-1,
红色面处于D则为旋转状态为1;
这也就是说,只要我将20个魔方块的状态全部罗列出来,就可以用魔方的坐标反推出它的旋转状态,由是我写了个程序,让程序让它帮我罗列了出来,总有480种(由于我不小心将红和橙混了,所以下面的结果,红这字与橙这字对调即可,只有图片有问题,其它没问题):
上面的图:24个位置是写错了,应该是24种旋转状态。
以上是12个棱块的状态罗列。
以上就是8个角块的罗列情况。
完成后:
以上就是我用魔方块的坐标和它的旋转状态来反推魔方状态的全部内容。
还有就是魔方块的坐标与魔方块在此位置的旋转状态是,1对2和1对3的对应情况,也就是说,当你读取某个魔方块时,知道魔方的坐标可以反推它的颜色朝向,知道它的颜色朝向也可以反推它的坐标
棱块:
蓝橙、蓝红、绿红、绿橙为一组
白绿、白蓝为一组
白红、白橙为一组
角块:
白、黄为一组
红、橙为一组
蓝、绿为一组
代码实现部分:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MofanRestore : MonoBehaviour
{
public static MofanRestore Instance = null;
private void Start()
{
Instance = this;
}
//确定魔方的状态
public void AN(string mofanName, string mofanPos, int mofanNum)
{
switch (MofanStata(mofanName, mofanNum))
{
case 0:
switch (mofanName)
{
//棱块
case "M22":
Debug.Log(mofanPos[0] + "白" + mofanPos[1] + "橙");
break;
case "M4":
Debug.Log(mofanPos[0] + "白" + mofanPos[1] + "红");
break;
case "M10":
Debug.Log(mofanPos[0] + "白" + mofanPos[1] + "绿");
break;
case "M16":
Debug.Log(mofanPos[0] + "白" + mofanPos[1] + "蓝");
break;
case "M24":
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "橙");
break;
case "M6":
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "红");
break;
case "M12":
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "绿");
break;
case "M18":
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "蓝");
break;
case "M20":
Debug.Log(mofanPos[0] + "绿" + mofanPos[1] + "橙");
break;
case "M2":
Debug.Log(mofanPos[0] + "绿" + mofanPos[1] + "红");
break;
case "M26":
Debug.Log(mofanPos[0] + "蓝" + mofanPos[1] + "橙");
break;
case "M8":
Debug.Log(mofanPos[0] + "蓝" + mofanPos[1] + "红");
break;
//角块,白色或黄色,向上或向下
case "M3"://黄橙绿
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "红" + mofanPos[2] + "绿");
break;
case "M27"://黄红蓝
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "橙" + mofanPos[2] + "蓝");
break;
case "M21"://黄绿红
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "绿" + mofanPos[2] + "红");
break;
case "M9"://黄蓝橙
Debug.Log(mofanPos[0] + "黄" + mofanPos[1] + "蓝" + mofanPos[2] + "橙");
break;
case "M25"://白红蓝
Debug.Log(mofanPos[0] + "白" + mofanPos[2] + "橙" + mofanPos[1] + "蓝");
break;
case "M1"://白橙绿
Debug.Log(mofanPos[0] + "白" + mofanPos[2] + "红" + mofanPos[1] + "绿");
break;
case "M19"://白绿红
Debug.Log(mofanPos[0] + "白" + mofanPos[2] + "绿" + mofanPos[1] + "红");
break;
case "M7"://白蓝橙
Debug.Log(mofanPos[0] + "白" + mofanPos[2] + "蓝" + mofanPos[1] + "橙");
break;
}
break;
case 1:
switch (mofanName)
{
//棱块
case "M22":
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "橙");
break;
case "M4":
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "红");
break;
case "M10":
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "绿");
break;
case "M16":
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "蓝");
break;
case "M24":
Debug.Log(mofanPos[1] + "黄" + mofanPos[0] + "橙");
break;
case "M6":
Debug.Log(mofanPos[1] + "黄" + mofanPos[0] + "红");
break;
case "M12":
Debug.Log(mofanPos[1] + "黄" + mofanPos[0] + "绿");
break;
case "M18":
Debug.Log(mofanPos[1] + "黄" + mofanPos[0] + "蓝");
break;
case "M20":
Debug.Log(mofanPos[1] + "绿" + mofanPos[0] + "橙");
break;
case "M2":
Debug.Log(mofanPos[1] + "绿" + mofanPos[0] + "红");
break;
case "M26":
Debug.Log(mofanPos[1] + "蓝" + mofanPos[0] + "橙");
break;
case "M8":
Debug.Log(mofanPos[1] + "蓝" + mofanPos[0] + "红");
break;
//角块,绿色或蓝色,向上或向下
case "M3"://黄橙绿
Debug.Log(mofanPos[1] + "黄" + mofanPos[2] + "红" + mofanPos[0] + "绿");
break;
case "M27"://黄红蓝
Debug.Log(mofanPos[1] + "黄" + mofanPos[2] + "橙" + mofanPos[0] + "蓝");
break;
case "M21"://黄绿红
Debug.Log(mofanPos[2] + "黄" + mofanPos[0] + "绿" + mofanPos[1] + "红");
break;
case "M9"://黄蓝橙
Debug.Log(mofanPos[2] + "黄" + mofanPos[0] + "蓝" + mofanPos[1] + "橙");
break;
case "M25"://白红蓝
Debug.Log(mofanPos[2] + "白" + mofanPos[1] + "橙" + mofanPos[0] + "蓝");
break;
case "M1"://白橙绿
Debug.Log(mofanPos[2] + "白" + mofanPos[1] + "红" + mofanPos[0] + "绿");
break;
case "M19"://白绿红
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "绿" + mofanPos[2] + "红");
break;
case "M7"://白蓝橙
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "蓝" + mofanPos[2] + "橙");
break;
}
break;
case 2:
switch (mofanName)
{
//角块,红色或橙色,向上或向下
case "M3"://黄橙绿
Debug.Log(mofanPos[2] + "黄" + mofanPos[0] + "红" + mofanPos[1] + "绿");
break;
case "M27"://黄红蓝
Debug.Log(mofanPos[2] + "黄" + mofanPos[0] + "橙" + mofanPos[1] + "蓝");
break;
case "M21"://黄绿红
Debug.Log(mofanPos[1] + "黄" + mofanPos[2] + "绿" + mofanPos[0] + "红");
break;
case "M9"://黄蓝橙
Debug.Log(mofanPos[1] + "黄" + mofanPos[2] + "蓝" + mofanPos[0] + "橙");
break;
case "M25"://白红蓝
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "橙" + mofanPos[2] + "蓝");
break;
case "M1"://白橙绿
Debug.Log(mofanPos[1] + "白" + mofanPos[0] + "红" + mofanPos[2] + "绿");
break;
case "M19"://白绿红
Debug.Log(mofanPos[2] + "白" + mofanPos[1] + "绿" + mofanPos[0] + "红");
break;
case "M7"://白蓝橙
Debug.Log(mofanPos[2] + "白" + mofanPos[1] + "蓝" + mofanPos[0] + "橙");
break;
}
break;
}
}
//魔方状态的数据集
public int MofanStata(string mofanName,int mofanNum)
{
switch (mofanName)
{
#region 顶棱块
case "M22"://白红
case "M4"://白橙
switch (mofanNum)
{
case 19:
case 15:
case 5:
case 12:
case 1:
case 3:
case 2:
case 0:
case 17:
case 7:
case 14:
case 8:
return 0;
case 11:
case 23:
case 16:
case 4:
case 13:
case 22:
case 6:
case 18:
case 9:
case 21:
case 10:
case 20:
return 1;
}
break;
case "M10"://白绿
case "M16"://白蓝
switch (mofanNum)
{
case 5:
case 12:
case 15:
case 19:
case 0:
case 2:
case 1:
case 3:
case 11:
case 13:
case 22:
case 23:
return 0;
case 7:
case 14:
case 9:
case 20:
case 17:
case 8:
case 10:
case 21:
case 6:
case 4:
case 16:
case 18:
return 1;
}
break;
#endregion
#region 底棱块
case "M24"://黄红
case "M6"://黄橙
switch (mofanNum)
{
case 1:
case 3:
case 2:
case 0:
case 19:
case 15:
case 5:
case 12:
case 14:
case 8:
case 17:
case 7:
return 0;
case 23:
case 11:
case 4:
case 16:
case 22:
case 13:
case 18:
case 6:
case 21:
case 9:
case 20:
case 10:
return 1;
}
break;
case "M12"://黄绿
case "M18"://黄蓝
switch (mofanNum)
{
case 0:
case 2:
case 1:
case 3:
case 5:
case 12:
case 15:
case 19:
case 22:
case 23:
case 11:
case 13:
return 0;
case 14:
case 7:
case 20:
case 9:
case 8:
case 17:
case 21:
case 10:
case 4:
case 6:
case 18:
case 16:
return 1;
}
break;
#endregion
#region 侧棱
case "M20"://绿红
case "M2"://绿橙
case "M26"://蓝红
case "M8"://蓝橙
switch (mofanNum)
{
case 12:
case 2:
case 0:
case 5:
case 10:
case 21:
case 8:
case 17:
case 20:
case 9:
case 7:
case 14:
return 0;
case 3:
case 15:
case 19:
case 1:
case 18:
case 6:
case 13:
case 22:
case 16:
case 4:
case 23:
case 11:
return 1;
}
break;
#endregion
#region 角块
case "M3"://黄橙绿
case "M27"://黄红蓝
case "M25"://白红蓝
case "M1"://白橙绿
case "M21"://黄绿红
case "M9"://黄蓝橙
case "M19"://白绿红
case "M7"://白蓝橙
switch (mofanNum)
{
case 0:
case 1:
case 2:
case 3:
case 5:
case 12:
case 15:
case 19:
return 0;
case 7:
case 8:
case 9:
case 10:
case 14:
case 17:
case 20:
case 21:
return 1;
case 4:
case 6:
case 11:
case 13:
case 16:
case 18:
case 22:
case 23:
return 2;
}
break;
#endregion
}
//错误返回-1
return -1;
}
//总20个位置,位置转成可读字符编号
public string pType(string s)
{
switch (s)
{
#region 棱块
//顶棱
case "(0.0, 1.0, -1.0)":
return "UF";
case "(1.0, 1.0, 0.0)":
return "UR";
case "(0.0, 1.0, 1.0)":
return "UB";
case "(-1.0, 1.0, 0.0)":
return "UL";
//底棱
case "(0.0, -1.0, -1.0)":
return "DF";
case "(1.0, -1.0, 0.0)":
return "DR";
case "(0.0, -1.0, 1.0)":
return "DB";
case "(-1.0, -1.0, 0.0)":
return "DL";
//前侧棱
case "(1.0, 0.0, -1.0)":
return "FR";
case "(-1.0, 0.0, -1.0)":
return "FL";
//后侧棱
case "(1.0, 0.0, 1.0)":
return "BR";
case "(-1.0, 0.0, 1.0)":
return "BL";
#endregion
#region 角块
//顶角块
case "(1.0, 1.0, -1.0)":
return "UFR";
case "(-1.0, 1.0, -1.0)":
return "ULF";
case "(1.0, 1.0, 1.0)":
return "URB";
case "(-1.0, 1.0, 1.0)":
return "UBL";
//底角块
case "(1.0, -1.0, -1.0)":
return "DRF";
case "(-1.0, -1.0, -1.0)":
return "DFL";
case "(1.0, -1.0, 1.0)":
return "DBR";
case "(-1.0, -1.0, 1.0)":
return "DLB";
#endregion
default:
return "";
}
}
//总24个状态角度,状态角度转成一个编号
public int rType(string s)
{
switch (s)
{
#region 0-8
case "(0.0, 0.0, 0.0)":
return 0;
case "(0.0, 90.0, 0.0)":
return 1;
case "(0.0, 180.0, 0.0)":
return 2;
case "(0.0, 270.0, 0.0)":
return 3;
case "(0.0, 0.0, 90.0)":
return 4;
case "(0.0, 0.0, 180.0)":
return 5;
case "(0.0, 0.0, 270.0)":
return 6;
case "(90.0, 0.0, 0.0)":
return 7;
case "(270.0, 0.0, 0.0)":
return 8;
#endregion
#region 9-23
case "(90.0, 90.0, 0.0)":
return 9;
case "(270.0, 270.0, 0.0)":
return 10;
case "(0.0, 90.0, 90.0)":
return 11;
case "(0.0, 180.0, 180.0)":
return 12;
case "(0.0, 270.0, 270.0)":
return 13;
case "(90.0, 180.0, 0.0)":
return 14;
case "(0.0, 90.0, 180.0)":
return 15;
case "(0.0, 180.0, 90.0)":
return 16;
case "(270.0, 180.0, 0.0)":
return 17;
case "(0.0, 180.0, 270.0)":
return 18;
case "(0.0, 270.0, 180.0)":
return 19;
case "(90.0, 270.0, 0.0)":
return 20;
case "(270.0, 90.0, 0.0)":
return 21;
case "(0.0, 90.0, 270.0)":
return 22;
case "(0.0, 270.0, 90.0)":
return 23;
#endregion
default:
return -1;
}
}
}