--- /dev/null
+-- Writeup at https://work.njae.me.uk/2024/12/25/advent-of-code-2024-day-23/
+
+import AoC
+
+import Data.Text (Text)
+import qualified Data.Text.IO as TIO
+import Data.Attoparsec.Text hiding (take)
+import Control.Applicative
+import qualified Data.Map.Strict as M
+import qualified Data.Set as S
+import Data.List
+import Data.Bits (xor, (.&.), (.|.), (.<<.))
+
+type Wires = M.Map String Int
+
+data GateType = And | Or | Xor
+ deriving (Show, Eq)
+
+data Gate = Gate { gType :: GateType, inputs :: [String], output :: String }
+ deriving (Show, Eq)
+
+type Device = [Gate]
+
+main :: IO ()
+main =
+ do dataFileName <- getDataFileName
+ text <- TIO.readFile dataFileName
+ let (wires, device) = successfulParse text
+ -- print wires
+ -- print device
+ -- print $ simulate wires device
+ -- print $ wiresOutput $ simulate wires device
+ print $ part1 wires device
+
+part1 :: Wires -> Device -> Int
+part1 wires device = wiresOutput $ simulate wires device
+
+simulate :: Wires -> Device -> Wires
+simulate wires device = wires'
+ where (wires', []) = until (null . snd) simulateOnce (wires, device)
+
+simulateOnce :: (Wires, Device) -> (Wires, Device)
+simulateOnce (wires, device) = (wires', remaining)
+ where
+ (run, remaining) = partition (canActivateGate wires) device
+ wires' = foldl simulateGate wires run
+
+canActivateGate :: Wires -> Gate -> Bool
+canActivateGate wires gate = all (`M.member` wires) gate.inputs
+
+simulateGate :: Wires -> Gate -> Wires
+simulateGate wires gate = M.insert gate.output result wires
+ where [i1, i2] = gate.inputs
+ result = case gate.gType of
+ And -> (wires M.! i1) .&. (wires M.! i2)
+ Or -> (wires M.! i1) .|. (wires M.! i2)
+ Xor -> (wires M.! i1) `xor` (wires M.! i2)
+
+isOutputWire :: String -> Bool
+isOutputWire (x:_) = x == 'z'
+
+wiresOutput :: Wires -> Int
+wiresOutput wires = M.foldlWithKey' go 0 outWires
+ where outWires = M.filterWithKey (\k _ -> isOutputWire k) wires
+ outShift w = read $ drop 1 w
+ go acc w v = acc .|. (v .<<. outShift w)
+
+-- parse the input file
+
+wiresDeviceP :: Parser (Wires, Device)
+wiresP :: Parser Wires
+wireP :: Parser (String, Int)
+nameP :: Parser String
+deviceP :: Parser Device
+gateP :: Parser Gate
+gateTypeP :: Parser GateType
+
+wiresDeviceP = (,) <$> wiresP <* endOfLine <* endOfLine <*> deviceP
+
+wiresP = M.fromList <$> wireP `sepBy` endOfLine
+wireP = (,) <$> nameP <* string ": " <*> decimal
+
+nameP = many1 (letter <|> digit)
+
+deviceP = gateP `sepBy` endOfLine
+gateP = gateify <$> nameP <* space <*> gateTypeP <* space <*> nameP <* string " -> " <*> nameP
+ where gateify i1 g i2 o = Gate g [i1, i2] o
+
+gateTypeP = (And <$ "AND") <|> (Or <$ "OR") <|> (Xor <$ "XOR")
+
+successfulParse :: Text -> (Wires, Device)
+successfulParse input =
+ case parseOnly wiresDeviceP input of
+ Left _err -> (M.empty, []) -- TIO.putStr $ T.pack $ parseErrorPretty err
+ Right wiresDevice -> wiresDevice