hs-pgms-0.1: src/GUI/Stats.hs
-- |
-- Module : GUI.Stats
-- Copyright : (c) 2008 Bertram Felgenhauer
-- License : BSD3
--
-- Maintainer : Bertram Felgenhauer <int-e@gmx.de>
-- Stability : experimental
-- Portability : portable
--
-- This module is part of Haskell PGMS.
--
-- Statistics window. Runs a thread for gathering game statistics and displays
-- continuously updating counts.
--
{-# LANGUAGE BangPatterns #-}
module GUI.Stats (
runStats,
) where
import Mine
import GUI.Common
import qualified Graphics.UI.Gtk as G
import System.Glib.Attributes (AttrOp (..))
import Control.Monad
import Control.Concurrent.MVar
import Control.Concurrent
import System.Random
import Data.IORef
import Numeric
data Stat = Stat !Int !Int !Int !Int
-- create window and thread for currently selected board config and strategy
runStats :: Globals -> IO ()
runStats g = do
s <- readIORef (gState g)
let strat = sStrategy s
cfg = sConfig s
-- communication: counters for won, unfinished, lost and total games
counter <- newMVar (Stat 0 0 0 0)
-- the worker works in chunks of this size
chunks <- newMVar 1
-- the worker sets this MVar when it has new data.
notify <- newMVar ()
-- start worker
thread <- forkIO $ gatherStats counter chunks notify strat cfg
-- create window. display strategy name in the title.
win <- G.windowNew
win `G.set` [G.windowTitle := "Statistics for " ++ sName strat]
vbox <- G.vBoxNew False 2
win `G.containerAdd` vbox
hbox <- G.hBoxNew False 2
vbox `G.containerAdd` hbox
-- display information about board configuration
configFrame <- G.frameNew
hbox `G.containerAdd` configFrame
configFrame `G.frameSetLabel` "Board"
configTable <- G.tableNew 3 2 False
configFrame `G.containerAdd` configTable
let Config { cSize = Pos sX sY, cMines = m } = sConfig s
forM_ (zip3 [0..] ["width", "height", "mines"] [sX, sY, m])
$ \(c, name, val) -> do
label <- G.labelNew (Just name)
G.miscSetAlignment label 0 0.5
G.tableAttach configTable label 0 1 c (c+1) [G.Fill] [] 5 2
label <- G.labelNew (Just (show val))
G.miscSetAlignment label 1 0.5
G.tableAttach configTable label 1 2 c (c+1) [G.Expand, G.Fill] [] 5 2
-- display running counts
statsFrame <- G.frameNew
hbox `G.containerAdd` statsFrame
statsFrame `G.frameSetLabel` "Statistics"
statsTable <- G.tableNew 3 3 False
statsFrame `G.containerAdd` statsTable
counters <- forM (zip [0..] ["won", "unfinished", "lost"])
$ \(c, label) -> do
label <- G.labelNew (Just label)
G.miscSetAlignment label 0 0.5
G.tableAttach statsTable label 0 1 c (c+1) [G.Fill] [] 5 2
label2 <- G.labelNew (Just "0")
G.miscSetAlignment label2 1 0.5
G.tableAttach statsTable label2 1 2 c (c+1) [G.Expand, G.Fill] [] 5 2
label3 <- G.labelNew (Just "0.00%")
G.miscSetAlignment label3 1 0.5
G.tableAttach statsTable label3 2 3 c (c+1) [G.Expand, G.Fill] [] 5 2
return (label2, label3)
-- provide a button for pausing the worker
button <- G.toggleButtonNewWithLabel "Pause"
vbox `G.containerAdd` button
button `G.onToggled` do
active <- G.toggleButtonGetActive button
if active then takeMVar chunks >> return () else putMVar chunks 1
-- callback for updating UI
-- we can't do Gtk+ calls from the worker, so we poll the notify MVar
let update = do
x <- tryTakeMVar notify
case x of
Just () -> do
Stat w u l t <- takeMVar counter
let total = w + u + l
d = total - t
putMVar counter (Stat w u l total)
c <- tryTakeMVar chunks
case c of
Just d' -> do
-- we got d updates, and the previous chunk size was d'.
-- set a new chunk size.
putMVar chunks (maximum [1, d' `div` 2, d `div` 2])
_ -> return ()
-- update the labels in the statistics frame
forM_ (zip [w, u, l] counters) $ \(c, (labelN, labelP)) -> do
labelN `G.labelSetText` show c
when (total > 0) $ do
let pct = 100 * fromIntegral c / fromIntegral total
labelP `G.labelSetText` showGFloat (Just 2) pct "%"
_ -> return ()
return True
timer <- G.timeoutAddFull update G.priorityDefaultIdle 100
-- don't forget to clean up when the user closes the window
win `G.onDestroy` do
killThread thread
G.timeoutRemove timer
G.widgetShowAll win
-- thread for collecting statistics
gatherStats :: MVar Stat -> MVar Int -> MVar () -> Strategy -> Config -> IO ()
gatherStats counter chunks notify strategy cfg = do
n <- readMVar chunks
let stats :: Int -> Int -> Int -> Int -> IO Stat
stats 0 !w !u !l = return (Stat w u l 0)
stats i !w !u !l = do
[gen1, gen2] <- replicateM 2 newStdGen
let (res, _) = playGame cfg gen1 (sRun strategy gen2)
case res of
Won -> stats (i-1) (w+1) u l
Unfinished _ -> stats (i-1) w (u+1) l
Lost -> stats (i-1) w u (l+1)
Stat w u l _ <- stats n 0 0 0
Stat w' u' l' t' <- takeMVar counter
putMVar counter $! Stat (w + w') (u + u') (l + l') t'
tryPutMVar notify ()
gatherStats counter chunks notify strategy cfg