lib: Added Rubix Cube modelization class for solvers
[nit.git] / lib / rubix.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 import console
12
13 private fun array1d_copy_to(fromarr: Array[Int], oarr: Array[Int]) do
14 while oarr.length > fromarr.length do oarr.pop
15 while oarr.length < fromarr.length do oarr.push 0
16 fromarr.copy_to(0, fromarr.length, oarr, 0)
17 end
18
19 private fun front_face: Int do return 0
20 private fun left_face: Int do return 1
21 private fun top_face: Int do return 2
22 private fun right_face: Int do return 3
23 private fun bottom_face: Int do return 4
24 private fun back_face: Int do return 5
25
26 private fun top_ln: Int do return 0
27 private fun mid_ln: Int do return 1
28 private fun bottom_ln: Int do return 2
29
30 private fun left_col: Int do return 0
31 private fun mid_col: Int do return 1
32 private fun right_col: Int do return 2
33
34 private fun clock: Int do return 0
35 private fun counterclock: Int do return 1
36
37 private fun square: String do return once "■"
38
39 redef class Int
40
41 # Returns a coloured square for a defined colour id
42 #
43 # Assume colours are:
44 #
45 # * Green -> 0
46 # * White (replaced with light gray) -> 1
47 # * Red -> 2
48 # * Yellow -> 3
49 # * Orange (replaced with purple) -> 4
50 # * Blue -> 5
51 #
52 private fun rubix_colour: String do
53 if self == 0 then return square.green
54 if self == 1 then return square.light_gray
55 if self == 2 then return square.red
56 if self == 3 then return square.yellow
57 if self == 4 then return square.purple
58 if self == 5 then return square.blue
59 abort
60 end
61 end
62
63 # In-memory representation of a Rubix Cube
64 #
65 # Faces are represented in memory as if they were a flattened cube like:
66 #
67 # ~~~raw
68 # B B B
69 # B B B
70 # B B B
71 # U U U
72 # U U U
73 # U U U
74 # L L L F F F R R R
75 # L L L F F F R R R
76 # L L L F F F R R R
77 # D D D
78 # D D D
79 # D D D
80 # ~~~
81 #
82 # All the commands detailed in the class respect the Singmaster notation
83 class RubixCube
84 # Faces of the Cube
85 #
86 # 0 - Front
87 # 1 - Left
88 # 2 - Up
89 # 3 - Right
90 # 4 - Down
91 # 5 - Back
92 #
93 # Each line is:
94 #
95 # 0 - Top line
96 # 1 - Middle line
97 # 2 - Down line
98 var faces: Array[Array[Array[Int]]]
99
100 # Build a new Rubix Cube with a solved layout
101 init solved do
102 var faces = new Array[Array[Array[Int]]]
103 var colour = 0
104 for i in [0 .. 6[ do
105 var face = new Array[Array[Int]]
106 faces.add face
107 for j in [0 .. 3[ do
108 var line = new Array[Int]
109 for k in [0 .. 3[ do
110 line.add colour
111 end
112 face.add line
113 end
114 colour += 1
115 end
116 init faces
117 end
118
119 # Build a new Rubix Cube with a scrambled layout
120 #
121 # NOTE: The layout is not random, but scrambled nonetheless
122 init scrambled do
123 var colours = once [0, 1, 2, 3, 4, 5]
124 var colour_pos = 0
125 var faces = new Array[Array[Array[Int]]]
126 var increment = 1
127 for i in [0 .. 6[ do
128 var face = new Array[Array[Int]]
129 faces.add face
130 for j in [0 .. 3[ do
131 var line = new Array[Int]
132 for k in [0 .. 3[ do
133 line.add colours[colour_pos]
134 colour_pos += increment
135 if colour_pos > 5 then
136 increment = -1
137 colour_pos = 5
138 end
139 if colour_pos < 0 then
140 increment = 1
141 colour_pos = 0
142 end
143 end
144 face.add line
145 end
146 end
147 init faces
148 end
149
150 # Reset the Rubix Cube to a solved position
151 fun reset do
152 for i in [0 .. 6[ do
153 var face = faces[i]
154 for j in [0 .. 3[ do
155 var line = face[j]
156 for k in [0 .. 3[ do
157 line[k] = i
158 end
159 end
160 end
161 end
162
163 # Checks if both objects are Rubix cubes and their content is equivalent
164 #
165 # NOTE: Rotationed versions are not yet considered equal
166 redef fun ==(o) do
167 if not o isa RubixCube then return false
168 for mf in faces, tf in o.faces do
169 for ml in mf, tl in tf do
170 for mc in ml, tc in tl do if mc != tc then return false
171 end
172 end
173 return true
174 end
175
176 # Is `self` a solved Rubix Cube ?
177 fun is_solved: Bool do
178 for face_id in [0 .. 6[ do
179 var i = faces[face_id]
180 var colour = i.first.first
181 for j in [0 .. 3[ do
182 var ln = i[j]
183 for k in [0 .. 3[ do
184 if ln[k] != colour then return false
185 end
186 end
187 end
188 return true
189 end
190
191 redef fun to_s do
192 var buf = new Buffer
193 buf.append(single_face(back_face))
194 buf.append(single_face(top_face))
195 buf.append(three_faces(left_face, front_face, right_face))
196 buf.append(single_face(bottom_face))
197 return buf.to_s
198 end
199
200 private fun single_face(face_id: Int): Text do
201 var b = new Buffer
202 var face = faces[face_id]
203 for i in [0 .. 3[ do
204 var ln = face[i]
205 b.append("{" " * 6}{ln[0].rubix_colour} {ln[1].rubix_colour} {ln[2].rubix_colour}{" " * 6}\n")
206 end
207 return b
208 end
209
210 private fun three_faces(face1, face2, face3: Int): Text do
211 var b = new Buffer
212 var face_ids = [face1, face2, face3]
213 for i in [0 .. 3[ do
214 for j in face_ids do
215 var face = faces[j]
216 var ln = face[i]
217 b.append("{ln[0].rubix_colour} {ln[1].rubix_colour} {ln[2].rubix_colour} ")
218 end
219 b.add '\n'
220 end
221 return b
222 end
223
224 private var rot_ln_buffer = new Array[Array[Int]].with_capacity(4)
225 private fun rotate_line(ln_id: Int, direction: Int) do
226 var line_data = rot_ln_buffer
227 if line_data.is_empty then for i in [0 .. 4[ do line_data.add(new Array[Int])
228 array1d_copy_to(faces[front_face][ln_id], line_data[0])
229 array1d_copy_to(faces[left_face][ln_id], line_data[1])
230 array1d_copy_to(faces[back_face][2 - ln_id], line_data[2])
231 array1d_copy_to(faces[right_face][ln_id], line_data[3])
232 if direction == counterclock then
233 line_data[3].swap_at(0, 2)
234 line_data[2].swap_at(0, 2)
235 rot_ln_buffer.rotate_left
236 else if direction == clock then
237 line_data[1].swap_at(0, 2)
238 line_data[2].swap_at(0, 2)
239 rot_ln_buffer.rotate_right
240 else
241 abort
242 end
243 array1d_copy_to(line_data[0], faces[front_face][ln_id])
244 array1d_copy_to(line_data[1], faces[left_face][ln_id])
245 array1d_copy_to(line_data[2], faces[back_face][2 - ln_id])
246 array1d_copy_to(line_data[3], faces[right_face][ln_id])
247 end
248
249 private var colbuf = new Array[Int].with_capacity(3)
250 private fun coldata(face_id: Int, col_id: Int): Array[Int] do
251 if colbuf.is_empty then for i in [0 .. 3[ do colbuf.add 0
252 var face = faces[face_id]
253 for i in [0 .. 3[ do colbuf[i] = face[i][col_id]
254 return colbuf
255 end
256
257 private fun set_coldata(face_id, col_id: Int, coldata: Array[Int]) do
258 var face = faces[face_id]
259 for i in [0 .. 3[ do face[i][col_id] = coldata[i]
260 end
261
262 private var rot_col_buffer = new Array[Array[Int]].with_capacity(4)
263 private fun rotate_column(col_id: Int, direction: Int) do
264 var col_data = rot_col_buffer
265 if col_data.is_empty then for i in [0 .. 4[ do col_data.add(new Array[Int])
266 array1d_copy_to(coldata(front_face, col_id), rot_col_buffer[0])
267 array1d_copy_to(coldata(top_face, col_id), rot_col_buffer[1])
268 array1d_copy_to(coldata(back_face, col_id), rot_col_buffer[2])
269 array1d_copy_to(coldata(bottom_face, col_id), rot_col_buffer[3])
270 if direction == clock then
271 rot_col_buffer.rotate_left
272 else if direction == counterclock then
273 rot_col_buffer.rotate_right
274 else
275 abort
276 end
277 set_coldata(front_face, col_id, rot_col_buffer[0])
278 set_coldata(top_face, col_id, rot_col_buffer[1])
279 set_coldata(back_face, col_id, rot_col_buffer[2])
280 set_coldata(bottom_face, col_id, rot_col_buffer[3])
281 end
282
283 private var r90_cache = new Array[Array[Int]]
284 private fun rotate_l90_face(face_id: Int) do
285 var lines = r90_cache
286 if lines.is_empty then for i in [0 .. 3[ do lines.add(new Array[Int])
287 array1d_copy_to(faces[face_id][top_ln], lines[0])
288 array1d_copy_to(faces[face_id][mid_ln], lines[1])
289 array1d_copy_to(faces[face_id][bottom_ln], lines[2])
290 for i in [0 .. 3[ do lines[i].swap_at(0, 2)
291 set_coldata(face_id, left_col, lines[0])
292 set_coldata(face_id, mid_col, lines[1])
293 set_coldata(face_id, right_col, lines[2])
294 end
295
296 private fun rotate_r90_face(face_id: Int) do
297 var lines = r90_cache
298 if lines.is_empty then for i in [0 .. 3[ do lines.add(new Array[Int])
299 array1d_copy_to(faces[face_id][top_ln], lines[0])
300 array1d_copy_to(faces[face_id][mid_ln], lines[1])
301 array1d_copy_to(faces[face_id][bottom_ln], lines[2])
302 set_coldata(face_id, right_col, lines[0])
303 set_coldata(face_id, mid_col, lines[1])
304 set_coldata(face_id, left_col, lines[2])
305 end
306
307 # U command
308 fun clock_U do
309 rotate_line(top_ln, clock)
310 rotate_r90_face(top_face)
311 end
312
313 # U' command
314 fun cclock_U do
315 rotate_line(top_ln, counterclock)
316 rotate_l90_face(top_face)
317 end
318
319 # D command
320 fun clock_D do
321 rotate_line(bottom_ln, counterclock)
322 rotate_r90_face(bottom_face)
323 end
324
325 # D' command
326 fun cclock_D do
327 rotate_line(bottom_ln, clock)
328 rotate_l90_face(bottom_face)
329 end
330
331 # L command
332 fun clock_L do
333 rotate_column(left_col, clock)
334 rotate_r90_face(left_face)
335 end
336
337 # L' command
338 fun cclock_L do
339 rotate_column(left_col, counterclock)
340 rotate_l90_face(left_face)
341 end
342
343 # R command
344 fun clock_R do
345 rotate_column(right_col, counterclock)
346 rotate_r90_face(right_face)
347 end
348
349 # R' command
350 fun cclock_R do
351 rotate_column(right_col, clock)
352 rotate_l90_face(right_face)
353 end
354
355 # M command
356 fun clock_M do rotate_column(mid_col, clock)
357
358 # M' command
359 fun cclock_M do rotate_column(mid_col, counterclock)
360
361 # E command
362 fun clock_E do rotate_line(mid_ln, counterclock)
363
364 # E' command
365 fun cclock_E do rotate_line(mid_ln, clock)
366
367 # F command
368 fun clock_F do
369 cube_Y_rotation
370 clock_L
371 ccube_Y_rotation
372 end
373
374 # F' command
375 fun cclock_F do
376 cube_Y_rotation
377 cclock_L
378 ccube_Y_rotation
379 end
380
381 # B command
382 fun clock_B do
383 ccube_Y_rotation
384 clock_L
385 cube_Y_rotation
386 end
387
388 # B' command
389 fun cclock_B do
390 ccube_Y_rotation
391 cclock_L
392 cube_Y_rotation
393 end
394
395 # S command
396 fun clock_S do
397 cube_Y_rotation
398 clock_M
399 ccube_Y_rotation
400 end
401
402 # S' command
403 fun cclock_S do
404 cube_Y_rotation
405 cclock_M
406 ccube_Y_rotation
407 end
408
409 # u command
410 fun clock_u do
411 clock_U
412 cclock_E
413 end
414
415 # u' command
416 fun cclock_u do
417 cclock_U
418 clock_E
419 end
420
421 # l command
422 fun clock_l do
423 clock_L
424 clock_M
425 end
426
427 # l' command
428 fun cclock_l do
429 cclock_L
430 cclock_M
431 end
432
433 # f command
434 fun clock_f do
435 clock_F
436 clock_S
437 end
438
439 # f' command
440 fun cclock_f do
441 cclock_F
442 cclock_S
443 end
444
445 # r command
446 fun clock_r do
447 clock_R
448 cclock_M
449 end
450
451 # r' command
452 fun cclock_r do
453 cclock_R
454 clock_M
455 end
456
457 # b command
458 fun clock_b do
459 clock_B
460 cclock_S
461 end
462
463 # b' command
464 fun cclock_b do
465 cclock_B
466 clock_S
467 end
468
469 # d command
470 fun clock_d do
471 clock_D
472 clock_E
473 end
474
475 # d' command
476 fun cclock_d do
477 cclock_D
478 cclock_E
479 end
480
481 # Y command
482 fun cube_Y_rotation do
483 clock_U
484 cclock_E
485 cclock_D
486 end
487
488 # Y' command
489 fun ccube_Y_rotation do
490 cclock_U
491 clock_E
492 clock_D
493 end
494
495 # X command
496 fun cube_X_rotation do
497 cclock_L
498 cclock_M
499 clock_R
500 end
501
502 # X' command
503 fun ccube_X_rotation do
504 clock_L
505 clock_M
506 cclock_R
507 end
508
509 # Z command
510 fun cube_Z_rotation do
511 ccube_Y_rotation
512 cube_X_rotation
513 cube_Y_rotation
514 end
515
516 # Z' command
517 fun ccube_Z_rotation do
518 cube_Y_rotation
519 cube_X_rotation
520 ccube_Y_rotation
521 end
522
523 # Applies a command `cmd` to `self`, returns the number of operations performed during the command
524 fun do_cmd(cmd: String): Int do
525 if cmd == "" then return 0
526 var iters = 1
527 var cmdln = cmd.length
528 if cmd[cmdln - 1] == '2' then
529 iters = 2
530 end
531 var cmd1 = cmd[0]
532 var cmd2 = '\0'
533 if cmdln > 1 then
534 cmd2 = cmd[1]
535 if cmd2 == '2' then cmd2 = '\0'
536 end
537 for i in [1 .. iters] do
538 if cmd1 == 'U' then
539 if cmd2 == '\'' then
540 cclock_U
541 else
542 clock_U
543 end
544 else if cmd1 == 'L' then
545 if cmd2 == '\'' then
546 cclock_L
547 else
548 clock_L
549 end
550 else if cmd1 == 'F' then
551 if cmd2 == '\'' then
552 cclock_F
553 else
554 clock_F
555 end
556 else if cmd1 == 'R' then
557 if cmd2 == '\'' then
558 cclock_R
559 else
560 clock_R
561 end
562 else if cmd1 == 'B' then
563 if cmd2 == '\'' then
564 cclock_B
565 else
566 clock_B
567 end
568 else if cmd1 == 'D' then
569 if cmd2 == '\'' then
570 cclock_D
571 else
572 clock_D
573 end
574 else if cmd1 == 'M' then
575 if cmd2 == '\'' then
576 cclock_M
577 else
578 clock_M
579 end
580 else if cmd1 == 'E' then
581 if cmd2 == '\'' then
582 cclock_E
583 else
584 clock_E
585 end
586 else if cmd1 == 'S' then
587 if cmd2 == '\'' then
588 cclock_S
589 else
590 clock_S
591 end
592 else if cmd1 == 'u' then
593 if cmd2 == '\'' then
594 cclock_u
595 else
596 clock_u
597 end
598 else if cmd1 == 'l' then
599 if cmd2 == '\'' then
600 cclock_l
601 else
602 clock_l
603 end
604 else if cmd1 == 'f' then
605 if cmd2 == '\'' then
606 cclock_f
607 else
608 clock_f
609 end
610 else if cmd1 == 'r' then
611 if cmd2 == '\'' then
612 cclock_r
613 else
614 clock_r
615 end
616 else if cmd1 == 'b' then
617 if cmd2 == '\'' then
618 cclock_b
619 else
620 clock_b
621 end
622 else if cmd1 == 'd' then
623 if cmd2 == '\'' then
624 cclock_d
625 else
626 clock_d
627 end
628 else if cmd1 == 'X' then
629 if cmd2 == '\'' then
630 ccube_X_rotation
631 else
632 cube_X_rotation
633 end
634 else if cmd1 == 'Y' then
635 if cmd2 == '\'' then
636 ccube_Y_rotation
637 else
638 cube_Y_rotation
639 end
640 else if cmd1 == 'Z' then
641 if cmd2 == '\'' then
642 ccube_Z_rotation
643 else
644 cube_Z_rotation
645 end
646 else
647 abort
648 end
649 end
650 return iters
651 end
652 end