packages feed

hsqml-demo-morris-0.3.1.0: src/Morris/Main.hs

{-# LANGUAGE TypeFamilies, DeriveDataTypeable #-}
module Main where

import Morris.Board

import Graphics.QML
import Control.Concurrent
import Control.DeepSeq
import Control.Exception
import Data.List
import Data.Maybe
import Data.Proxy
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Map (Map)
import qualified Data.Map as Map

import Paths_hsqml_demo_morris

newtype IdBoard = IdBoard (IntMap (Player, Position))

newIdBoard :: IdBoard
newIdBoard =
    IdBoard IntMap.empty

boardToId :: Board -> IdBoard
boardToId board =
    IdBoard $ fst $ foldr (\pl mn ->
        foldr (\ps (m,n) ->
            (IntMap.insert n (pl,ps) m,n+1)) mn $
        getPlayerPieces pl board) (IntMap.empty,0) [Red,Black]

findPieceId :: Position -> IdBoard -> Int
findPieceId pos (IdBoard m) =
    IntMap.foldrWithKey (\k' (_,pos') k -> if pos == pos' then k' else k) 0 m

playFirstActionId :: Player -> FirstAction -> IdBoard -> IdBoard
playFirstActionId player (Place p) (IdBoard m) =
    let i = if IntMap.null m then 0 else 1 + (fst $ IntMap.findMax m)
    in IdBoard $ IntMap.insert i (player,p) m
playFirstActionId player (Move p p') ib@(IdBoard m) =
    let i = findPieceId p ib
    in IdBoard $ IntMap.insert i (player,p') m

playSecondActionId :: SecondAction -> IdBoard -> IdBoard
playSecondActionId (Take pos) ib@(IdBoard m) =
    let i = findPieceId pos ib
    in IdBoard $ IntMap.delete i m

playActionId :: Player -> Action -> IdBoard -> IdBoard
playActionId player (FirstAction act) = playFirstActionId player act
playActionId _ (SecondAction act) = playSecondActionId act

moveToActions :: Move -> [Action]
moveToActions (FullMove act1 Nothing) =
    [FirstAction act1]
moveToActions (FullMove act1 (Just act2)) =
    [FirstAction act1,SecondAction act2]

actionToPositions :: Action -> [Position]
actionToPositions (FirstAction (Place p1))   = [p1]
actionToPositions (FirstAction (Move p1 p2)) = [p1,p2]
actionToPositions (SecondAction (Take p1))   = [p1]

moveToPositions :: Move -> [Position]
moveToPositions = concatMap actionToPositions . moveToActions

instance Marshal Player where
    type MarshalMode Player c d = ModeBidi c
    marshaller = bidiMarshaller from to
        where from txt = case T.unpack txt of
                  "red" -> Red
                  _     -> Black
              to plyr = T.pack $ case plyr of
                  Red   -> "red"
                  Black -> "black"

instance Marshal Position where
    type MarshalMode Position c d = ModeBidi c
    marshaller = bidiMarshaller Position (\(Position i) -> i)

data GameObj = GameObj {
    gameBoard :: Board,
    gameIdBoard :: IdBoard,
    gameActions :: [Position],
    gameBias :: Map Board Float,
    gamePrev :: Maybe GameObj}
    deriving Typeable

data PieceObj = PieceObj {
    piecePlayer :: Player,
    piecePrevPos :: Maybe Position,
    pieceCurrPos :: Maybe Position}
    deriving (Typeable, Show)

instance DefaultClass PieceObj where
    classMembers = [
        defPropertyConst "player" $ return . piecePlayer,
        defPropertyConst "currPos" $ return . pieceCurrPos,
        defPropertyConst "prevPos" $ return . piecePrevPos]

instance Marshal PieceObj where
    type MarshalMode PieceObj c d = ModeObjFrom PieceObj c
    marshaller = fromMarshaller fromObjRef

getPlayer :: GameObj -> IO Player
getPlayer = return . getBoardNextPlayer . gameBoard

getPieces :: GameObj -> IO [ObjRef PieceObj]
getPieces gs =
    let (IdBoard ib) = gameIdBoard $ fromMaybe gs $ gamePrev gs
        (IdBoard ib') = gameIdBoard gs
        merge _ (plyr,p) (_,p') = Just $ PieceObj plyr (Just p) (Just p')
        oldMap = fmap $ \(plyr,p) -> PieceObj plyr (Just p) Nothing
        newMap = fmap $ \(plyr,p') -> PieceObj plyr Nothing (Just p')
        ps = IntMap.elems $ IntMap.mergeWithKey merge oldMap newMap ib ib'
    in mapM newObjectDC ps
 
getTargets :: GameObj -> IO [Position]
getTargets gs =
    let actions = gameActions gs
        posOff  = length actions
    in return $ nub $ map (!! posOff) $
        filter (isPrefixOf actions) $ map moveToPositions $
        legalMoves $ gameBoard gs

getActions :: GameObj -> IO [Position]
getActions = return . gameActions

startAI :: ObjRef GameObj -> Int -> IO ()
startAI gsRef d = fmap (const ()) $ forkIO $ do
    let gs = fromObjRef gsRef
        ps = maybe [] moveToPositions $ aiMove d (gameBias gs) $ gameBoard gs
    evaluate $ force ps
    fireSignal (Proxy :: Proxy AIReady) gsRef ps

selectTarget :: GameObj -> Int -> IO (ObjRef GameObj)
selectTarget gs i =
    let actions' = gameActions gs ++ [Position i]
        board = gameBoard gs
        iboard = gameIdBoard gs
        player = getBoardNextPlayer board
        moves = legalMoves board
        maybeMove = fmap fst $ find ((actions' ==) . snd) $
            zip moves $ map moveToPositions moves
        board' = maybe board (\m -> playMove m board) maybeMove
        actionLists = concatMap (tail . inits . moveToActions) moves
        actionTargets = map (\as ->
            (last as, concatMap actionToPositions as)) actionLists
        maybeAction = fmap fst $ find ((actions' ==) . snd) actionTargets
        iboard' = maybe iboard (\a -> playActionId player a iboard) maybeAction
    in newObjectDC $ case maybeMove of
        Just move -> gs {
            gameBoard = board',
            gameIdBoard = iboard',
            gameActions = [],
            gameBias = {-Map.map (*0.9) $-} Map.alter (\x ->
                Just $ fromMaybe 0 x - 0.5) board' $ gameBias gs,
            gamePrev = Just gs}
        Nothing -> gs {
            gameIdBoard = iboard',
            gameActions = actions',
            gamePrev = Just gs}

data AIReady deriving Typeable

instance SignalKeyClass AIReady where
    type SignalParams AIReady = [Position] -> IO ()

instance DefaultClass GameObj where
    classMembers = [
        defPropertyRO "player" getPlayer,
        defPropertyRO "pieces" getPieces,
        defPropertyRO "targets" getTargets,
        defPropertyRO "actions" getActions,
        defMethod "startAI" startAI,
        defSignal "aiReady" (Proxy :: Proxy AIReady),
        defMethod "selectTarget" selectTarget]

instance Marshal GameObj where
    type MarshalMode GameObj c d = ModeObjFrom GameObj c
    marshaller = fromMarshaller fromObjRef

createGame :: ObjRef () -> IO (ObjRef GameObj)
createGame _ =
    newObjectDC $ GameObj newBoard newIdBoard [] Map.empty Nothing

main :: IO ()
main = do
    clazz <- newClass [defMethod "createGame" createGame]
    ctx <- newObject clazz ()
    qml <- getDataFileName "morris.qml"
    runEngineLoop defaultEngineConfig {
        initialDocument = fileDocument qml,
        contextObject = Just $ anyObjRef ctx}