aboutsummaryrefslogtreecommitdiff
path: root/2020/20-Jurassic_Jigsaw/second.hs
blob: 0990e4fa435e0fac0d37908e91656a5d291ebeac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
-- requires cabal install --lib megaparsec parser-combinators
module Main (main) where

import Control.Monad (mapM_, void, when)
import Data.List (elemIndex, foldl', intercalate, intersect, transpose)
import Data.Maybe (catMaybes, fromJust, isJust)
import Data.Map qualified as M
import Data.Void (Void)
import Text.Megaparsec
import Text.Megaparsec.Char
import System.Exit (die)

exampleExpectedOutput = 273

type TileID = Int
type Edge = [Bool]
type Image = [Edge]
data Tile = Tile { tileID :: TileID
                 , image :: Image
                 , edgesPermutations :: [Edge]
                 }
type Input = [Tile]

type Parser = Parsec Void String

parseInt :: Parser Int
parseInt = do
  n <- some digitChar
  return $ read n

parseLine :: Parser Edge
parseLine = do
  elts <- some (char '#' <|> char '.')
  void $ char '\n'
  return $ map (== '#') elts

parseTile :: Parser Tile
parseTile = do
  void $ string "Tile "
  n <- parseInt
  void $ string ":\n"
  image <- some parseLine
  let edges = [head image, last image, map head image, map last image]
      inverted = map reverse edges
  return $ Tile n image (edges ++ inverted)

parseInput' :: Parser Input
parseInput' = do
  tiles <- some $ parseTile <* (optional $ char '\n')
  void eof
  return tiles

parseInput :: String -> IO Input
parseInput filename = do
  input <- readFile filename
  case runParser parseInput' filename input of
    Left bundle -> die $ errorBundlePretty bundle
    Right input' -> return input'

oneCornerTopLeftOriented :: [Tile] -> Tile
oneCornerTopLeftOriented tiles = topLeftOrientation $ (map fst . filter ((== 4) . snd) $ map matchingEdges tiles) !! 0
  where
    matchingEdges :: Tile -> (Tile, Int)
    matchingEdges tile = (tile, sum $ map (matches tile) tiles)
    matches :: Tile -> Tile -> Int
    matches Tile{tileID=a, image=_, edgesPermutations=e} Tile{tileID=b, image=_, edgesPermutations=f}
      | a == b = 0
      | otherwise = length $ intersect e f
    topLeftOrientation :: Tile -> Tile
    topLeftOrientation Tile{tileID=tID, image=img, edgesPermutations=e} = case (leftMatches, topMatches) of
      (False, False) -> Tile tID img e
      (False, True)  -> Tile tID (rotateRight img) e
      (True, False)  -> Tile tID (rotateLeft img) e
      (True, True)   -> Tile tID (rotateRight $ rotateRight img) e
      where
        leftMatches = or $ map (edgeMatch tID (map head img)) tiles
        topMatches = or $ map (edgeMatch tID (head img)) tiles
        edgeMatch :: TileID -> Edge -> Tile -> Bool
        edgeMatch i e tile
          | i == tileID tile = False
          | otherwise = isJust $ elemIndex e (edgesPermutations tile)

orientToMatchLeft :: Edge -> Image -> Image
orientToMatchLeft edge img = rotateLeft . orientToMatchTop (reverse edge) $ rotateRight img -- it took me a long time to find out I needed to reverse the edge!

orientToMatchTop :: Edge -> Image -> Image
orientToMatchTop edge img
  | edge == head img = img -- top is top
  | redg == head img = map reverse img -- top is reverse top
  | edge == head timg = timg -- top is left
  | redg == head timg = map reverse timg -- top is reverse left
  | edge == head rimg = rimg -- top is bottom
  | redg == head rimg = map reverse rimg -- top is reverse bottom
  | edge == head rtimg = rtimg -- top is right
  | redg == head rtimg = map reverse rtimg -- top is reverse right
  where
    redg = reverse edge
    timg = transpose img
    rimg = reverse img
    rtimg = reverse timg

buildTileGrid :: [[Tile]] -> M.Map TileID Tile -> [[Tile]]
buildTileGrid tiles tilesMap
  | M.size tilesMap == 0 = tiles
  | isJust nextTileToTheRight = buildTileGrid ((init tiles) ++ [(last tiles) ++ [fromJust nextTileToTheRight]]) (M.delete (tileID $ fromJust nextTileToTheRight) tilesMap)
  | otherwise = buildTileGrid (tiles ++ [[nextTileBellow]]) (M.delete (tileID nextTileBellow) tilesMap)
  where
    lastPlacedTile :: Tile
    lastPlacedTile = last $ last tiles
    firstOnLastLine :: Tile
    firstOnLastLine = head $ last tiles
    nextTileToTheRight :: Maybe Tile
    nextTileToTheRight = case filter (edgeMatch rightEdge) (M.elems tilesMap) of
      [] -> Nothing
      [a] -> Just (orientedOnTheLeft a)
      where
        orientedOnTheLeft :: Tile -> Tile
        orientedOnTheLeft Tile{tileID=tid, image=img, edgesPermutations=e} = Tile{tileID=tid, image=orientToMatchLeft rightEdge img, edgesPermutations=e}
        rightEdge :: Edge
        rightEdge = map last $ image lastPlacedTile
    nextTileBellow :: Tile
    nextTileBellow = orientedOnTop . head $ filter (edgeMatch bottomEdge) (M.elems tilesMap)
      where
        orientedOnTop :: Tile -> Tile
        orientedOnTop Tile{tileID=tid, image=img, edgesPermutations=e} = Tile{tileID=tid, image=orientToMatchTop bottomEdge img, edgesPermutations=e}
        bottomEdge :: Edge
        bottomEdge = last . image $ firstOnLastLine
    edgeMatch :: Edge -> Tile -> Bool
    edgeMatch e tile = isJust $ elemIndex e (edgesPermutations tile)

cropImage :: Image -> Image
cropImage = tail . init . transpose . tail . init . transpose

assembleLines :: [Image] -> Image
assembleLines images
  | length (images !! 0) == 0 = []
  | otherwise = (concat $ map head images) : (assembleLines $ map tail images)

rotateLeft :: Image -> Image
rotateLeft = reverse . transpose

rotateRight :: Image -> Image
rotateRight = transpose . reverse

monster :: Image
monster = [[ False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True,  False ]
          ,[ True,  False, False, False, False, True,  True,  False, False, False, False, True,  True,  False, False, False, False, True,  True,  True ]
          ,[ False, True,  False, False, True,  False, False, True,  False, False, True,  False, False, True,  False, False, True,  False, False, False ]]

countMonsters :: Bool -> Image -> Int
countMonsters goDown i = --(if length z1 < length monster
                         -- then 0
                         -- else if length (z2 !! 0) < length (monster !! 0)
                         --      then 0
                         --      else if match then 1 else 0
                         (if match then 1 else 0) + nexts
  where
    z1 :: [(Edge, Edge)]
    z1 = zip i monster
    z2 :: [[(Bool, Bool)]]
    z2 = map (\(e, e') -> zip e e') $ z1
    match :: Bool
    match = and $ map (and . map matchOne) z2
    matchOne :: (Bool, Bool) -> Bool
    matchOne (_, False) = True
    matchOne (i, True) = i
    nexts :: Int
    nexts = (if goDown && length i > 3 then (countMonsters True $ tail i) else 0) + (if length (i !! 0) > 20 then countMonsters False $ map tail i else 0)

compute :: Input -> Int
compute tiles = spots - (monsters * 15)
  where
    topLeftTile :: Tile
    topLeftTile = oneCornerTopLeftOriented tiles
    tilesMap :: M.Map TileID Tile
    tilesMap = M.delete (tileID topLeftTile) (M.fromList $ zip (map tileID tiles) tiles)
    imagesGrid :: [[Image]]
    imagesGrid = map (map image) $ buildTileGrid [[topLeftTile]] tilesMap
    croppedImages :: [[Image]]
    croppedImages = map (map cropImage) imagesGrid
    assembledImage :: Image
    assembledImage = concat $ map assembleLines croppedImages
    permutations :: [Image]
    permutations = let r1 = rotateRight assembledImage
                       r2 = rotateRight r1
                       r3 = rotateRight r2
                       rotations = [assembledImage, r1, r2, r3]
                       inverted = map reverse rotations
                   in rotations ++ inverted
    monsters :: Int
    monsters = sum $ map (countMonsters True) permutations
    spots :: Int
    spots = length . filter id $ concat assembledImage

main :: IO ()
main = do
  example <- parseInput "example"
  --mapM_ (printImage . image) $ corners example
  --let topLeftTile = oneCornerTopLeftOriented example
  --    tilesMap = M.delete (tileID topLeftTile) (M.fromList $ zip (map tileID example) example)
  --    imagesGrid = map (map image) $ buildTileGrid [[topLeftTile]] tilesMap
  --    croppedImages = map (map cropImage) imagesGrid
  --    assembledImage = concat $ map assembleLines croppedImages
  --    permutations = let r1 = rotateRight assembledImage
  --                       r2 = rotateRight r2
  --                       r3 = rotateRight r3
  --                       rotations = [assembledImage, r1, r2, r3]
  --                       inverted = map reverse rotations
  --                   in rotations ++ inverted
  --printImage assembledImage
  let exampleOutput = compute example
  when  (exampleOutput /= exampleExpectedOutput)  (die $ "example failed: got " ++ show exampleOutput ++ " instead of " ++ show exampleExpectedOutput)
  input <- parseInput "input"
  print $ compute input
 --where
 -- printImage img = mapM_ print $ map (map (\x -> if x then '#' else '.')) img