{- A simple single player snake game. -} module Snake where import ANSI(ansiGoto, ansiColour, Colour(..)) import Program(Program, putS, getC) import Game(runGame, Game) import Coord(Coord, Dir(..), outOfBounds, movePos) -- | A snake is a list of coord.s for the body and a dir. of travel. data Snake = Snake { pos :: [Coord] , dir :: Dir } -- | The starting position of the snake. startingSnake :: Snake startingSnake = Snake ((11,10) : replicate 20 (10,10)) East -- | Check if a snake has collided with itself. collision :: Snake -> Bool collision g = case pos g of [] -> False p : ps -> outOfBounds p || any (p==) ps -- | Output a string at a given coordinate (uses some ANSI magic). putStrAt :: Coord -> String -> Program () putStrAt p s = putS $ gotoPos p ++ s where gotoPos (x, y) = ansiGoto (x * 2 + 1) (y + 1) -- | Draw the snake. The last part of the tail is erased. drawSnake :: Colour -> String -> Snake -> Program () drawSnake col px s = do let ps = pos s putStrAt (last ps) " " -- erase previous tail putStrAt (head ps) $ ansiColour col px -- print new head -- | The different actions that the player can take. data Action = Turn Dir | Exit deriving Show -- | Keyboard controls. Binds keys to actions. controls :: [(Char, Action)] controls = zip "wasd" (map Turn [North, West, South, East]) ++ [ ('q', Exit), ('\ESC', Exit) ] -- | One step of the actual game snake :: Game Snake snake g | collision g = do putStrAt (0, 28) "Game Over!" stop | otherwise = do drawSnake Red "()" g putStrAt (0,0) "" mc <- getC case mc >>= \c -> lookup c controls of -- Maybe is a monad Nothing -> continue_ Just (Turn d) -> continue d Just Exit -> stop where -- Moving the snake means adding a new head and removing the last -- element of the tail. move (p:ps) d = movePos p d : p : init ps stop = return Nothing continue_ = continue (dir g) continue d = return $ Just $ g { pos = move (pos g) d , dir = d }