{-# LANGUAGE DeriveGeneric #-} -------------------------------------------------------------------------------- module Main where -------------------------------------------------------------------------------- import Data.Hashable import Data.Function ((&)) import GHC.Generics import Text.ParserCombinators.ReadP import Control.Applicative import qualified Data.HashSet as HS -------------------------------------------------------------------------------- data Direction = DirLeft | DirRight | DirUp | DirDown deriving (Eq, Show) data Point = Point Int Int deriving (Eq, Show, Ord, Generic) instance Hashable Point data Orientation = Horizontal | Vertical deriving (Eq, Show) data Anchor = Beg | End deriving (Eq, Show) data Rotation = CW | CCW deriving (Eq, Show) data Line = Line Point Point deriving (Show, Generic) instance Hashable Line instance Eq Line where Line begA endA == Line begB endB = (begA == begB && endA == endB) || (begA == endB && endA == begB) data Game = Game (HS.HashSet Line) [Line] deriving (Eq, Show) data Scoreboard = Scoreboard Int Int deriving (Eq) instance Semigroup Scoreboard where (Scoreboard a b) <> (Scoreboard x y) = Scoreboard (a + x) (b + y) instance Monoid Scoreboard where mempty = Scoreboard 0 0 data Turn = Player1 | Player2 deriving (Eq, Show) next :: Turn -> Turn next Player1 = Player2 next Player2 = Player1 instance Show Scoreboard where show (Scoreboard p1 p2) = "Player 1: " ++ show (p1) ++ " Player 2: " ++ show (p2) digit :: ReadP Char digit = satisfy (\c -> c >= '0' && c <= '9') int :: ReadP Int int = read <$> many1 digit inputLine :: ReadP String inputLine = manyTill get (char '\n') direction :: ReadP Direction direction = do c <- char 'L' <|> char 'R' <|> char 'U' <|> char 'D' case c of 'L' -> pure DirLeft 'R' -> pure DirRight 'U' -> pure DirUp 'D' -> pure DirDown _ -> fail $ "Unexpected direction: " ++ show c validMove :: Int -> Int -> ReadP Line validMove w h = do x <- int skipSpaces y <- int skipSpaces dir <- direction _ <- char '\n' if x >= 0 && x <= w && y >= 0 && y <= h then do let beg = Point x y pure $ mkLine beg (shiftPoint dir beg) else fail "Expected a move on the game board" game :: ReadP Game game = do w <- read <$> inputLine h <- read <$> inputLine locs <- read <$> inputLine moves <- count locs (validMove w h) eof pure $ Game mempty moves parseInput :: String -> Maybe Game parseInput x = do case readP_to_S game x of [(res, "")] -> Just res _ -> Nothing -- | Smart constructor to ensure that beg is always < end. mkLine :: Point -> Point -> Line mkLine beg end = if beg < end then Line beg end else Line end beg mkLineDir :: Int -> Int -> Direction -> Line mkLineDir x y dir = let beg = Point x y in mkLine beg (shiftPoint dir beg) mkLineDir' :: Point -> Direction -> Line mkLineDir' (Point x y) dir = mkLineDir x y dir shiftPoint :: Direction -> Point -> Point shiftPoint DirLeft (Point x y) = Point (x - 1) y shiftPoint DirRight (Point x y) = Point (x + 1) y shiftPoint DirUp (Point x y) = Point x (y + 1) shiftPoint DirDown (Point x y) = Point x (y - 1) shiftLine :: Direction -> Line -> Line shiftLine dir (Line beg end) = mkLine (shiftPoint dir beg) (shiftPoint dir end) rotateLine :: Anchor -> Rotation -> Line -> Line rotateLine anchor rotation line = doRotateLine (classifyOrientation line) anchor rotation line doRotateLine :: Orientation -> Anchor -> Rotation -> Line -> Line doRotateLine Horizontal Beg CW (Line beg _) = mkLineDir' beg DirDown doRotateLine Horizontal Beg CCW (Line beg _) = mkLineDir' beg DirUp doRotateLine Horizontal End CW (Line _ end) = mkLineDir' end DirUp doRotateLine Horizontal End CCW (Line _ end) = mkLineDir' end DirDown doRotateLine Vertical Beg CW (Line beg _) = mkLineDir' beg DirRight doRotateLine Vertical Beg CCW (Line beg _) = mkLineDir' beg DirLeft doRotateLine Vertical End CW (Line _ end) = mkLineDir' end DirLeft doRotateLine Vertical End CCW (Line _ end) = mkLineDir' end DirRight classifyOrientation :: Line -> Orientation classifyOrientation (Line (Point _ y1) (Point _ y2)) = if y1 == y2 then Horizontal else Vertical closesAnySquare :: HS.HashSet Line -> Line -> Bool closesAnySquare allMoves line = do let alreadyDrawn x = HS.member x allMoves case classifyOrientation line of Horizontal -> all alreadyDrawn [ shiftLine DirUp line , rotateLine Beg CCW line , rotateLine End CW line ] || all alreadyDrawn [ shiftLine DirDown line , rotateLine Beg CW line , rotateLine End CCW line ] Vertical -> all alreadyDrawn [ shiftLine DirLeft line , rotateLine Beg CCW line , rotateLine End CW line ] || all alreadyDrawn [ shiftLine DirRight line , rotateLine Beg CW line , rotateLine End CCW line ] incScoreboard :: Turn -> Scoreboard -> Scoreboard incScoreboard Player1 score = score <> Scoreboard 1 0 incScoreboard Player2 score = score <> Scoreboard 0 1 scoreGame :: Turn -> Game -> Scoreboard -> Maybe Scoreboard scoreGame _ (Game _ []) score = Just $ score scoreGame player (Game allMoves (line:rest)) score = if HS.member line allMoves then Nothing else do let allMoves' = HS.insert line allMoves score' = if closesAnySquare allMoves line then incScoreboard player score else score scoreGame (next player) (Game allMoves' rest) score' (|>) :: a -> (a -> b) -> b (|>) = (&) main :: IO () main = do input <- readFile "game.txt" case parseInput input of Nothing -> putStrLn "invalid" Just parsedGame -> case scoreGame Player1 parsedGame mempty of Nothing -> putStrLn "invalid" Just score -> print score