module Exercise08 where import Data.Bits import Data.List import System.Random (mkStdGen, randoms, randomIO, Random) -- Player is either 1 or -1 type Player = Int -- A field is just an Int value where the absolute gives the number of pieces on the field -- and the sign corresponds to the player -- e.g. -3 would mean there are three blobs in this field of player -1 type Field = Int type Row = [Field] type Column = [Field] -- boards are rectangles represented as a list of rows type Board = [Row] -- A position on the board is represented as (row, column) -- (0,0) is the top left corner, coordinate values increase towards the bottom right type Pos = (Int, Int) -- A size represented as (height,width) type Size = (Int, Int) -- A strategy takes the player who's move it is, optionally takes a list of double values -- to allow for probabilistic strategies, takes the current board and gives back the position -- of the move the player should do type Strategy = [Double] -> Player -> Board -> Pos -- A stateful strategy can additionally pass some object between invocations type StatefulStrategyFunc a = a -> [Double] -> Player -> Board -> (Pos, a) -- first value is the state object to pass to the first invocation of each game type StatefulStrategy a = (a, StatefulStrategyFunc a) defaultSize :: (Int, Int) defaultSize = (9,6) -- Some useful helper functions row :: Board -> Int -> Row row = (!!) column :: Board -> Int -> Column column = row . transpose width :: Board -> Int width (x : _) = length x width _ = 0 height :: Board -> Int height = length size :: Board -> Size size b = (height b, width b) getCell :: Pos -> Board -> Field getCell (y, x) b = b !! y !! x -- pretty print a single cell showCell :: Field -> String showCell c = "- +" !! succ (signum c) : show (abs c) -- pretty print the given board showBoard :: Board -> String showBoard = unlines . map (unwords . map showCell) -- print a board to the console printBoard :: Board -> IO () printBoard = putStr . showBoard -- check if a position is one a board of the given size isValidPos :: Size -> Pos -> Bool isValidPos (r, c) (y, x) = y >= 0 && y < r && x >= 0 && x < c example2 :: Board example2 = [[1,0,-1,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]] example3 :: Board example3 = [[-1,-2,-1,0,-2,0],[-1,-3,-3,-3,-1,-2],[-2,-2,0,-1,-3,-2],[-2,-3,-3,-3,-3,1],[0,-3,0,-2,1,0],[-2,-1,-2,-2,3,1],[-1,-3,-1,-3,2,1],[2,0,-1,-1,3,1],[0,1,-2,0,1,1]] example4 :: Board example4 = [[1,1,2,2,1,0],[2,2,2,3,1,2],[2,3,3,1,3,1],[2,0,3,2,1,2],[1,2,3,2,2,1],[2,0,3,1,3,1],[2,3,2,3,3,2],[1,2,3,3,1,1],[0,1,2,-2,2,0]] {- x.1 -} -- Check if the given player can put an orb on the given position canPlaceOrb :: Player -> Pos -> Board -> Bool canPlaceOrb 1 (y, x) b = ((b!!y)!!x) >= 0 canPlaceOrb (-1) (y, x) b = ((b!!y)!!x) <= 0 canPlaceOrb p (y, x) b = undefined -- Check if the given player has won the game, -- you can assume that the opponent has made at least one move before hasWon :: Player -> Board -> Bool hasWon 1 ((x:xs):ys) = (x >= 0) && hasWon 1 (xs : ys) hasWon (-1) ((x:xs):ys) = (x <= 0) && hasWon (-1) (xs : ys) hasWon p ([]:ys) = hasWon p ys hasWon _ [] = True -- the list of neighbors of a cell neighbors :: Size -> Pos -> [Pos] neighbors (r, c) (y, x) = [(a, b) | (a, b) <- [(y-1,x),(y+1,x),(y,x+1),(y,x-1)], a >= 0, b >= 0, a < r, b < c] -- update a single position on the board -- f: function that modifies the number of orbs in the cell -- p: player to whom the updated cell should belong updatePos :: (Int -> Int) -> Player -> Pos -> Board -> Board updatePos f p (0, 0) ((x:xs):ys) = (f2 x:xs):ys where f2 x = p * f (abs x) updatePos f p (0, x) ((lx:xs):ys) = (lx:ny):ys where (ny:_) = updatePos f p (0, x-1) (xs:ys) updatePos f p (y, x) (ly:ys) = ly:updatePos f p (y-1, x) ys exampleBoard :: Board exampleBoard = [[1, 0, 1,0,0,1],[0,0,0,0,0,0],[1,0,1,0,0,1],[0,0,0,1,0,0],[1,0,1,0,0,1],[1,0,0,1,0,0],[1,1,0,0,0,-1],[-1,-3,-1,-1,-1,-1],[1,-1,-2,-1,-1,-1]] exampleSecondaryDisadvantage :: Int exampleSecondaryDisadvantage = isNotSecondaryDisadvantage 1 (9, 6) (8, 0) [[1, 0, 1,0,0,1],[0,0,0,0,0,0],[1,0,1,0,0,1],[0,0,0,1,0,0],[1,0,1,0,0,1],[1,0,0,1,0,0],[1,1,0,0,0,-1],[-1,-3,-1,-1,-1,-1],[1,-1,-2,-1,-1,-1]] getAllSecondaryOverflows :: Player -> Size -> Pos -> Board -> (Board, [Pos]) getAllSecondaryOverflows p size position b = (newb, secoverflowneighbors) where newb = putOrb p position b newsecondaryneighbors = getSecondaryNeighbors size position secondaryenemyneighbors = filter (\secpos -> isEnemyPlace p secpos newb) newsecondaryneighbors oneBeforeOverflow (y, x) = abs ((newb!!y)!!x) == (getNumberOfNeighbors size (y, x) - 1) secoverflowneighbors = filter oneBeforeOverflow secondaryenemyneighbors isOneBeforeOverflow :: Size -> Int -> Pos -> Bool isOneBeforeOverflow size elementAt (y, x) = abs elementAt == getNumberOfNeighbors size (y, x) - 1 getAllOneBeforeOverflow :: Player -> Size -> Board -> Pos -> [Pos] getAllOneBeforeOverflow _ _ [] _ = [] getAllOneBeforeOverflow p size ([]:ys) (accY, _) = getAllOneBeforeOverflow p size ys (accY+1, 0) getAllOneBeforeOverflow p size ((x:xs):ys) (accY, accX) = if isFromPlayer p && isOneBeforeOverflow size x (accY, accX) then (accY, accX) : next else next where isFromPlayer 1 = x > 0 isFromPlayer (-1) = x < 0 next = getAllOneBeforeOverflow p size (xs:ys) (accY, accX+1) isNotSecondaryDisadvantage :: Player -> Size -> Pos -> Board -> Int isNotSecondaryDisadvantage p size (y, x) b | not oneBeforeOverflow = 1 | not firstCond = 1 | otherwise = myCaptures-maxEnemyCapture where elementAt = (b!!y)!!x cnb = getNumberOfNeighbors size (y, x) oneBeforeOverflow = abs elementAt == cnb - 1 firstCond = isOneStepOverflow size (y, x) b myCaptures = countCaptures p size (y, x) b (newb, secEnemyOverflows) = getAllSecondaryOverflows p size (y, x) b mappedToEnemyCaptures = map (\overflowPosition -> countCaptures (-p) size overflowPosition newb) secEnemyOverflows maxEnemyCapture = foldr (\x acc -> if x > acc then x else acc) 0 mappedToEnemyCaptures {- x.2 -} putOrbForEach :: (Int -> Int) -> [Pos] -> (Bool, Board) -> Player -> Int -> (Bool, Board) putOrbForEach f positions b p depth = foldr (\x acc -> putOrbHelp f x acc p (depth+1)) b positions countCaptures :: Player -> Size -> Pos -> Board -> Int countCaptures p size position b = sum enemyList where nb = neighbors size position contentList = map (\(y, x) -> (b!!y)!!x) nb enemyList = map (\x -> if isEnemyField p x then abs x else 0) contentList isOneStepOverflow :: Size -> Pos -> Board -> Bool isOneStepOverflow size position b = all (\(ny, nx) -> abs ((b !! ny) !! nx) < (getNumberOfNeighbors size (ny, nx) - 1)) (neighbors size position) putOrbForEachLimited :: (Int -> Int) -> [Pos] -> Board -> Player -> Int -> Int -> Board putOrbForEachLimited f positions b p depth limit = foldr (\x acc -> putOrbLimitedStepsHelp f x acc p (depth+1) limit) b positions putOrbNTimes :: Player -> Pos -> Board -> Int -> Int -> Board putOrbNTimes p pos b odist n = if odist < n then foldr (\x acc -> putOrbLimitedSteps p pos acc 35) b [1..odist] else foldr (\x acc -> putOrbLimitedSteps p pos acc 35) b [1..n] isNBeforeOverflow :: Size -> Int -> Pos -> Int -> Bool isNBeforeOverflow size elementAt (y, x) n = abs elementAt >= getNumberOfNeighbors size (y, x) - n getAllNBeforeOverflow :: Player -> Size -> Board -> Pos -> Int -> [Pos] getAllNBeforeOverflow _ _ [] _ _ = [] getAllNBeforeOverflow p size ([]:ys) (accY, _) n = getAllNBeforeOverflow p size ys (accY+1, 0) n getAllNBeforeOverflow p size ((x:xs):ys) (accY, accX) n = if isFromPlayer p && isNBeforeOverflow size x (accY, accX) n then (accY, accX) : next else next where isFromPlayer 1 = x >= 0 isFromPlayer (-1) = x <= 0 next = getAllNBeforeOverflow p size (xs:ys) (accY, accX+1) n isThreatenedField :: Player -> Pos -> Board -> Size -> Bool isThreatenedField p position b size = (n /= 0) && canBeCaptured allEnemyOverflow p where overflowdistance = getOverflowDistance size position b n = overflowdistance - 1; getElement bo (y, x) = (bo!!y)!!x isEnemy bo (y, x) 1 = (bo!!y)!!x < 0 isEnemy bo (y, x) (-1) = (bo!!y)!!x > 0 friendPlayerPutBoard = putOrbNTimes p position b n n allEnemyOverflow = getAllNBeforeOverflow (-p) size friendPlayerPutBoard (0, 0) n elemAt b (posy, posx) = (b!!posy)!!posx canBeCaptured [] _ = False canBeCaptured ((y, x):enemypositions) 1 = if elemAt (putOrbNTimes (-p) (y, x) friendPlayerPutBoard (getNumberOfNeighbors size (y, x) - abs (elemAt b (y, x))) n) position > 0 then canBeCaptured enemypositions 1 else True canBeCaptured ((y, x):enemypositions) (-1) = if elemAt (putOrbNTimes (-p) (y, x) friendPlayerPutBoard (getNumberOfNeighbors size (y, x) - abs (elemAt b (y, x))) n) position < 0 then canBeCaptured enemypositions (-1) else True putOrbLimitedStepsHelp :: (Int -> Int) -> Pos -> Board -> Player -> Int -> Int -> Board putOrbLimitedStepsHelp f (y, x) b p depth limit | depth == limit = updatePos f p (y, x) b | otherwise = if overflowOccured then putOrbForEachLimited f nb boardZeroedCell p depth limit else updatePos f p (y, x) b where (r, c) = (length b, length (head b)) zerofunction x = 0 nb = neighbors (r, c) (y, x) numberOfNeighbors = length nb firstUpdateBoard = updatePos f p (y, x) b focusContent = (firstUpdateBoard!!y)!!x overflowOccured = abs focusContent == numberOfNeighbors boardZeroedCell = updatePos zerofunction p (y, x) b putOrbHelp :: (Int -> Int) -> Pos -> (Bool, Board) -> Player -> Int -> (Bool, Board) putOrbHelp _ _ (True, b) _ _ = (True, b) putOrbHelp f (y, x) (_, b) p 5 = if hasWon p b then (True, b) else putOrbHelp f (y, x) (False, b) p 0 putOrbHelp f (y, x) (_, b) p depth = if overflowOccured then putOrbForEach f nb (False, boardZeroedCell) p depth else (False, updatePos f p (y, x) b) where (r, c) = (length b, length (head b)) zerofunction x = 0 nb = neighbors (r, c) (y, x) numberOfNeighbors = length nb firstUpdateBoard = updatePos f p (y, x) b focusContent = (firstUpdateBoard!!y)!!x overflowOccured = abs focusContent == numberOfNeighbors boardZeroedCell = updatePos zerofunction p (y, x) b isOverflowField :: Int -> Int -> Bool isOverflowField absValue numberOfNeighbors = (absValue+1) == numberOfNeighbors getTotalCaptures :: Player -> Board -> Pos -> Int -> Int -> (Board, Int) getTotalCaptures p b position limit ownOrbsBefore = (newb, getNumberOfOwnOrbs p newb 0 - ownOrbsBefore - 1) where newb = putOrbLimitedSteps p position b limit maxCaptures2 :: Player -> Board -> Size -> Int -> (Int, Pos) maxCaptures2 p bo size limit = (resultCaptures, (resultY, resultX)) where overflowFields = getAllOneBeforeOverflow p size bo (0, 0) ownOrbsBefore = getNumberOfOwnOrbs p bo 0 mapToCapturesTouple = map (\(a, b) -> (a, b, getTotalCaptures p bo (a,b) limit ownOrbsBefore)) overflowFields getNumber (num, posi) = num mapEnemyCapturesDifference = map (\(a, b, (board, number)) -> (a, b, number - getNumber (maxCaptures (-p) board size limit))) mapToCapturesTouple (resultY, resultX, resultCaptures) = foldr (\(xa, xb, xc) (a, b, c) -> if xc > c then (xa, xb, xc) else (a,b,c)) (-1,-1,-100) mapEnemyCapturesDifference maxCaptures3 :: Player -> Board -> Size -> Int -> (Int, Pos) maxCaptures3 p bo size limit = (resultCaptures, (resultY, resultX)) where overflowFields = getAllOneBeforeOverflow p size bo (0, 0) ownOrbsBefore = getNumberOfOwnOrbs p bo 0 mapToCapturesTouple = map (\(a, b) -> (a, b, getTotalCaptures p bo (a,b) limit ownOrbsBefore)) overflowFields getNumber (num, posi) = num mapEnemyCapturesDifference = map (\(a, b, (board, number)) -> (a, b, number - getNumber (maxCaptures2 (-p) board size limit))) mapToCapturesTouple (resultY, resultX, resultCaptures) = foldr (\(xa, xb, xc) (a, b, c) -> if xc > c then (xa, xb, xc) else (a,b,c)) (-1,-1,-100) mapEnemyCapturesDifference maxCaptures :: Player -> Board -> Size -> Int -> (Int, Pos) maxCaptures p bo size limit = (resultCaptures, (resultY, resultX)) where overflowFields = getAllOneBeforeOverflow p size bo (0, 0) getNum (board, number) = number ownOrbsBefore = getNumberOfOwnOrbs p bo 0 mappedToCaptures = map (\(a, b) -> (a, b, getNum (getTotalCaptures p bo (a,b) limit ownOrbsBefore))) overflowFields (resultY, resultX, resultCaptures) = foldr (\(xa, xb, xc) (a, b, c) -> if xc > c then (xa, xb, xc) else (a,b,c)) (-1,-1,0) mappedToCaptures putOrbLimitedSteps :: Player -> Pos -> Board -> Int -> Board putOrbLimitedSteps p position b limit = result where f x = x + 1 result = putOrbLimitedStepsHelp f position b p 0 limit -- place an orb for the given player in the given cell putOrb :: Player -> Pos -> Board -> Board putOrb p (y, x) b = if canPlaceOrb p (y, x) b then result else b where f x = x + 1 (_, result) = putOrbHelp f (y, x) (False, b) p 0 {- x.3 -} hasPlacedElements :: Player -> Board -> Bool hasPlacedElements _ [] = False hasPlacedElements p ([]:ys) = hasPlacedElements p ys hasPlacedElements 1 ((x:xs):ys) = (x > 0) || hasPlacedElements 1 (xs : ys) hasPlacedElements (-1) ((x:xs):ys) = (x < 0) || hasPlacedElements 1 (xs : ys) firstPos :: Pos -> Pos firstPos (r, c) = (r-1, c-1) isEnemyPlace :: Player -> Pos -> Board -> Bool isEnemyPlace 1 (y, x) b = (b!!y)!!x < 0 isEnemyPlace (-1) (y, x) b = (b!!y)!!x > 0 noOwnOrbs :: Int -> Bool noOwnOrbs i = i == 0 noOverflowOrbs :: Int -> Int -> Bool noOverflowOrbs elementAt neighborsNum = not (abs elementAt + 1 == neighborsNum) -- second pos is the last valid pos. returns a touple with (False, _) if no save field was found. -- first bool tuple has false bool if there is no next pos because pos with (0,0) reached. findBestOrbPlacingPlace :: Player -> Size -> (Bool, Pos) -> (Int, Pos) -> Board -> Bool -> (Int, Pos) findBestOrbPlacingPlace _ _ (False, _) lastValid _ _ = lastValid findBestOrbPlacingPlace p size (True, testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed | optionalCondition = if nextPriority > saveFound then findBestOrbPlacingPlace p size (nextPos size testPos) (nextPriority, testPos) b oneBeforeOverflowAllowed else findBestOrbPlacingPlace p size (nextPos size testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed | requiredCondition && noOwnOrbs elementAt = if saveFound > 3 then findBestOrbPlacingPlace p size (nextPos size testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed else findBestOrbPlacingPlace p size (nextPos size testPos) (3, testPos) b oneBeforeOverflowAllowed | requiredCondition && noOverflowOrbs elementAt (length nb) = if saveFound > 2 then findBestOrbPlacingPlace p size (nextPos size testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed else findBestOrbPlacingPlace p size (nextPos size testPos) (2, testPos) b oneBeforeOverflowAllowed | betterField = if saveFound > 1 then findBestOrbPlacingPlace p size (nextPos size testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed else findBestOrbPlacingPlace p size (nextPos size testPos) (1, testPos) b oneBeforeOverflowAllowed | legalField = if saveFound > 0 then findBestOrbPlacingPlace p size (nextPos size testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed else findBestOrbPlacingPlace p size (nextPos size testPos) (0, testPos) b oneBeforeOverflowAllowed | otherwise = findBestOrbPlacingPlace p size (nextPos size testPos) (saveFound, lastValidPos) b oneBeforeOverflowAllowed where (testy, testx) = testPos elementAt = (b!!testy)!!testx legalField = canPlaceOrb p testPos b saveField = isSaveField size testPos b nb = neighbors size testPos hitField = hasEnemyNeighbors2 p nb b 0 noDisadvantage = not (isOverflowField (abs elementAt) (length nb)) betterField = legalField && noDisadvantage requiredCondition = betterField && saveField && not (isThreatenedField p testPos b size) optionalCondition = requiredCondition && (hitField>0) nextPriority = hitField+3 -- The Bool value is true if the player would win with this action. hasEnemyNeighbors3 :: Player -> Pos -> Board -> Int -> Int -> (Bool, Int) hasEnemyNeighbors3 p (y, x) b cNeighbors elementAt = undefined where explodingCount = cNeighbors - elementAt getNumberOfOwnOrbs :: Player -> Board -> Int -> Int getNumberOfOwnOrbs _ [] x = x getNumberOfOwnOrbs p ([]:ys) x = getNumberOfOwnOrbs p ys x getNumberOfOwnOrbs 1 ((x:xs):ys) i = if x > 0 then getNumberOfOwnOrbs 1 (xs:ys) (i+x) else getNumberOfOwnOrbs 1 (xs:ys) i getNumberOfOwnOrbs (-1) ((x:xs):ys) i = if x < 0 then getNumberOfOwnOrbs (-1) (xs:ys) (i-x) else getNumberOfOwnOrbs (-1) (xs:ys) i hasEnemyNeighbors2 :: Player -> [Pos] -> Board -> Int -> Int hasEnemyNeighbors2 _ [] _ lastCount = lastCount hasEnemyNeighbors2 p ((y, x):xs) b lastCount | isEnemy = if lastCount < enemyCount then hasEnemyNeighbors2 p xs b enemyCount else hasEnemyNeighbors2 p xs b lastCount | otherwise = hasEnemyNeighbors2 p xs b lastCount where isEnemy = isEnemyPlace p (y,x) b enemyCount = abs ((b!!y)!!x) hasEnemyNeighbors :: Player -> [Pos] -> Board -> Bool hasEnemyNeighbors _ [] _ = False hasEnemyNeighbors p (x:xs) b = isEnemyPlace p x b || hasEnemyNeighbors p xs b isEnemyField :: Player -> Int -> Bool isEnemyField 1 x = x < 0 isEnemyField (-1) x = x > 0 getSecondaryNeighbors :: Size -> Pos -> [Pos] getSecondaryNeighbors (r, c) (y, x) = filter filterer allPossiblyNeighbors where allPossiblyNeighbors = [(y-1,x-1),(y-1,x+1),(y+1,x+1),(y+1,x-1),(y,x-2),(y,x+2),(y-2,x),(y+2,x)] filterer (-1, _) = False filterer (-2, _) = False filterer (_, -1) = False filterer (_, -2) = False filterer (fy, fx) = (fy < r) && (fx < c) isSaveField :: Size -> Pos -> Board -> Bool isSaveField size (y, x) b = isSave nb where nb = neighbors size (y, x) elementAtPosition = (b!!y)!!x overflowDistanceHere = getOverflowDistance2 size (y, x) b (abs elementAtPosition) isSave [] = True isSave (n:ns) = getOverflowDistance size n b >= overflowDistanceHere && isSave ns getOverflowDistance :: Size -> Pos -> Board -> Int getOverflowDistance size (y, x) b = numberOfNeighbors - abs ((b!!y)!!x) where numberOfNeighbors = getNumberOfNeighbors size (y, x) getOverflowDistance2 :: Size -> Pos -> Board -> Int -> Int getOverflowDistance2 size position b absElementAtPos = numberOfNeighbors - absElementAtPos where numberOfNeighbors = getNumberOfNeighbors size position getNumberOfNeighbors :: Size -> Pos -> Int getNumberOfNeighbors _ (0, 0) = 2 getNumberOfNeighbors (_, width) (0, x) = if x == width - 1 then 2 else 3 getNumberOfNeighbors (height, _) (y, 0) = if y == height - 1 then 2 else 3 getNumberOfNeighbors (height, width) (y, x) | (y == height - 1) && (x == width - 1) = 2 | y == height - 1 = 3 | x == width - 1 = 3 | otherwise = 4 isOverflowAndInfectPlace :: Player -> Pos -> Pos -> Board -> Bool isOverflowAndInfectPlace p (y, x) (r, c) b = isOverflowPlace && isInfectPlace nb where nb = neighbors (r, c) (y, x) nbCount = length nb element = (b!!y)!!x isOverflowPlace = abs element + 1 == nbCount isInfectPlace [] = False isInfectPlace (n:ns) = isEnemyPlace p n b || isInfectPlace ns nextPos :: Pos -> Pos -> (Bool, Pos) nextPos _ (0, 0) = (False, (0,0)) nextPos (_, c) (y, 0) = (True, (y-1, c-1)) nextPos _ (y, x) = (True, (y, x-1)) --nextPos :: Pos -> Pos -> Pos --nextPos (y, x) (maxY, maxX) -- | newx < maxX = (y, newx) -- | otherwise = (y+1, 0) -- where newx = x+1 -- first argument is (r, c), second argument is position. {-WETT-} -- Your strategy strategy :: Strategy strategy darr p b = res2 where (r, c) = (length b, length (head b)) (enemyMaxNum, (enemyMaxY, enemyMaxX)) = maxCaptures (-p) b (r, c) 35 (maxNum, (maxY, maxX)) = maxCaptures2 p b (r, c) 35 (saveFound2, res2) | maxY < 0 = findBestOrbPlacingPlace p (r, c) (True, (r-1,c-1)) (0, (0,0)) b True | maxNum > negate enemyMaxNum = (1, (maxY, maxX)) | otherwise = findBestOrbPlacingPlace p (r, c) (True, (r-1,c-1)) (0, (0,0)) b False -- adds state to a strategy that doesn't use it wrapStrategy :: Strategy -> StatefulStrategy Int wrapStrategy strat = (0, \s r p b -> (strat r p b, succ s)) -- the actual strategy submissions -- if you want to use state modify this instead of strategy -- additionally you may change the Int in this type declaration to any type that is usefully for your strategy strategyState :: StatefulStrategy Int strategyState = wrapStrategy strategy {-TTEW-} -- Simulate a game between two strategies on a board of the given size and -- returns the state of the board before each move together with the player that won the game play :: [Int] -> Size -> StatefulStrategy a -> StatefulStrategy b -> [(Board, Pos)] play rss (r, c) (isa, sa) (isb, sb) = go rss isa sa isb sb 1 0 (replicate r (replicate c 0)) where -- type signature is necessary, inferred type is wrong! go :: [Int] -> a -> StatefulStrategyFunc a -> b -> StatefulStrategyFunc b -> Player -> Int -> Board -> [(Board, Pos)] go (rs:rss) stc sc stn sn p n b | won = [] | valid = (b, m) : go rss stn sn st' sc (-p) (succ n) (putOrb p m b) | otherwise = [] where won = n > 1 && hasWon (-p) b (m, st') = sc stc (mkRandoms rs) p b valid = isValidPos (size b) m && canPlaceOrb p m b -- Play a game and print it to the console playAndPrint :: Size -> StatefulStrategy a -> StatefulStrategy b -> IO () playAndPrint size sa sb = do seed <- randomIO -- let seed = 42 let moves = play (mkRandoms seed) size sa sb putStr $ unlines (zipWith showState moves $ cycle ['+', '-']) ++ "\n" ++ (case length moves `mod` 2 of { 1 -> "Winner: +"; 0 -> "Winner: -" }) ++ "\n" ++ "View at https://vmnipkow16.in.tum.de/christmas2020/embed.html#i" ++ base64 (1 : t size ++ concatMap (t . snd) moves) ++ "\n" where showState (b, pos) p = showBoard b ++ p : " places at " ++ show pos ++ "\n" t (a, b) = [a, b] mkRandoms :: Random a => Int -> [a] mkRandoms = randoms . mkStdGen base64 :: [Int] -> String base64 xs = case xs of [] -> "" [a] -> f1 a : f2 a 0 : "==" [a, b] -> f1 a : f2 a b : f3 b 0 : "=" a : b : c : d -> f1 a : f2 a b : f3 b c : f4 c : base64 d where alphabet = (!!) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" f1 a = alphabet $ shiftR a 2 f2 a b = alphabet $ shiftL (a .&. 3 ) 4 .|. shiftR b 4 f3 b c = alphabet $ shiftL (b .&. 15) 2 .|. shiftR c 6 f4 c = alphabet $ c .&. 63