Haskell Quiz/The Solitaire Cipher/Solution Stoltze

From HaskellWiki
module Main where

import Char
import List

data Card = Card Int
          | JokerA
          | JokerB
            deriving (Show, Eq)

instance Enum Card where
    fromEnum JokerA = 53
    fromEnum JokerB = 54
    fromEnum (Card n) = n
    toEnum n | n == 53 = JokerA
             | n == 54 = JokerB
             | otherwise = Card n
    succ JokerB = Card 1
    

getValue :: Card -> Int
getValue (Card n) = n
getValue JokerA = 53
getValue JokerB = 53

deck :: [Card]
deck = [Card 1 .. JokerB]

alphabet :: [Char]
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

letterToNumber :: Char -> Int
letterToNumber ch = f' 1 ch where
    f' n ch = if alphabet !! (n-1) == ch then n else f' (n+1) ch

numberToLetter :: Int -> Char
numberToLetter n | n <  1    = numberToLetter (n + 26)
                 | n > 26    = numberToLetter (n - 26)
                 | otherwise = alphabet !! (n - 1)

-- Not really needed, but since they specified cutting it into chuncks of 5, I thought I'd comply
splitInto :: Int -> [Char] -> [Char]
splitInto n list = unwords $ splitInto' n list where
    splitInto' n list | null list = []
                      | length list < n = [list ++ replicate (n - length list) 'X']
                      | otherwise = [take n list] ++ splitInto' n (drop n list)

-- Takes some words and a function for parsing. Since encode and decode were very similar, I thought this would be easier
process :: [Char] -> (Int -> Int -> Int) -> [Char]
process line fn = let noSpaces   = map toUpper $ filter isAlpha line
                      key        = genKey $ length noSpaces
                      keyvalues  = map letterToNumber key
                      linevalues = map letterToNumber noSpaces
                      newline    = map (\(x, y) -> numberToLetter (fn x y)) $ zip linevalues keyvalues
                  in splitInto 5 newline

encode :: [Char] -> [Char]
encode line = process line (\line key -> line + key)

decode :: [Char] -> [Char]
decode line = process line (\line key -> line - key)

-- Generates the keycode
genKey :: Int -> [Char]
genKey n = repl n deck where
    repl 0 _ = []
    repl n val = let res = convert $ move val
                 in if res == ' ' then repl n (move val) else res : (repl (n-1) (move val))

toLetter :: Card -> Char
toLetter JokerA = ' '
toLetter JokerB = ' '
toLetter card = (cycle alphabet !!) $ getValue card - 1

convert :: [Card] -> Char
convert (x:xs) = toLetter $ (x:xs) !! getValue x


-- Moves the deck one step ahead
move :: [Card] -> [Card]
move cards = stepFour $ stepThree $ stepTwo $ stepOne cards where
    findStart :: (Eq a) => a -> [a] -> [a]
    findStart n (x:xs) = if x == n
                             then (x:xs)
                             else findStart n (xs ++ [x])
                                  
    stepOne, stepTwo, stepThree, stepFour :: [Card] -> [Card]
    stepOne (JokerA:x:xs) = x:JokerA:xs
    stepOne (x:xs) = let change (x:y:xs) = if x == JokerA
                                               then (y:x:xs)
                                               else change ((y:xs) ++ [x])
                     in findStart x $ change (x:xs)

    stepTwo (JokerB:x:y:xs) = x:y:JokerB:xs
    stepTwo (x:xs) = let change (x:y:z:xs) = if x == JokerB
                                                 then (y:z:x:xs)
                                                 else change ((y:z:xs) ++ [x])
                     in findStart x $ change (x:xs)
                
    stepThree (x:xs) = let fn c    = c /= JokerA && c /= JokerB
                           toFirst = takeWhile fn (x:xs)
                           toLast  = reverse $ takeWhile fn $ reverse (x:xs)
                       in toLast ++ (reverse (drop (length toLast) (reverse (drop (length toFirst) (x:xs))))) ++ toFirst
                 
    stepFour cards = let l = last cards
                         r = getValue l
                     in drop r (init cards) ++ take r (cards) ++ [l]