battleships-1.0.0: aibenchmark/Main.hs
module Main
( main
, playGame
, benchmark
) where
import Prelude
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.State (runStateT, StateT)
import Data.List (sort)
import qualified Data.Map as Map
import Logic.AIUtil
import Logic.CleverAI
import Logic.StupidAI
import Logic.Game
import Logic.Types
import System.Environment
main :: IO ()
main = do
putStrLn "Usage: aibenchmark (mov|immov) (Hard|Medium|Easy) numGames [--verbose]"
putStrLn "Example: aibenchmark immov Hard 20 --verbose"
a1:a2:a3:rest <- getArgs
let
moveable = case a1 of
"mov" -> True
"immov" -> False
_ -> error $ "Error: `" ++ a2 ++ "` is neither `mov` nor `immov`!"
difficultyLvl = read a2
repetitions = read a3
verbose = case rest of
"--verbose":_ -> True
_ -> False
benchmark
verbose
benchmarkRules { rulesMove = moveable, rulesDifficulty = difficultyLvl }
repetitions
-- | Tests the performance of the AI, that is the average number of shots
-- | needed to sink all the ships. This way, we can estimate how well the AI plays.
-- | The AI plays against itself.
benchmark :: Bool -> Rules -> Int -> IO ()
benchmark verbose rules repetitions = do
shotCounts <- mapM f [1..repetitions]
let sorted = sort shotCounts
let avg = (fromIntegral $ sum shotCounts :: Double) / fromIntegral (length shotCounts)
let mn = minimum shotCounts
let mx = maximum shotCounts
let mdn = sorted !! (length sorted `div` 2)
putStrLn "----------\n"
putStrLn "SUMMARY\n"
putStrLn $ "Average: " ++ show avg ++ " shots."
putStrLn $ "Minimum: " ++ show mn ++ " shots."
putStrLn $ "Maximum: " ++ show mx ++ " shots."
putStrLn $ "Median: " ++ show mdn ++ " shots."
putStrLn "Complete list: "
mapM_ print sorted where
f :: Int -> IO Int
f i = do
putStrLn $ "\n---\n" ++ show i ++ "th run:"
shotCount <- playGame verbose rules
putStrLn $ show shotCount ++ " shots needed."
return shotCount
-- | Returns number of shots the AI needed.
playGame :: Bool -> Rules -> IO Int
playGame verbose rules = do
(ai, fleetPlacement) <- aiInit rules
putStrLn $ showFleetPlacement fleetPlacement
let fleet = generateFleet fleetPlacement
(count, _newAi) <- runStateT (turn verbose rules [] fleet Map.empty 0) (ai :: CleverAI)
return $ count `div` 2 -- only count AI's shots, not the turn number
-- | Let the AI play against itself. Returns the number of shots fired.
turn :: AI a => Bool -> Rules -> TrackingList -> Fleet -> Fleet -> Int -> StateT a IO Int
turn verbose rules shots fleet sunk count = do
when verbose . liftIO $ do
putStrLn "Fleet:"
putStrLn $ showFleet fleet
putStrLn "Sunk:"
putStrLn $ showFleet sunk
putStrLn "Tracking:"
putStrLn $ showTrackingList shots
-- get target position from AI
pos <- aiFire
when verbose . liftIO . putStrLn $ "AI's target: " ++ show pos
-- fire the AI's shot against itself
let
(response, fleet', sunk') = case shipsAt (fleet Map.\\ sunk) pos of
[] -> (Water, fleet, sunk)
[ship] ->
let
-- inflict damage to the ship
Just idx = shipCellIndex pos ship
newShip = damageShip count idx ship
-- replace ship
newFleet = Map.insert (shipID ship) newShip fleet
in if isShipSunk newShip
then (Sunk, newFleet, Map.insert (shipID ship) newShip sunk)
else (Hit, newFleet, sunk)
_ -> error $ "Error: Multiple ships at position " ++ show pos ++ ". This shouldn't happen!"
-- update the tracking list
let shots' = Shot pos response count:shots
-- notify the AI
aiResponse pos response
-- should any ships be moved?
fleet'' <- if rulesMove rules
then do
mMove <- aiMove fleet' shots'
when verbose . liftIO . putStrLn $ "AI's movement: " ++ show mMove
return $ case mMove of
Nothing -> fleet'
Just (sID, movement) -> case Map.lookup sID fleet' of
Just ship -> if not $ isDamaged ship
then moveShip ship movement fleet'
else fleet'
Nothing -> fleet'
else return fleet'
-- all ships sunk now?
if allSunk fleet''
then return (count + 2)
else turn verbose rules shots' fleet'' sunk' (count + 2)
benchmarkRules :: Rules
benchmarkRules = Rules
{ rulesAgainWhenHit = False
, rulesMove = undefined
, rulesDifficulty = undefined
, rulesMaximumTurns = 150
, rulesCountdownTurns = 40
}