packages feed

minesweeper-0.4: Minesweeper.hs

import Table
import Delay
import Timer
import Game
import Place

import Paths_minesweeper

import Graphics.UI.Gtk hiding (Clear)
import Graphics.UI.Gtk.Glade

import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad (when)

import System.Random

--------------------------------------

type UGState = ([GState], [GState]) 

-- | Program state
data ProgramState = ProgramState
    { timer  :: Tim             -- ^ timer state
    , label  :: Label           -- ^ label component (constant)
    , gstate :: MVar UGState    -- ^ game state (model)
    , table  :: GameTable       -- ^ table state (view)
    , seed   :: MVar StdGen
    }


msglade :: IO GladeXML
msglade = do
    f <- getDataFileName "ms.glade" 
    Just xml <- xmlNew f
    return xml

main :: IO ()
main = mdo
    initGUI
    timeoutAddFull (yield >> return True) priorityDefaultIdle 50

    xml <- msglade
    window      <- xmlGetWidget xml castToWindow "window1"
    lab         <- xmlGetWidget xml castToLabel "label1"
    timeLabel   <- xmlGetWidget xml castToLabel "label7"
    asp_        <- xmlGetWidget xml castToAspectFrame "aspectframe1"

    asp     <- newTable asp_ (checkWin pst . flag') (checkWin pst . revealRec)

    se <- newStdGen
    ss <- newMVar se

    let pst = ProgramState tim lab gst asp ss

    mapM_ (addMenuItem xml)
        [ ("imagemenuitem5",  widgetDestroy window)
        , ("imagemenuitem1",  newGame pst)
        , ("imagemenuitem6",  preferences pst)
        , ("imagemenuitem3",  helpDialog)
        , ("imagemenuitem10", aboutDialog)
        , ("imagemenuitem9",  clear pst $ checkWin pst . revealRec)
        , ("imagemenuitem11", clear pst $ checkWin pst . flag')
        , ("imagemenuitem12", undo pst)
        , ("imagemenuitem15", redo pst)
        , ("menuitem2",  reveal False pst)
        , ("menuitem6",  reveal True pst)
        ]

    onDestroy window mainQuit

    gst     <- newMVar ([initGState (10,10) 20], [])
    tim     <- newTimer (labelSetText timeLabel) 

    hideTableVar <- newCancelVar
    showTableVar <- newCancelVar

    onFocusOut window $ \_ -> do
        when' (isActiveTimer tim) $ do
            stopTimer True tim
            doLater hideTableVar (1::Double) (setTableSt asp $ \s -> if s == Fixed then s else Stopped)
        return False

    onFocusIn window $ \_ -> do
        when' (fmap not $ isActiveTimer tim) $ do
            cancelAction hideTableVar
            setTableSt asp $ \s -> if s == Fixed then s else Normal Nothing
            startTimer True tim
        return False

    widgetShowAll window
    resizeTable_ pst

    mainGUI

flag' a b r = (c, d, r) where (c, d) = flag a b

clear :: ProgramState -> (Place -> IO ()) -> IO ()
clear pst m = do
    p <- getFocusPos (table pst)
    m p 

reveal :: Bool -> ProgramState -> IO ()
reveal all pst = withUState pst f where
    f s@(g: _, _) = do
        startTimer False (timer pst)
        stopTimer False (timer pst)
        return $ smartUndo (reveal' all g) s

undo :: ProgramState -> IO ()
undo pst = withUState pst f where
    f (g:g':gs, redos) = do
        stopTimer False (timer pst)
        return (g':gs, g:redos)
    f x = return x

redo :: ProgramState -> IO ()
redo pst = withUState pst f where
    f (gs, g:redos) = return (g:gs, redos)
    f x = return x

newGame :: ProgramState -> IO ()
newGame pst = withUState pst f where
    f (as, _) = do
        resetTimer (timer pst)
        return ([last as], [])

resizeTable_ :: ProgramState -> IO ()
resizeTable_ pst = do

    (g:_, _) <- readMVar (gstate pst)
    resizeTable (size g) (table pst)
    newGame pst


addMenuItem 
    :: GladeXML
   -> (String, IO ())
   -> IO (ConnectId MenuItem)

addMenuItem xml (str, action) = do
    m   <- xmlGetWidget xml castToMenuItem str
    onActivateLeaf m action


preferences :: ProgramState -> IO ()
preferences pst = do
    xml <- msglade
    a   <- xmlGetWidget xml castToDialog "dialog1"
    sx  <- xmlGetWidget xml castToSpinButton "spinbutton3"
    sy  <- xmlGetWidget xml castToSpinButton "spinbutton1"
    ms  <- xmlGetWidget xml castToSpinButton "spinbutton4"

    al@(g:_, _) <- takeMVar $ gstate pst
    let (xS,yS) = size g
    set sx [spinButtonValue := fromIntegral xS]
    set sy [spinButtonValue := fromIntegral yS]
    set ms [spinButtonValue := fromIntegral $ mines g]

    let setRange = do
        x <- get sx spinButtonValue
        y <- get sy spinButtonValue
        spinButtonSetRange ms 0 (x*y)

    onValueSpinned sx setRange
    onValueSpinned sy setRange

    setRange

    r <- dialogRun a
    case r of
        ResponseOk  -> do
            x   <- get sx spinButtonValue
            y   <- get sy spinButtonValue
            mines_   <- get ms spinButtonValue
            putMVar (gstate pst) ([initGState (round x, round y) (round mines_)], [])
            resizeTable_ pst

        _ -> 
            putMVar (gstate pst) al

    widgetDestroy a
    return ()



checkWin :: ProgramState -> (GState -> StdGen -> ([(Place, State)], GState, StdGen)) -> IO ()
checkWin pst f = withUState pst $ \x -> case x of
    s@(g:gs, redo)   | not (isEnd g) ->  do
        startTimer False $ timer pst
        r <- takeMVar $ seed pst
        let (ps, g', r') = f g r
        adjustButtsInc (table pst) ps
        putMVar (seed pst) r' 
        when (isEnd g') $ do
            _ti <- stopTimer False $ timer pst
            return ()
        return $ smartUndo g' s

    _   -> return x

smartUndo g' (gs, redo) = (g': drop x gs, if x==1 then redo else [])
 where
   x = deservesUndo g' gs


--------------------------

aboutDialog :: IO ()
aboutDialog =  do
    xml <- msglade
    a   <- xmlGetWidget xml castToAboutDialog "aboutdialog1"
    dialogRun a
    widgetDestroy a
    return ()

helpDialog :: IO ()
helpDialog =  do
    xml <- msglade
    a   <- xmlGetWidget xml castToDialog "dialog2"
    dialogRun a
    widgetDestroy a
    return ()


withUState pst f = modifyMVarInSeparateThread (gstate pst) (\x -> f x >>= h)  where
    h s@(g: gs, redos) = do
        adjustButts (table pst) (board g)
        setTableSt (table pst) $ if isEnd g then const Fixed else ff
        labelSetText (label pst) $ info g
        return s

    ff (Normal x) = Normal x
    ff _ = Normal Nothing
-------------------

when' :: Monad m => m Bool -> m a -> m ()
when' f g = f >>= \b -> if b then g >> return () else return ()

-- | modifies an mvar if it is not taken already
modifyMVarInSeparateThread :: MVar a -> (a -> IO a) -> IO ()
modifyMVarInSeparateThread v f = do
    b <- isEmptyMVar v
    if not b 
        then do
            forkIO (modifyMVar_ v f)
            return () 
        else 
            return ()


------------