{- 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 body coord.s 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 || p `elem` 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 (5, 7) "Game Over!" stop | otherwise = do drawSnake Yellow "()" 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 }