Rubik's triangle

Posted on April 8, 2018 by Ilya

Intro

Recently, we were taking a break with a colleague by trying to compute the number of states of Rubik’s cube. After a while, we decided to consider a simpler problem, so Rubik’s triangle was born.

It’s initial state:

   B↑
 1/ \3
A↑ − C↑
   2

You can swap corners along edges 1,2,3. Swapping along 1 leads to

   A↓
  / \
B↓ − C↑

If you then swap along 2:

   A↓
  / \
C↑ − B↓

Note that each time you swap, not only letters (A,B,C) are swapped between corners, but so does the orientation of corners (↑ or ↓). This adds a constraint to imitate constraints that you have in Rubik’s cube.

The goal of the game is to go back to the initial state after many random swaps. Here is your goal:

   B↑
  / \ 
A↑ − C↑

Play it with Haskell

For your pleasure, I’ve coded this game in Haskell. Run it with runhaskell rubik_triangle.hs or compile it ghc rubik_triangle.hs.


```haskell
import System.IO (BufferMode(NoBuffering), stdin, hSetBuffering)
import System.Random (newStdGen, randomRs)
import Data.Char (digitToInt)
import Control.Monad (when)

data Orientation = Up | Down deriving (Eq)
instance Show Orientation where
    show Up   = "↑"
    show Down = "↓"
data Letter = A | B | C deriving (Show, Eq)

data Angle = Angle Orientation Letter deriving (Eq)
instance Show Angle where
    show (Angle o l) = show l ++ show o
data Triangle = Triangle Angle Angle Angle deriving (Eq)
instance Show Triangle where
    show (Triangle a b c) = 
        "    "++show b++"    \n" ++
        "   /"++" "++"\\    \n" ++
        " "++show a++" −"++" "++show c

flipO :: Orientation -> Orientation
flipO Up   = Down
flipO Down = Up

trans :: (Angle, Angle) -> (Angle, Angle)
trans (Angle o1 l1, Angle o2 l2) = (Angle (flipO o1) l2, Angle (flipO o2) l1)

transition :: Int -> Triangle -> Triangle
transition 1 (Triangle a b c) = Triangle a' b' c
    where (a', b') = trans (a, b)
transition 2 (Triangle a c b) = Triangle a' c b'
    where (a', b') = trans (a, b)
transition 3 (Triangle c a b) = Triangle c a' b'
    where (a', b') = trans (a, b)

seqToTransition :: [Int] -> (Triangle -> Triangle)
seqToTransition [] = id
seqToTransition (n:ns)
  | n `elem` [1, 2, 3] = transition n . seqToTransition ns
  | otherwise = error "Bad n"

tr0 = Triangle (Angle Up A) (Angle Up B) (Angle Up C)

loop :: Triangle -> IO ()
loop tr = do
    print tr
    putStrLn "\n"
    when (tr == tr0) $ putStrLn "It is solved!"
    x <- getChar
    putStrLn ("You pressed: " ++ [x])
    if x `elem` ['1', '2', '3']
       then loop $ transition (digitToInt x) tr
       else case x of
              'q'       -> return ()
              'h'       -> do
                  putStrLn helpMsg
                  loop tr
              otherwise -> loop tr

helpMsg = "\n\nPress 1,2,3 to play, q to quit. h to print this message.\n\n"

main :: IO ()
main = do
    hSetBuffering stdin NoBuffering
    putStrLn helpMsg
    putStrLn "Initial solved state state:\n"
    print tr0
    putStrLn "\nMixing it:\n"
    g <- newStdGen
    let moves   = randomRs (1, 3 :: Int) g
        trMixed = seqToTransition (take 40 moves) $ tr0
    loop trMixed

```