├── .DS_Store ├── C++ ├── .DS_Store ├── CUBE_STATE.txt └── SOLVECUBE.cpp ├── README.md └── Arduino MEGA2560 └── cubot_v0.ino /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g20150120/cubot/HEAD/.DS_Store -------------------------------------------------------------------------------- /C++/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g20150120/cubot/HEAD/C++/.DS_Store -------------------------------------------------------------------------------- /C++/CUBE_STATE.txt: -------------------------------------------------------------------------------- 1 | RBY 2 | OGW 3 | RWO 4 | 5 | WRG 6 | BWG 7 | BWG 8 | 9 | ROO 10 | GRR 11 | YBY 12 | 13 | OYW 14 | WOY 15 | OYW 16 | 17 | YYB 18 | GBR 19 | RBG 20 | 21 | GOB 22 | GYO 23 | WRB -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cubot 2 | A robot to solve the Rubik's Cube 3 | 4 | ## Introduction 5 | This project used a C++ program, which takes in colors (represented by 'R''B''G''Y''W''O') at each face and gives out the solution (represented by "F1R2U3B2D3L1"), and an Arduino MEGA2560 program to execute the solution by giving digital signals to corresponding L298N drivers to control the rotation of steppers. 6 | 7 | In this project, Arduino MEGA2560 is powered by my Mac; drivers and steppers (rated 3.3V 1.5A) are powered by an external DC power source. 8 | 9 | To control each stepper, 4 consecutive digital output pin of Arduino MEGA2560 are connected to IN 1 through 4 on the driver board; OUT 1 through 4 are connected to A+ A- B+ B- of my dipolar stepper. +12V on each driver board is connected to + of the power source; GND of each driver board and Arduino MEGA2560 are wired together and are connected to - of the power source. 10 | 11 | ## Instruction 12 | 1. Record the colors on the Rubik's Cube exactly as is recored in CUBE_STATE.txt. Also remember to follow the description in SOLVECUBE.cpp. The final solution will be given very quickly in SOLUTION.txt; otherwise, terminate the program because the state of the cube is entered in a wrong way. 13 | 14 | 2. Open SOLUTION.txt, paste the solution to "String command" in "void solve()" in cubot_vo.ino, and upload to the chip. 15 | 16 | ## Reference 17 | 1. an online code which implemented Thistlethwaite Method, solving Rubik's Cube very efficiently according to group theory. 18 | 19 | 2. some online tutorial which explained the wiring of the chip, driver, and stepper. 20 | 21 | ## Futher Development 22 | 1. A stable structure to hold the steppers and cube tight. 23 | 24 | 2. color recognition 25 | 26 | ## Links 27 | [Video Demo of Cubot](https://drive.google.com/file/d/1wsp5uFWPZzSRX_DbzotPYWj6P4qXcqLH/view?usp=sharing) 28 | 29 | [Video Demo of color recognition and the solver](https://drive.google.com/file/d/1V_OiSLvOBZE_k8G8301FmiSfF_KlJNpP/view?usp=sharing) 30 | -------------------------------------------------------------------------------- /Arduino MEGA2560/cubot_v0.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | //R1L3F2B2R1L3U1L1R3B2F2L1R3=D 5 | /* 6 | 7 | All the parameters below are well-adjusted. Please don't change anything except time. 8 | 9 | */ 10 | const int STEPS = 205; // 360/1.8 11 | const int stepperSpeed = 240; // rpm 15.8V 12 | const int timeBetweenMoves = 5; // ms between U2 R2 or U R etc. 13 | const int timeBetweenComs = 1000*8; // ms between commands 14 | const int steps_90 = 3*STEPS/4 ; 15 | const int steps_180 = STEPS/2; 16 | const int steps_270 = STEPS/4; 17 | 18 | const int _angle[4]={0,steps_90,steps_180,steps_270}; 19 | // U2 -> stepperU.step(_angle[2]); 20 | 21 | String command = ""; 22 | 23 | // adjust them according to your wiring 24 | Stepper FF(STEPS,28,29,30,31); 25 | Stepper DD(STEPS,22,23,24,25); 26 | Stepper BB(STEPS,34,35,36,37); 27 | Stepper LL(STEPS,38,39,40,41); 28 | Stepper UU(STEPS,44,45,46,47); 29 | Stepper RR(STEPS,50,51,52,53); 30 | 31 | void Getcom(); 32 | void Solve(); 33 | 34 | void setup() 35 | { 36 | FF.setSpeed(stepperSpeed); 37 | UU.setSpeed(stepperSpeed); 38 | RR.setSpeed(stepperSpeed); 39 | LL.setSpeed(stepperSpeed); 40 | DD.setSpeed(stepperSpeed); 41 | BB.setSpeed(stepperSpeed); 42 | 43 | pinMode(13,OUTPUT); 44 | } 45 | 46 | void loop() 47 | { 48 | delay(timeBetweenComs); 49 | command = ""; 50 | Getcom(); 51 | } 52 | 53 | // I was going to implement a funtion to receive solutions from serial monitor but did not manage to do so. 54 | // I just simply paste the solution here and upload the code. 55 | void Getcom() 56 | { 57 | command = "U2F2L2D2B2U2F2L2F2R2F2U1L2R2F2U3B2U1L2U3R1U3D2F2R1D2U1F1U1B1R3L2U3"; 58 | 59 | Solve(); 60 | } 61 | 62 | void Solve() 63 | { 64 | int com_len=command.length(); 65 | for(int i=0;i with 40 entries, the first 20 4 | * are a permutation of {0,...,19} and describe which cubie is at 5 | * a certain position (regarding the input ordering). The first 6 | * twelve are for edges, the last eight for corners. 7 | * 8 | * The last 20 entries are for the orientations, each describing 9 | * how often the cubie at a certain position has been turned 10 | * counterclockwise away from the correct orientation. Again the 11 | * first twelve are edges, the last eight are corners. The values 12 | * are 0 or 1 for edges and 0, 1 or 2 for corners. 13 | * 14 | **********************************************************************/ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | using namespace std; 25 | 26 | char cube[9][12]; 27 | /* 28 | cube[x][y] 储存9行12列的颜色展开图 29 | 30 | XXX 31 | XWX 32 | XXX 33 | 34 | XXX XXX XXX XXX 35 | XOX XGX XRX XBX 36 | XXX XXX XXX XXX 37 | 38 | XXX 39 | XYX 40 | XXX 41 | 42 | */ 43 | 44 | const int 45 | 46 | //展开图中六个面填色的起始位置 0-5为 GWORYB 47 | start_x[6]={3,0,3,3,6,3}, 48 | start_y[6]={3,3,0,6,3,9}, 49 | 50 | //代表颜色信息和位置信息的关系 人工总结 51 | edge_x[24]={2,3,1,3,0,3 ,1,3, 6,5,7,5,8,5, 7,5,4,4,4,4,4, 4,4 ,4}, 52 | edge_y[24]={4,4,5,7,4,10,3,1, 4,4,5,7,4,10,3,1,5,6,3,2,9, 8,11,0}, 53 | apex_x[24]={2,3,3,0,3,3, 0,3, 3,2,3,3,6,5, 5,6,5,5,8,5,5, 8,5 ,5}, 54 | apex_y[24]={5,5,6,5,8,9, 3,11,0,3,2,3,5,6, 5,3,3,2,3,0,11,5,9 ,8}; 55 | 56 | int func(char ch) 57 | { 58 | //对应start_x/start_y中的颜色顺序 59 | if(ch=='W') 60 | return 1; 61 | if(ch=='G') 62 | return 0; 63 | if(ch=='O') 64 | return 2; 65 | if(ch=='R') 66 | return 3; 67 | if(ch=='B') 68 | return 5; 69 | if(ch=='Y') 70 | return 4; 71 | return 0; 72 | } 73 | 74 | char convert(char ch) 75 | { 76 | //将展开图中的颜色信息转换为FU LR etc的位置信息 77 | //其中 绿色为F 白色为U 形如 cube[9][12] 78 | if(ch=='W') 79 | return 'U'; 80 | if(ch=='G') 81 | return 'F'; 82 | if(ch=='O') 83 | return 'L'; 84 | if(ch=='R') 85 | return 'R'; 86 | if(ch=='B') 87 | return 'B'; 88 | if(ch=='Y') 89 | return 'D'; 90 | return 0; 91 | } 92 | 93 | 94 | //---------------------------------------------------------------------- 95 | 96 | typedef vector vi; 97 | 98 | //---------------------------------------------------------------------- 99 | 100 | int applicableMoves[] = { 0, 262143, 259263, 74943, 74898 }; 101 | 102 | // TODO: Encode as strings, e.g. for U use "ABCDABCD" 103 | 104 | int affectedCubies[][8] = 105 | { 106 | { 0, 1, 2, 3, 0, 1, 2, 3 }, // U 107 | { 4, 7, 6, 5, 4, 5, 6, 7 }, // D 108 | { 0, 9, 4, 8, 0, 3, 5, 4 }, // F 109 | { 2, 10, 6, 11, 2, 1, 7, 6 }, // B 110 | { 3, 11, 7, 9, 3, 2, 6, 5 }, // L 111 | { 1, 8, 5, 10, 1, 0, 4, 7 }, // R 112 | }; 113 | 114 | vi applyMove ( int move, vi state ) 115 | { 116 | int turns = move % 3 + 1; 117 | int face = move / 3; 118 | while( turns-- ) 119 | { 120 | vi oldState = state; 121 | for( int i=0; i<8; i++ ) 122 | { 123 | int isCorner = i > 3; 124 | int target = affectedCubies[face][i] + isCorner*12; 125 | int killer = affectedCubies[face][(i&3)==3 ? i-3 : i+1] + isCorner*12;; 126 | int orientationDelta = (i<4) ? (face>1 && face<4) : (face<2) ? 0 : 2 - (i&1); 127 | state[target] = oldState[killer]; 128 | state[target+20] = oldState[killer+20] + orientationDelta; 129 | if( !turns ) 130 | state[target+20] %= 2 + isCorner; 131 | } 132 | } 133 | return state; 134 | } 135 | 136 | int inverse ( int move ) 137 | { 138 | return move + 2 - 2 * (move % 3); 139 | } 140 | 141 | //---------------------------------------------------------------------- 142 | 143 | int phase; 144 | 145 | //---------------------------------------------------------------------- 146 | 147 | vi id ( vi state ) 148 | { 149 | 150 | //--- Phase 1: Edge orientations. 151 | if( phase < 2 ) 152 | return vi( state.begin() + 20, state.begin() + 32 ); 153 | 154 | //-- Phase 2: Corner orientations, E slice edges. 155 | if( phase < 3 ) 156 | { 157 | vi result( state.begin() + 31, state.begin() + 40 ); 158 | for( int e=0; e<12; e++ ) 159 | result[0] |= (state[e] / 8) << e; 160 | return result; 161 | } 162 | 163 | //--- Phase 3: Edge slices M and S, corner tetrads, overall parity. 164 | if( phase < 4 ) 165 | { 166 | vi result( 3 ); 167 | for( int e=0; e<12; e++ ) 168 | result[0] |= ((state[e] > 7) ? 2 : (state[e] & 1)) << (2*e); 169 | for( int c=0; c<8; c++ ) 170 | result[1] |= ((state[c+12]-12) & 5) << (3*c); 171 | for( int i=12; i<20; i++ ) 172 | for( int j=i+1; j<20; j++ ) 173 | result[2] ^= state[i] > state[j]; 174 | return result; 175 | } 176 | 177 | //--- Phase 4: The rest. 178 | return state; 179 | } 180 | 181 | //---------------------------------------------------------------------- 182 | 183 | int main () 184 | { 185 | //输入从文件读取 魔方按cube[9][12]展开 每个面用RBGYWO的3*3矩阵表示 六个矩阵彼此空任意行 顺序任意 186 | freopen("CUBE_STATE.txt","r",stdin); 187 | 188 | //输出到文件 可直接作为command给arduino执行 189 | freopen("SOLUTION.txt","w",stdout); 190 | 191 | memset(cube,0,sizeof(cube)); 192 | 193 | //储存一个面的3*3矩阵 194 | string tmp[3]; 195 | 196 | for(int ii=0;ii<6;ii++)//执行六次 读取六个面的3*3矩阵 197 | { 198 | for(int jj=0;jj<3;jj++)//读取一个矩阵的三行 199 | cin>>tmp[jj]; 200 | 201 | //tmp[1][1]记录中心块颜色 从而确定这一面的颜色 和 在展开图中的相对位置 202 | int q=func(tmp[1][1]); 203 | 204 | //将tmp中的信息转存到cube[9][12]展开图中 205 | for(int i=0;i<3;i++) 206 | for(int j=0;j<3;j++) 207 | cube[start_x[q]+i][start_y[q]+j]=tmp[i][j]; 208 | } 209 | 210 | //argv[1-20]存储魔方的状态 值为 UF DBR etc 211 | string argv[21]; 212 | 213 | //后面通过+=写入数据 务必先初始化置为空 214 | for(int i=0;i<21;i++) 215 | argv[i]=""; 216 | //代表现在向argv[index]写入数据 217 | int index=1; 218 | 219 | for(int i=0;i<24;i++) 220 | { 221 | //把颜色信息转换成位置信息 222 | argv[index]+=convert(cube[edge_x[i]][edge_y[i]]); 223 | 224 | //前12组表示棱的位置 每组两个 UF UR etc 225 | if(i%2==1) 226 | index++; 227 | } 228 | for(int i=0;i<24;i++) 229 | { 230 | //把颜色信息转换成位置信息 231 | argv[index]+=convert(cube[apex_x[i]][apex_y[i]]); 232 | 233 | //后8组表示角的位置 每组三个 ULF DBR etc 234 | if(i%3==2 && i!=23) 235 | index++; 236 | } 237 | 238 | //--- Define the goal. 239 | string goal[] = { "UF", "UR", "UB", "UL", "DF", "DR", "DB", "DL", "FR", "FL", "BR", "BL", 240 | "UFR", "URB", "UBL", "ULF", "DRF", "DFL", "DLB", "DBR" }; 241 | 242 | //--- Prepare current (start) and goal state. 243 | vi currentState( 40 ), goalState( 40 ); 244 | for( int i=0; i<20; i++ ) 245 | { 246 | 247 | //--- Goal state. 248 | goalState[i] = i; 249 | 250 | //--- Current (start) state. 251 | string cubie = argv[i+1]; 252 | while( (currentState[i] = find( goal, goal+20, cubie ) - goal) == 20) 253 | { 254 | cubie = cubie.substr( 1 ) + cubie[0]; 255 | currentState[i+20]++; 256 | } 257 | } 258 | 259 | //--- Dance the funky Thistlethwaite... 260 | while( ++phase < 5 ) 261 | { 262 | 263 | //--- Compute ids for current and goal state, skip phase if equal. 264 | vi currentId = id( currentState ), goalId = id( goalState ); 265 | if( currentId == goalId ) 266 | continue; 267 | 268 | //--- Initialize the BFS queue. 269 | queue q; 270 | q.push( currentState ); 271 | q.push( goalState ); 272 | 273 | //--- Initialize the BFS tables. 274 | map predecessor; 275 | map direction, lastMove; 276 | direction[ currentId ] = 1; 277 | direction[ goalId ] = 2; 278 | 279 | //--- Dance the funky bidirectional BFS... 280 | while( 1 ) 281 | { 282 | 283 | //--- Get state from queue, compute its ID and get its direction. 284 | vi oldState = q.front(); 285 | q.pop(); 286 | vi oldId = id( oldState ); 287 | int& oldDir = direction[oldId]; 288 | 289 | //--- Apply all applicable moves to it and handle the new state. 290 | for( int move=0; move<18; move++ ) 291 | { 292 | if( applicableMoves[phase] & (1 << move) ) 293 | { 294 | 295 | //--- Apply the move. 296 | vi newState = applyMove( move, oldState ); 297 | vi newId = id( newState ); 298 | int& newDir = direction[newId]; 299 | 300 | //--- Have we seen this state (id) from the other direction already? 301 | //--- I.e. have we found a connection? 302 | if( newDir && newDir != oldDir ) 303 | { 304 | 305 | //--- Make oldId represent the forwards and newId the backwards search state. 306 | if( oldDir > 1 ) 307 | { 308 | swap( newId, oldId ); 309 | move = inverse( move ); 310 | } 311 | 312 | //--- Reconstruct the connecting algorithm. 313 | vi algorithm( 1, move ); 314 | while( oldId != currentId ) 315 | { 316 | algorithm.insert( algorithm.begin(), lastMove[ oldId ] ); 317 | oldId = predecessor[ oldId ]; 318 | } 319 | while( newId != goalId ) 320 | { 321 | algorithm.push_back( inverse( lastMove[ newId ] )); 322 | newId = predecessor[ newId ]; 323 | } 324 | 325 | //--- Print and apply the algorithm. 326 | for( int i=0; i<(int)algorithm.size(); i++ ) 327 | { 328 | cout << "UDFBLR"[algorithm[i]/3] << algorithm[i]%3+1; 329 | currentState = applyMove( algorithm[i], currentState ); 330 | } 331 | 332 | //--- Jump to the next phase. 333 | goto nextPhasePlease; 334 | } 335 | 336 | //--- If we've never seen this state (id) before, visit it. 337 | if( ! newDir ) 338 | { 339 | q.push( newState ); 340 | newDir = oldDir; 341 | lastMove[ newId ] = move; 342 | predecessor[ newId ] = oldId; 343 | } 344 | } 345 | } 346 | } 347 | nextPhasePlease: 348 | ; 349 | } 350 | return 0; 351 | } --------------------------------------------------------------------------------