minesweeper (empty) → 0.4
raw patch · 14 files changed
+2210/−0 lines, 14 filesdep +basedep +cairodep +containerssetup-changed
Dependencies added: base, cairo, containers, glade, gtk, random, time
Files
- Core.hs +247/−0
- Delay.hs +35/−0
- Game.hs +253/−0
- LICENSE +31/−0
- Minesweeper.hs +254/−0
- Place.hs +76/−0
- PlaceMap.hs +39/−0
- PlaceSet.hs +86/−0
- Setup.hs +5/−0
- Table.hs +275/−0
- TableGraphics.hs +168/−0
- Timer.hs +117/−0
- minesweeper.cabal +49/−0
- ms.glade +575/−0
+ Core.hs view
@@ -0,0 +1,247 @@+-- | Core data types and functions for the game.+module Core+ ( MMap+ , emptyMMap+ , setSum+ , solutions'+ ) where++-------------------------------------------++import Place+import PlaceSet+import qualified PlaceMap as M++import Data.Function+import Data.Maybe+import Data.List hiding ((\\), insert, delete)+import qualified Data.List as List+++-------------------------------------------++data Constraint+ = Constraint+ { places_ :: PlaceSet+ , value :: Int+ }++ deriving Eq++data ConstraintView+ = BigConstraint PlaceSet Int+ | SmallConstraint PlaceSet Int+ | Clear Place+ | Mine Place++view :: Constraint -> ConstraintView+view (Constraint ps v)+ | size ps == 1 && v == 1 = Mine $ head $ placeSetToList ps+ | size ps == 1 && v == 0 = Clear $ head $ placeSetToList ps+ | size ps > big = BigConstraint ps v+ | otherwise = SmallConstraint ps v++big :: Int+big = 10+++---------------------------------------++constraint :: [Place] -> Int -> Constraint+constraint ps v + = Constraint (listToPlaceSet ps) v++subConstraint :: Constraint -> Constraint -> Bool+subConstraint a b = places_ a `isSubsetOf` places_ b++similarConstraint :: Constraint -> Constraint -> Bool+similarConstraint a b = places_ a == places_ b+++-- csak akkor, ha subConstraint!+(.-.) :: Constraint -> Constraint -> Constraint+Constraint ps v .-. Constraint ps' v' = Constraint (ps \\ ps') (v-v')++connectionStrength :: Constraint -> Constraint -> [Constraint]+connectionStrength (Constraint ps v) (Constraint ps' v') = [Constraint l i | i<-[min_ .. max_]] where ++ min_ = maximum [0, v - (size ps - m), v' - (size ps' - m)]+ max_ = minimum [m, v, v']++ m = size l+ l = intersection ps ps'++constraintSolutions :: Constraint -> Integer+constraintSolutions (Constraint ps v) = binom (size ps) v+++binom :: Int -> Int -> Integer+binom m n = binoms !! m !! min n (m-n)++binoms :: [[Integer]]+binoms = iterate f [1] where f l = zipWith (+) (l++[0]) ([0]++l)+++-------------------------------------------+++data Constraints+ = Unsolvable+ | Solvable + [Constraint] -- big constraints (usually one) + PlaceSet -- cleared places+ PlaceSet -- places with mines+ (M.PlaceMap [Constraint]) -- other constraints++---------------------------------------++addConstraintSimple :: Constraint -> Constraints -> Constraints+addConstraintSimple _ Unsolvable = Unsolvable +addConstraintSimple c (Solvable bc cl mi m) = case view c of+ Clear p -> Solvable bc (insert p cl) mi m+ Mine p -> Solvable bc cl (insert p mi) m+ BigConstraint _ _ -> Solvable (c:bc) cl mi m+ SmallConstraint ps _-> Solvable bc cl mi $ foldr f m $ placeSetToList ps+ where++ f p m' = case M.lookup p m' of+ Nothing -> M.insert p [c] m'+ Just cs -> M.insert p (c:cs) m'++deleteConstraint :: Constraint -> Constraints -> Constraints+deleteConstraint _ Unsolvable = Unsolvable +deleteConstraint c (Solvable bc cl mi m) = case view c of+ Clear p -> Solvable bc (delete p cl) mi m+ Mine p -> Solvable bc cl (delete p mi) m+ BigConstraint _ _ -> Solvable (deleteBy similarConstraint c bc) cl mi m+ SmallConstraint ps _-> Solvable bc cl mi $ foldr f m $ placeSetToList ps + where++ f p m' = case M.lookup p m' of+ Just cs -> case deleteBy similarConstraint c cs of+ [] -> M.delete p m'+ l -> M.insert p l m'++deleteConstraints :: [Constraint] -> Constraints -> Constraints+deleteConstraints cs m = foldr deleteConstraint m cs++dependentConstraints :: Constraint -> Constraints -> [Constraint]+dependentConstraints c@(Constraint ps _) (Solvable bc cl mi m)+ = List.delete c $ l ++ filter (not . disjunct ps . places_) bc+ where + l = map toClear (placeSetToList $ intersection cl ps) + ++ map toMine (placeSetToList $ intersection mi ps) + ++ nub (concatMap f $ placeSetToList ps) ++ toClear p = constraint [p] 0+ toMine p = constraint [p] 1++ f p = case M.lookup p m of+ Just cs -> cs+ Nothing -> []++---------------------------------------++-- calculate number of different solutions+solutions :: Constraints -> Integer+solutions Unsolvable = 0+solutions (Solvable bc _ _ cs) = product (map (constraintSolutions . fst) indep) * case dep of+ [] -> 1+ ((c, xs): _) -> let+ cs' = minimumBy (compare `on` length) $ map (connectionStrength c) xs+ in sum [solutions (addConstraint y s'') | y<-cs']+ where+ s' = Solvable bc empty empty cs+ s'' = deleteConstraints (map fst indep) s'++ (indep, dep) = partition (null . snd) $ map (`dependentConstraints_` s') $ nub (concatMap snd $ M.toList cs) ++ bc++dependentConstraints_ c cs = (c, dependentConstraints c cs)++solvable :: Constraints -> Bool+solvable Unsolvable = False+solvable (Solvable bc _ _ cs) = case concatMap snd (M.toList cs) ++ bc of+ [] -> True+ (c: _) -> case dependentConstraints c s' of+ [] -> solvable (deleteConstraint c s')+ (x:_) -> let+ cs' = connectionStrength c x+ in or [solvable (addConstraint y s') | y<-cs']+ where+ s' = Solvable bc empty empty cs++-- may be faster?+omittable :: Constraint -> Constraints -> Bool+omittable c@(Constraint ps v) cs = all (not . solvable) [addConstraint (Constraint ps i) cs | i <- [v-1,v-2..0] ++ [v+1..size ps]]+{-+ - if p1 + ... + pI = n has no solution except for n = n0, then p1 + ... + pI = n0 is not needed:+ pA + pB = 1, pB + pC = 1, pC + pD = 1, pD + pA = 1 + --> pA + pB = 1, pB + pC = 1, pC + pD = 1+ - ...+-}++---------------------------------------++addConstraintSmart :: Constraint -> Constraints -> Constraints+addConstraintSmart c cs+ | not (solvable cs') = Unsolvable+-- | omittable c cs = cs -- not really needed+ | otherwise = cs'+ where+ cs' = addConstraint c cs+++addConstraint :: Constraint -> Constraints -> Constraints+addConstraint _ Unsolvable = Unsolvable +addConstraint n@(Constraint ps v) c+ | v<0 || v>s = Unsolvable+ | s==0 = c+ | s>1 && v==0 = addConstraints [constraint [p] 0 | p<- placeSetToList ps] c+ | s>1 && v==s = addConstraints [constraint [p] 1 | p<- placeSetToList ps] c+ | any ((/= v) . value) similar = Unsolvable+ | not (null similar) = c+ | any (==0) str = Unsolvable+ | ((x, [y]): _) <- oneStrength+ = addConstraints [n .-. y, x .-. y, y] $ deleteConstraint x c+ | otherwise = addConstraints [x .-. n | x <- supDomains] $ addConstraintSimple n $ deleteConstraints supDomains c + where+ s = size ps++ supDomains = [x | x <- dep, n `subConstraint` x]++ oneStrength = filter ((==1) . length . snd) strength+ str = map (length . snd) strength++ strength = [(x, connectionStrength n x) | x <- dep, x `subConstraint` n]++ (similar, dep) = partition (similarConstraint n) $ dependentConstraints n c+++addConstraints :: [Constraint] -> Constraints -> Constraints+addConstraints l c = foldr addConstraint c l++++-----------------------------------------------++data MMap = MMap Constraints Integer++emptyMMap :: MMap+emptyMMap = mkMMap $ Solvable [] empty empty M.empty++mkMMap :: Constraints -> MMap+mkMMap c = MMap (if s==0 then Unsolvable else c) s+ where+ s = solutions c++solutions' :: MMap -> Integer+solutions' (MMap _ i) = i++setSum :: PlaceSet -> Int -> MMap -> MMap+setSum ps v (MMap c _) = mkMMap $ addConstraint (Constraint ps v) c++---------------------------------------++++
+ Delay.hs view
@@ -0,0 +1,35 @@+module Delay + ( doLater+ , cancelAction+ , newCancelVar+ , CancelVar+ ) where++import Control.Concurrent+import Control.Concurrent.MVar++--------------------++newtype CancelVar = C (MVar (Maybe ThreadId))++newCancelVar :: IO CancelVar+newCancelVar = fmap C $ newMVar Nothing++cancelAction :: CancelVar -> IO ()+cancelAction (C v) = do+ swapMVar v Nothing+ return ()++doLater :: RealFrac t => CancelVar -> t -> IO () -> IO ()+doLater (C v) t action = do+ tid <- forkIO $ do+ threadDelay $ round $ 1000000 * t+ mytid <- myThreadId+ b <- readMVar v+ case b of+ Just tid | tid == mytid -> action+ _ -> return ()+ swapMVar v (Just tid)+ return ()++
+ Game.hs view
@@ -0,0 +1,253 @@++module Game+ ( GState+ , size+ , board+ , Board+ , initGState+ , deservesUndo+ , revealRec+ , flag+ , info+ , isEnd+ , isWin+ , State (..)+ , getP+ , mines+ , reveal'+-- , diff+ ) where++import Place +import PlaceSet hiding (size, empty, insert, delete)+import qualified PlaceSet+import Core++import Numeric+import Data.Maybe+import Data.List hiding (delete, insert)+import qualified Data.List as List+import Data.Map hiding (size, singleton, map)+import qualified Data.Map as Map++import System.Random++----------------------------------------++isNotMineAt :: Place -> MMap -> (Rational, MMap)+isNotMineAt p m+ = (fromIntegral (solutions' m') / fromIntegral (solutions' m), m')+ where+ m' = setSum (singleton p) 0 m++degree :: Size -> Place -> MMap -> StdGen -> (Int, MMap, StdGen)+degree s p m r = (x, l !! x, r')+ where+ ps = listToPlaceSet $ neighbours s p++ (x, r') = integerDomino (map solutions' l) r++ l = [setSum ps i m | i<-[0..PlaceSet.size ps]]++-----------------------------++data State + = Hidden !Bool (Maybe Rational)+ | Death+ | Clear Rational !Int -- veszélyesség; szomszédos aknák száma+ deriving (Eq, Show)++-----++type Board = Map Place State++get :: Place -> Board -> State+get p b = case Map.lookup p b of+ Just s -> s+-- _ -> Hidden++set :: Place -> State -> Board -> Board+set p s b = insert p s b++-----------------------------++data GState = GS + { field :: MMap+ , board_ :: Board+ , flagged :: Int+ , cleared :: Int+ , alive :: Rational + , mines :: Int + , size :: Size+-----------+ , revMod :: Maybe Rational+ }++deservesUndo g (g':_) | board_ g == board_ g' = 1+deservesUndo g (_:g':_) | board_ g == board_ g' = 2+deservesUndo _ _ = 0++initGState :: Size -> Int -> GState+initGState s mines_ = GS + { field = setSum (places s) mines_ emptyMMap+ , board_ = Map.fromList $ zip (placeSetToList $ places s) $ repeat $ Hidden False Nothing+ , flagged = 0+ , cleared = 0+ , alive = 1+ , mines = mines_+ , size = s+ , revMod = Nothing+ }++reveal' :: Bool -> GState -> GState+reveal' all gs + = case revMod gs of+ Just 0 | all -> unreveal gs+ Just i | i > 0 && not all -> unreveal gs+ _ -> revealBoard all gs++unreveal gs | isJust (revMod gs)+ = gs { revMod = Nothing, board_ = Map.map f $ board_ gs } where+ + f (Hidden fl _) = Hidden fl Nothing+ f x = x+unreveal gs = gs++revealBoard all gs -- | revMod gs+ | solutions' (field gs) == 0 = gs+ | otherwise+ = gs { board_ = foldr (uncurry Map.insert) (board_ gs) b, revMod = Just $ if all then 0 else mi } where+ + b = concatMap h $ Map.toList $ board_ gs++ mi = maximum $ map (k . snd) b where k (Hidden _ (Just i)) = i++ h (p, Hidden fl _) = [(p, Hidden fl $ Just $ fst $ isNotMineAt p (field gs))]+ h _ = []++board gs + = case revMod gs of+ Just i | i /= 0 -> Map.map (f i) $ board_ gs+ _ -> board_ gs+ where+ f i (Hidden False (Just j)) | j /= 0 && j < i = Hidden False Nothing+ f _ x = x+++isHidden (Hidden False x) = True+isHidden _ = False++reveal :: Place -> GState -> StdGen -> (GState, StdGen)+reveal p g r+ | not $ isHidden (get p (board_ g)) = (g, r)+ | pr == 0 = (g { field = f, alive = 0, board_ = insert p Death (board_ g) }, r)+ | otherwise = (g { field = f', board_ = insert p (Clear pr d) (board_ g), alive = pr * alive g, cleared = 1 + cleared g }, r')+ where+ (pr, f) = isNotMineAt p (field g)+ (d, f', r')= degree (size g) p f r++flag :: Place -> GState -> ([(Place, State)], GState)+flag p g = case get p $ board_ g of+ Hidden True r -> h (Hidden False r) (-1)+ Hidden False r | flagged g < mines g -> h (Hidden True r) 1+ _ -> ([], g)+ where+ h x c = ([(p, x)], g { flagged = flagged g + c, board_ = set p x $ board_ g })++getP :: Place -> GState -> State+getP p g = get p (board_ g)+++-------------++data Report + = Report Int Int Rational Double+ deriving Eq++instance Show Report where+ show (Report _ _ 0 _) = "Sorry, you died of necessity."+ show (Report 0 0 x y) = "Congratulations! You won with " ++ show_ 2 (luckinessFunction x) ++ " luckyness."+ show (Report i _ x y) = "Mines left: " ++ show i ++ " Information: " ++ show_ 1 (100*y) ++ "%" ++ " Luckyness: " ++ show_ 2 (luckinessFunction x)++luckinessFunction :: Rational -> Double+luckinessFunction x = max (- log (realToFrac x) / log 4) 0++show_ :: Int -> Double -> [Char]+show_ i x = showFFloat (Just i) x ""++eval :: GState -> Report+eval g@(GS {size= s@(xS, yS)}) + = Report (mines g - flagged g) (xS*yS - cleared g - flagged g) (alive g) y+ where+ i = solutions' $ setSum (places s) (mines g) emptyMMap++ a = fromIntegral (solutions' $ field g) / fromIntegral i+ b = 1 / fromIntegral i++ y+ | b == 1 = 1+ | otherwise = luckinessFunction a / luckinessFunction b++isEnd :: GState -> Bool+isEnd g = case eval g of+ Report _ _ 0 _ -> True+ Report 0 0 _ _ -> True+ _ -> False++isWin :: GState -> Bool+isWin g = case eval g of+ Report 0 0 _ _ -> True+ _ -> False++info :: GState -> String+info = show . eval++-----------------------------+fff ~(x,g,r) = (x, unreveal g,r)++isFlagged (Hidden True _) = True+isFlagged _ = False++revealRec :: Place -> GState -> StdGen -> ([(Place, State)], GState, StdGen)+revealRec p g r = fff $ case getP p g of+ Clear _ x | x == sum [1 | q <- neighbours (size g) p, isFlagged (getP q g) ] + -> revRecL (neighbours (size g) p) g r+ _ -> revealRecS p g r++revealRecS :: Place -> GState -> StdGen -> ([(Place, State)], GState, StdGen)+revealRecS p g r = case getP p g of+ Hidden False _ -> revRec p $ reveal p g r+ _ -> ([], g, r)+++revRec :: Place -> (GState, StdGen) -> ([(Place, State)], GState, StdGen)+revRec p (g, r) = strictT2 p gp .: case gp of+ Clear _ 0 -> revRecL (neighbours (size g) p) g r+ _ -> ([], g, r)+ where+ gp = getP p g++revRecL :: [Place] -> GState -> StdGen -> ([(Place, State)], GState, StdGen)+revRecL [] g r = ([], g, r)+revRecL (p:ps) g r = l .++ revRecL ps g' r' where ++ ~(l, g', r') = revealRecS p g r++strictT2 a b = a `seq` b `seq` (a, b)++p .: ~(ps, g, r) = (p: ps, g, r)++l .++ ~(l', g, r) = (l ++ l', g, r)++----------++-- | Get a value from a discrete distribution with the domino algorithm.+integerDomino :: [Integer] -> StdGen -> (Int, StdGen)+integerDomino [_] r = (0, r)+integerDomino l r = (j, r')+ where+ (x, r') = randomR (1, sum l) r++ j = length $ takeWhile (<x) $ scanl1 (+) l++
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright Péter Diviánszky 2008++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Péter Diviánszky nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ Minesweeper.hs view
@@ -0,0 +1,254 @@++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 ()+++------------+
+ Place.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE ViewPatterns #-}+-- | Places.+module Place + (+ -- * Size+ Size++ -- * Place+ , Place+ , place+ , coords++ -- * For performance+ , hashPlace+ , unHashPlace+ , placesInAColumn++ -- * Useful functions+ , neighbours++ ) where++import Data.Bits++-----------++-- | Size of a board.+type Size = (Int, Int)+++-- | Place on a board (with fixed width).+--+-- Simpler but slower implementation: +--+-- > data Place = P !Int !Int +-- > deriving (Eq, Ord, Show)++newtype Place = P Int deriving (Eq, Ord)++instance Show Place where+ show (coords -> (x, y)) = "place " ++ show x ++ " " ++ show y++place :: Int -> Int -> Place+place i j = P $ shiftL i 8 .|. j++coords :: Place -> (Int, Int)+coords (P i) = (shiftR i 8, i .&. 255)++-- | Perfect hash function.+hashPlace :: Place -> Int+hashPlace (P i) = i++unHashPlace :: Int -> Place+unHashPlace = P++placesInAColumn :: Int -> (Int, Int) -> [Place]+placesInAColumn i (j1, j2) = map P $ [i' + j1 .. i' + j2] where i' = shiftL i 8++-- | Neighbours of a place.+--+-- Examples:+--+-- > neighbours (8, 8) (place 2 3) +-- > == [place 1 2, place 1 3, place 1 4, place 2 2, place 2 4, place 3 2, place 3 3, place 3 4]+--+-- > neighbours (8, 8) (place 2 1) +-- > == [place 1 1, place 1 2, place 2 2, place 3 1, place 3 2]+neighbours :: Size -> Place -> [Place]+neighbours (xS, yS) p@(P q) = f' q ++ (if j > 1 then f (q-1) else []) ++ (if j < yS then f (q+1) else [])+ where+ (i, j) = coords p++ f r = P r: (if i > 1 then [P (r - 256)] else []) ++ (if i < xS then [P (r + 256)] else [])+ f' r = (if i > 1 then [P (r - 256)] else []) ++ (if i < xS then [P (r + 256)] else [])++
+ PlaceMap.hs view
@@ -0,0 +1,39 @@+-- | Maps of places+module PlaceMap+ ( PlaceMap+ , fromList+ , toList+ , empty+ , lookup+ , insert+ , delete+ ) where++import Place++import qualified Data.IntMap as M+import Prelude hiding (lookup)++-------------++-- | Map of places.+type PlaceMap = M.IntMap++fromList :: [(Place, a)] -> PlaceMap a+fromList = M.fromList . map (\(p, a) -> (hashPlace p, a))++toList :: PlaceMap a -> [(Place, a)]+toList = map (\(p, a)-> (unHashPlace p, a)) . M.toList++empty :: PlaceMap a+empty = M.empty++lookup :: Place -> PlaceMap a -> Maybe a+lookup p m = M.lookup (hashPlace p) m++insert :: Place -> a -> PlaceMap a -> PlaceMap a+insert p a m = M.insert (hashPlace p) a m++delete :: Place -> PlaceMap a -> PlaceMap a+delete p m = M.delete (hashPlace p) m+
+ PlaceSet.hs view
@@ -0,0 +1,86 @@+-- | Sets of places+module PlaceSet+ ( PlaceSet+ , listToPlaceSet+ , placeSetToList+ , singleton+ , empty+ , nullPS+ , insert+ , delete+ , member+ , size+ , (\\)+ , unions+ , intersection+ , disjunct+ , isSubsetOf++ -- * Useful functions+ , places++ ) where++import Place++import qualified Data.IntSet as S++-------------++-- | Set of places.+type PlaceSet = S.IntSet++listToPlaceSet :: [Place] -> PlaceSet+listToPlaceSet = S.fromList . map hashPlace++placeSetToList :: PlaceSet -> [Place]+placeSetToList = map unHashPlace . S.toList++singleton :: Place -> PlaceSet+singleton = S.singleton . hashPlace++-- | The empty PlaceSet.+empty :: PlaceSet+empty = S.empty++insert :: Place -> PlaceSet -> PlaceSet+insert p ps = S.insert (hashPlace p) ps++delete :: Place -> PlaceSet -> PlaceSet+delete p ps = S.delete (hashPlace p) ps++-- | True if the PlaceSet is empty.+nullPS :: PlaceSet -> Bool+nullPS = S.null++member :: Place -> PlaceSet -> Bool+member p = S.member (hashPlace p)++size :: PlaceSet -> Int+size = S.size++-- | Difference.+(\\) :: PlaceSet -> PlaceSet -> PlaceSet+(\\) = (S.\\)++unions :: [PlaceSet] -> PlaceSet+unions = S.unions++disjunct :: PlaceSet -> PlaceSet -> Bool+disjunct a b = S.null (S.intersection a b)++intersection :: PlaceSet -> PlaceSet -> PlaceSet+intersection = S.intersection++isSubsetOf :: PlaceSet -> PlaceSet -> Bool+isSubsetOf = S.isSubsetOf++----------------------++-- | All places at a board.+places :: Size -> PlaceSet+places (xS, yS) = listToPlaceSet [p | x<-[1..xS], p <- placesInAColumn x (1, yS)]++++
+ Setup.hs view
@@ -0,0 +1,5 @@++import Distribution.Simple++main = defaultMainWithHooks defaultUserHooks+
+ Table.hs view
@@ -0,0 +1,275 @@+{-# LANGUAGE ViewPatterns #-}+module Table + ( GameTable+ , newTable+ , St (Stopped, Normal, Fixed)+ , setTableSt+ , resizeTable+ , adjustButts+ , adjustButtsInc+ , getFocusPos + ) where++import TableGraphics+import Place+import PlaceSet+import Game++import Graphics.Rendering.Cairo+import Graphics.UI.Gtk.Gdk.EventM++import Graphics.UI.Gtk hiding (Clear, fill)++import Control.Monad (when)+import Control.Concurrent.MVar++import qualified Data.Map as M++import Data.Maybe++---------------------++-- ez van kivetítve+data IState = IState+ { size_ :: (Int, Int)+ , board_ :: Board+ , focusPos :: Place+ , hidden :: St+ } ++data St + = Stopped + | Fixed + | Normal (Maybe Place)+ deriving Eq++data GameTable + = T + { frame :: AspectFrame+ , area :: DrawingArea + , states :: MVar IState+ , flag_ :: Place -> IO ()+ , reveal :: Place -> IO ()+ }++---------------------++newTable :: AspectFrame -> (Place -> IO ()) -> (Place -> IO ()) -> IO GameTable+newTable asp fA rA = do+ canvas <- drawingAreaNew+ m <- newMVar undefined+ let g = T asp canvas m fA rA++ containerAdd asp canvas+ set canvas [widgetCanFocus := True]+ widgetAddEvents canvas [LeaveNotifyMask, PointerMotionMask, KeyPressMask]++ canvas `on` exposeEvent $ updateCanvas g+ canvas `on` leaveNotifyEvent $ changeCanvas_ g+ canvas `on` motionNotifyEvent $ changeCanvas g+ canvas `on` buttonPressEvent $ changeCanvas' g+ canvas `on` keyPressEvent $ changeCanvasKey g+ return g++getFocusPos :: GameTable -> IO Place+getFocusPos g = fmap focusPos $ readMVar (states g)++setTableSt :: GameTable -> (St -> St) -> IO ()+setTableSt g fx = redrawButts_ g f where+ f ss+ | x == hh = ([], ss)+ | Stopped `elem` [x, hh]+ = (M.toList $ board_ ss, ss')+ | otherwise + = (map h $ focusPos ss: ff hh ++ ff x, ss')+ where+ ss' = ss { hidden = x }+ h p = (p, board_ ss M.! p)+ x = fx hh+ hh = hidden ss+ ff (Normal (Just x)) = [x]+ ff _ = []+++resizeTable :: (Int, Int) -> GameTable -> IO ()+resizeTable s@(xS, yS) g = do++ set (frame g) [aspectFrameRatio := fromIntegral xS / fromIntegral yS]++ swapMVar (states g) $ IState+ { size_ = s+ , board_ = M.fromList $ zip (placeSetToList $ places s) $ repeat $ Hidden False Nothing+ , focusPos = place 1 1+ , hidden = Normal Nothing+ } ++ win <- drawingAreaGetDrawWindow' g+ drawWindowClear win+ updateCanvas' g++------------------++updateCanvas :: GameTable -> EventM EExpose Bool+updateCanvas g = do+ liftIO $ updateCanvas' g+ return True++updateCanvas' g = do+ redrawButts_ g $ \ss -> (M.toList $ board_ ss, ss)++adjustButts :: GameTable -> Board -> IO ()+adjustButts g b =+ redrawButts_ g $ \ss -> ([p | (p,y) <- zip (M.assocs b) (M.elems $ board_ ss), snd p /= y], ss { board_ = b })+++adjustButtsInc :: GameTable -> [(Place, State)] -> IO ()+adjustButtsInc g [] = return ()+adjustButtsInc g ((p, s):ps) = do++ redrawButts_ g $ \ss -> ([(p, s)], ss { board_ = M.insert p s $ board_ ss })+ adjustButtsInc g ps++redrawButts :: GameTable -> [Place] -> IO ()+redrawButts g l + = redrawButts_ g $ \ss -> ([(p, board_ ss M.! p)| p <- l], ss)+++redrawButts_ :: GameTable -> (IState -> ([(Place, State)], IState)) -> IO ()+redrawButts_ g f = do++ ss <- takeMVar (states g)+ let (ps, ss') = f ss+ redrawButts__ g ss' ps+ putMVar (states g) $ ss'+++redrawButts__ :: GameTable -> IState -> [(Place, State)] -> IO ()+redrawButts__ gg g l = do+ win <- drawingAreaGetDrawWindow' gg+ let+ size = size_ g+ m = board_ g+ b = hidden g+ pp = case b of+ Normal x -> x+ _ -> Nothing+ foc = focusPos g+ let f (p, s) = do+ renderWithDrawable' p size win $ drawPlace (b == Stopped) (isNormal b && pp == Just p) (p == foc && isNormal b) p s+ mapM_ f l+ +isNormal (Normal _) = True+isNormal _ = False++renderWithDrawable' (coords -> (x,y)) (xx, yy) win m = do+ (width, height) <- drawableGetSize_ win+ let+ a = width/fromIntegral xx+ b = height/fromIntegral yy+ drawWindowBeginPaintRect win $ Rectangle (round $ a*fromIntegral (x-1)) (round $ b*fromIntegral (y-1)) (round a) (round b)+ renderWithDrawable win $ do+ scale a b+ translate (fromIntegral x - 0.5) (fromIntegral y - 0.5)+ m+ drawWindowEndPaint win+++drawableGetSize_ win = do + (width, height) <- drawableGetSize win+ return (realToFrac width, realToFrac height)++changeCanvas :: GameTable -> EventM EMotion Bool+changeCanvas g = do+ pos <- eventCoordinates+ liftIO $ do+ n <- calcPos_ pos g+ changeC n g+++changeCanvas_ :: GameTable -> EventM ECrossing Bool+changeCanvas_ g = liftIO $ do+ let n = Nothing+ changeC n g++changeC n g = do+ ss <- readMVar (states g)+ case hidden ss of+ Normal m -> do+ ss <- takeMVar (states g)+ putMVar (states g) $ ss { hidden = Normal n }++ if m /= n + then redrawButts g $ catMaybes [m, n]+ else return ()+ _ -> return ()+ return True+++changeCanvas' :: GameTable -> EventM EButton Bool+changeCanvas' g = do+ pos <- eventCoordinates+ b <- eventButton+ liftIO $ do+ ss <- readMVar (states g)+ when (isNormal (hidden ss)) $ do+ n <- calcPos_ pos g+ case (n, b) of+ (Just p, LeftButton) -> reveal g p+ (Just p, RightButton) -> flag_ g p+ _ -> return ()+ return True++++changeCanvasKey :: GameTable -> EventM EKey Bool+changeCanvasKey g = do+ k <- eventKeyName+ liftIO $ do+ case k of+ "Right" -> moveFocus ( 1, 0)+ "Left" -> moveFocus (-1, 0)+ "Down" -> moveFocus ( 0, 1)+ "Up" -> moveFocus ( 0,-1)+ _ -> return False+ where+ moveFocus (dx, dy) = do+ ss <- readMVar (states g)+ when (isNormal (hidden ss)) $ do+ ss <- takeMVar (states g)+ let + foc@(coords -> (x, y)) = focusPos ss+ (sx, sy) = size_ ss+ foc' = place (max 1 $ min sx $ dx + x) (max 1 $ min sy $ dy + y)+ putMVar (states g) $ ss { focusPos = foc' }+ if foc /= foc'+ then redrawButts g [foc, foc']+ else return ()+ return True+++drawingAreaGetDrawWindow' = widgetGetDrawWindow . area++calcPos_ pos g = do+ win <- drawingAreaGetDrawWindow' g+ ss <- readMVar (states g)++ (width, height) <- drawableGetSize_ win+ return $ calcPos (size_ ss) (width, height) pos++calcPos (xx, yy) (width, height) (x,y)+ | i + 1 `between` (1, xx) && j + 1 `between` (1, yy) {- && ii>0 && ii<9 && jj>0 && jj<9 -} + = Just $ place (i+1) (j+1)+ | otherwise = Nothing+ where+ a = floor (x/width*10* fromIntegral xx)+ b = floor (y/height*10* fromIntegral yy)++ (i,_ii) = a `divMod` 10+ (j,_jj) = b `divMod` 10++infix 4 `between`+a `between` (b, c) = b <= a && a <= c++++
+ TableGraphics.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE ViewPatterns #-}+module TableGraphics+ ( drawPlace+ ) where++import Place+import Game (State (..))++import Graphics.Rendering.Cairo++import Control.Monad (when)++-----------------------------------------+++drawPlace + :: Bool -- ^ signs are not shown+ -> Bool -- ^ highlighted+ -> Bool -- ^ focused+ -> Place+ -> State + -> Render ()++drawPlace bb b foc (coords -> (i,j)) st = do++++ rectangle (-0.4) (-0.4) 0.8 0.8+ setLineWidth 0.03+ setSourceRGB' bg+ strokePreserve+ setSourceRGB' $ case st of+ Clear _ _ -> bg+ Hidden True _ -> bg+ Death -> bg+ _ -> if b then bg else blue++ fill+ setSourceRGB' black+ if foc + then do+ rectangle (-0.35) (-0.35) 0.7 0.7+ setLineWidth 0.02+ setDash [0.1,0.1] (if even (i + j) then 0 else 0.1)+ stroke+ setDash [] 0+ else return ()+ setLineCap LineCapRound+ setLineWidth 0.1++ if bb + then return ()+ else case st of+ Death -> do+ setLineCap LineCapSquare+ moveTo (-0.25) (-0.25)+ lineTo 0.25 0.25+ stroke+ moveTo 0.25 (-0.25)+ lineTo (-0.25) 0.25+ stroke++ Hidden False (Just d) -> do+ setLineWidth 0.03+ arc 0 0 0.25 0 (2*pi*(1- realToFrac d))+ setSourceRGB' $ Color_ 0.3 0.4 0.5+ stroke+ arc 0 0 0.25 (2*pi*(1- realToFrac d)) (2*pi)+ setSourceRGB' $ Color_ 1 1 1+ stroke+ + Hidden True bbb -> do+ setLineWidth 0.03+ arc 0 0 0.25 0 (2*pi)+ setSourceRGB' $ if b then bg else blue+ fillPreserve+ setSourceRGB' black+ stroke+ when (maybe False (/=0) bbb) $ do+ setLineCap LineCapSquare+ moveTo (-0.25) (-0.25)+ lineTo 0.25 0.25+ stroke+ moveTo 0.25 (-0.25)+ lineTo (-0.25) 0.25+ stroke+ Clear _ n -> do+ case n of + 0 -> do+ setLineCap LineCapSquare+ setLineWidth 0.03+ moveTo (-0.2) 0+ lineTo 0.2 0+ stroke+ 1 -> do+ point 0 0+ 2 -> do+ point (-0.2) 0+ point 0.2 0+ 3 -> do+ point 0 (-0.20)+ point (-0.2) 0.14+ point 0.2 0.14+ 4 -> do+ point (-0.2) 0.2+ point 0.2 0.2+ point (-0.2) (-0.2)+ point 0.2 (-0.2)+ 5 -> do+ point 0 0+ point (-0.2) 0.2+ point 0.2 0.2+ point (-0.2) (-0.2)+ point 0.2 (-0.2)+ 6 -> do+ point (-0.2) 0+ point 0.2 0+ point (-0.2) 0.2+ point 0.2 0.2+ point (-0.2) (-0.2)+ point 0.2 (-0.2)+ 7 -> do+ point 0 0+ point (-0.2) 0+ point 0.2 0+ point (-0.2) 0.2+ point 0.2 0.2+ point (-0.2) (-0.2)+ point 0.2 (-0.2)+ 8 -> do+ point (-0.2) 0+ point 0.2 0+ point 0 (-0.2)+ point 0 0.2 + point (-0.2) 0.2+ point 0.2 0.2+ point (-0.2) (-0.2)+ point 0.2 (-0.2)+ return ()+ _ -> return ()+++point :: Double -> Double -> Render ()+point x y = do+ moveTo x y+ relLineTo 0 0+ stroke+++data Color_ = Color_ Double Double Double++bg, blue, black, white :: Color_+blue = Color_ 0.65 0.8 1+black = Color_ 0 0 0+white = Color_ 1 1 1+bg = Color_ 0.9 0.9 0.9++setSourceRGB' :: Color_ -> Render ()+setSourceRGB' (Color_ r g b) = setSourceRGB r g b+++betw ::+ Double -> Color_ -> Color_ -> Color_+betw pr (Color_ a b c) (Color_ a' b' c') = Color_ (f a a') (f b b') (f c c') where++ f i j = max 0 $ min 1 $ i + pr * (j - i)++
+ Timer.hs view
@@ -0,0 +1,117 @@+module Timer+ ( Tim+ , newTimer+ , startTimer+ , stopTimer+ , resetTimer+ , isActiveTimer++ , showSeconds+ ) where++import Control.Concurrent+import Control.Concurrent.MVar++import Data.Time.Clock++-------------------------------------------++data StoppedTimerState + = Initial | Stopped | Halted+ deriving Eq++data Timer+ = ActiveTimer + ThreadId -- which thread is responsible for the updating of the timer+ UTCTime+ | StoppedTimer + StoppedTimerState+ NominalDiffTime++type Action = String -> IO ()++type Tim = MVar + ( Action -- this is global for this module, but Haskell has no parameterized modules..+ , Timer)++-------------------++newTimer :: Action -> IO Tim+newTimer action + = newMVar (action, StoppedTimer Initial 0)++startTimer :: Bool -> Tim -> IO ()+startTimer b tim = do+ a@(timeL, xx) <- takeMVar tim+ case xx of+ StoppedTimer x s | b && x == Stopped || not b && x /= Halted -> do+ tid <- forkIO $ modTime tim+ ti <- getCurrentTime+ putMVar tim (timeL, ActiveTimer tid $ addUTCTime (-s) ti)+ _ -> do+ putMVar tim a++stopTimer :: Bool -> Tim -> IO NominalDiffTime+stopTimer b tim = do+ a@(timeL, x) <- takeMVar tim+ case x of+ ActiveTimer _ ti -> do+ ti' <- getCurrentTime+ let d = diffUTCTime ti' ti+ putMVar tim (timeL, StoppedTimer xx d)+ timeL $ (if b then "Stopped at " else "Halted at ") ++ showSeconds (round d)+ return d+ StoppedTimer _ d -> do+ putMVar tim (timeL, StoppedTimer xx d)+ return d+ where+ xx = if b then Stopped else Halted++resetTimer :: Tim -> IO ()+resetTimer tim = do+ (timeL, _) <- takeMVar tim+ timeL "Timer will start"+ putMVar tim (timeL, StoppedTimer Initial 0)+ return ()++isActiveTimer :: Tim -> IO Bool+isActiveTimer tim = do+ (_, x) <- readMVar tim+ return $ case x of+ ActiveTimer _ _ -> True+ _ -> False+++-----------------++modTime :: Tim -> IO ()+modTime tim = do+ tid <- myThreadId+ (timeL, x) <- readMVar tim+ case x of+ ActiveTimer tid' ti | tid == tid' -> do+ ti' <- getCurrentTime+ let diff = diffUTCTime ti' ti+ timeL $ "Time: " ++ showSeconds (round diff)+ threadDelay (computeWaitTime diff)+ modTime tim+ _ ->+ return ()++computeWaitTime :: NominalDiffTime -> Int {-milliseconds-}+computeWaitTime x = 1000000 * (1 + round y) - round (1000000 * y) - 100000 where (_, y) = properFraction x :: (Integer, NominalDiffTime)++-------------++showSeconds :: Integer -> String+showSeconds s = f h ++ ":" ++ f m' ++ ":" ++ f s' where++ (m, s') = divMod s 60+ (h, m') = divMod m 60++ f :: Integer -> String+ f i = reverse $ take 2 $ reverse $ "0" ++ show i++++
+ minesweeper.cabal view
@@ -0,0 +1,49 @@+name: minesweeper+version: 0.4+category: Game+synopsis: Minesweeper game which is always solvable without guessing+description: + Minesweeper game which is always solvable without guessing. + .+ TODO list:+ .+ * Source code documentation and improvement+ .+ * Implement the score table.+stability: alpha+license: BSD3+license-file: LICENSE+author: Péter Diviánszky+maintainer: divip@aszt.inf.elte.hu+data-files: ms.glade+cabal-version: >=1.4+build-type: Simple++executable minesweeper+ ghc-options: -Wall -fno-warn-incomplete-patterns -fno-warn-name-shadowing+ build-depends: base >= 3.0, + containers, + random, + time, + glade, + gtk >= 0.10, + cairo >= 0.10++ main-is: Minesweeper.hs++ other-modules: Place,+ PlaceSet,+ PlaceMap,+ Delay,+ Core, + Game,+ Table,+ TableGraphics,+ Timer++ extensions: RecursiveDo,+ NamedFieldPuns,+ ViewPatterns,+ PatternGuards++
+ ms.glade view
@@ -0,0 +1,575 @@+<?xml version="1.0"?>+<glade-interface>+ <!-- interface-requires gtk+ 2.16 -->+ <requires lib="gnome"/>+ <!-- interface-requires gnome 2475.30408 -->+ <!-- interface-naming-policy toplevel-contextual -->+ <widget class="GtkWindow" id="window1">+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="title" translatable="yes">The Lucky Minesweeper</property>+ <property name="window_position">center</property>+ <property name="default_width">500</property>+ <property name="default_height">590</property>+ <child>+ <widget class="GtkVBox" id="vbox1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <child>+ <widget class="GtkMenuBar" id="menubar1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <child>+ <widget class="GtkMenuItem" id="menuitem1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">_File</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem1">+ <property name="label">gtk-new</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ </widget>+ </child>+ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ </widget>+ </child>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem5">+ <property name="label">gtk-quit</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem5">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">_Edit</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu5">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <child>+ <widget class="GtkMenuItem" id="imagemenuitem9">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">_Clear</property>+ <property name="use_underline">True</property>+ <accelerator key="space" signal="activate"/>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="imagemenuitem11">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">_Flag</property>+ <property name="use_underline">True</property>+ <accelerator key="Return" signal="activate"/>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem2">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Hint</property>+ <property name="use_underline">True</property>+ <accelerator key="h" signal="activate" modifiers="GDK_CONTROL_MASK"/>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem6">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Probabilities</property>+ <property name="use_underline">True</property>+ <accelerator key="p" signal="activate" modifiers="GDK_CONTROL_MASK"/>+ </widget>+ </child>+ <child>+ <widget class="GtkSeparatorMenuItem" id="separatormenuitem6">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ </widget>+ </child>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem12">+ <property name="label">gtk-undo</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ <accelerator key="z" signal="activate" modifiers="GDK_CONTROL_MASK"/>+ </widget>+ </child>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem15">+ <property name="label">gtk-redo</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ <accelerator key="z" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">_Preferences</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu4">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem6">+ <property name="label">gtk-preferences</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem4">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">_Help</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem3">+ <property name="label">gtk-help</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkImageMenuItem" id="imagemenuitem10">+ <property name="label">gtk-about</property>+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_underline">True</property>+ <property name="use_stock">True</property>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">x</property>+ <property name="justify">center</property>+ <property name="single_line_mode">True</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="padding">5</property>+ <property name="position">3</property>+ </packing>+ </child>+ <child>+ <widget class="GtkHSeparator" id="hseparator1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">4</property>+ </packing>+ </child>+ <child>+ <widget class="GtkAspectFrame" id="aspectframe1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label_xalign">0</property>+ <property name="shadow_type">none</property>+ <property name="obey_child">False</property>+ <child>+ <placeholder/>+ </child>+ </widget>+ <packing>+ <property name="padding">5</property>+ <property name="position">5</property>+ </packing>+ </child>+ <child>+ <widget class="GtkHSeparator" id="hseparator2">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="pack_type">end</property>+ <property name="position">2</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label7">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">x</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="padding">5</property>+ <property name="pack_type">end</property>+ <property name="position">1</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+ <widget class="GtkAboutDialog" id="aboutdialog1">+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="border_width">5</property>+ <property name="title" translatable="yes">About The Lucky Minesweeper</property>+ <property name="resizable">False</property>+ <property name="window_position">center-on-parent</property>+ <property name="type_hint">dialog</property>+ <property name="has_separator">False</property>+ <property name="program_name">The Lucky Minesweeper</property>+ <property name="version">0.1</property>+ <property name="copyright" translatable="yes">Péter Diviánszky © 2008</property>+ <property name="comments" translatable="yes">Written in Haskell with GHC, Gtk2Hs and Glade</property>+ <property name="license" translatable="yes">Copyright Péter Diviánszky 2008++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Péter Diviánszky nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+</property>+ <property name="authors">Péter Diviászky, divip aszt inf elte hu</property>+ <property name="documenters"></property>+ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="spacing">2</property>+ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="layout_style">end</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="pack_type">end</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+ <widget class="GtkDialog" id="dialog1">+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="border_width">5</property>+ <property name="title" translatable="yes">Preferences</property>+ <property name="window_position">center-on-parent</property>+ <property name="type_hint">dialog</property>+ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="spacing">2</property>+ <child>+ <widget class="GtkTable" id="table1">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="n_rows">3</property>+ <property name="n_columns">2</property>+ <child>+ <widget class="GtkLabel" id="label2">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">Rows</property>+ </widget>+ </child>+ <child>+ <widget class="GtkSpinButton" id="spinbutton1">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="adjustment">10 1 15 1 10 0</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ </packing>+ </child>+ <child>+ <widget class="GtkSpinButton" id="spinbutton3">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="adjustment">10 1 15 1 10 0</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="top_attach">1</property>+ <property name="bottom_attach">2</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">Columns</property>+ </widget>+ <packing>+ <property name="top_attach">1</property>+ <property name="bottom_attach">2</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label4">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">Mines</property>+ </widget>+ <packing>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ </packing>+ </child>+ <child>+ <widget class="GtkSpinButton" id="spinbutton4">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="adjustment">20 0 100 1 10 0</property>+ <property name="snap_to_ticks">True</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="position">2</property>+ </packing>+ </child>+ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="layout_style">end</property>+ <child>+ <widget class="GtkButton" id="button1">+ <property name="label" translatable="yes">gtk-cancel</property>+ <property name="response_id">-6</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_stock">True</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GtkButton" id="button2">+ <property name="label" translatable="yes">gtk-ok</property>+ <property name="response_id">-5</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_stock">True</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ <property name="position">1</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="pack_type">end</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+ <widget class="GtkDialog" id="dialog2">+ <property name="width_request">450</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="border_width">5</property>+ <property name="window_position">center-on-parent</property>+ <property name="type_hint">dialog</property>+ <property name="has_separator">False</property>+ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox4">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="spacing">2</property>+ <child>+ <widget class="GtkTextView" id="textview1">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="pixels_above_lines">10</property>+ <property name="editable">False</property>+ <property name="wrap_mode">word</property>+ <property name="left_margin">10</property>+ <property name="right_margin">10</property>+ <property name="cursor_visible">False</property>+ <property name="text" translatable="yes">Minesweeper game which is always solvable without guessing. +The luck of the gamer is measured with 'luckiness'.+Luckiness = -1/2 * log2 p, where p is the probability of that the player would be alive if the game was a normal minesweeper game.+For example, luckiness = 10 means that since the game start the player cleared 20 positions where a mine was with 50% probability, so in a normal game the player probably would die 10 times.++Known bugs: +* There is no score table.+* The game may slow down at larger tables.+</property>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ <child>+ <widget class="GtkVBox" id="vbox3">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="border_width">5</property>+ <child>+ <widget class="GtkLabel" id="label6">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="label" translatable="yes">See also:</property>+ </widget>+ <packing>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GnomeHRef" id="href1">+ <property name="label" translatable="yes">_Minesweeper (Wikipedia)</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="relief">none</property>+ <property name="use_underline">True</property>+ <property name="url">http://en.wikipedia.org/wiki/Minesweeper_(computer_game)</property>+ <property name="text"></property>+ </widget>+ <packing>+ <property name="position">2</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">2</property>+ </packing>+ </child>+ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area4">+ <property name="visible">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="layout_style">end</property>+ <child>+ <widget class="GtkButton" id="button3">+ <property name="label" translatable="yes">gtk-ok</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>+ <property name="use_stock">True</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="pack_type">end</property>+ <property name="position">1</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+</glade-interface>