{- Game playing module for Babylon Pedro Vasconcelos, 2009-2010 -} module Babylon where import Data.List import Data.Maybe import Data.Tree import Control.Monad import Minimax -- piece colors data Color = White | Black | Green | Brown deriving (Eq,Ord,Show,Enum) -- the board is represented by list of stacks; each stack is a list of colors type Board = [[Color]] -- a move is given by a pair of indices -- (i,j) means move stack i on top of stack j type Move = (Int,Int) -- list of all colors colors :: [Color] colors = [White .. Brown] -- the initial board: 4 colors x 3 pieces -- each piece in a separate stack initialBoard :: Board initialBoard = [[c] | c<-colors, _<-[1..3]] -- play a move and update the game -- assumes the move is valid play :: Board -> Move -> Board play b (i,j) = let from = b!!i (b', to:b'')= splitAt j b in deleteIndex i (b' ++ (from++to):b'') -- delete element of a list at index i deleteIndex :: Int -> [a] -> [a] deleteIndex i xs = let (xs',x:xs'') = splitAt i xs in xs' ++ xs'' -- list all valid moves of a given board valid :: Board -> [Move] valid b = [(i,j) | (xs,i)<-zip b [0..], (ys,j)<-zip b [0..], i/=j && (head xs==head ys || length xs==length ys)] -- a game position: a board labeled with an optional move -- this is used to identify which move lead to which position type Position = (Board, Maybe Move) -- subsequent positions from a given position -- labelled with the first move; subsequent moves are not needed -- optimization: reduces the branching factor -- by removing "equivalent" child positions positions :: Position -> [Position] positions (b,m) = nubBy (\x y->sort (fst x)==sort (fst y)) [(play b m', m `mplus` Just m') | m'<-valid b] -- build the game tree from a starting position gametree :: Position -> Tree Position gametree p = Node p (map gametree (positions p)) -- the static evaluation function -- yields -1 if the position is a loss (no available moves); -- yields 0 in other positions (unknown) static :: Position -> Eval Position static p@(b,m) | null (valid b) = Eval (-1) p | otherwise = Eval 0 p -- dynamic evaluation to choose the best move -- uses alpha-beta prunning minimax with a fixed ply-depth dynamic :: Int -> Board -> Eval Position dynamic depth b = bbminimax (-1) 1 $ fmap static $ prune depth $ gametree (b,Nothing) -- get the best move from a given board (using the above) bestmove :: Int -> Board -> Maybe Move bestmove d b = snd $ fromEval (dynamic d b) bestmove' :: Int -> Board -> (Int, Maybe Move) bestmove' d b = case dynamic d b of Eval x (b,m) -> (x, m)