Categories: cubing

RSS - Atom - Subscribe via email

Cubing and Emacs: Checking out the competition

| cubing, emacs

There's an upcoming cubing competition. For the 3x3 event, the top 75% of the first round will get to advance to the second round. I wanted to know what our chances were of making it in. There's an unofficial REST API for the World Cube Association (WCA) results so I don't need to download and unpack megabytes of data.

Here's the basic code I used:

(let* ((comp-id "COMP-ID-GOES-HERE")
       (url (format "https://www.worldcubeassociation.org/competitions/%s/registrations" comp-id))
       (dom (with-temp-buffer (insert (plz 'get url)) (libxml-parse-html-region)))
       (data-dir (expand-file-name "data" (expand-file-name comp-id "~/proj/cubing/")))
       len
       list)
  (unless (file-directory-p data-dir)
    (make-directory data-dir t))
  (setq list (sort
              (seq-keep
               (lambda (o)
                 (let* ((id (replace-regexp-in-string "/persons/" "" (dom-attr o 'href)))
                        (file (expand-file-name (concat id ".json") data-dir))
                        best)
                   (unless (file-exists-p file)
                     (with-temp-file file
                       (insert
                        (plz 'get (format "https://raw.githubusercontent.com/robiningelbrecht/wca-rest-api/master/api/persons/%s.json" id)))))
                   (let-alist (with-temp-buffer
                                (insert-file-contents file)
                                (json-parse-buffer :object-type 'alist))
                     (setq best
                           (alist-get 'best
                                      (seq-find (lambda (o) (string= (alist-get 'eventId o) "333")) .rank.averages)))
                     (when best
                       (list .id .name (format "%.2f" (/ best 100.0)))))))
               (dom-by-tag (dom-by-id dom "competition-data") 'a))
              (lambda (a b)
                (< (string-to-number (elt a 2)) (string-to-number (elt b 2))))))
  (setq len (length list))
  (seq-map-indexed
   (lambda (o i)
     (cons (format "%d" (/ (* 100.0 i) len))
           o))
   list))

It makes a table with percentile, ID, name, and average time for 3x3. Then I can find out where the 75% mark and see if we can make it in. I think I'll be a bit too slow, but the kiddo might be able to make it to the second round. Could be fun.

Posting here in case someone else might find it handy someday!

Using rubik.el to make SVG last-layer diagrams from algorithms

| cubing, emacs, org

So I checked out emacs-cube, but I had a hard time figuring out how to work with the data model without getting into all the rendering because it figures "left" and "right" based on camera position. rubik.el seemed like an easier starting point. As far as I can tell, the rubik-cube-state local variable is an array with the faces specified as 6 groups of 9 integers in this order: top, front, right, back, left, bottom, with cells specified from left to right, top to bottom.

First, I wanted to recolour rubik so that it matched the setup of the Roofpig JS library I'm using for animations.

(defconst my-cubing-rubik-faces "YRGOBW")
;; make it match roofpig's default setup with yellow on top and red in front
(defconst rubik-faces [rubik-yellow
                       rubik-red
                       rubik-green
                       rubik-orange
                       rubik-blue
                       rubik-white])

Here are some functions to apply an algorithm (or actually, the inverse of the algorithm, which is useful for exploring a PLL case):

(defun my-cubing-normalize (alg)
  "Remove parentheses and clean up spaces in ALG."
  (string-trim
   (replace-regexp-in-string "[() ]+" " " alg)))

(defun my-cubing-reverse-alg (alg)
  "Reverse the given ALG."
  (mapconcat
   (lambda (step)
     (if (string-match "\\`\\([rludfsbRLUDFSBxyz]\\)\\(['i]\\)?\\'" step)
         (concat (match-string 1 step)
                 (if (match-string 2 step)
                     ""
                   "'"))
       step))
   (reverse
    (split-string (my-cubing-normalize alg) " "))
   " "))

(defun my-cubing-rubik-alg (alg)
  "Apply the reversed ALG to a solved cube.
Return the rubik.el cube state."
  (let ((reversed (my-cubing-reverse-alg alg)))
    (seq-reduce
     (lambda (cube o)
       (when (intern (format "rubik-%s"
                             (replace-regexp-in-string "'" "i" o)))
         (unless (string= o "")
           (rubik-apply-transformation
            cube
            (symbol-value
             (intern
              (format "rubik-%s"
                      (replace-regexp-in-string "'" "i" o)))))))
       cube)
     (split-string reversed " ")
     (rubik-make-initial-cube))))

Then I got the strings specifying the side colours and the top colours in the format that I needed for the SVG diagrams. I'm optimistically using number-sequence here instead of hard-coding the numbers so that I can figure out how to extend the idea for 4x4 someday.

(defun my-cubing-rubik-top-face-strings (&optional cube)
  ;; edges starting from back left
  (let ((cube (or cube rubik-cube-state)))
    (list
     (mapconcat
      (lambda (i)
        (char-to-string (elt my-cubing-rubik-faces (aref cube i))))
      (append
       (reverse (number-sequence (* 3 9) (+ 2 (* 3 9))))
       (reverse (number-sequence (* 2 9) (+ 2 (* 2 9))))
       (reverse (number-sequence (* 1 9) (+ 2 (* 1 9))))
       (reverse (number-sequence (* 4 9) (+ 2 (* 4 9))))))
     (mapconcat
      (lambda (i)
        (char-to-string (elt my-cubing-rubik-faces (aref cube i))))
      (number-sequence 0 8)))))

Then theoretically, it can make a diagram like this:

(defun my-cubing-rubik-last-layer-with-sides-from-alg (alg &optional arrows)
  (apply 'my-cubing-last-layer-with-sides
         (append
          (my-cubing-rubik-top-face-strings (my-cubing-rubik-alg alg))
          (list
           arrows))))

So I can invoke it with:

(my-cubing-rubik-last-layer-with-sides-from-alg
 "R U R' F' R U R' U' R' F R2 U' R' U'"
 '((1 7 t) (2 8 t)))
last-layer.svg

It's also nice to be able to interactively step through the algorithm. I prefer a more compact view of the undo/redo state.

;; Override undo information
(defun rubik-display-undo ()
  "Insert undo information at point."
  (cl-loop with line-str = "\nUndo: "
           for cmd in (reverse (cdr rubik-cube-undo))
           for i = 1 then (1+ i)
           do (progn
                (setq line-str (concat line-str (format "%s " (get cmd 'name))))
                (when (> (length line-str) fill-column)
                  (insert line-str)
                  (setq line-str (concat "\n" (make-string 6 ?\s)))))
           finally (insert line-str)))

;; Override redo information
(defun rubik-display-redo ()
  "Insert redo information at point."
  (cl-loop with line-str = "\nRedo: "
           for cmd in (cdr rubik-cube-redo)
           for i = 1 then (1+ i)
           do (progn
                (setq line-str (concat line-str (format "%s " (get cmd 'name))))
                (when (> (length line-str) fill-column)
                  (insert line-str)
                  (setq line-str (concat "\n" (make-string 6 ?\s)))))
           finally (insert line-str)))
  
(defun my-cubing-convert-alg-to-rubik-commands (alg)
  (mapcar
   (lambda (step)
     (intern
      (format "rubik-%s-command"
              (replace-regexp-in-string "'" "i" step))))
   (split-string (my-cubing-normalize alg) " ")))

(rubik-define-commands
  rubik-U "U" rubik-U2 "U2" rubik-Ui "U'"
  rubik-F "F" rubik-F2 "F2" rubik-Fi "F'"
  rubik-R "R" rubik-R2 "R2" rubik-Ri "R'"
  rubik-L "L" rubik-L2 "L" rubik-Li "L'"
  rubik-B "B" rubik-B2 "B" rubik-Bi "B'"
  rubik-D "D" rubik-D2 "D" rubik-Di "D'"
  rubik-x "x" rubik-x2 "x" rubik-xi "x'"
  rubik-y "y" rubik-y2 "y" rubik-yi "y'"
  rubik-z "z" rubik-z2 "z2" rubik-zi "z'")

(defun my-cubing-rubik-set-to-alg (alg)
  (interactive "MAlg: ")
  (rubik)
  (fit-window-to-buffer)
  (setq rubik-cube-state (my-cubing-rubik-alg alg))
  (setq rubik-cube-redo (append (list 'redo)
                                (my-cubing-convert-alg-to-rubik-commands
                                 alg)))
  (setq rubik-cube-undo '(undo))
  (rubik-draw-all)
  (display-buffer (current-buffer)))

And now I can combine all those pieces together in a custom Org link type that will allow me to interactively step through an algorithm if I open it within Emacs and that will export to a diagram and an animation.

(org-link-set-parameters
 "3x3"
 :follow #'my-cubing-rubik-open
 :export #'my-cubing-rubik-export)

(defun my-cubing-rubik-open (path &optional _)
  (my-cubing-rubik-set-to-alg (if (string-match "^\\(.*\\)\\?\\(.*\\)$" path)
                                  (match-string 1 path)
                                path))) 
  
(defun my-cubing-rubik-export (path _ format _)
  "Export PATH to FORMAT."
  (let (alg arrows params)
    (setq alg path)
    (when (string-match "^\\(.*\\)\\?\\(.*\\)$" path)
      (setq alg (match-string 1 path)
            params (org-protocol-convert-query-to-plist (match-string 2 path))
            arrows
            (mapcar (lambda (entry)
                      (mapcar 'string-to-number
                               (split-string entry "-"))) 
                    (split-string
                     (plist-get params :arrows) ","))))
    (concat
     (my-cubing-rubik-last-layer-with-sides-from-alg
      alg
      arrows)
     (format "<div class=\"roofpig\" data-config=\"base=PLL|alg=%s\"></div>"
             (my-cubing-normalize alg)))))

Let's try that with this F-perm, which I haven't memorized yet:

[[3x3:(R' U' F')(R U R' U')(R' F R2 U')(R' U' R U)(R' U R)?arrows=1-7,7-1,2-8,8-2]]

At some point, I'd like to change the display for rubik.el so that it uses SVGs. (Or the OpenGL hacks in https://github.com/Jimx-/emacs-gl, but that might be beyond my current ability.) In the meantime, this might be fun.

In rubik.el, M-r redoes a move and M-u undoes it. Here's what it looks like with my tweaked interface:

output-2023-02-09-15:25:42.gif
Figure 1: Animated GIF of rubik.el stepping through an F-perm
View org source for this post

Supporting A+'s cubing journey so far

| cubing

When I was in high school, I had friends who cubed, but I didn't get into it much myself. I did keep a Pyraminx around as a reminder of those days, though. In September 2021, A+ got curious about the Pyraminx, so we decided to order a bunch of 3x3 and 2x2 cubes. We printed out the 2x2 tutorial and helped her learn the notation. The 2x2 cube skips all the middle sections, so it's a good way to practise. Here's how her learning journey has gone so far.

2x2: first layer

She learned to keep the white squares on the bottom, then line up the next piece with a white square on top of the space she wanted it to go down and repeat the 4-move sequence (R U R' U') until it was in the right place and facing the right way. She learned the 4-move sequence by doing it slowly while we showed her on a different cube, and then she was able to practise it on her own easily, with the quick pay-off of being able to complete a layer.

2x2: yellow squares in the second layer

The next step is to get all the yellow pieces to face up. For this, I think we blended the 2x2 guide from the actual Rubik's company with a print-out that showed the different ways to hold it. She liked that because it was easy to solve by repeating just one sequence (fish: R U R' U R U2 R' - also known as Sune). The solution guide suggested the chant "Up, Over, Down, Over, Up, Over, Over, Down," but I think she quickly moved to just knowing it in her fingers. (Now that I'm looking things up for this post, I've found a nice story about a knight and a dragon in Rubik's Cube 2x2 Storytelling Method for Kids.)

Jumping to the 3x3: fish

The fish algorithm is also very useful for solving 3x3s (and it looks more like a fish there), so around this time, she wanted me to make the fish pattern for her so that she could "solve" it. One time we did this more than a hundred times in a row. Here it really helped to have several cubes, so I could set up one cube while she solved another.

2x2: Run to me fast

Back to the 2x2. She wanted to be able to solve the whole thing herself, so she memorized the "Run to me fast" sequence from the Rubik's cube 2x2 solution guide.

  • R' Run to me
  • F Fast
  • R' Run to me
  • B2 Back back
  • R Run away
  • F' Fast away
  • R' Run to me
  • B2 Back back

That was, again, an algorithm that could easily be translated to the 3x3 cube to get the corners of the last layer all matched up.

3x3: swapping the yellow middles

The last thing A+ needed to solve the 3x3 cube starting from the fish was to be able to swap the last unsolved pieces. She memorized the algorithm from the Rubik's solution guide for the 3x3 (F2 U L R' F2 L' R U F2), probably by thinking of the pattern of bringing both sides down and up.

Then she started snatching partially-solved cubes out of our hands as soon as she recognized something she could handle. Again it helped to have several cubes on the go, and it helped that both W- and I were learning along with her. (I think I was learning more about patience than about cubing…)

3x3: working backwards from there

Eventually she wanted to be able to solve more of the cube for herself. She learned how to put the yellow pieces into a cross, then learned how to put the middle edges into the second layer. Somewhere along the way, she started using the daisy method to make the white cross on the first layer, using her old favourite R U R' U' to put the white corners into the right places.

Going forward

She's been improving on her own. After watching videos about doing the white cross on the bottom, she stopped using the daisy and started doing the white cross on the bottom too. I think she's been working on F2L, pairing up the corner and the middle before inserting it. I've been learning some PLL algorithms, so she's been working on them too. Tucked under a blanket, we swap cubes back and forth, setting up and solving the same perms. Sometimes it's difficult for her to choose the right speed to practise something. She gets the algorithm right when she does it slowly, but mixes up a step if she tries to move quickly too soon, and then she gets frustrated. But she's learning to slow down and try again, and that's fantastic.

She doesn't usually confuse L and R, but she still asks me about clockwise and counterclockwise sometimes. I made it easier to add algorithm animations to the little website I keep for her on our network. Hmm, maybe I could try some more mnemonics to help her remember which way clockwise is using fingertricks and letter sounds: Four balls (front right, back left)? Leave them to me (left) right away: up right, down low (left).

It's fun to learn with her. I've been challenging myself to plan more of my crosses and pairs by closing my eyes in between sets of moves. She sometimes does the same, calling it "part-blind." She shows me fingertricks. She's faster than I am and will probably reach the 20-second PB or 30-second average before I reach the 30-second PB or 40-second average. I think there's something really interesting about having your hands learn something that your brain might still be a little slow to grasp. I'm glad this tickled her curiosity (and ours).

Using Org Babel to learn Rubik's cube algorithms

| emacs, cubing, org

A+ has started learning Rubik's cube algorithms for permutation of the last layer (PLL) algorithms for the Rubik's cube. To help her focus on just a few at a time instead of getting distracted by the long list in the Cubeskills PLL PDF, I made a page that listed the algorithms that she was working on so that I could export it with ox-hugo to the mini-site I made for her interests.

She sometimes gets a little confused about clockwise and counter-clockwise, so I used Roofpig to add an animated view of the algorithm that she can step through. I wanted to make it easier for her to follow the algorithm without constantly moving her hands from the cube to the tablet or looking up and down all the time, but I didn't want to recompile the source just yet. I used the roofpig_and_three.min.js file and hacked speech synthesis into it by modifying the object prototype. For example, here's the Org source for adding a Jb perm:

#+begin_export html
<div class="roofpig" data-config="base=PLL|alg=R U R' F' R U R' U' R' F R2 U' R' U'"></div>
#+end_export

and here's what it looks like. Might only look nice on my website, and I added speech synthesis so you may want to mute it if you need to be quiet.

Code for setting up Roofpig with speech synthesis
<!--  -*- mode: web -*- -->
<style>
 .roofpig { max-width: 400px; margin-bottom: 80px; }
 </style>
 <script>

  function waitToAddSpeech() {
    if (window.cubesSpeakMoves || !window.speechSynthesis) return;
    if (window.CubeAnimation) {
      window.cubesSpeakMoves = true;
        addSpeechToCubeAnimations();
      } else {
        setTimeout(setUpCubes, 300);
      }
  }
  
  function setUpCubes() {
    if (!document.querySelector('script.roofpig')) {
      var script = document.createElement('script');
      script.setAttribute('src', '/blog/2023/02/using-org-babel-to-learn-rubik-s-cube-algorithms/roofpig_and_three.min.js');
      script.classList.add('roofpig');
      document.head.appendChild(script);
      waitToAddSpeech();
    }
  }
  
  function addSpeechToCubeAnimations() {
   if (!window.CubeAnimation || !window.CubeAnimation['by_id'] || !window.CubeAnimation['by_id'][1]) return;
   var cachedFunc = Object.getPrototypeOf(CubeAnimation['by_id'][1].dom).alg_changed;
   Object.getPrototypeOf(CubeAnimation['by_id'][1].dom).alg_changed = function() {
     if (arguments[4].past.length > lastNoted.length) {
       let moves = arguments[4].past.split(' ');
       let lastMove = moves[moves.length - 1];
       // is it lower-case? speak lowercase explicitly
       if (lastMove.match(/[rludbf]/)) {
         lastMove = 'lower ' + lastMove;
       } else { // avoid awkward-sounding moves like "capital R"
         lastMove = lastMove.toLowerCase();
       }
       lastMove = lastMove.replace(/'/, ' prime');
      window.speechSynthesis.speak(new SpeechSynthesisUtterance(lastMove));
     } else {
       console.log('going backwards');
     }
     lastNoted = arguments[4].past;
     return cachedFunc.apply(this, arguments);
   }
 }
 ROOFPIG_CONF_F2L = "solved=U*|hover=none|colored=U-|flags=canvas,showalg|speed=1000";
 ROOFPIG_CONF_PLL = "solved=U-|hover=near|colored=U*|flags=canvas,showalg|speed=1000";
 var lastNoted = '';
window.addEventListener('load', setUpCubes);
</script>

I also wanted to include diagrams to make it easier for her to choose the right algorithm and position the cube the right way at the beginning, but I didn't want to fuss around with lots of screenshots and little files. It turns out you can define arrows in SVG pretty easily, so I wrote some Emacs Lisp functions to generate those types of diagrams. First I started with just the arrows.

(my-cubing-last-layer-arrows '((2 8 t) (5 7 t)))
arrows.svg

For practising recognition, I wanted to also include the colors on top and on the sides:

(my-cubing-last-layer-with-sides "OOGRROGGRBBB" "YYYYYYYYY" '((2 8 t) (5 7 t)))
colors.svg

Emacs Lisp functions for cubing diagrams
;; Start of cubing code
(defun my-cubing-pos (size n i)
  (list
   (* (/ size n) (% i n))
   (* (/ size n) (/ i n))))
  
(defun my-cubing-last-layer-arrows (arrows)
  "Draw ARROWS.
Arrows are defined as a list of lists of the form
((from to) (from to t) ...). Ex: '(my-cubing-last-layer-arrows '((3 1 t) (2 8 t)))
Cells are numbered from left to right, top to bottom, with the top left box being 0.
"
  (let* ((size 99)
         (n 3)
         (arrow-color "#000")
         (svg (svg-create size size)))
    (svg--append
     svg
     (dom-node
      'defs
      nil
      (dom-node
       'marker
       '((id . "arrowhead")
         (markerWidth . "10")
         (markerHeight . "7")
         (refX . "0")
         (refY . "3.5")
         (orient . "auto-start-reverse"))
       (dom-node
        'polygon
        `((fill . ,arrow-color)
          (points . "0 0, 4 3.5, 0 7")))
       )))
    (dotimes (i (* n n))
      (let ((pos (my-cubing-pos size n i)))
        (svg-rectangle
         svg
         (car pos)
         (cadr pos)
         (/ size n)
         (/ size n)
         :fill "#fff"
         :stroke-width 1
         :stroke "#666")))
    (dolist (arrow arrows)
      (let ((from (car arrow))
            (to (cadr arrow)))
        (apply 'svg-line
               (append
                (list svg)
                (mapcar (lambda (o) (+ o (/ size (* 2 n))))
                        (my-cubing-pos size n from))
                (mapcar (lambda (o) (+ o (/ size (* 2 n))))
                        (my-cubing-pos size n to))
                (list
                 :stroke-width 2
                 :stroke arrow-color
                 :marker-start (if (elt arrow 2) "url(#arrowhead)")
                 :marker-end "url(#arrowhead)")))))
    (with-temp-buffer
      (svg-print svg)
      (buffer-string))))

(defvar my-cubing-colors '((?R  . "#ff0000")
                           (?G  . "#00ff00")
                           (?B  . "#0000ff")
                           (?O  . "#ed7117")
                           (?Y  . "#ffff00")
                           (?W  . "#ffffff")
                           (?\? . "#666666")))

(defun my-cubing-last-layer-with-sides (sides top arrows)
  "Draw a diagram of the top of the cube.
The style is similar to https://www.cubeskills.com/uploads/pdf/tutorials/pll-algorithms.pdf .
SIDES is a string specifying colors going clockwise from the back-left side.
TOP is a string specifying colors going from left to right, top to bottom.
Arrows are defined as a list of lists of the form ((from to) (from to t) ...).
Cells are numbered from left to right, top to bottom, with the top left box being 0.
Ex: (my-cubing-last-layer-with-sides \"ORRBOOGGGRBB\" \"YYYYYYYYY\" '((3 1 t) (2 8 t)))
"
  (let* ((size 99)
         (n 3)
         (side-size 10)
         (cell-size (/ (- size (* 2 side-size)) n))
         (arrow-color "#000")
         (svg (svg-create size size)))
    (svg--append
     svg
     (dom-node
      'defs
      nil
      (dom-node
       'marker
       '((id . "arrowhead")
         (markerWidth . "10")
         (markerHeight . "7")
         (refX . "0")
         (refY . "3.5")
         (orient . "auto-start-reverse"))
       (dom-node
        'polygon
        `((fill . ,arrow-color)
          (points . "0 0, 4 3.5, 0 7"))))))
    ;; Draw the sides. It's a string of colors going clockwise from back left
    (when sides
      (dotimes (i (* n 4))
        (apply 'svg-rectangle
               (append
                (list svg)
                (pcase (/ i n)
                  (0 (list (+ (* (% i n) cell-size) side-size)
                           0
                           cell-size
                           side-size))
                  (1 (list (+ side-size (* n cell-size))
                           (+ (* (% i n) cell-size) side-size)
                           side-size
                           cell-size))
                  (2 (list (+ (* (- n (% i n) 1) cell-size) side-size)
                           (+ (* n cell-size) side-size)
                           cell-size
                           side-size))
                  (3 (list 0
                           (+ (* (- n (% i n) 1) cell-size) side-size)
                           side-size
                           cell-size)))
                (list
                 :stroke-width 1
                 :stroke "#666"
                 :fill (assoc-default (elt sides i)
                                      my-cubing-colors
                                      'eq
                                      (assoc-default ?\? my-cubing-colors)))))))
    ;; Draw the top face specified by a string of colors going from left to right, top to bottom
    (dotimes (i (* n n))
      (let ((pos (my-cubing-pos (* cell-size n) n i)))
        (svg-rectangle
         svg
         (+ side-size (car pos))
         (+ side-size (cadr pos))
         cell-size
         cell-size
         :fill (if top
                   (assoc-default (elt top i) my-cubing-colors
                                  'eq
                                  (assoc-default ?\? my-cubing-colors))
                 (assoc-default ?\? my-cubing-colors))
         :stroke-width 1
         :stroke "#666")))
    ;; Draw the arrows
    (dolist (arrow arrows)
      (let ((from (car arrow))
            (to (cadr arrow)))                  
        (apply 'svg-line
               (append
                (list svg)
                (mapcar (lambda (o) (+ side-size o (/ cell-size 2)))
                        (my-cubing-pos (* n cell-size) n from))
                (mapcar (lambda (o) (+ side-size o (/ cell-size 2)))
                        (my-cubing-pos (* n cell-size) n to))
                (list
                 :stroke-width 2
                 :stroke arrow-color
                 :opacity 0.5
                 :marker-start (if (elt arrow 2) "url(#arrowhead)")
                 :marker-end "url(#arrowhead)")))))
    (with-temp-buffer
      (svg-print svg)
      (buffer-string))))
;; end of cubing code

I'll probably need to tweak the arrows when we eventually get to the G perms, but we're still a long way off. And it would probably be pretty cool to be able to generate the colours by going backwards from the algorithm, maybe building on top of emacs-cube, so that I can write my own notes about recognizing the in-between steps and recovering from the typical mistakes we make. (That wasn't around the last time I wrote about Emacs and cubing. Thanks to Akib for making and sharing it!) I'm curious about this LaTeX approach, too, but that can wait for another day.

View org source for this post

Turns out the Rubik's cube is just right for this stage with A-

| parenting, fun, cubing

I spend a lot of time waiting for A-. Sometimes I'm waiting for her to finish reading a book or watching a video. Sometimes it takes her forever to get to bed. She can sometimes amuse herself independently, but she often still wants me around somewhere in the room. Someday she won't, so in the meantime, I wait. I can't be on my phone or laptop during times like that, because then she'll want screentime too. Sometimes I tidy, sometimes I read, sometimes I write.

It turns out that learning to solve the Rubik's cube is an interest that slots neatly into my life with A-. We picked it up recently because A- was interested in my old Pyraminx.

Our order from Cubing Out Loud turned out to be a pretty good introduction to the world of speedcubing:

  • a MoYu RS3 M 2020, a magnetized 3x3x3 cube for $10 CAD
  • a YuXin Little Magic 3x3x3 M, another magnetized 3x3x3 cube for $9 CAD
  • a YJ MGC 2x2x2 M, a magnetized 2x2x2 cube for $11 CAD
  • and some lubricant

The speed cubes were way smoother than the Rubik's cubes I remember from high school and university. The 2x2x2 cube was great for helping A- practise simple algorithms and get that feeling of success. She quickly graduated to the 3x3x3 cubes. She loves solving it from the fish position, so W- and I solve the first two layers, and then she solves it from there. I was pleasantly surprised at how quickly she picked up the beginner algorithms that we showed her, and she took great delight in learning finger tricks and being able to do the Sune move in three seconds. I can do the Anti-Sune just about as fast as she can do the Sune, so we trade cubes back and forth. Sometimes I mix things up so that she has to permute the last layer, too. She's gradually branching out to more algorithms, and will sometimes even take on solving it from a full scramble.

Cubing seems to be a good way for her to practise distinguishing left from right, clockwise from counter-clockwise. We talk about averages, minimums, and moves per second. She likes taking apart our cubes, tweaked the tension, and lubing them. (Reassembling them is a job for grown-ups, apparently.) She likes playing around with different patterns. It spread into her pretend play too. She loves watching JPerm and parroting his lines.

For my part, I enjoy slowly learning different algorithms and feeling things start to click. I can usually solve the 3x3 in under two minutes now (nothing remarkable; most beginners get there), and have lately been averaging around 1:30. I'm getting the hang of solving colour-neutral crosses by moving edges around and ignoring centers, and of solving the first two layers together. I like practising algorithms while keeping an eye on her at the playground. I'm getting better at smiling even when A- snatches the partially-solved cube I was working on with the timer running. I'm not aiming for any records, anyway.

Since W- has gotten into cubing as well, we have determined that we need more cubes. Also, to save our phones from A-'s rather enthusiastic timer use, a StackMat timer and a mat are probably a good idea. Speed Cube Shop had a wider selection than Cubing Out Loud, so we ordered a few cubes and accessories from there. She insisted on getting a Gan cube with some of her savings. Hey, at least these highly-engineered bits of plastic generally stay in one piece, don't get scattered all over the floor, don't need to be sorted into various bins, and don't get stepped on. (I'm kidding, LEGO, we still like you.)

In terms of Android apps, I like Nano Timer. It's free and allows me to keep times in different categories, like a regular solve, A- starting from the fish, or co-op. There's even a multi-step timer for breaking down things like CFOP. A- likes Finger Timer because it looks like a StackMat timer.

Naturally, I'm getting the urge to do something about Rubik's cubes and Emacs. A timer that will let me quickly reassign my current time from "Regular 3x3 solve" to "Solved until A- grabbed the fish"? (It'll have to work on my phone - maybe Termux or SSH, or a web-based approach…) An Org Babel block type for visualizing cubes and moves so that I can make my own notes and blog posts? An SVG version of that text-based Rubik's cube that someone wrote for Emacs? A scramble generator that lets me pick the type of scramble I want and then uses the Kociemba algorithm to generate the steps for scrambling it? Anyway, it'll have to wait until I get a few things off my plate, like EmacsConf and the usual year-end paperwork.

In the meantime, I have things to learn while I wait. I think I'd like to get to the point of being able to do the cross blind. I'm also working on memorizing the rest of 4LLL, and then full OLL/PLL after that.. Anyway, so that's what we've been up to in the evenings while waiting for A- to go to bed.