packages feed

battleships-1.0.0: Logic/AIUtil.hs

----------------------------------------------------------------------------
-- |
-- 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