寫代碼的時候應該怎樣的思路將現實世界轉化為代碼, 魔方做一個例子?
昨晚轉了下魔方, 就在想我要是能在計算機上自己編程一個魔方就好了.
但想想這在我的極限以外, 就算給了我繪製 3D 圖形的 API, 我依然搞不定,中間最重要的問題就是, 現實的物體, 怎樣將其轉化為代碼的結構?代碼應該是怎樣一個模型去建構世界呢, 我們又應該怎樣思考?
魔方的狀態表示 。
基礎
魔方特指三階魔方(3*3*3),由 8 個角塊、12 個棱塊和 6 個中心塊組成。中心塊的位置固定在十字架上。每個角塊 3 面有顏色,每個棱塊 2 面有顏色,每個中心塊 1 面有顏色。
官方配色白黃相對、紅橘相對、藍綠相對,且藍、橘、黃三色以順時鐘排列。
F、B、L、R、U、D 分別代表前、後、左、右、上、下層。若是順時針旋轉 90°,則直接寫上符號;若是逆時針旋轉,則在符號後加上「"」或是「i」;若是旋轉180°,則在符號後加上「2」或是「2」。狀態表示
1.色片
一個還原好的魔方可以看做由 3*3*6 個色片組成,編號如下。
每一次轉動會將這些色片的位置重排。稱為置換。
做一次 F ,將得到:
僅觀察黃色面上色片。色片的置換表達有下面兩種方式:
F1 被移動到了 F3 (F1-?F3),
F2-?F6, F3-?F9, F4-?F2, F5-?F5, F6-?F8, F7-?F1, F8-?F4, F9-?F7. F1 被替換為了 F7 (F1?-F7), F2?-F4, F3?-F1, F4?-F8, F5?-F5, F6?-F2, F7?-F9, F8?-F6, F9?-F3. 這些表的第一行是確定的,忽略,記為:(F3,F6,F9,F2,F5,F8,F1,F4,F7) 在「被移動到」表示方式下,或(F7,F4,F1,F8,F5,F2,F9,F6,F3) 在「被替換為」表示方式下。F面周圍一圈的 12 個色片同理。
兩個置換的積。
通常情況下,兩個置換是不能相互交換先後順序的。
考慮轉動 F 以及 F",積為
也就是:F * F" = 1,這兩個互為逆置換。並不只有轉動可以看作置換,任何打亂的魔方都可以寫成一個置換。
色片表示的方法不適合快速計算。
2.色塊
在色塊層,置換的對象不是色片,而是 12 個棱塊和 8 個角塊。
這種表示方法跟現實中的魔方結構最接近。角塊用 URF, UFL, ULB, UBR, DFR, DLF, DBL 和 DRB 來命名。
棱塊用 UR, UF, UL, UB, DR ,DF, DL, DB, FR, FL, BL 和 BR 來命名。
例如上圖中標出了:URF 角,DFR 角,FL 棱以及 UL 棱。角塊可以在原地扭轉,棱塊也可以翻轉過來。
這些色塊在他們的原位,但是方向改變了。UFL 角被順時針扭轉了,UBR 角被逆時針扭轉了,DF 棱和 FR 棱被翻轉了。對於二階段演算法,定義參考方向:還原的魔方上標出來的色片作為參考方向。
一次 F:
位於 URF 和 DLF 的角塊被順時針扭轉了,位於 UFL 和 DFR 的角塊被逆時針扭轉了,位於UF, DF, FL 和 FR 位置的棱塊被翻轉了。
使用 "被替換為" 的表示方式來記錄位置置換,對於上面例子:
URF 被替換為 UFL(URF?-UFL), UFL?-DLF, ULB ?-ULB, UBR?-UBR, DFR?-URF, DLF?-DFR, DBL ?-DBL, DRB?-DRB. 這是角塊的位置置換。再包括方向,
F =
角塊使用 "0" 表示方向沒有變化,用 "1" 表示順時針的原地扭轉,"2" 表示逆時針的原地扭轉。棱塊使用 "1" 表示棱塊被翻轉,"0" 表示沒有被翻轉。當然 F 也可以記成:
F(URF).c = UFLF(URF).o = 1F(UFL).c = DLFF(UFL).o = 2……
再做另一個轉動
R =F*R 作用在UFL角上
(F*R)(UBR).c = F(R(UBR).c).c = UFL
(F*R)(UBR).o=F(R(UBR).c).o+R(UBR).o = 23.坐標
用自然數來表示角塊和棱塊的位置與方向。適合實現解魔方的快速演算法。
角塊方向坐標的定義。
一次R轉動,用「被代替為」表示 忽略 DRB 角塊的方向,因為這一方向是由其他 7 個角塊決定的:所有角塊方向的和必須被 3 整除。8 個角塊的方向可以用 0 到 2186 (3^7 - 1) 的一個整數表示。計算2*3^6 + 0*3^5 + 0*3^4 + 1*3^3 +1*3^2 + 0*3^1 + 0*3^0 = 1494其實就是三進位中的數字2001100。棱塊方向也可以用類似的方式來定義。
12 個棱塊的方向由一個 0 到 2047 (2^11 - 1) 的數字表示。 這裡用二進位來代替三進位。同樣忽略了 BR 棱的方向,因為它是可以由其他 11 個棱決定的:所有棱塊的方向和必須為偶數。角塊位置可以由一個 0 到 40319 (8! - 1) 中的一個整數表示。
R轉動,忽略角塊的方向。 對角塊定義一個大小順序 URF&棱塊的位置可以類似的用一個 0 到 12! - 1 的數字表示。
綜上,可以用一組坐標 (x1,x2,x3,x4) 來表示一個魔方。
代碼表示typedef enum {URF,UFL,ULB,UBR,DFR,DLF,DBL,DRB} Corner;
//8個角塊
typedef enum {UR,UF,UL,UB,DR,DF,DL,DB,FR,FL,BL,BR} Edge;
//12個棱塊
typedef enum
{ U1,U2,U3,U4,U5,U6,U7,U8,U9,R1,R2,R3,R4,R5,R6,R7,R8,R9,
F1,F2,F3,F4,F5,F6,F7,F8,F9,D1,D2,D3,D4,D5,D6,D7,D8,D9,
L1,L2,L3,L4,L5,L6,L7,L8,L9,B1,B2,B3,B4,B5,B6,B7,B8,B9} Facelet;
//54個面
typedef enum {mU1,mU3,mR1,mR3,mF1,mF3,mD1,mD3,mL1,mL3,mB1,mB3} Move;
//12種90度轉動
typedef enum {U,R,F,D,L,B} Axis;
//6種顏色或轉動
typedef struct faceletCube
{
Color f[54];
}FaceletCube;
//色片法表示魔方
struct corner_o
{ Corner c;
char o;
};
//帶方向的角塊位置
struct edge_o
{ Edge e;
char o;
};
//帶方向的棱塊位置
typedef struct cubieCube
{
struct corner_o co[8];
struct edge_o eo[12];
}CubieCube;
//色塊法表示魔方
typedef struct coordCube
{
int x1;
int x2;
int x3;
int x4;
//由一定方法計算得出
}CoordCube;
//坐標法表示魔方
CubieCube faceletCubeToCubieCube(FaceletCube fc);
FaceletCube cubieCubeToFaceletCube(CubieCube cc);
CoordCube cubieCubeToCoordCube(CubieCube cc);
//不同表示方法轉換
CubieCube cubeAxMove(CubieCube cc, Axis ax);
//對色塊法表示的魔方做一次(U,R,F,D,L,B)轉動
解魔方演算法
人工:層先,CFOP,橋式……
機器:降群,二階段演算法……可以用計算機模擬人類還原步驟,也可以計算出步數更短的高效轉法。
演算法實現
略
交互界面
命令行
二維圖形(C++/Qt)
三維圖形(MFC/OpenGL)
參考:
Rubik"s Cube grouphttp://kociemba.org/cube.htmoptiqtmSrc(C)CubeExplorer2.25(Delphi)CubeExercise1.2.401(C#)把現實世界中的物品轉化成代碼,主要是抽象行為。
考慮下用以下步驟實現抽象(建模):1)確定你的問題,和與之相關的對象:
題目很明確,創建一個魔方,可以生成謎題和進行解密(這裡假定並未任何顯示需求)。2)確定你的對象的行為,你這個對象所需要承擔的職責,決定了它所必須的屬性。
對這個題目而言,一個抽象的魔方,只需要能進行3軸的旋轉,各個面有不同的識別顏色就可以了。 可以用偽代碼確認一下介面,簡單如下: rotateX(int idx) // 繞X軸旋轉,idx表示第幾行/列 rotateY(int idx) rotateZ(int idx) getColor(int faceIdx, int blockIdx) //獲取某面某塊的顏色3)分析行為,得出必須的(必要的)屬性:
可以判斷,rotate 行為本身只需要對 顏色的位置進行修改而已,而 getColor 也只是定義了返回顏色而已。可以得出結論:實現上述行為的最少屬性,應該是 6*9 個顏色值(數字0~5)而已。 注意:屬性的抽取永遠取決於你所需要實現的行為。比如說你需要3d顯示它,那麼可能還需要 大小, 三維角度,坐標 之類的屬性。如果你需要加入物理特徵,那麼它可能還有 質量,摩擦力 之類的屬性。但是一個僅僅可以顯示謎題和進行解密的魔方是不需要這些特徵的。4)設計最合適需求的數據結構和演算法。 我們可以用無數種方式來滿足 「6*9 個顏色值」 這個條件,比如一個 int[54] 但這個數據結構顯然會讓 rotate 行為的實現代碼顯得冗長並且低效,或者說,不優雅。 好一點的方式是用 二維或者三維數組來維護我們的數據。 更好的方式,則需要花更多的時間去思考了 : )推薦閱讀:
※用程序實現自動寫小說,難度有多大?有哪些需要克服的難點,大體思路是怎樣的?
※獲得ACM ICPC Regional金牌是一種什麼樣的體驗?
※如何看待杭電舉辦的acm女生專場?
※如何理解 Tarjan 的 LCA 演算法?
※二分查找有幾種寫法?它們的區別是什麼?