|
| 1 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 2 | + |
| 3 | +module Battleship where |
| 4 | + |
| 5 | +import Control.Applicative ((<|>)) |
| 6 | +import Control.Monad (replicateM) |
| 7 | +import Control.Monad.Trans.State.Strict |
| 8 | +import Data.Bool (bool) |
| 9 | +import System.Random (randomRIO) |
| 10 | +import qualified Data.Map.Strict as M |
| 11 | +import qualified Data.Set as S |
| 12 | + |
| 13 | +data Row = R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | R10 |
| 14 | + deriving (Bounded, Eq, Ord, Enum, Show) |
| 15 | + |
| 16 | +data Col = Ca | Cb | Cc | Cd | Ce | Cf | Cg | Ch | Ci | Cj |
| 17 | + deriving (Bounded, Eq, Ord, Enum, Show) |
| 18 | + |
| 19 | +data Coord = Coord Row Col deriving (Eq, Show, Ord) |
| 20 | + |
| 21 | +data Ship = Patrol | Cruiser | Submarine | BattleShip | Carrier |
| 22 | + deriving (Eq, Ord, Show, Enum) |
| 23 | + |
| 24 | +data Move = Miss | Hit deriving (Eq, Show) |
| 25 | + |
| 26 | +data Player = Player1 | Player2 deriving (Eq, Show) |
| 27 | + |
| 28 | +type Coords = S.Set Coord |
| 29 | + |
| 30 | +type Board = M.Map Ship Coords |
| 31 | + |
| 32 | +data Status = Won Player | InPlay deriving (Show) |
| 33 | + |
| 34 | +data Game = Game |
| 35 | + { player1Board :: Board |
| 36 | + , player1Track :: Coords |
| 37 | + , player2Board :: Board |
| 38 | + , player2Track :: Coords |
| 39 | + } deriving (Eq, Show) |
| 40 | + |
| 41 | +safeToEnum :: forall a. (Enum a, Bounded a) => Int -> Maybe a |
| 42 | +safeToEnum from = bool (Just $ toEnum from) Nothing |
| 43 | + $ from < fromEnum (minBound :: a) || from > fromEnum (maxBound :: a) |
| 44 | + |
| 45 | +safeFromTo :: (Enum a, Bounded a) => Int -> Int -> Maybe [a] |
| 46 | +safeFromTo from to = traverse safeToEnum [from..to] |
| 47 | + |
| 48 | +makeHorizontal :: (Int, Int) -> Ship -> Maybe Coords |
| 49 | +makeHorizontal (x, y) ship = let |
| 50 | + len = fromEnum ship + 1 |
| 51 | + rows = replicateM len (safeToEnum y) |
| 52 | + cols = safeFromTo x (x+len) |
| 53 | + in S.fromList . map (uncurry Coord) <$> (zip <$> rows <*> cols) |
| 54 | + |
| 55 | +makeVertical :: (Int, Int) -> Ship -> Maybe Coords |
| 56 | +makeVertical (x, y) ship = let |
| 57 | + len = fromEnum ship + 1 |
| 58 | + rows = safeFromTo y (y+len) |
| 59 | + cols = replicateM len (safeToEnum x) |
| 60 | + in S.fromList . map (uncurry Coord) <$> (zip <$> rows <*> cols) |
| 61 | + |
| 62 | +place :: Ship -> Coords -> Board -> Board |
| 63 | +place = M.insert |
| 64 | + |
| 65 | +allCoords :: Board -> Coords |
| 66 | +allCoords = S.unions . M.elems |
| 67 | + |
| 68 | +pair :: IO (Int, Int) |
| 69 | +pair = (,) <$> r <*> r where r = randomRIO (0,9) |
| 70 | + |
| 71 | +genBoard' :: [Ship] -> Board -> IO Board |
| 72 | +genBoard' [] board = return board |
| 73 | +genBoard' (s:ss) board = pair >>= \p -> |
| 74 | + case makeHorizontal p s <|> makeVertical p s of |
| 75 | + Just cs | S.null $ S.intersection cs (allCoords board) -> |
| 76 | + genBoard' ss $ place s cs board |
| 77 | + _ -> genBoard' (s:ss) board |
| 78 | + |
| 79 | +genBoard :: IO Board |
| 80 | +genBoard = genBoard' [Carrier, BattleShip, Submarine, Patrol, Cruiser] M.empty |
| 81 | + |
| 82 | +makeAttackingMove :: Player -> Coord -> State Game Move |
| 83 | +makeAttackingMove Player1 coord = get >>= \game -> let |
| 84 | + move = bool Miss Hit (coord `S.member` allCoords (player2Board game)) |
| 85 | + p1t = S.insert coord $ player1Track game |
| 86 | + in put game {player1Track = p1t} >> return move |
| 87 | +makeAttackingMove Player2 coord = get >>= \game -> let |
| 88 | + move = bool Miss Hit (coord `S.member` allCoords (player1Board game)) |
| 89 | + p2t = S.insert coord $ player2Track game |
| 90 | + in put game {player2Track = p2t} >> return move |
| 91 | + |
| 92 | +status :: State Game Status |
| 93 | +status = get >>= \game -> let |
| 94 | + p1b = allCoords $ player1Board game |
| 95 | + p2t = S.intersection (player2Track game) p1b |
| 96 | + p2b = allCoords $ player2Board game |
| 97 | + p1t = S.intersection (player2Track game) p2b |
| 98 | + in return $ case () of |
| 99 | + _ | p1t == p2b -> Won Player1 |
| 100 | + | p2t == p1b -> Won Player2 |
| 101 | + | otherwise -> InPlay |
| 102 | + |
| 103 | +emptyGame :: Game |
| 104 | +emptyGame = Game M.empty S.empty M.empty S.empty |
0 commit comments