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