That's a good point. You can get away with that with a new language, but adding nullability as a non-default option would break existing code much like making things const by default in C++ would, I suppose.
Lombok had a bunch of great things that should've been part of the java language to begin with. They've slowly been folded in (so now you have to work out which @NotNull annotation you want) but the language does still improve.
I broke down the outline into a set of boxes by scanning over them.
haskell
type Box = (C, C) -- inclusive coordinates
makeBoxes :: [C] -> [Box]
makeBoxes cs =
let cs' = sort cs -- left-to-right first, top-to-bottom second
scanLines = cs' & groupOn fst
in scanOver 0 [] scanLines
where
scanOver lastX currentYs [] = []
scanOver lastX currentYs (new : rest) =
let newX = new & head & fst
closedBoxes = do
[y1, y2] <- currentYs & chunksOf 2
pure ((lastX, y1), (newX, y2))
newYs =
-- Take the new column and remove anything that
-- corresponds to a y value that appears in both
merge currentYs (map snd new)
in -- Close the current boxes
closedBoxes ++ scanOver newX newYs rest
merge [] ns = ns
merge ms [] = ms
merge (m : ms) (n : ns)
| m < n = m : merge ms (n : ns)
| m > n = n : merge (m : ms) ns
| otherwise = merge ms ns
The fiddly bit was handling all the cases for shape subtraction. I don't give it here because it's just a slog, but the gist is this:
haskell
type Shape = [Box]
subtractBox :: Box -> Box -> Shape -- returns whatever's left
subtractShape :: Shape -> Shape -> Shape -- this is just a fold.
The idea: take a bounding box that's just large enough to cover all coordinates. From that, subtract the set of boxes above. You get a set of boxes that are in the "outside", ie, illegal region. [I did it this way because managing shape subtraction from the set of "inside" boxes is just more work.]
Then for each candidate rectangle, if it overlaps with any of the "out-of-bounds" boxes, it's not a solution.
There's an alternative approach to merging ranges. Put starting and ending points into a heap then scan it, keeping track of the number of open ranges present. Something like this:
haskell
mergeRanges rs =
let
-- We put in this order so we count openings before closings
starts = map (\(a, b) -> (a, -1)) rs
ends = map (\(a, b) -> (b, 1)) rs
heap = H.fromList (starts ++ ends) :: H.MinHeap (Int, Int)
in
mergeNo heap
where
-- not in a range currently
mergeNo :: H.MinHeap (Int, Int) -> [R]
mergeNo h =
case H.view h of
Nothing -> []
Just ((a, -1), h') -> mergeYes a 1 h'
-- in a range
mergeYes :: Int -> Int -> H.MinHeap (Int, Int) -> [R]
mergeYes start height h =
let Just ((v, delta), h') = H.view h
newHeight = height - delta
in if newHeight == 0
then (start, v) : mergeNo h'
else mergeYes start newHeight h'
The issue here is that the author of that post, and potentially the fictional author of the thing being lampooned, are not drawing a distinction between a tutorial (or an explanation) and a how-to.
Either you want to get a task done, or you want to spend a lot longer learning how to work that out for yourself.
(Many tutorials will include small set of how-to-like instructions because emulating the actions of a master will improve one's vocabulary of what can be done as well as how it is achieved.)
Start with the core gameplay loop. Look for the fun. Look for what you can take out, not add.
Play-testing something like this with pencil and paper first might be a good idea. Once you work out why it always plays the same way, then you can look for how to perturb the gameplay.
Unfortunately high. It's the same reason there's a "space force". He didn't know about the coastguard and guessed something else, then doubled down.