MoMonkey 的个人资料Monkey Coder日志列表网络 工具 帮助

日志


7月30日

The Feniello Keyboard

Some years ago (while suffering a little RSI) I learned to touch type on a Dvorak keyboard; difficult to break old habits, but much, much more comfortable feeling. Seriously, I would recommend it! Then a year or so ago I was very bored one day and decided to invent my own keyboard (yes, I am that much of a dork).

Beyond ETAOIN SHRDLU

I ran a key logger on my dev machine for a couple of weeks and came up with key-pair-timings. For example, after pressing key ‘A’, how long does it take to press key ‘B’; and do that for every combination of keys. Here’s a heat map of exactly that:

A

Darker red is slower. You can see that after pressing ‘A’ (with my pinky) it’s very slow to follow up with another pinky-key. Quickest by far is to follow up with an opposite-handed home-row key. Well of course! The results were pretty much as expected. For example, here’s after pressing ‘L’:

L

Look at the L-K pair; very fast; I suspect because you can kind of roll your fingers. Just imagine if T-H-E (the most common English word) was on the L-K-J keys. You could type it almost as a single motion! Optimizing for “the” is not necessarily best though.

Optimized For Me

I optimized for the sequences of keys that I actually type. Lots of email. Lots of C# and F# code. Lots of technical documentation. I used a simulated annealing type algorithm to come up with a keyboard layout that was optimized for time; given key timings and sequences based on my personal actual usage over two weeks. Here’s the result:

Feniello 

The punctuation keys aren’t placed. The algorithm was coming up with totally bizarre layouts that were just way to hard to use!

Notice there are two ‘E’s! One above each middle finger. Genetic algorithms are so strange; coming up with things I would never have thought of! In practice though, I found it actually difficult to use the ‘correct’ E-key while typing; I’d instead favor one or the other regardless of the follow-up key.

I’m Not Dork Enough…

I don’t actually use the “Feniello” keyboard layout. Some day when I’m bored enough again…

What I really use when writing code is:

supercoder

:-)

7月17日

Rubik's Cube Solver

A while back, I had some fun making a Rubik's cube solver in F#. I was planning to make a "pluggable algorithm" mechanism so that I could have it solve using other human-style systems such as Petrus, Fridrich, etc., but right now it just uses a vanilla layer-by-layer system. I've lost interest in this hobby-project so now just posting as is:
 
The UI
 
The UI is a bit ugly (nice 3D cube would be good) but works. It's an "unfolded" projection of the cube faces:
 
Reset
You can flip the left, right, up, down, front, and back faces by pressing L, R, U, D, F or B keys respectively and you can rotate the whole cube on it's X, Y or Z axis with those keys or with the arrow keys (X/Y axis only). You can also mix it up with M and reset it with Spacebar.
For example, U R D L produces:
URDL
Or, from the reset position, U U D D L L R R F F B B produces the nice pattern:
UUDDLLRRFFBB
The real purpose though was to make a solver. Each time you press S, it manipulates the cube a step closer to being solved. The interesting thing (to my anyway) is that the solving algorithm is a single function that takes a single argument of a cube in a particular state and returns a new cube that's been manipulated a bit. The process of solving is just to iterate this function. The only state in the system is the state of the cube being displayed; literally you will see in the code that there is a single cube ref and no other mutables at all.
 
The Code
 
The Cube is represented as a 3x3x3 array of Cublets, each of which have an up/down/left/right/front/back Sticker:
 
#light
 
open System.Drawing
open System.Windows.Forms
 
type Sticker = Red | Orange | Yellow | Green | Blue | White
 
Cublets just have the six stickers and methods for manipulating their orientation:
 
// The naming conventions for all the transforms (e.g X,
// X2, X', Y, Z, F, U, D, D', L2, etc.)
// come from http://www.cubewhiz.com/notation.html
 
type Cublet = class
    val up    : Sticker; val down  : Sticker
    val left  : Sticker; val right : Sticker
    val front : Sticker; val back  : Sticker
    new(u, d, l, r, f, b) = { up = u; down = d; left = l; right = r; front = f; back = b }
    new() = { up    = Sticker.Red;    down  = Sticker.Orange;
              left  = Sticker.Yellow; right = Sticker.White;
              front = Sticker.Blue;   back  = Sticker.Green }
    member c.X  = new Cublet(c.front, c.back, c.left, c.right, c.down, c.up) // back on X-axis
    member c.X2 = c.X.X // 180 on X-axis
    member c.X' = c.X2.X // inverse - forward on X-axis (same as back 3)
    member c.Y  = new Cublet(c.up, c.down, c.front, c.back, c.right, c.left) // left on Y-axis
    member c.Y2 = c.Y.Y // 180 on Y-axis
    member c.Y' = c.Y2.Y // inverse - right on Y-axis (same as left 3)
    member c.Z  = c.X.Y.X' // clockwise on Z-axis (same as X, Y, X')
    member c.Z2 = c.Z.Z // 180 on Z-axis
    member c.Z' = c.Z2.Z // inverse - counter clockwise on Z-axis (same as clockwise 3)
end
 
Finally, the Cube is a 3x3x3 array of Cublets with methods for manipulating sets of them (rotating slices or changing orientation of the whole cube):
 
let s = 3 // Cube dimentions
let a = Array3.init s s s
 
type Cube = class
    val cublets : Cublet [,,]
    new(c) = { cublets = c }
    new() = { cublets = a (fun _ _ _ -> new Cublet()) }
    member c.X = new Cube(a (fun x y z -> c.cublets.[x, s - z - 1, y].X)) // back on X-axis
    member c.X2 = c.X.X // 180 on X-axis
    member c.X' = c.X.X.X // inverse - forward on X-axis (same as back 3)
    member c.Y = new Cube(a (fun x y z -> c.cublets.[s - z - 1, y, x].Y)) // left on Y-axis
    member c.Y2 = c.Y.Y // 180 on Y-axis
    member c.Y' = c.Y2.Y // inverse - right on Y-axis (same as left 3)
    member c.Z  = c.X.Y.X' // clockwise on Z-axis (same as X, Y, X')
    member c.Z2 = c.Z.Z // 180 on Z-axis
    member c.Z' = c.Z2.Z // inverse - counter clockwise on Z-axis (same as clockwise 3)
    member c.F = new Cube(a (fun x y z -> if z = 0 then c.cublets.[y, s - x - 1, z].Z else c.cublets.[x, y, z]) )
    member c.F2 = c.F.F
    member c.F' = c.F2.F
    member c.B = c.Y2.F.Y2 // everything defined in terms of F, X and Y!
    member c.B2 = c.B.B
    member c.B' = c.B2.B
    member c.L = c.Y'.F.Y
    member c.L2 = c.L.L
    member c.L' = c.L2.L
    member c.R = c.Y.F.Y'
    member c.R2 = c.R.R
    member c.R' = c.R2.R
    member c.U = c.X'.F.X
    member c.U2 = c.U.U
    member c.U' = c.U2.U
    member c.D = c.X.F.X'
    member c.D2 = c.D.D
    member c.D' = c.D2.D
end 
 
The solver is a giant beast! The cool thing about it is that you just give it a Cube (in any state) and it gives you one back, having done some manipulations. It reports to the console what it's "thinking" as it goes.
Here's a sample solve (showing completion of each phase; it actually took ~80 steps pressing 'S' key):
 
Mixed  Mixed
Phase1 Phase 1 - White cross
Phase2 Phase 2 - Bottom layer corners
Phase3 Phase 3 - Middle layer edges
Phase4 Phase 4 - Yellow cross on top
Phase5 Phase 5 - Yellow top complete
Phase6 Phase 6 - Top corners aligned
Solved  Solved!
Report
 
The code is one giant "solve" function:
 
[Dang it! Silly MSN Spaces won't let me paste so much in with color highlighting... Confused]
 
let solve (c : Cube) =
    let furc = c.cublets.[2, 0, 0].front
    let ufrc = c.cublets.[2, 0, 0].up
    let rufc = c.cublets.[2, 0, 0].right
    let fdrc = c.cublets.[2, 2, 0].front
    let dfrc = c.cublets.[2, 2, 0].down
    let rdfc = c.cublets.[2, 2, 0].right
    let dfe = c.cublets.[1, 2, 0].down
    let fde = c.cublets.[1, 2, 0].front
    let dbe = c.cublets.[1, 2, 2].down
    let dre = c.cublets.[2, 2, 1].down
    let bde = c.cublets.[1, 2, 2].back
    let dle = c.cublets.[0, 2, 1].down
    let ule = c.cublets.[0, 0, 1].up
    let ure = c.cublets.[2, 0, 1].up
    let ufe = c.cublets.[1, 0, 0].up
    let ube = c.cublets.[1, 0, 2].up
    let ublc = c.cublets.[0, 0, 2].up
    let ubrc = c.cublets.[2, 0, 2].up
    let uflc = c.cublets.[0, 0, 0].up
    let ufrc = c.cublets.[2, 0, 0].up
    let bulc = c.cublets.[0, 0, 2].back
    let burc = c.cublets.[2, 0, 2].back
    let lubc = c.cublets.[0, 0, 2].left
    let lufc = c.cublets.[0, 0, 0].left
    let fulc = c.cublets.[0, 0, 0].front
    let furc = c.cublets.[2, 0, 0].front
    let rubc = c.cublets.[2, 0, 2].right
    let rufc = c.cublets.[2, 0, 0].right
    let fue = c.cublets.[1, 0, 0].front
    let lue = c.cublets.[0, 0, 1].left
    let rue = c.cublets.[2, 0, 1].right
    let bue = c.cublets.[1, 0, 2].back
    let fs = c.cublets.[1, 1, 0].front
    let bs = c.cublets.[1, 1, 2].back
    let ls = c.cublets.[0, 1, 1].left
    let rs = c.cublets.[2, 1, 1].right
    let w = Sticker.White
    let y = Sticker.Yellow
    let fre_done (c : Cube) =
        c.cublets.[2, 1, 0].front = c.cublets.[1, 1, 0].front &&
        c.cublets.[2, 1, 0].right = c.cublets.[2, 1, 1].right
    printfn "------------------------------------------------------------"
    match c.cublets.[1, 0, 1].up (* up center *), c.cublets.[1, 1, 0].front (*front center *) with
    | Sticker.Yellow, _ -> // top already yellow
        printfn "Phase 0: Top is yellow."
        if // white cross on down-side complete?
            dfe = w && fde = fs && dbe = w && bde = bs && dle = w && bde = bs && dre = w
        then
            printfn "Phase 1: White cross is complete.";
            let frdc_done (c : Cube) =
                c.cublets.[2, 2, 0].down = w &&
                c.cublets.[2, 2, 0].front = c.cublets.[1, 1, 0].front &&
                c.cublets.[2, 2, 0].right = c.cublets.[2, 1, 1].right
            if frdc_done c && frdc_done c.Y && frdc_done c.Y2 && frdc_done c.Y' // corners complete?
            then
                printfn "Phase 2: Bottom layer corners are complete.";
                if fre_done c && fre_done c.Y && fre_done c.Y2 && fre_done c.Y'
                then
                    printfn "Phase 3: Middle layer edges in place."
                    if ule = y && ure = y && ufe = y && ube = y // yellow cross?
                    then
                        printfn "Phase 4: Yellow cross on top complete."
                        let n = Array.sumByInt (fun c -> if c = y then 1 else 0) [| uflc; ufrc; ublc; ubrc |]
                        if n = 4 then
                            printfn "Phase 5: Yellow top face complete."
                            let bp = bulc = burc
                            let lp = lubc = lufc
                            let fp = fulc = furc
                            let rp = rubc = rufc
                            if bp && lp && fp && rp
                            then
                                printfn "Phase 6: Top corners aligned."
                                if lue = ls && bue = bs && rue = rs && fue = fs // solved?!
                                then printfn "Phase 7: SOLVED!"; c
                                elif lue = lubc then printfn "  Move finished edge from left to the back."; c.U
                                elif rue = rubc then printfn "  Move finished edge from right to the back."; c.U'
                                elif fue = fulc then printfn "  Move finished edge from front to the back."; c.U2
                                elif fue = rubc then printfn "  Move front edge (right) into place."; c.F2.U'.L.R'.F2.L'.R.U'.F2.U2
                                elif fue = lubc then printfn "  Move front edge (left) into place."; c.F2.U.L.R'.F2.L'.R.U.F2.U2
                                else c
                            elif lp then printfn "  Move paired corners from left to the back."; c.Y
                            elif rp then printfn "  Move paired corners from right to the back."; c.Y'
                            elif lp then printfn "  Move paired corners from front to the back."; c.Y2
                            else printfn "  Permutate top corners."; c.R'.F.R'.B2.R.F'.R'.B2.R2
                        else
                            // get top yellow face
                            if
                                if n = 0 then
                                    printfn "  No yellow corners on top."
                                    c.cublets.[0, 0, 0].left = y
                                elif n = 1 then
                                    printfn "  1 yellow corners on top."
                                    c.cublets.[0, 0, 0].up = y
                                elif n = 2 then
                                    printfn "  2 yellow corners on top."
                                    c.cublets.[0, 0, 0].front = y
                                else failwith "Impossible position!"
                            then printfn "  Permutate yellow corders."; c.R.U.R'.U.R.U2.R'
                            else printfn "  Rotate yellow corner into position according to corner count."; c.U
                    // get yellow cross
                    elif ule <> y && (ure = y || ufe = y || ube = y)
                    then printfn "  Line up top yellow pattern."; c.U
                    else printfn "  Permutate yellow edges."; c.F.U.R.U'.R'.F'.U
                else
                    // get middle layer edges in place
                    if fre_done c
                    then printfn "  Middle layer edge in place. Next..."; c.Y
                    elif c.cublets.[2, 1, 0].front = rs && c.cublets.[2, 1, 0].right = fs
                    then printfn "  Middle layer edge in place, but flipped wrong."; c.R.U.R'.U'.F'.U'.F.U'.R.U.R'.U'.F'.U'.F
                    elif c.cublets.[2, 0, 0].front = fs && c.cublets.[2, 0, 0].up <> y
                    then printfn "  Middle layer edge just nearly in place."; c.U // it will go in next most
                    else printfn "  Permutate top and right front edges."; c.R.U.R'.U'.F'.U'.F.U.Y
            else
                // get bottom corners in
                if // corner in place?
                    dfrc = w && fdrc = fs && rdfc = rs
                then printfn " Down-right corner already in place. Rotate view."; c.Y
                elif // corner at least lined up vertically?
                    ( // furc lined up?
                        (furc = w || ufrc = w || rufc = w) && // white?
                        (furc = fs || ufrc = fs || rufc = fs) && (furc = rs || ufrc = rs || rufc = rs) // correct corner?
                    ) ||
                    ( // fdrc lined up?
                        (fdrc = w || dfrc = w || rdfc = w) && // white?
                        (fdrc = fs || dfrc = fs || rdfc = fs) && (fdrc = rs || dfrc = rs || rdfc = rs) // correct corner?
                    )
                then printfn "  Slip white corner into place."; c.R.U.R'.U'
                else printfn "  Safely permutate corners."; c.U2.R.U.R'.U'.Y
        // line up white edges to make cross on down-side
        elif // white edge lined up with correct right-center?
            c.cublets.[2, 1, 0].front (* front right edge *) = w &&
            c.cublets.[2, 1, 0].right (* right front edge *) = rs
        then printfn "  Rotate right white edge in."; c.R' // rotate white edge in
        elif // white edge lined up with correct right-center, but flipped wrong way?
            c.cublets.[2, 1, 0].front (* front right edge *) = rs &&
            c.cublets.[2, 1, 0].right (* right front edge *) = w
        then printfn "  Rotate flipped right white edge in."; c.D'.F.D // rotate white edge in
        elif // white edge lined up with correct down-center, but flipped wrong way?
            c.cublets.[2, 2, 1].right (* right down edge *) = w &&
            c.cublets.[2, 2, 1].down (* down right edge *) = rs
        then printfn "  Rotate flipped down white edge in."; c.R.D'.F.D // rotate white edge in
        elif // white edge on down side, but wrong one?
            c.cublets.[2, 2, 1].down (* right down edge *) = w &&
            c.cublets.[2, 2, 1].right (* down right edge *) <> rs // wrong color?
        then printfn "  Rotate wrong down white edge to top."; c.R2.U // rotate white edge out
        else printfn "  Safely permutate edges."; c.R.U.R'.U'.Y // just safely "stir thing up" and continue
    // look at two faces and flip yellow to up
    | _,              Sticker.Yellow -> printfn "  Flip yellow side from front to up."; c.X  // front is yellow
    | _,              Sticker.White  -> printfn "  Flip yellow side from back to up."; c.X' // back must be yellow
    | Sticker.White,  _              -> printfn "  Flip yellow side from down to up."; c.X2 // down must be yellow
    | Sticker.Red,    Sticker.Blue
    | Sticker.Blue,   Sticker.Orange
    | Sticker.Orange, Sticker.Green
    | Sticker.Green,  Sticker.Red
        -> printfn "  Flip yellow side from left to up."; c.Z  // left must be yellow
    | Sticker.Blue,   Sticker.Red
    | Sticker.Red,    Sticker.Green
    | Sticker.Green,  Sticker.Orange
    | Sticker.Orange, Sticker.Blue
        -> printfn "  Flip yellow side from right to up."; c.Z'  // right must be yellow
    | _ -> failwith "Impossible position!"
 
So, finally the UI is very simple; just painting and key wireup:
let form =
    let c = ref (new Cube())
    let key_mapping k s =
        match k with
            | Keys.Left  -> (!c).Y'
            | Keys.Right -> (!c).Y
            | Keys.Up    -> (!c).X
            | Keys.Down  -> (!c).X'
            | Keys.X     -> if s then (!c).X' else (!c).X
            | Keys.Y     -> if s then (!c).Y' else (!c).Y
            | Keys.Z     -> if s then (!c).Z' else (!c).Z
            | Keys.F     -> if s then (!c).F' else (!c).F
            | Keys.B     -> if s then (!c).B' else (!c).B
            | Keys.L     -> if s then (!c).L' else (!c).L
            | Keys.R     -> if s then (!c).R' else (!c).R
            | Keys.U     -> if s then (!c).U' else (!c).U
            | Keys.D     -> if s then (!c).D' else (!c).D
            | Keys.S     -> solve (!c)
            | Keys.P     -> (!c).U.R2.F.B.R.B2.R.U2.L.B2.R.U'.D'.R2.F.R'.L.B2.U2.F2 // so called "super-flip"
            | Keys.M     -> (!c).B.U2.B2.U.R.U2.R'.D'.U.F.L.F2.R.L.D.R2.U'.B'.F2.L'.B2.R2.U2.L2.B
            | Keys.Space -> (new Cube()) // reset
            | _          -> !c // no change
    let face_to_color =
        let brush r g b = new SolidBrush(Color.FromArgb(r, g, b))
        function // "official" Rubik's cube colors
        | Sticker.Red    -> brush 0xcc 0x00 0x00
        | Sticker.Orange -> brush 0xff 0x66 0x00
        | Sticker.Yellow -> brush 0xff 0xff 0x00
        | Sticker.Green  -> brush 0x00 0x99 0x00
        | Sticker.Blue   -> brush 0x00 0x00 0xdd
        | Sticker.White  -> brush 0xff 0xff 0xff
    let paintFace (s : int) (g : Graphics) x y f =
        g.FillRectangle(face_to_color f, x, y, s, s)
        g.DrawRectangle(Pens.Black, x, y, s, s)
    let paintCube (w : int) h =
        let b = new Bitmap(w, h)
        use g = Graphics.FromImage(b)
        g.Clear(Color.Gray)
        let s = Array3.length1 (!c).cublets // assumes cubic
        let q = 40 // cublet size
        let ox = 150 // origin
        let oy = 150 // origin
        let p = 6 // padding
        let f = q * 3 + p
        let pf = paintFace q g  // partially applied
        for i = 0 to s - 1 do
            for j = 0 to s - 1 do
                let x = i * q + ox
                let y = j * q + oy
                let ii = s - i - 1
                let jj = s - j - 1
                pf  x           y       (!c).cublets.[i,  j, 0 ].front
                pf (x - f)      y       (!c).cublets.[0,  j, ii].left
                pf (x + f)      y       (!c).cublets.[2,  j, i ].right
                pf (x + f * 2)  y       (!c).cublets.[ii, j, 2 ].back
                pf x           (y - f)  (!c).cublets.[i,  0, jj].up
                pf x           (y + f)  (!c).cublets.[i,  2, j ].down
        b
    let f = new Form(
                     Text = "Rubik's Cube",
                     Width  = 570,
                     Height = 450,
                     Visible = true)
    f.Paint.Add(fun a -> a.Graphics.DrawImage(paintCube f.Width f.Height, 0, 0))
    f.KeyDown.Add(fun a -> c := key_mapping a.KeyCode a.Shift; f.Refresh())
    f
Application.Run(form)
Looks like a lot of work but really less than 300 lines of code; Fun little toy project!