packages feed

lgtk-0.8: lgtkdemo/LGtk/Demos/Maze.hs

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
module LGtk.Demos.Maze where

import Control.Monad
import Control.Monad.State
import Data.List
import Data.Array
import qualified Data.Set as S
import System.Random
import Diagrams.Prelude hiding (vcat, hcat, Point, Start)

import Control.Lens hiding ((#))
import LGtk

import LGtk.Demos.Maze.Types
import qualified LGtk.Demos.MazeGen as Maze1
import qualified LGtk.Demos.Maze.Maze as Maze2

---------------------------------- Game state

data GameState
    = Start
    | Explore Point
    | Fail
    | Success

instance Show GameState where
    show Success = "Congratulation!"
    show Fail = "Failure, try again!"
    show Start = "Move the pointer to the filled circle"
    show (Explore _) = "Reach the bottom left corner"

------------------------ maze drawing

drawMaze :: (Maze, S.Set Point, Maybe Point) -> Dia [Point]
drawMaze (maze, hi, pos) =
    (  mconcat (map drawCell $ assocs maze) # centerXY
    <> rect (fromIntegral $ x2-x1+1) (fromIntegral $ y2-y1+1) # lw 0.005 # fc (sRGB 0.95 0.95 0.95) # value []
    ) # scale (1 / fromIntegral (max (x2-x1+1) (y2-y1+1)))
  where
    drawCell (p@(i,j), C cs) =
            (   (if b then mconcat (map drawWall $ complement cs) # lw 0.005 # value [] else mempty)
            <>  (if Just p == pos then circle 0.35 # lw 0.003 # fc blue # value [] else mempty)
            <>  (if p == q2 then circle 0.35 # lw 0.003 # value [] else mempty)
            <>  (if p == q1 then circle 0.35 # lw 0.003 # value [] else mempty)
            <>  rect 1 1 # lw 0 # (if b then fc yellow else id) # value [p]
            )   # translate (r2 (fromIntegral i, fromIntegral j))
        where b = S.member p hi

    drawWall E = fromVertices [p2 (-d, d), p2 (d, d)]
    drawWall S = fromVertices [p2 (d, -d), p2 (d, d)]
    drawWall W = fromVertices [p2 (-d, -d), p2 (d, -d)]
    drawWall N = fromVertices [p2 (-d, -d), p2 (-d, d)]

    d = 0.5

    (q1@(x1,y1), q2@(x2,y2)) = bounds maze

complement :: [Cardinal] -> [Cardinal]
complement = f [N,E,S,W] . sort
  where
    f (x:xs) (y:ys) | x == y = f xs ys
    f (x:xs) ys = x: f xs ys
    f [] _ = []

----------------------- game logic

-- | check whether a move does not hit the wall
isOk :: Maze -> Point -> Point -> Bool
isOk maze p q = p == q || maybe False (`elem` unC (maze ! p)) (dir p q)
  where
    unC (C xs) = xs

    dir (a,b) (c,d)
        | a == c && b == d + 1 = Just W
        | a == c && b == d - 1 = Just E
        | a == c + 1 && b == d = Just N
        | a == c - 1 && b == d = Just S
        | otherwise = Nothing

checkBounds ((a,b),(c,d)) (e,f)
    | a <= e && e <= c && b <= f && f <= d = Just (e,f)
    | otherwise = Nothing

gameLogic b maze p (s, st) = case st of
    Start | p == snd (bounds maze) -> commit
    Explore q
        | isOk maze q p || b && S.member p s -> commit
        | not b -> (s, Fail)
    _ -> (s, st)
  where
    commit = (S.insert p s, if p == fst (bounds maze) then Success else Explore p)

------------------------ GUI

mazeGame :: forall m . MonadRegister m => Widget m
mazeGame = do
    forgiving <- newRef False
    let init = (0,(4,4))
    dim_ <- newRef init
    let dim = _2 `lensMap` dim_
        mazekind = _1 `lensMap` dim_
        dimX = (_2 . iso id (max 1 . min 40)) `lensMap` toEqRef dim
        dimY = (_1 . iso id (max 1 . min 40)) `lensMap` toEqRef dim
        genMaze (0, d) = Maze1.genMaze d
        genMaze (1, d) = Maze2.genMaze d
    maze_ <- extRef_ dim_ (runState (genMaze init) (mkStdGen 323401)) $ \d (_, s) -> runState (genMaze d) s
    r <- extRef_ maze_ (S.empty, Start) $ \_ _ -> (S.empty, Start)

    let handler (MoveTo (MousePos _ [p]), _) = domove p
        handler _ = return ()

        domove p = do
            (maze, _) <- readRef maze_
            b <- readRef forgiving
            modRef r $ gameLogic b maze p

        move f = do
            (maze, _) <- readRef maze_
            (_, st) <- readRef r
            let m = case st of
                    Start -> Just $ snd $ bounds maze
                    Explore p -> checkBounds (bounds maze) $ f p
                    _ -> Nothing
            maybe (return True) ((>> return True) . domove) m

        key (SimpleKey Key'Left)  = move $ \(x,y)->(x-1,y)
        key (SimpleKey Key'Right) = move $ \(x,y)->(x+1,y)
        key (SimpleKey Key'Up)    = move $ \(x,y)->(x,y+1)
        key (SimpleKey Key'Down)  = move $ \(x,y)->(x,y-1)
        key _ = return False

        pos maze Start = Just $ snd $ bounds maze
        pos maze Success = Just $ fst $ bounds maze
        pos _ (Explore p) = Just p
        pos _ _ = Nothing

    vcat
        [ hcat
            [ canvas 400 400 1 handler (Just key) (liftM2 (\(m,_) (s, st) -> (m,s, pos m st)) (readRef maze_) (readRef r)) drawMaze

            , vcat
                [ hcat
                    [ checkbox forgiving
                    , label $ return "forgiving mode"
                    ]
                , combobox ["cdsmith's", "Mihai Maruseac's"] mazekind
                , label $ return "maze generator"
                ]
            ]

        , label $ liftM (show . snd) $ readRef r
        , hcat
            [ button (return "Try again") $ return $ Just $ modRef maze_ id
            , button (return "New maze") $ return $ Just $ modRef dim id
            ]
        , hcat
            [ entryShow dimX
            , smartButton (return "+1") dimX succ
            , smartButton (return "-1") dimX pred
            , label $ return "width"
            ]
        , hcat
            [ entryShow dimY
            , smartButton (return "+1") dimY succ
            , smartButton (return "-1") dimY pred
            , label $ return "height"
            ]
        ]

----------------------------- utils

extRef_ :: MonadRegister m => Ref m b -> a -> (b -> a -> a) -> m (Ref m a)
extRef_ r def f = do
    r0 <- readRef r
    v <- extRef r (lens fst set) (r0, def)
    return $ _2 `lensMap` v
  where
    set (_, y) x = (x, f x y)