| 1 |
{-# LANGUAGE MultiWayIf #-}
|
| 2 |
|
| 3 |
module Main where
|
| 4 |
|
| 5 |
import Data.Array (Array, array, (!), (//))
|
| 6 |
import Data.Char (chr, ord)
|
| 7 |
import Data.Maybe (fromMaybe)
|
| 8 |
import Control.Monad (void, forever)
|
| 9 |
import Control.Monad.IO.Class (liftIO)
|
| 10 |
import Control.Monad.State ( StateT(runStateT)
|
| 11 |
, get
|
| 12 |
, put
|
| 13 |
, modify
|
| 14 |
)
|
| 15 |
import System.Exit (exitSuccess)
|
| 16 |
import System.Environment (getArgs)
|
| 17 |
import System.IO (stdout, hFlush)
|
| 18 |
import System.Random (randomIO)
|
| 19 |
|
| 20 |
type Board = Array (Int, Int) Char
|
| 21 |
data Direction = U | R | D | L deriving (Eq, Show, Enum)
|
| 22 |
data FungeState = FS
|
| 23 |
{ location :: (Int, Int)
|
| 24 |
, stack :: [Int]
|
| 25 |
, direction :: Direction
|
| 26 |
, board :: Board
|
| 27 |
, stringMode :: Bool
|
| 28 |
} deriving (Eq, Show)
|
| 29 |
type FungeM a = StateT FungeState IO a
|
| 30 |
|
| 31 |
terminate :: String -> FungeM a
|
| 32 |
terminate s = liftIO $ do
|
| 33 |
putStrLn ("\n" ++ s)
|
| 34 |
exitSuccess
|
| 35 |
|
| 36 |
debug :: FungeM ()
|
| 37 |
debug = do
|
| 38 |
st <- get
|
| 39 |
liftIO $ do
|
| 40 |
putStrLn $ "FS {"
|
| 41 |
putStrLn $ " , loc=" ++ show (location st)
|
| 42 |
putStrLn $ " , stk=" ++ show (stack st)
|
| 43 |
putStrLn $ " , dir=" ++ show (direction st)
|
| 44 |
putStrLn $ " }"
|
| 45 |
|
| 46 |
pop :: FungeM Int
|
| 47 |
pop = do
|
| 48 |
st <- get
|
| 49 |
case stack st of
|
| 50 |
[] -> terminate "Popping from empty stack!"
|
| 51 |
(x:xs) -> do
|
| 52 |
put st { stack = xs }
|
| 53 |
return x
|
| 54 |
|
| 55 |
push :: Int -> FungeM ()
|
| 56 |
push x = modify go where go st = st { stack = x : stack st }
|
| 57 |
|
| 58 |
binOp :: (Int -> Int -> Int) -> FungeM ()
|
| 59 |
binOp op = do
|
| 60 |
a <- pop
|
| 61 |
b <- pop
|
| 62 |
push (b `op` a)
|
| 63 |
|
| 64 |
unOp :: (Int -> Int) -> FungeM ()
|
| 65 |
unOp op = do
|
| 66 |
a <- pop
|
| 67 |
push (op a)
|
| 68 |
|
| 69 |
setDirection :: Direction -> FungeM ()
|
| 70 |
setDirection d = modify go where go st = st { direction = d }
|
| 71 |
|
| 72 |
randomDirection :: FungeM ()
|
| 73 |
randomDirection = do
|
| 74 |
x <- liftIO randomIO
|
| 75 |
setDirection (toEnum x)
|
| 76 |
|
| 77 |
fungeIf :: Direction -> Direction -> FungeM ()
|
| 78 |
fungeIf thenDir elseDir = do
|
| 79 |
c <- pop
|
| 80 |
setDirection (if c /= 0 then thenDir else elseDir)
|
| 81 |
|
| 82 |
toggleStringMode = modify go
|
| 83 |
where go st = st { stringMode = not (stringMode st) }
|
| 84 |
|
| 85 |
goDir :: Direction -> (Int, Int) -> (Int, Int)
|
| 86 |
goDir U (x, y) = (x, y - 1)
|
| 87 |
goDir R (x, y) = (x + 1, y)
|
| 88 |
goDir D (x, y) = (x, y + 1)
|
| 89 |
goDir L (x, y) = (x - 1, y)
|
| 90 |
|
| 91 |
move :: FungeM ()
|
| 92 |
move = modify go
|
| 93 |
where go st@(FS { direction = d, location = l }) =
|
| 94 |
st { location = goDir d l }
|
| 95 |
|
| 96 |
getInstr :: FungeM ()
|
| 97 |
getInstr = do
|
| 98 |
y <- pop
|
| 99 |
x <- pop
|
| 100 |
st <- get
|
| 101 |
push (ord (board st ! (x, y)))
|
| 102 |
|
| 103 |
putInstr :: FungeM ()
|
| 104 |
putInstr = do
|
| 105 |
y <- pop
|
| 106 |
x <- pop
|
| 107 |
c <- pop
|
| 108 |
modify (\ st -> st { board = board st // [((x, y), chr c)] })
|
| 109 |
|
| 110 |
step :: Char -> FungeM ()
|
| 111 |
step '+' = binOp (+)
|
| 112 |
step '-' = binOp (-)
|
| 113 |
step '*' = binOp (*)
|
| 114 |
step '/' = binOp div
|
| 115 |
step '%' = binOp mod
|
| 116 |
step '!' = unOp go where go 0 = 1; go _ = 0
|
| 117 |
step '`' = binOp go where go x y | x > y = 1 | otherwise = 0
|
| 118 |
step '>' = setDirection R
|
| 119 |
step '<' = setDirection L
|
| 120 |
step '^' = setDirection U
|
| 121 |
step 'v' = setDirection D
|
| 122 |
step '?' = randomDirection
|
| 123 |
step '_' = fungeIf L R
|
| 124 |
step '|' = fungeIf U D
|
| 125 |
step '"' = toggleStringMode
|
| 126 |
step ':' = do { x <- pop; push x; push x }
|
| 127 |
step '\\' = do { x <- pop; y <- pop; push x; push y }
|
| 128 |
step '$' = void pop
|
| 129 |
step '.' = do { x <- pop; liftIO (print x) }
|
| 130 |
step ',' = do { x <- pop; liftIO (putChar (chr x) >> hFlush stdout) }
|
| 131 |
step '#' = move
|
| 132 |
step 'g' = getInstr
|
| 133 |
step 'p' = putInstr
|
| 134 |
step '&' = do
|
| 135 |
liftIO (putStr "num> " >> hFlush stdout)
|
| 136 |
n <- liftIO getLine
|
| 137 |
push (read n)
|
| 138 |
step '~' = do
|
| 139 |
liftIO (putStr "chr> " >> hFlush stdout)
|
| 140 |
n <- liftIO getLine
|
| 141 |
push (ord (head n))
|
| 142 |
step '@' = terminate "Finished"
|
| 143 |
step n | n >= '0' && n <= '9' = push (ord n - ord '0')
|
| 144 |
step _ = return ()
|
| 145 |
|
| 146 |
run :: FungeM ()
|
| 147 |
run = do
|
| 148 |
st <- get
|
| 149 |
let c = board st ! location st
|
| 150 |
if | stringMode st && c == '"' -> toggleStringMode
|
| 151 |
| stringMode st && c /= '"' -> push (ord c)
|
| 152 |
| otherwise -> step c
|
| 153 |
move
|
| 154 |
|
| 155 |
(!?) :: [[a]] -> (Int, Int) -> Maybe a
|
| 156 |
xs !? (x, y)
|
| 157 |
| x < 0 || y < 0 = Nothing
|
| 158 |
| y >= length xs = Nothing
|
| 159 |
| x >= length (xs !! y) = Nothing
|
| 160 |
| otherwise = Just ((xs !! y) !! x)
|
| 161 |
|
| 162 |
buildBoard :: String -> Board
|
| 163 |
buildBoard s =
|
| 164 |
array ((0, 0), (width-1, height-1))
|
| 165 |
[ ((x, y), c)
|
| 166 |
| x <- [0..width-1]
|
| 167 |
, y <- [0..height-1]
|
| 168 |
, let c = fromMaybe ' ' (strs !? (x, y))
|
| 169 |
]
|
| 170 |
where strs = lines s
|
| 171 |
height = length strs
|
| 172 |
width = maximum (map length strs)
|
| 173 |
|
| 174 |
buildInitialState :: String -> FungeState
|
| 175 |
buildInitialState s =
|
| 176 |
FS { board = buildBoard s
|
| 177 |
, stack = []
|
| 178 |
, direction = R
|
| 179 |
, location = (0, 0)
|
| 180 |
, stringMode = False
|
| 181 |
}
|
| 182 |
|
| 183 |
runFunge :: FungeState -> IO ()
|
| 184 |
runFunge st = void (runStateT (forever run) st)
|
| 185 |
|
| 186 |
main :: IO ()
|
| 187 |
main = do
|
| 188 |
args <- getArgs
|
| 189 |
case args of
|
| 190 |
[] -> putStrLn "No filename given!"
|
| 191 |
(f:_) -> do
|
| 192 |
c <- readFile f
|
| 193 |
runFunge (buildInitialState c)
|