aboutsummaryrefslogtreecommitdiffstats
path: root/day22/main.hs
blob: 10feed9f0741f9ee94e0870ece5c3506e3fcdea9 (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
-- Mostly plagiarized from https://github.com/sndels/aoc21 since 3D is hard
module Main where

import Data.Foldable (Foldable (foldl'), find)
import Data.Maybe (isJust, mapMaybe)
import Data.Set (Set)
import qualified Data.Set as S
import Debug.Trace (traceShow)
import Utils

type Point = (Int, Int, Int)

data Cuboid = Cuboid Point Point

data StepOp = StepOn | StepOff

data Step = Step StepOp Cuboid

parseStep :: String -> Step
parseStep str =
  let [cmd, rangeStr] = words str
      ((x, y, z), (x', y', z')) =
        splitOn (== ',') rangeStr
          $> map (drop 2 .> splitOn (== '.') .> filter (not . null))
          .> (\[[x, x'], [y, y'], [z, z']] -> ((read x, read y, read z), (read x', read y', read z')))
   in case cmd of
        "on" -> Step StepOn (Cuboid (x, y, z) (x', y', z'))
        "off" -> Step StepOff (Cuboid (x, y, z) (x', y', z'))
        other -> error $ "invalid cmd: " ++ other

mini :: Point -> Point -> Point
mini (x0, y0, z0) (x1, y1, z1) = (min x0 x1, min y0 y1, min z0 z1)

maxi :: Point -> Point -> Point
maxi (x0, y0, z0) (x1, y1, z1) = (max x0 x1, max y0 y1, max z0 z1)

(<=!) :: Point -> Point -> Bool
(x0, y0, z0) <=! (x1, y1, z1) = x0 <= x1 && y0 <= y1 && z0 <= z1

volume :: Cuboid -> Int
volume (Cuboid (minX, minY, minZ) (maxX, maxY, maxZ)) =
  (maxX - minX + 1) * (maxY - minY + 1) * (maxZ - minZ + 1)

cuboidContains :: Cuboid -> Cuboid -> Bool
cuboidContains (Cuboid c0Min c0Max) (Cuboid c1Min c1Max) = c0Min <=! c1Min && c1Max <=! c0Max

cuboidIntersection :: Cuboid -> Cuboid -> Maybe Cuboid
cuboidIntersection c0 c1 = if minC <=! maxC then Just (Cuboid minC maxC) else Nothing
  where
    maxC = mini c0Max c1Max
    minC = maxi c0Min c1Min
    (Cuboid c0Min c0Max) = c0
    (Cuboid c1Min c1Max) = c1

intersects :: Cuboid -> Cuboid -> Bool
intersects c0 c1 = isJust $ cuboidIntersection c0 c1

diffPart c0@(Cuboid c0Min@(c0MinX, c0MinY, c0MinZ) c0Max@(c0MaxX, c0MaxY, c0MaxZ)) c1@(Cuboid c1Min@(c1MinX, c1MinY, c1MinZ) c1Max@(c1MaxX, c1MaxY, c1MaxZ))
  | c0MinX < c1MinX =
    Cuboid c0Min (c1MinX - 1, c0MaxY, c0MaxZ) :
    diffPart (Cuboid (c1MinX, c0MinY, c0MinZ) c0Max) c1
  | c0MinY < c1MinY =
    Cuboid c0Min (c0MaxX, c1MinY - 1, c0MaxZ) :
    diffPart (Cuboid (c0MinX, c1MinY, c0MinZ) c0Max) c1
  | c0MinZ < c1MinZ =
    Cuboid c0Min (c0MaxX, c0MaxY, c1MinZ - 1) :
    diffPart (Cuboid (c0MinX, c0MinY, c1MinZ) c0Max) c1
  | c0MaxX > c1MaxX =
    Cuboid (c1MaxX + 1, c0MinY, c0MinZ) c0Max :
    diffPart (Cuboid c0Min (c1MaxX, c0MaxY, c0MaxZ)) c1
  | c0MaxY > c1MaxY =
    Cuboid (c0MinX, c1MaxY + 1, c0MinZ) c0Max :
    diffPart (Cuboid c0Min (c0MaxX, c1MaxY, c0MaxZ)) c1
  | c0MaxZ > c1MaxZ =
    Cuboid (c0MinX, c0MinY, c1MaxZ + 1) c0Max :
    diffPart (Cuboid c0Min (c0MaxX, c0MaxY, c1MaxZ)) c1
  | otherwise = []

cuboidDifference :: Cuboid -> Cuboid -> [Cuboid]
cuboidDifference c0 c1 =
  if intersects c0 c1
    then
      if cuboidContains c1 c0
        then []
        else diffPart c0 c1
    else [c0]

doStep :: [Cuboid] -> Step -> [Cuboid]
doStep cuboids (Step op cuboid)
  | null cuboids = case op of
    StepOn -> [cuboid]
    StepOff -> []
  | otherwise = case op of
    StepOn -> cuboids ++ foldl' (\cs c -> concatMap (`cuboidDifference` c) cs) [cuboid] cuboids
    StepOff -> concatMap (`cuboidDifference` cuboid) cuboids

main :: IO ()
main = do
  contents <- getContents
  let rows = lines contents
  let steps = rows $> map parseStep
  let result1Cuboids =
        steps
          $> filter (\(Step _ cuboid) -> cuboidIntersection (Cuboid (-50, -50, -50) (50, 50, 50)) cuboid $> isJust)
          .> foldl' doStep []
  let result2Cuboids = foldl' doStep [] steps
  result1Cuboids $> map volume .> sum .> print
  result2Cuboids $> map volume .> sum .> print