Treetop Tree House

Description: https://adventofcode.com/2022/day/8

open System.IO
open System

type Index(x: int, y: int) =
    member _.X = x
    member _.Y = y

type Tree(index: Index, height: int) =
    let index = index
    let height = height
    member _.Index = index
    member _.Height = height

    member val visibleLeft = false with get, set
    member val visibleRight = false with get, set
    member val visibleUp = false with get, set
    member val visibleDown = false with get, set

    member this.visible() =
        this.visibleLeft || this.visibleRight || this.visibleUp || this.visibleDown

type Forest() =
    let mutable trees: Tree[][] = Array.empty
    let mutable down: Tree[][] = Array.empty
    let mutable up: Tree[][] = Array.empty
    let mutable right: Tree[][] = Array.empty
    let mutable left: Tree[][] = Array.empty

    let addHorizontal (tree: Tree) =
        if Array.isEmpty left[tree.Index.Y] then
            left[tree.Index.Y] <- Array.append left[tree.Index.Y] [| tree |]
            tree.visibleLeft <- true
        else if (Array.last left[tree.Index.Y]).Height < tree.Height then
            left[tree.Index.Y] <- Array.append left[tree.Index.Y] [| tree |]
            tree.visibleLeft <- true
            Array.ForEach(right[tree.Index.Y], (fun (x: Tree) -> x.visibleRight <- false))
            right[tree.Index.Y] <- Array.empty<Tree>
        else if Array.isEmpty right[tree.Index.Y] then
            right[tree.Index.Y] <- Array.append right[tree.Index.Y] [| tree |]
            tree.visibleRight <- true
        else
            Array.ForEach(right[tree.Index.Y], (fun (x: Tree) -> x.visibleRight <- false))

            right[tree.Index.Y] <-
                (right[tree.Index.Y] |> (Array.filter (fun x -> x.Height > tree.Height)), [| tree |])
                ||> Array.append

            Array.ForEach(right[tree.Index.Y], (fun (x: Tree) -> x.visibleRight <- true))

    let addVertical (tree: Tree) =
        if tree.Index.X > down.Length - 1 then
            down <- Array.append down [| [| tree |] |]
            tree.visibleDown <- true
            up <- Array.append up [| [||] |]
        else if (Array.last down[tree.Index.X]).Height < tree.Height then
            down[tree.Index.X] <- Array.append down[tree.Index.X] [| tree |]
            tree.visibleDown <- true
            Array.ForEach(up[tree.Index.X], (fun (x: Tree) -> x.visibleUp <- false))
            up[tree.Index.X] <- Array.empty<Tree>
        else if Array.isEmpty up[tree.Index.X] then
            up[tree.Index.X] <- Array.append up[tree.Index.X] [| tree |]
            tree.visibleUp <- true
        else
            Array.ForEach(up[tree.Index.X], (fun (x: Tree) -> x.visibleUp <- false))

            up[tree.Index.X] <-
                (up[tree.Index.X] |> Array.filter (fun x -> x.Height > tree.Height), [| tree |])
                ||> Array.append

            Array.ForEach(up[tree.Index.X], (fun (x: Tree) -> x.visibleUp <- true))

    member _.Visible =
        Array.fold (fun (acc: Tree[]) (x: Tree[]) -> Array.append acc x) Array.empty trees
        |> Array.filter (fun x -> x.visible ())

    member this.VisibleCount = this.Visible |> Array.length

    member _.NewRow() =
        left <- Array.append left [| [||] |]
        right <- Array.append right [| [||] |]
        trees <- Array.append trees [| [||] |]

    member _.Add (index: Index) (height: int) =
        let tree = new Tree(index, height)
        trees[tree.Index.Y] <- Array.append trees[tree.Index.Y] [| tree |]

        addHorizontal tree
        addVertical tree

    member this.Score() =

        let lowerIndex (axis: Index -> int) (trees: Tree[]) =
            if Array.isEmpty trees then
                0
            else
                Array.last trees |> fun x -> x.Index |> axis

        let higherIndex (maxIndex: int) (axis: Index -> int) (trees: Tree[]) =
            if Array.isEmpty trees then
                maxIndex
            else
                trees[0] |> fun x -> x.Index |> axis

        let higherOrEqualHeight (height: int) (trees: Tree[]) =
            trees |> Array.filter (fun x -> x.Height >= height)

        let mutable maxScore = 0

        for tree in this.Visible do
            let filter (selector: (Index -> int)) (comparer: int -> int -> bool) (trees: Tree[]) =
                Array.filter (fun (x: Tree) -> (x.Index |> selector, tree.Index |> selector) ||> comparer) trees

            let vertical = trees |> Array.map (fun x -> x[tree.Index.X])
            let horizontal = trees[tree.Index.Y]

            let upScore =
                vertical
                |> filter (fun x -> x.Y) (<)
                |> higherOrEqualHeight tree.Height
                |> lowerIndex (fun x -> x.Y)
                |> fun x -> tree.Index.Y - x

            let downScore =
                vertical
                |> filter (fun x -> x.Y) (>)
                |> higherOrEqualHeight tree.Height
                |> higherIndex (trees.Length - 1) (fun x -> x.Y)
                |> fun x -> x - tree.Index.Y

            let leftScore =
                horizontal
                |> filter (fun x -> x.X) (<)
                |> higherOrEqualHeight tree.Height
                |> lowerIndex (fun x -> x.X)
                |> fun x -> tree.Index.X - x

            let rightScore =
                horizontal
                |> filter (fun x -> x.X) (>)
                |> higherOrEqualHeight tree.Height
                |> higherIndex (trees[0].Length - 1) (fun x -> x.X)
                |> fun x -> x - tree.Index.X

            let score = downScore * upScore * leftScore * rightScore

            if (score > maxScore) then
                maxScore <- score

        maxScore

type Input() =

    member _.Compute(part: int) =
        use stream = new StreamReader "./input.txt"
        let mutable endOfFile = false

        let tree = new Forest()
        let mutable index: Index = new Index(0, 0)

        while (not endOfFile) do
            let line = stream.ReadLine()

            if (line = null) then
                endOfFile <- true
            else
                index <- new Index(0, index.Y)

                tree.NewRow()

                for x in line do
                    let num: int = (int) x - (int) '0'

                    tree.Add index num
                    index <- new Index(index.X + 1, index.Y)

                index <- new Index(index.X, index.Y + 1)

        if part = 1 then tree.VisibleCount else tree.Score()

let input = Input()
let part = (Environment.GetCommandLineArgs()[1]) |> int

printfn "%d" (input.Compute(part))