The main questions for today's task are how to represent what's important. There are two things: the commands given to the submarine, and the current position. I decided to go with clarity over cleverness.
I could represent the position as a pair (or triple) of numbers. I could do something clever with complex numbers. I could use the Linear package to represent coordinates. I could use records. But in this case, some simple algebraic types are more than good enough.
type Course = [Command]
data Command = Forward Int
| Up Int
| Down Int
deriving (Eq, Show)
data Position = Position Int Int -- forward, depth
deriving (Eq, Show)
data AimedPosition = AimedPosition Int Int Int -- forward, depth, aim
deriving (Eq, Show)
This has the additional advantage from the "parse, don't validate" approach, in that I can't have an invalid Command
.
It's back to attoparsec for parsing, which remains remarkably clear.
courseP = commandP `sepBy` endOfLine
commandP = forwardP <|> upP <|> downP
forwardP = Forward <$> ("forward " *> decimal)
upP = Up <$> ("up " *> decimal)
downP = Down <$> ("down " *> decimal)
A course is a list of commands separated by newlines. A command is either forward, up, or down. Each of those is the text string followed by a number, and we don't bother recording the text string.
Finding the final position requires writing down the rules as a function (using pattern matching for the different cases), then fold
ing the commands into a final Position
.
followCourse :: Course -> Position
followCourse = foldl courseStep (Position 0 0)
courseStep :: Position -> Command -> Position
courseStep (Position h d) (Forward n) = Position (h + n) d
courseStep (Position h d) (Up n) = Position h (d - n)
courseStep (Position h d) (Down n) = Position h (d + n)
followAimedCourse :: Course -> AimedPosition
followAimedCourse = foldl courseAimedStep (AimedPosition 0 0 0)
courseAimedStep :: AimedPosition -> Command -> AimedPosition
courseAimedStep (AimedPosition h d a) (Forward n) = AimedPosition (h + n) (d + a * n) a
courseAimedStep (AimedPosition h d a) (Up n) = AimedPosition h d (a - n)
courseAimedStep (AimedPosition h d a) (Down n) = AimedPosition h d (a + n)
The only wrinkle is to remember to fold from the left, so commands are applied from first to last!
Code
You can get the code from my locally-hosed Git repo, or from Gitlab.