packages feed

midimory-0.0.1: src/Main.hs

module Main where

import qualified Configuration as Config
import qualified Option
import qualified MIDI

import qualified Sound.ALSA.Sequencer.Event as Event

import qualified Graphics.UI.WX as WX
import Graphics.UI.WX
   (Prop((:=)), set, get, text, selection, command, on,
    close, container, widget,
    layout, margin, row, column, )

import qualified System.Random as Rnd

import qualified Control.Monad.Trans.State as MS
import qualified Control.Monad.Trans.Class as MT
import Control.Monad (forM, )
import Control.Applicative ((<$>))

import qualified Data.Sequence as Seq
import qualified Data.Array as Array
import Data.Sequence (Seq, ViewL((:<)), (><), )
import Data.Array ((!))
import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef, )


pick :: Int -> Seq a -> (a, Seq a)
pick n as =
   let (prefix, suffix) = Seq.splitAt n as
   in  case Seq.viewl suffix of
          Seq.EmptyL -> error "pick: index too large"
          a :< rest -> (a, prefix >< rest)

shuffle :: (Rnd.RandomGen g) => g -> [a] -> [a]
shuffle g xs =
   flip MS.evalState g $
   flip MS.evalStateT (Seq.fromList xs) $
   forM (takeWhile (>=0) $ tail $ iterate (subtract 1) (length xs)) $ \maxN -> do
      n <- MT.lift $ MS.state $ Rnd.randomR (0, maxN)
      MS.state $ pick n


data Player = PlayerA | PlayerB

switchPlayer :: Player -> Player
switchPlayer PlayerA = PlayerB
switchPlayer PlayerB = PlayerA

formatPlayer :: Player -> String
formatPlayer PlayerA = "Player A"
formatPlayer PlayerB = "Player B"

makeMessage :: Player -> Int -> String
makeMessage player count =
   formatPlayer player ++ ": Hit " ++
   (if count == 0 then "first" else "second") ++
   " button!"


shufflePitches :: Config.T -> IO (Array.Array (Int, Int) Event.Pitch)
shufflePitches cfg = do
   seed <- Rnd.randomIO
   return $
      Array.listArray
         ((0, 0), (Config.rows cfg - 1, Config.columns cfg - 1))
         (shuffle (Rnd.mkStdGen seed) ((\ps -> ps++ps) $ Config.pitches cfg))

makeGUI :: Config.T -> MIDI.Sequencer -> IO ()
makeGUI cfg sequ = do
   f <- WX.frame [text := "Midimory"]
   p <- WX.panel f []
   pitches <- newIORef =<< shufflePitches cfg
   selected <- newIORef Nothing
   player <- newIORef PlayerA
   message <- WX.staticText p [ text := makeMessage PlayerA 0 ]
   let maxScore = div (Config.rows cfg * Config.columns cfg) 2
   scoreA <- WX.vgauge p maxScore []
   scoreB <- WX.vgauge p maxScore []
   let playerScore pl =
          case pl of
             PlayerA -> scoreA
             PlayerB -> scoreB
       isGameOver = do
          a <- get scoreA selection
          b <- get scoreB selection
          return $
             if a+b < maxScore
               then []
               else
                  case compare a b of
                     LT -> [PlayerB]
                     GT -> [PlayerA]
                     EQ -> [PlayerA, PlayerB]
   matrix <-
      forM (zip [0..] (Config.texts cfg)) $ \(r,ln) ->
         forM (zip [0..] ln) $ \(c,label) -> do
            b <- WX.button p [ text := label ]
            set b [
               on command := do
                  pitch <- (! (r,c)) <$> readIORef pitches
                  MIDI.sendNote sequ pitch
                  mfirst <- readIORef selected
                  case mfirst of
                     Nothing -> do
                        writeIORef selected $ Just (b, pitch)
                        set b [ WX.enabled := False ]
                        pl <- readIORef player
                        set message [ text := makeMessage pl 1 ]
                     Just (firstButton, firstPitch) -> do
                        writeIORef selected Nothing
                        pl <- readIORef player
                        if firstPitch == pitch
                          then do
                             set b [ WX.enabled := False ]
                             let score = playerScore pl
                             n <- get score selection
                             set score [ selection := succ n ]
                          else do
                             set firstButton [ WX.enabled := True ]
                             modifyIORef player switchPlayer
                        newpl <- readIORef player
                        gameOver <- isGameOver
                        set message [ text :=
                           case gameOver of
                              [] -> makeMessage newpl 0
                              [winner] ->
                                 "Game Over! The winner is " ++
                                 formatPlayer winner
                              _ -> "Game Over! Stalemate!" ]
             ]
            return b
   restart <-
      WX.button p [
         text := "Restart",
         on command := do
            mapM_ (mapM_ (\b -> set b [ WX.enabled := True ])) matrix
            set scoreA [ selection := 0 ]
            set scoreB [ selection := 0 ]
            set message [ text := makeMessage PlayerA 0 ]
            writeIORef selected Nothing
            writeIORef player PlayerA
            writeIORef pitches =<< shufflePitches cfg
         ]
   quit <- WX.button p [text := "Quit", on command := close f]
   set f [layout :=
      container p $ margin 10 $
         row 5 $
            WX.vfill (widget scoreA) :
            (column 5 $
                WX.hfill (widget message) :
                WX.grid (Config.columns cfg) (Config.rows cfg)
                   (map (map (WX.fill . widget)) matrix) :
                row 5 [WX.hfill (widget restart), WX.hfill (widget quit)] :
                []) :
            WX.vfill (widget scoreB) :
            []
    ]


main :: IO ()
main = do
   (config, (dests,chan)) <- Option.multiArgs "Concentration game for tones"
   MIDI.withSequencer "Midimory" chan $ \sequ -> do
      mapM_ (MIDI.parseAndConnect sequ) dests
      WX.start $ makeGUI config sequ