module Exercise_10 where import Data.List import Data.Tuple import Test.QuickCheck {-H10.1-} data Player = V | H -- vertical or horizontal player deriving (Eq,Show) data Field = P Player | E -- a field is occupied by a player or empty deriving (Eq,Show) type Row = [Field] type Column = [Field] type Board = [Row] -- we assume that boards are squares and encode a board row by row data Game = Game Board Player -- stores the currenty board and player deriving (Eq,Show) -- get a given row of a board row :: Board -> Int -> Row row = (!!) -- get a given column of a board column :: Board -> Int -> Column column = row . transpose -- width of a board width :: Board -> Int width [] = 0 width (x:_) = length x -- height of a board height :: Board -> Int height = length {-H10.1.1-} prettyShowBoard :: Board -> String prettyShowBoard [] = "" prettyShowBoard (([]):ys) = "\n" ++ prettyShowBoard ys prettyShowBoard ((x:xs):ys) = showPos x ++ prettyShowBoard (xs:ys) showPos (P a) = show a showPos E = "+" {-H10.1.2-} -- position on a board (row, column) -- (0,0) corresponds to the top left corner type Pos = (Int, Int) board = [[ P H , P H , P H ] ,[ E ,E , E ] ,[ E ,E , E ]] isValidMove :: Game -> Pos -> Bool isValidMove (Game b H) p@(r,c) = onBoard b p 0 1 && get b p == E && get b (r,c+1) == E isValidMove (Game b V) p@(r,c) = onBoard b p 1 0 && get b p == E && get b (r+1,c) == E onBoard b (r,c) a d = r >= 0 && c >= 0 && r+a < h && c+d < w where h = height b w = width b get b (r, c) = (b !! r) !! c {-H10.1.3-} canMove :: Game -> Bool canMove g@(Game b _) = or [isValidMove g (r,c) | r <- [0..height b - 1], c <- [0..width b - 1]] {-H10.1.4-} updateBoard :: Board -> Pos -> Field -> Board updateBoard b (r,c) f = update b r (update (row b r) c f) update (x:xs) 0 y= y:xs update (x:xs) p y = x:(update xs (p-1) y) {-H10.1.5-} playMove :: Game -> Pos -> Game playMove (Game b H) (r,c) = Game (updateBoard (updateBoard b (r,c+1) (P H)) (r,c) (P H)) V playMove (Game b V) (r,c) = Game (updateBoard (updateBoard b (r+1,c) (P V)) (r,c) (P V)) H {-H10.1.6-} -- the first paramter of a strategy is an infite list of -- random values between (0,1) (in case you wanna go wild with -- probabilistic methods) type Strategy = [Double] -> Game -> Pos {-WETT-} christmasAI :: Strategy -- receives a game and plays a move for the next player christmasAI _ = bestSpot bestSpot :: Game -> Pos bestSpot g = snd . minimum $ [(length (spots (playMove g x)), x) | x <- xs ] where xs = spots g spots :: Game -> [Pos] spots (Game b V) = [(r,c) | r <-[0..height b - 2], c <- [0..width b - 1], get b (r,c) == E, get b (r+1,c) == E] spots (Game b H) = map swap $ spots (Game (transpose b) V) {-TTEW-} {-H10.1.7-} play :: [[Double]] -> Int -> Strategy -> Strategy -> ([Board],Player) play rss dim sv sh = playGame rss (Game (replicate dim (replicate dim E)) V) sv sh playGame :: [[Double]] -> Game -> Strategy -> Strategy -> ([Board], Player) playGame (rs:rss) g@(Game board player) s1 s2 = if not (canMove g) then ([], if player == V then H else V) else let move = s1 rs g valid = isValidMove g move in if valid then let played@(Game nextBoard nextPlayer) = playMove g move (bs, p) = playGame rss played s2 s1 in (nextBoard:bs, p) else ([], if player == V then H else V) rss = [[] | _ <- [0..999999]] -- generates infinite list of values between (0,1) genRandomZeroOne :: Gen [Double] genRandomZeroOne = mapM (const $ choose (0::Double,1)) [1..] -- plays a game and prints it to the console playAndPrint :: Int -> Strategy -> Strategy -> IO () playAndPrint dim sh sv = do rss <- generate $ mapM (const $ genRandomZeroOne) [1..] let (bs, w) = play rss dim sh sv putStr $ (unlines $ map prettyShowBoard bs) ++ "\nWinner: " ++ show w ++ "\n"