#% text_encoding = iso8859_1 _package user # This file contains the following exemplars: # - rubiks_cube_canvas_agent # - rubiks_cube # I'd normally split these, but the first one is trivial, # and it's nice to distribute this in a single file. _pragma(classify_level=basic) def_slotted_exemplar(:rubiks_cube_canvas_agent, {}, {:canvas_agent}) $ _pragma(classify_level=restricted) _method rubiks_cube_canvas_agent.handle_event(event) ## ## _dynamic !rc_current_event! << event _return _super.handle_event(event) _endmethod $ _pragma(classify_level=basic) ## ## rubiks_cube ## ## A Rubik's Cube application that includes the capability for ## manual solving (visualize the cube from any angle, rotate ## any face), plus a fairly brute-force automatic solving ## function. ## ## Usage notes: ## - To launch a new instance: rubiks_cube.open() ## - To manipulate the cube: refer to the Help functionality ## provided, or look at the help_wanted() method below. ## ## Credits: ## - GUI interaction functionality adapted from Karl Hornell, 1996. ## - Solving algorithm adapted from Grandpa Brown, 1982. ## - The rest: Copyright 2004, Tim Minto. ## ## Redistribution for non-commercial uses permitted as long as ## credits are retained. ## # 2004/06/14 TM Created. def_slotted_exemplar(:rubiks_cube, { {:items, _unset, :readable}, {:mode, _unset, :readable}, {:vis_canvas, _unset, :readable}, {:mem_canvas, _unset, :readable}, {:cur_blocks, _unset, :readable}, {:animation_speed, _unset, :readable}, {:move_count, _unset, :readable}, {:solve_stage, _unset, :readable}, {:view_x, _unset, :readable}, {:view_y, _unset, :readable}, {:view_z, _unset, :readable}, {:press_x, _unset, :readable}, {:press_y, _unset, :readable} }, {:engine_model}) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :items, ## Cache of menu display elements. :readable, :private) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :mode, ## Identifies the current auto-processing mode - either :solve, ## :scramble, or unset. Used to allow interactive functions ## (like dragging the cube to view another side) while ## automatic things are running in a background thread. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :vis_canvas, ## The canvas used to display the cube. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :mem_canvas, ## The memory canvas used for drawing the cube while it is ## being animated (for double-buffering to reduce flicker). :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :cur_blocks, ## A six-item vector. Each item in the vector is a nine-item ## vector, which defines the current color of the nine facelets ## that comprise each face (from left-to-right and top-to-bottom): ## 1 2 3 ## 4 5 6 ## 7 8 9 :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :animation_speed, ## The number of milliseconds taken to animate the rotation of ## any face. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :move_count, ## The number of moves performed by auto-solve logic. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :solve_stage, ## A feeback string describing the progress of auto-solve logic. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :view_x, ## See view_z description below. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :view_y, ## See view_z description below. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :view_z, ## This is a 3D unit vector, stored as a coordinate3d. It ## represents the direction that your eyeball is looking at the ## cube. This is closely related to the view_x and view_y ## slots, which define the X and Y directions of the 2D plane ## that is perpendicular to your field of vision. Note that ## each of view_x, view_y, and view_z are always perpendicular ## to the other two. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :press_x, ## Combined with the press_y slot, this defines the last pixel ## coordinate for a dynamic drag operation (used to rotate the ## cube). :readable) $ _pragma(classify_level=basic) rubiks_cube.define_slot_access( :press_y, ## See press_x. :readable) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :corners, ## The eight corners of the cube have been arbitrarily ## numbered, and this defines the XYZ position of each corner. {coordinate3d.new(-1, 1, 1), coordinate3d.new(1, 1, 1), coordinate3d.new(1, -1, 1), coordinate3d.new(-1, -1, 1), coordinate3d.new(1, -1, -1), coordinate3d.new(-1, -1, -1), coordinate3d.new(-1, 1, -1), coordinate3d.new(1, 1, -1)}, :public) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :face_directions, ## The six faces of the cube have been arbitrarily numbered, ## and this defines the direction of each face as a 3D unit ## vector. Face #1 is the "top" (positive Z direction), face ## #2 is the "bottom" (negative Z direction), and #3 to #6 are ## the side faces going clockwise around the cube. {coordinate3d.new(0, 0, 1), coordinate3d.new(0, 0, -1), coordinate3d.new(0, 1, 0), coordinate3d.new(1, 0, 0), coordinate3d.new(0, -1, 0), coordinate3d.new(-1, 0, 0)}, :public) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :opposite_face, ## This vector defines the opposite face for each of the ## face_directions 1 to 6 (defined above). This could be ## calculated, but it is instead stored as a constant for ## simplicity. {2, 1, 5, 6, 3, 4}, :public) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :faces_to_corners, ## This relates self.face_directions to self.corners. Each ## item in this six-item vector consists of a four-item inner ## vector, which lists the four corners that define that face, ## in clockwise order. {{1, 2, 3, 4}, {6, 5, 8, 7}, {2, 1, 7, 8}, {3, 2, 8, 5}, {4, 3, 5, 6}, {1, 4, 6, 7}}, :public) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :face_adjacency_edge, ## This hairball defines the edge pieces of other faces that ## are adjacent to each face, as follows: ## - There are six items in the outer vector, corresponding to ## the six faces. ## - Each of the six items consists of a four-item vector, ## corresponding to the four faces that are adjacent to that ## face. ## - Each item in the four-item vector is a two-item vector, ## consisting of the following: ## - First item - the adjacent face number. ## - Second item - the three facelet numbers on that face ## which are adjacent. Note that the nine facelets on a face ## are numbered left-to-right and top-to-bottom, with ## facelet #1 corresponding to the first corner for that face ## given in self.faces_to_corners, i.e.: ## 1 2 3 ## 4 5 6 ## 7 8 9 ## Also, note that the ordering of these facelets in the ## three-item vector is important: it represents the three ## facelets in clockwise order, when looking directly at ## the face in question. ## ## If you've read this far, and you really want to understand ## this... try actually drawing the cube on a piece of paper, ## based on the information in self.corners and self.faces_to_corners ## given above, and examine face #1 as an example. Note that ## it consists of corners 1, 2, 3, and 4. Also, examining this ## face clockwise from corner 1, note that the four adjacent faces ## encountered are 3, 4, 5, and 6 (in that order). Now, consider ## the edge from corner 1 to corner 2. As you traverse around ## face 1 in a clockwise manner, the adjancent facelets on face ## 3 are facelets 3, 2, and 1 (in that order). Similarly, as ## you traverse around face 1 in a clockwise manner from corner ## 2 to corner 3, the adjacent facelets on face 4 are facelets 3, ## 2, and 1 (in that order)... and so on for adjacent faces 5 ## and 6. ## ## By putting this hairball here into one grand constant, it ## makes the code that rotates faces (and animates the drawing ## of those rotating faces) much, much simpler. {{{3, {3, 2, 1}}, {4, {3, 2, 1}}, {5, {3, 2, 1}}, {6, {3, 2, 1}}}, {{5, {7, 8, 9}}, {4, {7, 8, 9}}, {3, {7, 8, 9}}, {6, {7, 8, 9}}}, {{1, {3, 2, 1}}, {6, {1, 4, 7}}, {2, {7, 8, 9}}, {4, {9, 6, 3}}}, {{1, {9, 6, 3}}, {3, {1, 4, 7}}, {2, {9, 6, 3}}, {5, {9, 6, 3}}}, {{1, {7, 8, 9}}, {4, {1, 4, 7}}, {2, {3, 2, 1}}, {6, {9, 6, 3}}}, {{1, {1, 4, 7}}, {5, {1, 4, 7}}, {2, {1, 4, 7}}, {3, {9, 6, 3}}} }, :public) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :face_adjacency_slice, ## See self.face_adjacency_edge. This is the same thing, ## except it represents the middle slice facelets of the ## adjacent face, rather than the adjacent edge facelets. {{{3, {6, 5, 4}}, {4, {6, 5, 4}}, {5, {6, 5, 4}}, {6, {6, 5, 4}}}, {{5, {4, 5, 6}}, {4, {4, 5, 6}}, {3, {4, 5, 6}}, {6, {4, 5, 6}}}, {{1, {6, 5, 4}}, {6, {2, 5, 8}}, {2, {4, 5, 6}}, {4, {8, 5, 2}}}, {{1, {8, 5, 2}}, {3, {2, 5, 8}}, {2, {8, 5, 2}}, {5, {8, 5, 2}}}, {{1, {4, 5, 6}}, {4, {2, 5, 8}}, {2, {6, 5, 4}}, {6, {8, 5, 2}}}, {{1, {2, 5, 8}}, {5, {2, 5, 8}}, {2, {2, 5, 8}}, {3, {8, 5, 2}}} }, :public) $ _pragma(classify_level=basic) rubiks_cube.define_shared_constant( :face_styles, ## Vector of face colors. The seventh item (black) is used to ## render the spaces between faces. N.B.: I think that the ## colors on an "official" Rubik's Cube are actually different ## (i.e. white is opposite yellow), but for the cheap imitation ## that I'm using, this is indeed the color scheme. Feel free ## to change this if you want. To make solving this much ## harder, go ahead and make two or three faces the same color... {fill_style.new(colour.called(:blue)), fill_style.new(colour.called(:white)), fill_style.new(colour.called(:red)), fill_style.new(colour.called(:yellow)), fill_style.new(colour.called(:orange)), fill_style.new(colour.called(:green)), fill_style.new(colour.called(:black))}, :public) $ _pragma(classify_level=restricted) _method rubiks_cube.init() ## ## _super.init() .items << property_list.new() .animation_speed << 400 .move_count << 0 .solve_stage << "" # The following will provide an exact top-down view as the # initial view. #.view_x << _self.normalize(coordinate3d.new(1, 0, 0)) #.view_y << _self.normalize(coordinate3d.new(0, 1, 0)) # The following will provide an initial view at a small angle. # Any vectors can be used, as long as they are perpendicular. .view_x << _self.normalize(coordinate3d.new(0.93203070, 0.04919077, 0.35902512)) .view_y << _self.normalize(coordinate3d.new(0.03862561, 0.97161452, -0.23339514)) .view_z << _self.normalize(.view_x.cross(.view_y)) _self.init_blocks() _return _self _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.init_blocks() ## ## Initialize the cube to a solved state, by setting each of ## the nine facelets on each face to be equal to that face ## number. ## .cur_blocks << simple_vector.new(6) _for i _over 1.upto(6) _loop .cur_blocks[i] << {i, i, i, i, i, i, i, i, i} _endloop _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.activate_in(f) ## ## p << panel.new(f) p.border_width << p.border_height << 0 p.height_spacing << 0 p.width_spacing << 6 p.start_row() f.add_accelerator("Ctrl+S", _self, {:|run_engine()|, _thisthread.background_priority, :|scramble()|}) ca << rubiks_cube_canvas_agent.new(_self) ca.define_redraw(:|damaged()|, _self) ca.define_button_press(:|start_dynamic_drag()|, _self, :menu) ca.define_locator(:|do_dynamic_drag()|, _self, :drag) ca.define_button(:|end_dynamic_drag()|, _self, :menu) # The following is needed because Ctrl + left click gets # translated to middle click for a 2 button mouse. ca.define_button(:|do_rotate_face_or_slice_with_ctrl()|, _self, :other) ca.define_button(:|do_rotate_face_or_slice()|, _self, :select) ca.define_button(:|do_rotate_face_or_slice()|, _self, :select, :shift) ca.define_button(:|do_rotate_face_or_slice()|, _self, :select, :control) ca.define_button(:|do_rotate_face_or_slice()|, _self, :select, :meta) .vis_canvas << canvas.new(p, 300, 300, ca) .vis_canvas.resize_x? << .vis_canvas.resize_y? << _true # N.B.: if you plan on really displaying a big cube on a big # monitor, you'll need to up the size of the canvas. .mem_canvas << memory_canvas.new(1024, 1024) p.start_row() label_item.new(p, "Animation speed (ms) ") .items[:animation_speed] << slider_item.new(p, _unset, 0, 1000, :model, _self, :aspect, :animation_speed, :change_selector, :|animation_speed<<|, :value_selector, :animation_speed) p.start_row() label_item.new(p, "Auto-solve stage") .items[:solve_stage] << text_item.new(p, "", :display_length, 10, :enabled?, _false, :model, _self, :aspect, :solve_stage, :value_selector, :solve_stage) label_item.new(p, "Moves") .items[:move_count_string] << text_item.new(p, "", :display_length, 3, :enabled?, _false, :model, _self, :aspect, :move_count_string, :value_selector, :move_count_string) p.start_row() button_item.new_safe(p, "Solve", _self, {:|run_engine()|, _thisthread.background_priority, :|solve()|}, :min_width, 75) button_item.new_safe(p, "Stop", _self, :|interrupt_engine()|, :min_width, 75) button_item.new_safe(p, "Help", _self, :|help()|, :min_width, 75) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.help_wanted() ## ## s << internal_text_output_stream.new() s.write("Viewing:", newline_char) s.write(" Right click and drag to rotate cube", newline_char) s.write(newline_char, "Automatic solving:", newline_char) s.write(" Ctrl+S: Scramble", newline_char) s.write(" Solve: Solve the scrambled cube", newline_char) s.write(" Stop: Interrupt solving/scrambling", newline_char) s.write(newline_char, "Manual solving:", newline_char) s.write(" Left click: Spins face clockwise", newline_char) s.write(" Alt+left click: Spins opposite face", newline_char) s.write(" Ctrl+left click: Spins middle slice (between clicked and opposite faces)", newline_char) s.write(" Hold shift while clicking to spin in reverse direction", newline_char) _self.show_message(s.string) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.move_count_string ## ## _return .move_count.default("").write_string _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.move_count << x ## ## .move_count << x _self.changed(:move_count_string) _return x _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.solve_stage << x ## ## .solve_stage << x _self.changed(:solve_stage) _return x _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.animation_speed << x ## ## .animation_speed << x _self.changed(:animation_speed) _return x _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.interrupt_engine() ## ## .mode << _unset _super.interrupt_engine() _self.draw() _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.damaged() ## ## _self.draw() _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.start_dynamic_drag(x, y) ## ## When starting a dynamic drag, suspend any running process ## (solving or scrambling) for the duration of the drag. ## _if .mode _isnt _unset _then _self.suspend_engine() _endif .press_x << x .press_y << y _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.do_dynamic_drag(x, y) ## ## While dragging to the new pixel coordinate defined by X and ## Y, recalculate the new point-of-view defined by the view_z ## slot, and redisplay the screen to reflect the new ## point-of-view. ## _if .press_x _is _unset _then # Shouldn't happen, but just in case, defend against mismatched # events. _return _endif x_diff << x - .press_x y_diff << y - .press_y .press_x << x .press_y << y _if x_diff ~= 0 _then # Adjust the point-of-view (stored in the view_z) slot so that it # moves along the X-axis of the point-of-view (stored in the # view_x slot). # FIXME: size of following constant (0.012) should depend on # width/height. It works OK for the initial default screen # size, but the scrolling/rotation would probably be considered # to be more natural for most users if it adapted to the screen # size. .view_z -<< 0.012 * x_diff * .view_x # Reset the X-axis of the point-of-view so that it is # perpendicular again. .view_x << .view_y.cross(.view_z) # Ensure that the newly-changed values are still a unit vector, # as the above operations introduce a bit of distortion. .view_z << _self.normalize(.view_z) .view_x << _self.normalize(.view_x) _endif _if y_diff ~= 0 _then # Adjust the point-of-view (stored in the view_z) slot so that it # moves along the Y-axis of the point-of-view (stored in the # view_y slot). .view_z +<< 0.012 * y_diff * .view_y # Reset the Y-axis of the point-of-view so that it is # perpendicular again. .view_y << .view_z.cross(.view_x) # Ensure that the newly-changed values are still a unit vector, # as the above operations introduce a bit of distortion. .view_z << _self.normalize(.view_z) .view_y << _self.normalize(.view_y) _endif # Redraw using the new point-of-view. _self.draw() _if smallworld_product.release_version = 3 _then # Garbage collect. This is intentional, as forcing garbage # collects in various places in this code will ensure that each # is so short as to be unnoticeable to the user. Otherwise, a # system-generated ephemeral garbage collect will eventually # come along which causes a noticeable pause when doing # animation operations. # However... performance of this is much slower at V4.0 # compared to V3.3. So much so, that it drags the animation # down in V4.0. So, only do the gc() in V3.x. system.gc(_false) _endif _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.end_dynamic_drag() ## ## When ending a dynamic drag, resume any running process ## (solving or scrambling) that was suspended for the duration ## of the drag. ## _if .mode _isnt _unset _then _self.resume_engine() _endif _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.normalize(c3d) ## ## Normalize the coordinate3d C3D given, so that it is a unit ## vector. ## hypot << (c3d.x ** 2 + c3d.y ** 2 + c3d.z ** 2).sqrt _return coordinate3d.new(c3d.x / hypot, c3d.y / hypot, c3d.z / hypot) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.draw() ## ## Render the cube. ## vis_canv << .vis_canvas mem_canv << .mem_canvas # Set up a transform that fits the cube dimensions (which range # from -1 to +1 in the X, Y, and Z directions) nicely onto the # screen. Note that depending on the viewing angle, the # distance from the (0,0,0) center point to any corner can # appear to be up to (3.sqrt = 1.732) units long, so allow the # display to show data up to +/- 1.8 units in size. xfm << transform.scalexy(1, -1).translate(1.8, 1.8).scalexy(vis_canv.width / 3.6, vis_canv.height / 3.6) vis_canv.transform << xfm mem_canv.clear() ch << vis_canv.height cw << vis_canv.width mem_canv.transform << xfm # For each of the eight corners (which is a coordinate3d in the # form of (+/-1, +/-1, +/-1), project that onto the viewing # plane defined by the view_x and view_y slots). This is now a # 2D coordinate, suitable for rendering on the canvas. mod_corners << simple_vector.new(8) _for i _over 1.upto(8) _loop mod_corners[i] << coordinate.new(_self.corners[i].dot(.view_x), _self.corners[i].dot(.view_y)) _endloop # Commented-out code for debug only: draw the 12 edges of the # cube as a wire frame. # _for edge _over {{1,2}, # {2,3}, # {3,4}, # {4,1}, # {5,6}, # {6,7}, # {7,8}, # {8,5}, # {1,7}, # {2,8}, # {3,5}, # {4,6}}.fast_elements() # _loop # mem_canv.draw_line_transform(line_style, coords_vector.new_with_xy(mod_corners[edge[1]].x, # mod_corners[edge[1]].y, # mod_corners[edge[2]].x, # mod_corners[edge[2]].y)) # _endloop # Process the drawing logic for each of the six faces. _for face_idx _over 1.upto(6) _loop # If the face is actually facing away from the point-of-view, # do not draw it. _if .view_z.dot(_self.face_directions[face_idx]) <= 0 _then _continue _endif # Get the four corners of interest, which define the face being # drawn. f2c << _self.faces_to_corners[face_idx] xc1 << mod_corners[f2c[1]] xc2 << mod_corners[f2c[2]] xc3 << mod_corners[f2c[3]] xc4 << mod_corners[f2c[4]] xdist << (xc2 - xc1) / 3.0 ydist << (xc4 - xc1) / 3.0 # As a start, fill the face entirely with black. f_style << _self.face_styles[7] mem_canv.fill_line_transform(f_style, coords_vector.new_with_xy(xc1.x, xc1.y, xc2.x, xc2.y, xc3.x, xc3.y, xc4.x, xc4.y, xc1.x, xc1.y)) _for i _over 0.upto(2) _loop _for j _over 0.upto(2) _loop # Fill each of the nine individual facelets that constitute the # face with the appropriate color. color_num << .cur_blocks[face_idx][i + j * 3 + 1] f_style << _self.face_styles[color_num] c1 << xc1 + xdist * (i + 0.07) + ydist * (j + 0.07) c2 << c1 + xdist * 0.86 c3 << c2 + ydist * 0.86 c4 << c1 + ydist * 0.86 mem_canv.fill_line_transform(f_style, coords_vector.new_with_xy(c1.x, c1.y, c2.x, c2.y, c3.x, c3.y, c4.x, c4.y, c1.x, c1.y)) _endloop _endloop _endloop # Finally, copy from the memory canvas to the actual canvas # used for displaying the cube to the user. vis_canv.copy_area(1, 1, cw, ch, mem_canv, 1, 1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.draw_layer(face_num, rotation, _optional middle?) ## ## Variation of the draw() method above, used for rendering a ## single layer of the cube. Note that a layer represents ## one-third of the cube. ## - FACE_NUM defines the layer to be rendered. ## - ROTATION is the amount of rotation to apply when rendering ## the face. Zero rotation means render it as normal, and ## a non-zero value means that this is being done as an interim ## step in animating the rotation of a face. ## - MIDDLE? is true if rendering the middle third that corresponds ## to FACE_NUM. Otherwise, the layer being rendered is the ## one-third represented by FACE_NUM itself. ## middle? << middle?.default(_false) vis_canv << .vis_canvas mem_canv << .mem_canvas # Same transform logic as for the draw() method. xfm << transform.scalexy(1, -1).translate(1.8, 1.8).scalexy(vis_canv.width / 3.6, vis_canv.height / 3.6) vis_canv.transform << xfm ch << vis_canv.height cw << vis_canv.width mem_canv.transform << xfm # Set up temporary storage for manipulating the view plane to # be rendered, assuming that some rotation will be performed. rot_view_x << .view_x.copy() rot_view_y << .view_y.copy() cosval << rotation.cos sinval << rotation.sin rot_corners << _self.corners # N.B.: the following is hard-coded on a per-face basis. # Rotate the point of view around the +/- X, Y, or Z axis, # depending on which face is being drawn. _if face_num = 1 _then rot_view_x[1] << cosval * .view_x.x + sinval * .view_x.y rot_view_x[2] << -sinval * .view_x.x + cosval * .view_x.y rot_view_y[1] << cosval * .view_y.x + sinval * .view_y.y rot_view_y[2] << -sinval * .view_y.x + cosval * .view_y.y _elif face_num = 2 _then rot_view_x[1] << cosval * .view_x.x - sinval * .view_x.y rot_view_x[2] << sinval * .view_x.x + cosval * .view_x.y rot_view_y[1] << cosval * .view_y.x - sinval * .view_y.y rot_view_y[2] << sinval * .view_y.x + cosval * .view_y.y _elif face_num = 3 _then rot_view_x[1] << cosval * .view_x.x - sinval * .view_x.z rot_view_x[3] << sinval * .view_x.x + cosval * .view_x.z rot_view_y[1] << cosval * .view_y.x - sinval * .view_y.z rot_view_y[3] << sinval * .view_y.x + cosval * .view_y.z _elif face_num = 4 _then rot_view_x[2] << cosval * .view_x.y + sinval * .view_x.z rot_view_x[3] << -sinval * .view_x.y + cosval * .view_x.z rot_view_y[2] << cosval * .view_y.y + sinval * .view_y.z rot_view_y[3] << -sinval * .view_y.y + cosval * .view_y.z _elif face_num = 5 _then rot_view_x[1] << cosval * .view_x.x + sinval * .view_x.z rot_view_x[3] << -sinval * .view_x.x + cosval * .view_x.z rot_view_y[1] << cosval * .view_y.x + sinval * .view_y.z rot_view_y[3] << -sinval * .view_y.x + cosval * .view_y.z _elif face_num = 6 _then rot_view_x[2] << cosval * .view_x.y - sinval * .view_x.z rot_view_x[3] << sinval * .view_x.y + cosval * .view_x.z rot_view_y[2] << cosval * .view_y.y - sinval * .view_y.z rot_view_y[3] << sinval * .view_y.y + cosval * .view_y.z _endif # Set the point-of-view vector to be perpendicular to the # newly-defined view plane. rot_view_z << rot_view_x.cross(rot_view_y) # For each of the eight corners (which is a coordinate3d in the # form of (+/-1, +/-1, +/-1)), project that onto the viewing # plane defined by the rot_view_x and rot_view_y local # variables. This is now a 2D coordinate, suitable for # rendering on the canvas. mod_corners << simple_vector.new(8) _for i _over 1.upto(8) _loop mod_corners[i] << coordinate.new(rot_corners[i].dot(rot_view_x), rot_corners[i].dot(rot_view_y)) _endloop adj_data << simple_vector.new(6) _if middle? _then # If rendering the middle slice, then don't actually draw # FACE_NUM at all. adj_data[face_num] << _unset lkp_table << _self.face_adjacency_slice slice_distance << 2.0 / 3.0 _else # If rendering an outer layer, then draw FACE_NUM in its # entirety. adj_data[face_num] << :all lkp_table << _self.face_adjacency_edge slice_distance << 4.0 / 3.0 _endif _for i _over lkp_table[face_num].fast_elements() _loop # For each adjacent face, make note of the three facelets on that # face which must be drawn as part of this slice. adj_data[i[1]] << i[2] _endloop fstyles << _self.face_styles black_style << fstyles[7] _for face_idx _over 1.upto(6) _loop # If the face is actually facing away from the point-of-view, # do not draw it. _if rot_view_z.dot(_self.face_directions[face_idx]) <= 0 _then _continue _endif blocks_to_draw << adj_data[face_idx] _if blocks_to_draw _is _unset _then # This is the backside of the slice - need to calculate the # plane of that slice, and fill it black. move_dir << _self.face_directions[face_idx] move_f2c << _self.faces_to_corners[face_idx] move_corners << simple_vector.new(4) _for k, corner_num _over move_f2c.fast_keys_and_elements() _loop temp_c << rot_corners[corner_num] temp_c << coordinate3d.new(temp_c.x - move_dir.x * slice_distance, temp_c.y - move_dir.y * slice_distance, temp_c.z - move_dir.z * slice_distance) move_corners[k] << coordinate.new(temp_c.dot(rot_view_x), temp_c.dot(rot_view_y)) _endloop plane_xc1 << move_corners[1] plane_xc2 << move_corners[2] plane_xc3 << move_corners[3] plane_xc4 << move_corners[4] mem_canv.fill_line_transform( black_style, coords_vector.new_with_xy(plane_xc1.x, plane_xc1.y, plane_xc2.x, plane_xc2.y, plane_xc3.x, plane_xc3.y, plane_xc4.x, plane_xc4.y, plane_xc1.x, plane_xc1.y)) # No further facelet painting required. _continue _endif # Get the four corners of interest, which define the face being # drawn. f2c << _self.faces_to_corners[face_idx] xc1 << mod_corners[f2c[1]] xc2 << mod_corners[f2c[2]] xc3 << mod_corners[f2c[3]] xc4 << mod_corners[f2c[4]] xdist << (xc2 - xc1) / 3.0 ydist << (xc4 - xc1) / 3.0 _for i _over 0.upto(2) _loop _for j _over 0.upto(2) _loop # Process each of the nine individual facelets that constitute the # face. For the face that constitutes the slice being drawn, # all nine facelets will be drawn (indicated by blocks_to_draw # being :all). For adjacent faces, three facelets will be # drawn. For the opposite face, nothing will be drawn # (indicated by blocks_to_draw being unset, which is filtered # out above). block_num << i + j * 3 + 1 _if blocks_to_draw _is :all _orif blocks_to_draw.includes?(block_num) _then # Draw this facelet. _else # Skip this facelet. _continue _endif # Fill in a black square. c1 << xc1 + xdist * i + ydist * j c2 << c1 + xdist c3 << c2 + ydist c4 << c1 + ydist mem_canv.fill_line_transform(black_style, coords_vector.new_with_xy(c1.x, c1.y, c2.x, c2.y, c3.x, c3.y, c4.x, c4.y, c1.x, c1.y)) # Fill in the right color on the facelet. color_num << .cur_blocks[face_idx][block_num] f_style << _self.face_styles[color_num] c1 << xc1 + xdist * (i + 0.07) + ydist * (j + 0.07) c2 << c1 + xdist * 0.86 c3 << c2 + ydist * 0.86 c4 << c1 + ydist * 0.86 mem_canv.fill_line_transform(f_style, coords_vector.new_with_xy(c1.x, c1.y, c2.x, c2.y, c3.x, c3.y, c4.x, c4.y, c1.x, c1.y)) _endloop _endloop _endloop _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.do_rotate_face_or_slice_with_ctrl(x, y, a_canvas) ## ## The following is needed because Ctrl + left click gets ## translated to middle click for a 2 button mouse. ## _dynamic !rc_current_event! _self.do_rotate_face_or_slice(x, y, a_canvas, !rc_current_event!.modmask _or 2) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.do_rotate_face_or_slice(x, y, a_canvas, _optional modmask) ## ## Perform the work of rotating a face or a slice, depending on ## where the pointing was made (point X,Y in A_CANVAS). ## Optionally allow MODMASK to be specified, which represents ## the Shift-Ctrl-Alt combination held down while the pointing ## is made - if not entered, it will be determined from the ## actual event. ## _dynamic !rc_current_event! _if .mode _isnt _unset _then # Don't allow this while auto-solve or scramble is in process. _return _endif # Explicitly clear out dynamic drag information, in case those # events are somehow mismatched. .press_x << _unset .press_y << _unset _if modmask _is _unset _then modmask << !rc_current_event!.modmask _endif # Properly figure the face selected. face_num << _unset xfm << .vis_canvas.transform mod_corners << simple_vector.new(8) _for i _over 1.upto(8) _loop mod_corners[i] << coordinate.new(_self.corners[i].dot(.view_x), _self.corners[i].dot(.view_y)) _endloop # The craziness with mult_factor is to scale everything up to a # suitable size so that tolerances in spatial relation checking # do not come into play. I could explicitly change tolerances # here, but at the risk of making the GIS bent if I failed to # reset them properly. mult_factor << 10000.0 test_xfm1 << xfm.inverse().scale(mult_factor) test_xfm2 << transform.scale(mult_factor) test_coord << test_xfm1.convert(coordinate.new(x, y)) _for face_idx _over 1.upto(6) _loop # Only consider sides that are facing at least a wee bit forward. _if .view_z.dot(_self.face_directions[face_idx]) > 0.00001 _then f2c << _self.faces_to_corners[face_idx] c1 << test_xfm2.convert(mod_corners[f2c[1]]) c2 << test_xfm2.convert(mod_corners[f2c[2]]) c3 << test_xfm2.convert(mod_corners[f2c[3]]) c4 << test_xfm2.convert(mod_corners[f2c[4]]) pa << pseudo_area.new_with_sectors( sector_rope.new_with( sector.new_with(c1, c2, c3, c4, c1))) _if pa.spatial_relation_with(test_coord) _is :contains _then face_num << face_idx _leave _endif _endif _endloop _if face_num _is _unset _then # Pointing did not occur within any forward-facing face. _return _endif _if (modmask _and 1) = 1 _then # Shift pressed: use CCW direction. num_spin << 3 _else num_spin << 1 _endif # Use Ctrl key to spin the middle slice. _if (modmask _and 2) = 2 _then slice? << _true method_name << :|rotate_slice()| _else slice? << _false method_name << :|rotate_face()| _endif # Use meta (Alt) key to spin the opposite hidden face. _if (modmask _and 4) = 4 _then num_spin << 4 - num_spin face_num << _self.opposite_face[face_num] _endif _if num_spin = 3 _then turn_dir << -1 _else turn_dir << 1 _endif # Note that the animation is actually performed first. _self.animate_turn(face_num, turn_dir, slice?) # After the animation is performed, call rotate_face() or # rotate_slice() to transform the facelets held in the # cur_blocks slot. _for i _over 1.upto(num_spin) _loop _self.perform(method_name, face_num) _endloop # Upon completion, do another screen refresh to tidy everything # up. _self.draw() _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.scramble() ## ## Scramble the cube with 50 random turns. ## _if .mode _isnt _unset _then _return _endif _protect .mode << :scramble rnd1 << random.new(6) rnd2 << random.new(3) _for i _over 1.upto(50) _loop # Note: auto-scramble uses double speed as for regular # animation, since it isn't really all that interesting to # observe. turn_secs << .animation_speed / 2000.0 face_num << rnd1.get() + 1 iters << rnd2.get() + 1 _if iters = 3 _then _self.animate_turn(face_num, -1, _false, turn_secs) _endif _for j _over 1.upto(iters) _loop _if iters < 3 _then _self.animate_turn(face_num, 1, _false, turn_secs) _endif _self.rotate_face(face_num) _endloop _self.draw() _endloop _protection .mode << _unset _endprotect _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.animate_turn(face_num, dir, slice?, _optional spin_time) ## ## Animate graphics for turning the given face FACE_NUM in ## direction DIR (which is +1 for clockwise and -1 for ## counterclockwise). If SLICE? is true, the middle slice is ## animated - otherwise, the outer face is animated. SPIN_TIME ## is the number of seconds to complete the turn within ## (defaults to the animation_speed slot, which is a value in ## milliseconds). ## _if spin_time _is _unset _then spin_time << .animation_speed / 1000.0 _endif _if spin_time = 0 _then _if smallworld_product.release_version = 3 _then system.gc(_false) _endif _return _endif opp_face_num << _self.opposite_face[face_num] v1 << 1.0.half_pi * dir * -1 # If the face of interest is not visible, then note this fact, # as we'll draw the layers in reverse order in this case. rev? << (.view_z.dot(_self.face_directions[face_num]) < 0.0) t1 << system.elapsed_seconds() draw_count << 0 last? << _false _loop t2 << system.elapsed_seconds() - t1 # Set v2 to be the amount of rotation (in radians). Base this # on the relative amount of time spent so far - for example, if # the animation is to take 1.0 seconds, and 0.6 seconds has # elapsed so far, then draw with 60% of the total rotation. _if t2 >= spin_time _then last? << _true v2 << v1 _else v2 << v1 * t2 / spin_time _endif .mem_canvas.clear() # Draw the three layers, in the appropriate order. Note that # only one of the layers will be rotated by V2 - the other two # layers (which are not being rotated) will be drawn with zero # rotation. _if rev? _then _if slice? _then _self.draw_layer(face_num, 0) _self.draw_layer(face_num, v2, _true) _else _self.draw_layer(face_num, v2) _self.draw_layer(face_num, 0, _true) _endif _self.draw_layer(opp_face_num, 0) _else _self.draw_layer(opp_face_num, 0) _if slice? _then _self.draw_layer(face_num, v2, _true) _self.draw_layer(face_num, 0) _else _self.draw_layer(face_num, 0, _true) _self.draw_layer(face_num, v2) _endif _endif .vis_canvas.copy_area(1, 1, .vis_canvas.width, .vis_canvas.height, .mem_canvas, 1, 1) draw_count +<< 1 _if last? _then _leave _endif _endloop # Garbage collect. This is intentional, as forcing garbage # collects in various places in this code will ensure that each # is so short as to be unnoticeable to the user. Otherwise, a # system-generated ephemeral garbage collect will eventually # come along which causes a noticeable pause when doing # animation operations. # Unlike some other gc() references in this code, doing it here # is OK even in V4.0, as it does not visually uglify animation. system.gc(_false) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.rotate_face(face_num) ## ## Number-crunching required to rotate FACE_NUM by one turn ## clockwise. See the comments on self.face_adjacency_edge for ## additional details on this operation. ## data << .cur_blocks[face_num] temp << data[1] data[1] << data[7] data[7] << data[9] data[9] << data[3] data[3] << temp temp << data[2] data[2] << data[4] data[4] << data[8] data[8] << data[6] data[6] << temp rot_info << _self.face_adjacency_edge[face_num] r1 << .cur_blocks[rot_info[1][1]] r2 << .cur_blocks[rot_info[2][1]] r3 << .cur_blocks[rot_info[3][1]] r4 << .cur_blocks[rot_info[4][1]] f1 << rot_info[1][2] f2 << rot_info[2][2] f3 << rot_info[3][2] f4 << rot_info[4][2] temp << {r1[f1[1]], r1[f1[2]], r1[f1[3]]} (r1[f1[1]], r1[f1[2]], r1[f1[3]]) << (r4[f4[1]], r4[f4[2]], r4[f4[3]]) (r4[f4[1]], r4[f4[2]], r4[f4[3]]) << (r3[f3[1]], r3[f3[2]], r3[f3[3]]) (r3[f3[1]], r3[f3[2]], r3[f3[3]]) << (r2[f2[1]], r2[f2[2]], r2[f2[3]]) (r2[f2[1]], r2[f2[2]], r2[f2[3]]) << (temp[1], temp[2], temp[3]) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.rotate_slice(face_num) ## ## Number-crunching required to rotate the middle slice ## associated with FACE_NUM by one turn clockwise. See the ## comments on self.face_adjacency_slice for additional details ## on this operation. ## rot_info << _self.face_adjacency_slice[face_num] r1 << .cur_blocks[rot_info[1][1]] r2 << .cur_blocks[rot_info[2][1]] r3 << .cur_blocks[rot_info[3][1]] r4 << .cur_blocks[rot_info[4][1]] f1 << rot_info[1][2] f2 << rot_info[2][2] f3 << rot_info[3][2] f4 << rot_info[4][2] temp << {r1[f1[1]], r1[f1[2]], r1[f1[3]]} (r1[f1[1]], r1[f1[2]], r1[f1[3]]) << (r4[f4[1]], r4[f4[2]], r4[f4[3]]) (r4[f4[1]], r4[f4[2]], r4[f4[3]]) << (r3[f3[1]], r3[f3[2]], r3[f3[3]]) (r3[f3[1]], r3[f3[2]], r3[f3[3]]) << (r2[f2[1]], r2[f2[2]], r2[f2[3]]) (r2[f2[1]], r2[f2[2]], r2[f2[3]]) << (temp[1], temp[2], temp[3]) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!rotate_face_with_animate(face_num, rot) ## ## Workhorse method for rotating a face and animating it, used ## extensively by the auto-solve logic. FACE_NUM is the face ## to be rotated, and ROT is a number that represents the ## number of turns. A ROT of 1 equals one-quarter turn ## clockwise; 2 or -2 equals one-half turn clockwise; and 3 or ## -1 equals one-quarter turn counterclockwise. ## _if rot = 0 _then _return _endif _if rot = -2 _then rot << 2 _elif rot = -1 _then _self.animate_turn(face_num, -1, _false) rot << 3 _elif rot = 3 _then _self.animate_turn(face_num, -1, _false) _endif _for i _over 1.upto(rot) _loop _if rot < 3 _then _self.animate_turn(face_num, 1, _false) _endif _self.rotate_face(face_num) _endloop _self.draw() _self.move_count +<< 1 _endmethod $ # SOLVING METHODS _pragma(classify_level=basic, usage={subclassable}) _method rubiks_cube.solve() ## ## Do the work of solving the cube. If you think you can do ## better, go ahead and subclass this. I'd be interested in ## seeing some more elegant solutions than this one. ## _if .mode _isnt _unset _then # Don't solve if already solving or scrambling. _return _endif _protect .mode << :solve _self.move_count << 0 _self.int!solve_top_edge(1) _self.int!solve_top_edge(2) _self.int!solve_top_edge(3) _self.int!solve_top_edge(4) _self.int!solve_top_corner(1) _self.int!solve_top_corner(2) _self.int!solve_top_corner(3) _self.int!solve_top_corner(4) _self.int!solve_middle_edge(1, _false) _self.int!solve_middle_edge(2, _false) _self.int!solve_middle_edge(3, _false) _self.int!solve_middle_edge(4, _false) _self.int!solve_middle_edge(1, _true) _self.int!solve_middle_edge(2, _true) _self.int!solve_middle_edge(3, _true) _self.int!solve_middle_edge(4, _true) _self.int!solve_bottom_corners_1() _loop _if _self.int!solve_bottom_corners_2() _is _true _then _leave _endif _endloop _self.int!solve_bottom_corners_1() _self.int!solve_bottom_edges_1() _self.int!solve_bottom_edges_2() _self.solve_stage << "Done" _protection .mode << _unset _endprotect _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_top_edge(edge_num) ## ## Solve a single edge block along the top face. ## _self.solve_stage << write_string("Top edge ", edge_num) adj_faces << {3, 4, 5, 6, 3} adj_face_num << adj_faces[edge_num] color_1 << .cur_blocks[1][5] color_2 << .cur_blocks[adj_face_num][5] (f1, f2) << _self.find_edge_piece(color_1, color_2) _if f1 = 1 _orif f2 = 1 _then # On top face. _if f2 = adj_face_num _then # It is already correct. _return _endif # Give it a quarter turn and try again. _if f1 = 1 _then _self.int!rotate_face_with_animate(f2, 1) _else _self.int!rotate_face_with_animate(f1, 1) _endif _self.int!solve_top_edge(edge_num) _return _elif f1 = 2 _then # On bottom face, facing down. found_idx << adj_faces.index_of(f2) offset << (found_idx + 1 - edge_num) _mod 4 - 1 _self.int!rotate_face_with_animate(2, offset) _self.int!rotate_face_with_animate(adj_face_num, 2) _return _elif f2 = 2 _then # On bottom face, but facing out. found_idx << adj_faces.index_of(f1) offset << (found_idx + 1 - edge_num) _mod 4 - 1 _self.int!rotate_face_with_animate(2, offset) _self.int!rotate_face_with_animate(adj_face_num, 1) _self.int!rotate_face_with_animate(1, 1) _self.int!rotate_face_with_animate(adj_faces[edge_num + 1], -1) _self.int!rotate_face_with_animate(1, -1) _return _endif # At this point, it is in one of the four middle edges. found_idx << adj_faces.index_of(f2) found_idx2 << adj_faces.index_of(f1) offset << (found_idx + 1 - edge_num) _mod 4 - 1 _self.int!rotate_face_with_animate(1, offset) rot_dir << (found_idx2 - found_idx + 2) _mod 4 - 2 _self.int!rotate_face_with_animate(f2, rot_dir) _self.int!rotate_face_with_animate(1, -offset) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.find_edge_piece(color_1, color_2) ## ## Given COLOR_1 and COLOR_2, this returns the two faces that ## currently hold the edge piece with this color combination. ## (r1, r2) << _self.int!find_edge_piece(color_1, color_2) _if r1 _is _unset _then # Try reversing the colors. (r2, r1) << _self.int!find_edge_piece(color_2, color_1) _endif _return r1, r2 _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!find_edge_piece(color_1, color_2) ## ## # Brute-force it. There are 12 possible edges. b << .cur_blocks _if b[1][2] = color_1 _andif b[3][2] = color_2 _then _return 1, 3 _endif _if b[1][6] = color_1 _andif b[4][2] = color_2 _then _return 1, 4 _endif _if b[1][8] = color_1 _andif b[5][2] = color_2 _then _return 1, 5 _endif _if b[1][4] = color_1 _andif b[6][2] = color_2 _then _return 1, 6 _endif _if b[2][2] = color_1 _andif b[5][8] = color_2 _then _return 2, 5 _endif _if b[2][6] = color_1 _andif b[4][8] = color_2 _then _return 2, 4 _endif _if b[2][8] = color_1 _andif b[3][8] = color_2 _then _return 2, 3 _endif _if b[2][4] = color_1 _andif b[6][8] = color_2 _then _return 2, 6 _endif _if b[3][4] = color_1 _andif b[4][6] = color_2 _then _return 3, 4 _endif _if b[4][4] = color_1 _andif b[5][6] = color_2 _then _return 4, 5 _endif _if b[5][4] = color_1 _andif b[6][6] = color_2 _then _return 5, 6 _endif _if b[6][4] = color_1 _andif b[3][6] = color_2 _then _return 6, 3 _endif _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.find_corner_piece(c1, c2, c3) ## ## Given colors C1, C2, C3, this returns the three faces that ## currently hold the corner piece with this color combination. ## It also returns the corner number (c.f. self.corners). ## (r1, r2, r3, pos) << _self.int!find_corner_piece(c1, c2, c3) _if r1 _is _unset _then # Try rotating it once. (r2, r3, r1, pos) << _self.int!find_corner_piece(c2, c3, c1) _endif _if r1 _is _unset _then # Try rotating it twice. (r3, r1, r2, pos) << _self.int!find_corner_piece(c3, c1, c2) _endif _return r1, r2, r3, pos _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!find_corner_piece(c1, c2, c3) ## ## # Brute-force it. There are eight corners. b << .cur_blocks _if b[1][1] = c1 _andif b[6][1] = c2 _andif b[3][3] = c3 _then _return 1, 6, 3, 1 _endif _if b[1][3] = c1 _andif b[3][1] = c2 _andif b[4][3] = c3 _then _return 1, 3, 4, 2 _endif _if b[1][9] = c1 _andif b[4][1] = c2 _andif b[5][3] = c3 _then _return 1, 4, 5, 3 _endif _if b[1][7] = c1 _andif b[5][1] = c2 _andif b[6][3] = c3 _then _return 1, 5, 6, 4 _endif _if b[2][3] = c1 _andif b[5][9] = c2 _andif b[4][7] = c3 _then _return 2, 5, 4, 5 _endif _if b[2][1] = c1 _andif b[6][9] = c2 _andif b[5][7] = c3 _then _return 2, 6, 5, 6 _endif _if b[2][7] = c1 _andif b[3][9] = c2 _andif b[6][7] = c3 _then _return 2, 3, 6, 7 _endif _if b[2][9] = c1 _andif b[4][9] = c2 _andif b[3][7] = c3 _then _return 2, 4, 3, 8 _endif _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_top_corner(edge_num) ## ## Solve a single corner block along the top face. ## _self.solve_stage << write_string("Top corner ", edge_num) adj_faces << {3, 4, 5, 6, 3} adj_face_num1 << adj_faces[edge_num] adj_face_num2 << adj_faces[edge_num + 1] color_1 << .cur_blocks[1][5] color_2 << .cur_blocks[adj_face_num1][5] color_3 << .cur_blocks[adj_face_num2][5] (f1, f2, f3) << _self.find_corner_piece(color_1, color_2, color_3) _if f1 = 1 _orif f2 = 1 _orif f3 = 1 _then # On top face. _if f2 = adj_face_num1 _andif f3 = adj_face_num2 _then # It is already correct. _return _endif # Push it to the bottom face and try again. _if f1 = 1 _then _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f2, 1) _elif f3 = 1 _then _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f1, 1) _else _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, -1) _endif _self.int!solve_top_corner(edge_num) _return _endif _if f1 = 2 _then # Upside down on bottom face. goal << edge_num current << adj_faces.index_of(f3) diff << (current - goal + 1) _mod 4 - 1 _self.int!rotate_face_with_animate(2, diff) _self.int!rotate_face_with_animate(adj_face_num1, -1) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(adj_face_num1, 1) _self.int!solve_top_corner(edge_num) _return _endif found_idx << adj_faces.index_of(f1) _if f3 = 2 _then found_idx2 << adj_faces.index_of(f2) _else found_idx2 << adj_faces.index_of(f3) _endif rot_dir << (found_idx2 - found_idx + 2) _mod 4 - 2 _if rot_dir = -1 _then # Rotate the bottom face to prepare for merge with top row. goal << edge_num + 1 current << found_idx2 diff << (current - goal + 1) _mod 4 - 1 _self.int!rotate_face_with_animate(2, diff) other_face << adj_face_num1 _else # Rotate the bottom face to prepare for merge with top row. goal << edge_num current << found_idx2 diff << (current - goal + 1) _mod 4 - 1 _self.int!rotate_face_with_animate(2, diff) other_face << adj_face_num2 _endif _self.int!rotate_face_with_animate(other_face, rot_dir) _self.int!rotate_face_with_animate(2, -rot_dir) _self.int!rotate_face_with_animate(other_face, -rot_dir) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_middle_edge(edge_num, force_if_awkward?) ## ## Solve a single edge along the middle slice. If ## FORCE_IF_AWKWARD? is true, then this will force the ## performance of an additional seven move combination if the ## edge piece is already on the middle slice, but flipped the ## wrong way. Otherwise, it will just be skipped, in the hope ## that another solving combination will kick it out of there ## so that it can be positioned with fewer moves later. ## _self.solve_stage << write_string("Mid edge ", edge_num, %:, force_if_awkward?) adj_faces << {3, 4, 5, 6, 3} adj_face_num1 << adj_faces[edge_num] adj_face_num2 << adj_faces[edge_num + 1] color_1 << .cur_blocks[adj_face_num1][5] color_2 << .cur_blocks[adj_face_num2][5] (f1, f2) << _self.find_edge_piece(color_1, color_2) _if f1 = adj_face_num1 _then _if f2 = adj_face_num2 _then # Already correct. _return _elif f2 = _self.opposite_face[adj_face_num2] _then _self.int!rotate_face_with_animate(adj_face_num1, 2) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(adj_face_num1, 2) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(adj_face_num1, 2) _return _endif _endif _if f1 = 2 _then goal << edge_num + 2 current << adj_faces.index_of(f2) diff << (current - goal + 1) _mod 4 - 1 _self.int!rotate_face_with_animate(2, diff) _self.int!rotate_face_with_animate(adj_face_num1, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(adj_face_num1, 1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(adj_face_num2, 1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(adj_face_num2, -1) _return _elif f2 = 2 _then goal << edge_num - 1 current << adj_faces.index_of(f1) diff << (current - goal + 1) _mod 4 - 1 _self.int!rotate_face_with_animate(2, diff) _self.int!rotate_face_with_animate(adj_face_num2, 1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(adj_face_num2, -1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(adj_face_num1, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(adj_face_num1, 1) _return _endif # At this point, the block is awkwardly wedged in another mid # spot. Only work it if force_if_awkward? is set. _if _not force_if_awkward? _then _return _endif # Inelegantly ensure that this operates on the right-hand face. _if f1 > f2 _then _if f1 = 6 _andif f2 = 3 _then # Don't swap _else # Swap f1 << f2 ^<< f1 _endif _elif f1 = 3 _andif f2 = 6 _then f1 << f2 ^<< f1 _endif _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f2, -1) _self.int!solve_middle_edge(edge_num, force_if_awkward?) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_corners_1() ## ## First stage of solving bottom corners: put each in their ## proper place, but don't necessarily rotate them so that they ## are correctly oriented. ## _self.solve_stage << write_string("Bot corners 1") q << .cur_blocks[2][5] color_1 << .cur_blocks[5][5] color_2 << .cur_blocks[6][5] color_3 << .cur_blocks[3][5] color_4 << .cur_blocks[4][5] # Mathematically, the following isn't required, but it makes my # head hurt less trying to work it out: simply take corner 5 # and put it in the right place. corner_5 << (_allresults _self.find_corner_piece(q, color_1, color_4))[4] _self.int!rotate_face_with_animate(2, corner_5 - 5) cv1 << (_allresults _self.find_corner_piece(q, color_1, color_4))[4] # Always 5! cv2 << (_allresults _self.find_corner_piece(q, color_2, color_1))[4] cv3 << (_allresults _self.find_corner_piece(q, color_3, color_2))[4] cv4 << (_allresults _self.find_corner_piece(q, color_4, color_3))[4] _if cv2 = 6 _andif cv3 = 7 _andif cv4 = 8 _then # OK already. _elif cv2 = 6 _andif cv3 = 8 _andif cv4 = 7 _then _self.int!solve_bottom_corners_1a(6, 3) _self.int!rotate_face_with_animate(2, 2) _elif cv2 = 7 _andif cv3 = 6 _andif cv4 = 8 _then _self.int!solve_bottom_corners_1a(5, 6) _self.int!rotate_face_with_animate(2, 2) _elif cv2 = 7 _andif cv3 = 8 _andif cv4 = 6 _then _self.int!solve_bottom_corners_1a(4, 5) _self.int!rotate_face_with_animate(2, -1) _elif cv2 = 8 _andif cv3 = 6 _andif cv4 = 7 _then _self.int!solve_bottom_corners_1a(3, 4) _self.int!rotate_face_with_animate(2, 1) _elif cv2 = 8 _andif cv3 = 7 _andif cv4 = 6 _then _self.int!solve_bottom_corners_1a(3, 4, 2) _self.int!rotate_face_with_animate(2, -1) _endif _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_corners_1a(f1, f2, _optional flippage) ## ## Initiate a process that will either: ## - Cause the bottom two corners on face F2 to exchange ## positions (if FLIPPAGE is 1) ## - Cause the each of the two pairs of opposite corners on the ## bottom face to exchange positions (if FLIPPAGE is 2). ## Note that F1 must be the face immediately CCW of F2 when ## viewing the cube from above. ## _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(2, flippage.default(1)) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, 1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_corners_2() ## ## Second stage of solving bottom corners: orient the bottom ## four corners correctly. ## _self.solve_stage << write_string("Bot corners 2") q << .cur_blocks[2][5] num_ok << 0 cb << .cur_blocks _if cb[2][1] = q _then f1 << 4; f2 << 3; rh? << cb[5][9] = q; num_ok +<< 1 _endif _if cb[2][7] = q _then f1 << 5; f2 << 4; rh? << cb[6][9] = q; num_ok +<< 1 _endif _if cb[2][9] = q _then f1 << 6; f2 << 5; rh? << cb[3][9] = q; num_ok +<< 1 _endif _if cb[2][3] = q _then f1 << 3; f2 << 6; rh? << cb[4][9] = q; num_ok +<< 1 _endif _if num_ok = 4 _then # All done. _return _true _endif # The following enumerates all possible combinations of spins # of the bottom corners, although unless you're familiar with # Grandpa's patterns, this is far from obvious. _if num_ok = 1 _then # If exactly one corner is correct, then we're always one step # away. Just figure out if the pattern is "right-handed", or a # mirror image of it. _if rh? _then _self.int!solve_bottom_corners_2a(f1) _else _self.int!solve_bottom_corners_2b(f2) _endif _return _true _endif # Note that from here on, the patterns performed in # int!solve_bottom_corners_2a() and int!solve_bottom_corners_2b() # will have to be done multiple times. Returning false will # instruct the calling method to re-perform this method until # things are sorted out. _if num_ok = 2 _then # The following permutations deal with diagonals. _if cb[2][3] = q _andif cb[2][7] = q _then _if cb[3][7] = q _then _self.int!solve_bottom_corners_2a(6) _else _self.int!solve_bottom_corners_2b(5) _endif _return _false _endif _if cb[2][1] = q _andif cb[2][9] = q _then _if cb[4][7] = q _then _self.int!solve_bottom_corners_2a(3) _else _self.int!solve_bottom_corners_2b(6) _endif _return _false _endif # The following permutations deal with side-by-side. _if cb[3][7] = q _andif cb[3][9] = q _then _self.int!solve_bottom_corners_2a(6) _return _false _endif _if cb[4][7] = q _andif cb[4][9] = q _then _self.int!solve_bottom_corners_2a(3) _return _false _endif _if cb[5][7] = q _andif cb[5][9] = q _then _self.int!solve_bottom_corners_2a(4) _return _false _endif _if cb[6][7] = q _andif cb[6][9] = q _then _self.int!solve_bottom_corners_2a(5) _return _false _endif _if cb[3][7] = q _andif cb[5][9] = q _then _self.int!solve_bottom_corners_2a(6) _return _false _endif _if cb[4][7] = q _andif cb[6][9] = q _then _self.int!solve_bottom_corners_2a(3) _return _false _endif _if cb[5][7] = q _andif cb[3][9] = q _then _self.int!solve_bottom_corners_2a(4) _return _false _endif _if cb[6][7] = q _andif cb[4][9] = q _then _self.int!solve_bottom_corners_2a(5) _return _false _endif _endif _if num_ok = 0 _then _if cb[4][7] = q _andif cb[6][9] = q _then _self.int!solve_bottom_corners_2a(4) _return _false _endif _if cb[5][7] = q _andif cb[3][9] = q _then _self.int!solve_bottom_corners_2a(5) _return _false _endif _if cb[6][7] = q _andif cb[4][9] = q _then _self.int!solve_bottom_corners_2a(6) _return _false _endif _if cb[3][7] = q _andif cb[5][9] = q _then _self.int!solve_bottom_corners_2a(3) _return _false _endif _endif # Damn. write("Logic error in int!solve_bottom_corners_2()") _return _true _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_corners_2a(f1) ## ## This combination spins three of the bottom four corners ## (except for the one at the lower-right of face F1). ## _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(f1, 1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_corners_2b(f2) ## ## Mirror image of int!solve_bottom_corners_2a(). ## _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(f2, -1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_edges_1() ## ## First stage of solving bottom edges: put each edge in its ## proper spot, with regard to whether or not it is flipped ## correctly. ## _self.solve_stage << write_string("Bot edges 1") num_ok << 0 cb << .cur_blocks _if cb[5][5] = cb[5][8] _orif cb[5][5] = cb[2][2] _then num_ok +<< 1 f1 << 6 f2 << 4 f3 << 5 rh? << cb[4][5] = cb[6][8] _orif cb[4][5] = cb[2][4] _endif _if cb[6][5] = cb[6][8] _orif cb[6][5] = cb[2][4] _then num_ok +<< 1 f1 << 3 f2 << 5 f3 << 6 rh? << cb[5][5] = cb[3][8] _orif cb[5][5] = cb[2][8] _endif _if cb[3][5] = cb[3][8] _orif cb[3][5] = cb[2][8] _then num_ok +<< 1 f1 << 4 f2 << 6 f3 << 3 rh? << cb[6][5] = cb[4][8] _orif cb[6][5] = cb[2][6] _endif _if cb[4][5] = cb[4][8] _orif cb[4][5] = cb[2][6] _then num_ok +<< 1 f1 << 5 f2 << 3 f3 << 4 rh? << cb[3][5] = cb[5][8] _orif cb[3][5] = cb[2][2] _endif _if num_ok = 4 _then # All done. _return _endif _if num_ok = 1 _then _self.int!solve_bottom_edges_1a(f1, f2, f3, rh?) _return _endif # At this point, num_ok = 0. Pick any edge (although it would # be better to pick one that will result in the next correct # piece being right-side up), and repeat. _self.int!solve_bottom_edges_1a(6, 4, 5, _true) _self.int!solve_bottom_edges_1() _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_edges_1a(f1, f2, f3, rh?) ## ## Permute the bottom edge blocks on faces F1, F2, F3 so that ## each is moved one face to the right (if RH? is true) or ## left. ## _if rh? _then skip << 1 _else skip << -1 _endif _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, skip) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, skip) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_edges_2() ## ## Second stage of solving bottom edges: flip each one so that ## it is oriented the right way. ## _self.solve_stage << write_string("Bot edges 2") num_ok << 0 cb << .cur_blocks ok3? << ok4? << ok5? << ok6? << _false _if cb[2][5] = cb[2][2] _then ok5? << _true; num_ok +<< 1; _endif _if cb[2][5] = cb[2][4] _then ok6? << _true; num_ok +<< 1; _endif _if cb[2][5] = cb[2][8] _then ok3? << _true; num_ok +<< 1; _endif _if cb[2][5] = cb[2][6] _then ok4? << _true; num_ok +<< 1; _endif _if num_ok = 4 _then # All done. _return _endif _if num_ok = 0 _then _self.int!solve_bottom_edges_2a() _return _endif # At this point, num_ok = 2. Brute-force the various combinations. _if ok3? _andif ok4? _then _self.int!solve_bottom_edges_2b(5, 3, 4) _self.int!solve_bottom_edges_1a(4, 6, 3, _true) _elif ok4? _andif ok5? _then _self.int!solve_bottom_edges_2b(6, 4, 5) _self.int!solve_bottom_edges_1a(5, 3, 4, _true) _elif ok5? _andif ok6? _then _self.int!solve_bottom_edges_2b(3, 5, 6) _self.int!solve_bottom_edges_1a(6, 4, 5, _true) _elif ok6? _andif ok3? _then _self.int!solve_bottom_edges_2b(4, 6, 3) _self.int!solve_bottom_edges_1a(3, 5, 6, _true) _elif ok3? _andif ok5? _then _self.int!solve_bottom_edges_2c(5, 3, 4) _elif ok4? _andif ok6? _then _self.int!solve_bottom_edges_2c(6, 4, 5) _endif _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_edges_2a() ## ## Combination that causes all four bottom edges to flip. ## _self.solve_stage << write_string("Bot edges 2a") f1 << 6 f2 << 4 f3 << 5 _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 2) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 2) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 2) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, -1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_edges_2b(f1, f2, f3) ## ## Combination that does most of the work in causing two ## adjacent bottom edges to flip. ## _self.solve_stage << write_string("Bot edges 2b") _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, -1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, -1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 2) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _endmethod $ _pragma(classify_level=restricted) _method rubiks_cube.int!solve_bottom_edges_2c(f1, f2, f3) ## ## Combination that causes two opposite bottom edges to flip. ## _self.solve_stage << write_string("Bot edges 2c") _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 2) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 1) _self.int!rotate_face_with_animate(f1, -1) _self.int!rotate_face_with_animate(f2, 1) _self.int!rotate_face_with_animate(f3, 1) _self.int!rotate_face_with_animate(f1, 1) _self.int!rotate_face_with_animate(f2, -1) _self.int!rotate_face_with_animate(2, 2) _endmethod $