packages feed

HTicTacToe-0.1: HTicTacToe.hs

{-
    The MIT License
    
    Copyright (c) 2010 Korcan Hussein.
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
-}
module Main where

import Prelude hiding (init)

import System.Environment

import System.Console.GetOpt

--import Data.Word
import Data.List hiding (init)

import Control.Monad
import Control.Monad.Trans
import Control.Exception

import Graphics.UI.SDL hiding (init, quit, flip)
--import qualified Graphics.UI.SDL as SDL (init, quit, flip)

import Graphics.UI.SDL.TTF hiding (init, quit)
import qualified Graphics.UI.SDL.TTF as TTFG (init, quit)

import Surface
import Grid
import GameState hiding (playerType, screen, font, winner, playerStart)
import GameEnv
import Draw

import Paths_HTicTacToe

type ProgramOptions = (PlayerTurn,TileType)

parseArgs :: IO (ProgramOptions, [String])
parseArgs = do
    argv <- getArgs
    case getOpt Permute options argv of
        (o,n,[]  ) -> return (foldl' (flip id) defaultOptions o, n)
        (_,_,errs) -> ioError $ userError $ concat errs ++ usageInfo header options
 where header = "Usage: start player"
       defaultOptions = (Player, Cross)
       options :: [OptDescr (ProgramOptions -> ProgramOptions)]
       options = [Option ['s'] ["start"]  (ReqArg (\str (_,p) -> (pStart str, p)) "PlayerStart") "which players starts \"Player\" or \"Ai\" (default is \"Player\")",
                  Option ['p'] ["player"]  (ReqArg (\str (s,_) -> (s, pType str)) "PlayerType") "player is \"x\" or \"o\" (default is \"x\")"]
       --playerStart = Player
       pType "o" = Nought
       pType "O" = Nought
       pType _   = Cross -- default playerType.
       pStart "Ai" = Ai
       pStart _ = Player -- default playerStart.

init :: IO (AppConfig, AppData)
init = do
    
    (playerStart, playerType) <- fst `liftM` parseArgs
    
    xFileName    <- getDataFileName "x.png"
    oFileName    <- getDataFileName "o.png"
    xWinFileName <- getDataFileName "xWin.png"
    oWinFileName <- getDataFileName "oWin.png"
    fontFileName <- getDataFileName "Alice_in_WonderLand_3.ttf"
    
    screen  <- setVideoMode screenWidth screenHeight sceenBpp [HWSurface, DoubleBuf]
    setCaption "tic-tac-toe" []
    enableUnicode True
    
    font       <- openFont fontFileName 24
    xSprite    <- loadImage xFileName colorKey
    oSprite    <- loadImage oFileName colorKey
    xWinSprite <- loadImage xWinFileName colorKey
    oWinSprite <- loadImage oWinFileName colorKey
    
    seed    <- fromIntegral `liftM` getTicks
    
    let wh = surfaceGetWidth xSprite * 6
    
    let gridX = (screenWidth `div` 2) - (wh `div` 2)    
    return (AppConfig screen font oSprite xSprite oWinSprite xWinSprite (Rect gridX 5 wh wh) playerStart playerType, appData seed playerStart)
 where colorKey = Just (0, 0, 0)

initGame :: AppEnv ()
initGame = do
    putGrid newGrid
    putWinner (Nobody, [])
    playerTurn <- getTurn
    case playerTurn of
        Ai -> aiPlayGame
        _  -> return ()

aiPlayGame :: AppEnv ()
aiPlayGame = do
    playerTurn <- getTurn
    case playerTurn of
        Ai -> randomCell >>= uncurry playGame
        _  -> return ()
 where randomCell = do
        r <- rand 0 2
        c <- rand 0 2
        g <- getGrid
        if isEmpty g r c
            then return (r,c)
            else randomCell

playGame :: Int -> Int -> AppEnv ()
playGame row col = do    
    g <- getGrid
    when (isEmpty g row col) $ do 
        playerCell <- getPTurnCell
        modifyGrid $ \gr -> takeTurn gr playerCell row col    
        winner <- whoWon `liftM` getGrid
        putWinner winner        
        case fst winner of
            Draw    -> modifyStats $ \s@Stats{ tieCount=tc } -> s { tieCount=tc + 1 }
            Nobody  -> nextTurn
            _       -> modifyPlayerCount (+1)
        
        isOver <- isGameOver
        unless isOver aiPlayGame

handleEvent :: Event -> AppEnv ()
handleEvent VideoExpose = render
handleEvent (MouseButtonDown mx my ButtonLeft) = do
    gridBox@(Rect gx gy w h) <- getGridBounds
    when (isInside gridBox x y) $ do
        finished <- isGameOver
        if finished
            then do
                initGame
                pushEvent_ VideoExpose
            else do
                playerTurn <- getTurn
                case playerTurn of
                    Player -> do
                        let (row, col) = ((x - gx) `div` (w `div` 3), (y - gy) `div` (h `div` 3))
                        playGame row col
                        pushEvent_ VideoExpose
                    _ -> return ()
 where x = fromIntegral mx
       y = fromIntegral my
       pushEvent_ = liftIO . pushEvent

handleEvent _ = return ()

loop :: AppEnv ()
loop = do
    event <- liftIO waitEvent
    case event of
        Quit -> return ()
        _ -> do
            handleEvent event
            loop

main :: IO ()
main = withInit [InitEverything] $ -- withInit calls quit for us.
    withTtfInit $ do
        (config, state) <- init
        runApp config state $ do
            aiPlayGame -- if Ai was choosen to start first.
            render     -- initial render
            loop
        return ()

withTtfInit :: IO () -> IO ()
withTtfInit = bracket_ ttfInit TTFG.quit
 where ttfInit :: IO ()
       ttfInit = TTFG.init >>= \success -> when (not success) $ throwIO $ userError "Failed to init ttf\n"