about summary refs log tree commit diff
path: root/Maze.hs
blob: 99ed72686831d07207ef57f43dd069dd44781615 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
module Main where

import Control.Monad.State
import Prelude hiding (Word, null)
import ListT (fromFoldable, ListT, toList, fold)
import Text.Read (readMaybe)
import qualified Data.Map as M
import Text.Parsec.String (Parser)
import Text.Parsec hiding (State)
import Data.Maybe (catMaybes)
import Data.Either (partitionEithers)
import qualified Text.Parsec.Token as P
import Text.Parsec.Language (haskellDef)
import Data.List (delete, null)
import System.Environment

data Direction = U | L | R | D deriving (Show, Eq)
type Word = Either String Integer
type Position = (Int, Int)
type Opcode = (Char, Char)
data Car = Car Position Direction Word deriving Show
type Maze = [String]

data Condition = Signal | Cmp Word deriving Show
data Function = Eq Word
                | Dec Integer 
                | Inc 
                | Dir Direction
                | If Condition Function Function deriving Show
type Env = M.Map Opcode Function 

advance' :: Maze -> Car -> Car
-- so this is a little weird
-- this is how the "canonical" Maze implementation turns cars around on walls
-- its weird, i would have preferred fixed clockwise/counterclockwise
advance' m (Car pos dir v) = head $ filter
    ((('#','#')/=) . tile m)
    (step . flip (Car pos) v <$> moveDesired [U, D, L, R]) where
  -- prefer to move forward, turn around only if you have to
  moveDesired = (dir:) . delete dir
                . (++ [opposite dir]) . delete (opposite dir)
  opposite U = D
  opposite D = U
  opposite R = L
  opposite L = R

  step (Car (x,y) U v) = Car (x,y-1) U v
  step (Car (x,y) D v) = Car (x,y+1) D v
  step (Car (x,y) L v) = Car (x-3,y) L v
  step (Car (x,y) R v) = Car (x+3,y) R v

tile :: Maze -> Car -> Opcode
tile m (Car (x,y) _ _) = (row !! x, row !! (x+1)) where
  row = m !! y

act :: Function -> Car -> Car
act (Eq v) (Car pos dir _) = Car pos dir v 
act (If (Cmp v') lhs rhs) car@(Car _ _ v) = if v' == v then act lhs car else act rhs car 
act (Dir dir) (Car pos _ v) = Car pos dir v
act (Dec x) (Car pos dir (Right y)) = Car pos dir (Right $ y - x)
  
maze :: Env -> Maze -> [Car] -> ListT IO Car
maze env m cars = do
  let signalled' = any (\car -> tile m car == ('*', '*')) cars
  car@(Car pos dir v) <- fromFoldable cars
  let advance = advance' m;
  case tile m car of
    ('.', '.') -> return $ advance car
    ('^', '^') -> return $ advance car
    ('*', '*') -> return $ advance car
    ('(', ')') -> mempty 
    ('>', '>') -> do 
      lift $ case v of
        Right v' -> putStr $ show v' ++ " "
        Left v' -> putStrLn v'
      return $ advance car 
    ('<', '<') -> do
      lift $ putStr "> "
      line <- lift getLine
      case readMaybe line :: Maybe Integer of
        Just v' -> return $ advance (Car pos dir (Right v'))
        Nothing -> return $ advance (Car pos dir (Left line))
    ('<', '>') -> fromFoldable [advance $ Car pos L v, advance $ Car pos R v]
    ('%', 'D') -> return $ advance (Car pos D v)
    ('%', 'U') -> return $ advance (Car pos U v)
    ('%', 'L') -> return $ advance (Car pos L v)
    ('%', 'R') -> return $ advance (Car pos R v)
    unk -> case (M.!?) env unk of
      Just fn -> case fn of
        If Signal lhs rhs -> fromFoldable $ if signalled' then [advance $ act lhs car] else [advance $ act rhs car]
        fn' -> return $ advance (act fn' car)
      Nothing -> error $ show unk

assignment :: Parser Function
assignment = string "=" >> Eq <$> (Left <$> stringLiteral' <|> Right <$> decimal') where
  def = P.makeTokenParser haskellDef
  stringLiteral' = P.stringLiteral def
  decimal' = P.decimal def

condition :: Parser Condition
condition = (string "**" *> return Signal) <|> (string "== " >> Cmp <$> (Right . read <$> many1 digit))

ifStatement :: Parser Function
ifStatement = string "IF " >> If <$> condition <* string " THEN " <*> (assignment <|> direction) <* string "ELSE " <*> (assignment <|> direction)

direction :: Parser Function
direction = char '%' >> Dir <$> choice [char 'D' >> return D, char 'U' >> return U, char 'L' >> return L, char 'R' >> return R] <* spaces

decrement = string "-= " >> Dec <$> P.decimal (P.makeTokenParser haskellDef) <* spaces 
  
parseMaze = catMaybes <$> many1 maybeFunction where
  maybeFunction = (Just . Left <$> function) <|> (Just . Right <$> mazeStart)
  mazeStart = catMaybes <$> manyTill (Just <$> (try (string "^^") *> getPosition) <|> (anyChar >> return Nothing)) newline
  function :: Parser (Opcode, Function)
  function = (,) <$> ((,) <$> letter <*> letter) <* string "-> " <*> (assignment <|> ifStatement <|> decrement)

maze' :: Env -> Maze -> StateT [Car] IO ()
maze' env m = do
  cars <- get
  cars' <- lift $ toList $ maze env m cars
  put cars'
  if null cars'
    then return ()
    else maze' env m

runMaze m = do
  let (Right (fns, starts)) = partitionEithers <$> parse (parseMaze <* eof) "" m
  let starts' = [Car (sourceColumn x - 3, sourceLine x - 1) U (Right 0) | x <- concat starts]
  let env = M.fromList fns
  let m' = lines m
  execStateT (maze' env m') starts'
  return ()

main :: IO ()
main = getArgs >>= readFile . head >>= runMaze