hsqml-morris-0.2.0.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.Maybe
import Data.List
import Data.Tagged
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_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
getIdBoardCount :: IdBoard -> Int
getIdBoardCount (IdBoard m) =
if IntMap.null m then 0 else (1+) $ fst $ IntMap.findMax m
getIdBoardPiece :: Int -> IdBoard -> Maybe (Player,Position)
getIdBoardPiece i (IdBoard m) =
IntMap.lookup i m
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
data PosListObj = PosListObj [Position] deriving Typeable
posListElem :: PosListObj -> Int -> IO Int
posListElem (PosListObj posList) idx =
return $ (\(Position i) -> i) $ posList !! idx
posListCount :: PosListObj -> IO Int
posListCount (PosListObj posList) =
return $ length posList
instance Object PosListObj where
classDef = defClass [
defMethod "elem" posListElem,
defPropertyRO "count" posListCount]
instance Marshal PosListObj where
type MarshalMode PosListObj = ValObjToOnly PosListObj
marshaller = objSimpleMarshaller
data GameObj = GameObj {
gameBoard :: Board,
gameIdBoard :: IdBoard,
gameActions :: [Position],
gameBias :: Map Board Float}
deriving Typeable
getPlayer :: GameObj -> IO String
getPlayer gs =
return $ case getBoardNextPlayer $ gameBoard gs of
Red -> "red"
Black -> "black"
getTargets :: GameObj -> IO (ObjRef PosListObj)
getTargets gs =
let actions = gameActions gs
posOff = length actions
in newObject $ PosListObj $ nub $ map (!! posOff) $
filter (isPrefixOf actions) $ map moveToPositions $
legalMoves $ gameBoard gs
getActions :: GameObj -> IO (ObjRef PosListObj)
getActions gs =
newObject $ PosListObj $ gameActions gs
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
posObj <- newObject $ PosListObj ps
fireSignal (Tagged gsRef :: Tagged AIReady (ObjRef GameObj)) posObj
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 newObject $ 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}
Nothing -> gs {
gameIdBoard = iboard',
gameActions = actions'}
getIndexCount :: GameObj -> IO Int
getIndexCount gs =
return $ getIdBoardCount $ gameIdBoard gs
getPlayerAtIndex :: GameObj -> Int -> IO String
getPlayerAtIndex gs i =
return $ case getIdBoardPiece i $ gameIdBoard gs of
Nothing -> "none"
Just (Red,_) -> "red"
Just (Black,_) -> "black"
getPositionAtIndex :: GameObj -> Int -> IO Int
getPositionAtIndex gs i =
return $ case getIdBoardPiece i $ gameIdBoard gs of
Nothing -> -1
Just (_,Position pos) -> pos
data AIReady deriving Typeable
instance SignalKey AIReady where
type SignalParams AIReady = ObjRef PosListObj -> IO ()
instance Object GameObj where
classDef = defClass [
defPropertyRO "player" getPlayer,
defPropertyRO "targets" getTargets,
defPropertyRO "actions" getActions,
defMethod "startAI" startAI,
defSignal (Tagged "aiReady" :: Tagged AIReady String),
defMethod "selectTarget" selectTarget,
defPropertyRO "indexCount" getIndexCount,
defMethod "idxPlayer" getPlayerAtIndex,
defMethod "idxPosition" getPositionAtIndex]
instance Marshal GameObj where
type MarshalMode GameObj = ValObjToOnly GameObj
marshaller = objSimpleMarshaller
createGame :: ObjRef MainObj -> IO (ObjRef GameObj)
createGame _ =
newObject $ GameObj newBoard newIdBoard [] Map.empty
data MainObj = MainObj deriving Typeable
instance Object MainObj where
classDef = defClass [
defMethod "createGame" createGame]
main :: IO ()
main = do
ctx <- newObject $ MainObj
qml <- getDataFileName "morris.qml"
runEngineLoop defaultEngineConfig {
initialURL = filePathToURI qml,
initialWindowState = ShowWindowWithTitle "HsQML Morris",
contextObject = Just $ anyObjRef ctx}