You can keep reading here or jump to my blog to get the full experience, including the wonderful pink, blue and white palette.
Today we will TDD the Mars Rover Kata with the T in TDD meaning Type!
Red
The first step is to define all the types we need for our program to do it's job:
type Coord =
(Int, Int)
type CoordDir =
(Coord, Dir)
data Turn
= L
| R
data Dir
= N
| E
| S
| W
data Cmd
= Turn Turn
| Move
readMaybeT :: Read a => Text -> Maybe a
parsePlateauMax :: Text -> Maybe Coord
parseStartingCoordDir :: Text -> Maybe CoordDir
parseCommands :: Text -> Maybe [Cmd]
runCommands :: [Cmd] -> Coord -> CoordDir -> CoordDir
run :: Text -> Maybe CoordDir
Ideally we would use the functions above as follows:
main :: IO ()
main = do
print $ parsePlateauMax "5 5"
-- Just (5,5)
print $ parseStartingCoordDir "1 2 N"
-- Just ((1,2),N)
print $ parseCommands "LMLMLMLMM"
-- Just [Turn L,Move,Turn L,Move,Turn L,Move,Turn L,Move,Move]
let commands = "5 5\n1 2 N\nLMLMLMLMM"
maybe (print "ERR!") print $ run commands
-- ((1,3),N)
Green
With the types in place it's enough to make it compile :D
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Text (Text)
import qualified Data.Text as T (lines, words, unpack)
import Text.Read (readMaybe)
type Coord =
(Int, Int)
type CoordDir =
(Coord, Dir)
data Turn
= L
| R
deriving (Show, Read)
data Dir
= N
| E
| S
| W
deriving (Show, Read)
data Cmd
= Turn Turn
| Move
deriving (Show)
readMaybeT :: Read a => Text -> Maybe a
readMaybeT = readMaybe . T.unpack
parsePlateauMax :: Text -> Maybe Coord
parsePlateauMax txt =
case T.words txt of
[x, y] -> (,) <$> readMaybeT x <*> readMaybeT y
_ -> Nothing
parseStartingCoordDir :: Text -> Maybe CoordDir
parseStartingCoordDir txt = do
(x, y, d) <- case T.words txt of
[x, y, d] -> (,,) <$> readMaybeT x <*> readMaybeT y <*> readMaybeT d
_ -> Nothing
Just ((x, y), d)
parseCommands :: Text -> Maybe [Cmd]
parseCommands txt = toCmd `traverse` T.unpack txt
where
toCmd :: Char -> Maybe Cmd
toCmd 'L' = Just (Turn L)
toCmd 'R' = Just (Turn R)
toCmd 'M' = Just Move
toCmd _ = Nothing
runCommands :: [Cmd] -> Coord -> CoordDir -> CoordDir
runCommands [] _ coordDir = coordDir
runCommands (c:cmds) max coordDir = runCommands cmds max $ newCoordDir c
where
newCoordDir (Turn L) = turnL coordDir
newCoordDir (Turn R) = turnR coordDir
newCoordDir Move = move coordDir
turnL (c, N) = (c, W)
turnL (c, E) = (c, N)
turnL (c, S) = (c, E)
turnL (c, W) = (c, S)
turnR (c, N) = (c, E)
turnR (c, E) = (c, S)
turnR (c, S) = (c, W)
turnR (c, W) = (c, N)
move ((x, y), N) = (wrap max (x, y+1), N)
move ((x, y), E) = (wrap max (x+1, y), E)
move ((x, y), S) = (wrap max (x, y-1), S)
move ((x, y), W) = (wrap max (x-1, y), W)
wrap (maxX, maxY) (x, y) = (wrap' maxX x, wrap' maxY y)
wrap' max x | x < 0 = max + 1 + x
wrap' max x | x > max = x `rem` max
wrap' max x | otherwise = x
run :: Text -> Maybe CoordDir
run txt = do
(max, coordDir, cmds) <- go $ T.lines txt
Just $ runCommands cmds max coordDir
where
go [f, s, t] = (,,) <$> parsePlateauMax f <*> parseStartingCoordDir s <*> parseCommands t
go _ = Nothing
main :: IO ()
main = do
let commands = "5 5\n1 2 N\nLMLMLMLMM"
print $ parsePlateauMax "5 5"
print $ parseStartingCoordDir "1 2 N"
print $ parseCommands "LMLMLMLMM"
maybe (print "ERR!") print $ run commands
Refactor
I don't have any more time for today. We will be refactoring in a future post!
Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to my PinkLetter!
Top comments (1)
CodinGame have 3-parts series of interactive Mars lander puzzle.
You might want to checkout.
codingame.com/training/medium/mars...
codingame.com/training/medium/mars...
codingame.com/training/medium/mars...