---------------------------------------------------------------------------- -- | -- Module : Logic.AIUtil -- Stability : experimental -- Portability : portable -- -- Utilities for the enemy AI. module Logic.AIUtil ( -- * Type Synonyms Score , ScoreGrid , TrackingGrid -- * Grid Functions , isHit , isHitOrSunk , isWater -- * Ship Functions , completeFleet -- * Helper Functions , fromBool , maximum' -- * Debugging Functions , showFleet , showFleetPlacement , showPositions , showScoreGrid , showTracking , showTrackingList ) where import Prelude import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.List import Control.Monad.Random import Data.Array import Data.List import qualified Data.Map as Map import Data.Maybe (listToMaybe) import Logic.Util import Logic.Game import Logic.Random import Logic.Types import Text.Printf -------------------------------------------------------------------------------- -- * Data types -------------------------------------------------------------------------------- type Score = Double type ScoreGrid = Array Pos Score -- | A grid where the results of shots are tracked. type TrackingGrid = Grid (Maybe HitResponse) -------------------------------------------------------------------------------- -- * Placing ships -------------------------------------------------------------------------------- -- | Random completion of a given fleet, if one exists. completeFleet :: MonadRandom m => FleetPlacement -> m (Maybe FleetPlacement) completeFleet fleet = do let ships' = reverse fleetShips \\ fmap shipSize fleet fleets <- runRandM $ runListT $ foldM completeFleet' fleet ships' return $ listToMaybe fleets -- | Helper for 'completeFleet'. completeFleet' :: RandomGen g => FleetPlacement -> Int -> ListT (Rand g) FleetPlacement completeFleet' fleet len = do let admissible = admissibleShips fleet len shuffled <- lift $ shuffleRandom admissible placement <- choose shuffled return $ placement : fleet -- | Calculates all possible placements for a ship of the given length. admissibleShips :: FleetPlacement -> Int -> [ShipShape] admissibleShips fleet len = do let (w, h) = boardSize x <- [0 .. w - 1] y <- [0 .. h - 1] o <- [Horizontal, Vertical] let ship = ShipShape (x, y) len o guard $ shipAdmissible fleet ship return ship -------------------------------------------------------------------------------- -- * Hit Response -------------------------------------------------------------------------------- isHit :: Maybe HitResponse -> Bool isHit (Just Hit) = True isHit _ = False isSunk :: Maybe HitResponse -> Bool isSunk (Just Sunk) = True isSunk _ = False isWater :: Maybe HitResponse -> Bool isWater (Just Water) = True isWater _ = False isHitOrSunk :: Maybe HitResponse -> Bool isHitOrSunk h = isSunk h || isHit h -- equivalent: isJust h && not (isWater h) -------------------------------------------------------------------------------- -- * Misc -------------------------------------------------------------------------------- -- | Lift a list into a 'MonadPlus'. choose :: MonadPlus m => [a] -> m a choose = msum . fmap return -- | Bool to numeric. fromBool :: Num a => Bool -> a fromBool True = 1 fromBool False = 0 -- | Maximum function for nonnegative numbers which handles empty lists. maximum' :: (Ord a, Num a) => [a] -> a maximum' [] = 0 maximum' xs = maximum xs -------------------------------------------------------------------------------- -- * Debugging -------------------------------------------------------------------------------- showPositions :: Int -> Int -> [Pos] -> String showPositions width height ps = concat [(if (x,y) `elem` ps then "X" else " ") ++ (if x == width - 1 then "\n" else "") | y <- [0..height - 1] , x <- [0..width - 1] ] showScoreGrid :: ScoreGrid -> String showScoreGrid grid = concat [ printf "%6.2f" (grid ! (x,y)) ++ (if x == width' then "\n" else "|") | y <- [0..height'] , x <- [0..width'] ] where ((0,0), (width', height')) = bounds grid showTracking :: TrackingGrid -> String showTracking grid = concat [(case grid ! ((x,y) :: Pos) of Nothing -> " " Just Water -> "~" Just Hit -> "H" Just Sunk -> "S") ++ (if x == width' then "\n" else "") | y <- [0..height'] , x <- [0..width'] ] where ((0,0), (width', height')) = bounds grid showTrackingList :: TrackingList -> String showTrackingList list = showTracking . buildArray ((0,0), (fst boardSize - 1, snd boardSize - 1)) $ \pos -> shotResult `fmap` find ((== pos) . shotPos) list showFleetPlacement :: FleetPlacement -> String showFleetPlacement fleet = tail $ concat [ (if x == 0 then "\n" else "") ++ (if not . null . shipsAt fleet $ (x,y) then "O" else "~") | y <- [0..height - 1] , x <- [0..width - 1] ] where (width, height) = boardSize showFleet :: Fleet -> String showFleet = showFleetPlacement . map shipShape . Map.elems