

Thermals make a lot of sense. But I wonder how much of this is on purpose versus just and accident.
I can easily see an insect getting picked up and carried by an errant gust and then flung up by a strong thermal.
And storms can have nasty thermals.


Thermals make a lot of sense. But I wonder how much of this is on purpose versus just and accident.
I can easily see an insect getting picked up and carried by an errant gust and then flung up by a strong thermal.
And storms can have nasty thermals.


I was flying my paramotor one morning in Florida. I didn’t notice it at the time, but something caught my eye while editing the video from my helmet camera. It was a dragonfly at somewhere around 1100-1300 ft AGL.
No idea what it was doing up there.


I never thought about it, but I’d assume that if you had no internal monologue that you also wouldn’t be able to hear music in your head, either.
Brains are so weird.
I have aphantasia, but I assume that’s more common than no internal monologue. I wish there were stats on this.
Given that DF is free and has been around for nearly 20 years, I doubt that.


No, what I’m looking for is an example of a project in Voiden that is mature. As-in, a project that a team has been collaborating on for a while.
So how does a team structure their project in Voiden efficiently.


I installed it yesterday, and the biggest issue I’m having is envisioning what a mature project would look like in it. I have not gone looking for examples like that, but if you know of any, i’d love to see some.
I pushed my team to use trunk based development. We did cherry-picks from trunk to release branches for a couple years with no issues. Since then, I’ve written a GitHub action that automates the cherry-picks based on tickets in the commit messages.
But even before the automation, it drastically improved our dev processes.
We weren’t on Git Flow exactly, but it was a bastardized version of it.
Having used TBD successfully for like 5-6 years now. I can’t imagine using Git Flow.
None. Just a bunch of suburbanites retelling urban legends.
It was 12 days and the first time I’ve ever finished it.


What’s going on with the cigarette smoke in panel 4.


Looking at the puzzle, I knew that I had no clue how to solve it. So I came here to see if I was missing something or if there were any hints.
And the hint I saw was to do the simplest check possible, so I gave it a shot.
And that got the test input wrong, but I ran it against the real input anyway just to see if it was right. And it was.
I think if I had gone on my instincts and just tried to solve this, I could have gone around in circles for hours or days trying to get it right.
fun main() {
val input = getInput(12)
val (gifts, regions) = parseInput1(input)
var total = 0
for (i in regions.indices) {
val totalAreaOfGifts = regions[i].gifts.mapIndexed { index, count -> count * gifts[index].area }.sum()
if (totalAreaOfGifts <= regions[i].area) {
total++
}
}
println(total)
}
data class Gift(val shape: List<List<Char>>, val area: Int)
data class Region(val width: Int, val height: Int, val area: Int, val gifts: List<Int>)
fun parseInput1(input: String): Pair<List<Gift>, List<Region>> {
val gifts: MutableList<Gift> = mutableListOf()
val regions: MutableList<Region> = mutableListOf()
val lines = input.lines()
lines.forEachIndexed { index, line ->
if (line.contains(":")) {
if (line.contains("x")) {
val split = line.split(" ")
val shape = split.first().replace(":", "").split("x")
val width = shape.first().toInt()
val height = shape.last().toInt()
regions.add(
Region(
width,
height,
width * height,
split.slice(1..<split.size).map { str -> str.toInt() })
)
} else {
var nextBlankLineIndex = 0
for (i in index + 1..<lines.size) {
if (lines[i].isBlank()) {
nextBlankLineIndex = i
break
}
}
val shape = lines.slice(index + 1..<nextBlankLineIndex).map { it.toCharArray().toList() }
val area = shape.flatten().filter { it == '#' }.size
gifts.add(Gift(shape, area))
}
}
}
return gifts to regions
}


I think I got pretty lucky that the Java library I used was pretty straightforward and had good docs.
This was definitely an unfulfilling way to solve a puzzle. I did take linear algebra in college, but I really struggled in that class and retained none of it.


Part 1 had me assuming, like a lot of other folks, that Part 2 would be a simple weighted graph. So I coded Part 1 as a non-weighted graph and it was great.
Part 2 looked simple enough, but different. Part 1 was breadth first, I assumed depth first would work for Part 2. When it worked great on the test input, but took 12 seconds, I knew I was in trouble. So I added caching. And it was super quick on the test input.
Real input was a whole 'nother story. I watched my system resources balloon. At last count it was using 8GB of RAM before I killed it. And that was before solving even the first line.
So I went online to find what I’m missing to see people saying it’s a linear algebra problem, and that it’s best to use some kind of library for it.
I will admit that I leaned pretty heavily on asking Gemini questions to figure out how to use the Google OR-Tools library.
So here’s my Part 2 code:
import com.google.ortools.Loader
import com.google.ortools.linearsolver.MPSolver
import com.google.ortools.linearsolver.MPVariable
import utils.*
fun main() {
val input = getInput(10)
val machines = parseInput1(input)
Loader.loadNativeLibraries()
var total = 0
for (machine in machines) {
val buttons = machine.buttons
val joltages = machine.joltages
val solver = MPSolver.createSolver("SCIP") ?: throw Exception("Could not create solver")
val x = arrayOfNulls<MPVariable>(machine.buttons.size)
for (i in buttons.indices) {
x[i] = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "x$i")
}
val target = joltages.map { it.toDouble() }
val aMatrix = joltages.indices.map { joltageToArray(it, buttons) }.toTypedArray()
for (j in joltages.indices) {
val ct = solver.makeConstraint(target[j], target[j], "joltage_constraint_$j")
for (i in buttons.indices) {
ct.setCoefficient(x[i], aMatrix[j][i])
}
}
val objective = solver.objective()
for (i in buttons.indices) {
objective.setCoefficient(x[i], 1.0)
}
objective.setMinimization()
val resultStatus = solver.solve()
if (resultStatus == MPSolver.ResultStatus.OPTIMAL) {
val result = objective.value().toInt()
total += result
} else {
println("Problem could not be solved.")
}
}
println(total)
}
data class Machine(val configuration: List<Boolean>, val buttons: List<List<Int>>, val joltages: List<Int>)
fun parseInput1(input: String): List<Machine> {
return input.lines()
.filter { it.isNotBlank() }
.map {
val split = it.split(" ")
val configuration = split.first().toCharArray()
.slice(1..<split.first().length - 1)
.map { char ->
when (char) {
'#' -> true
else -> false
}
}
val buttons = split.slice(1..<split.size - 1)
.map { str ->
str.slice(1..<str.length - 1)
.split(",")
.map { number -> number.toInt() }
}
val joltages = split.last()
.slice(1..<split.last().length - 1)
.split(",")
.map { number -> number.toInt() }
Machine(configuration, buttons, joltages)
}
}
fun joltageToArray(joltageIndex: Int, buttons: List<List<Int>>): Array<Double> {
val array = DoubleArray(buttons.size) { 0.0 }.toTypedArray()
for (i in buttons.indices) {
if (joltageIndex in buttons[i]) {
array[i] = 1.0
}
}
return array
}


This was substantially easier than yesterday’s problem. Especially considering I still haven’t done Part 2 from yesterday.
Anyway, here is my Part 2 solution for today. Given how quickly Part 1 ran without a cache, I didn’t think Part 2 would be much different, until my computer fans spun up and stayed there for over a minute. So I added a cache and re-ran it and it ran in 31ms.
const val end = "out"
var map: Map<String, List<String>>? = null
val problemVertices = "dac" to "fft"
val cache: MutableMap<Pair<String, Pair<Boolean, Boolean>>, Long> = mutableMapOf()
fun main() {
val input = getInput(11)
map = parseInput1(input)
val total = countPaths2("svr", false to false).first
println(total)
}
fun countPaths2(vertex: String, problemVerticesEncountered: Pair<Boolean, Boolean>): Pair<Long, Pair<Boolean, Boolean>> {
val otherVertices = map!![vertex]!!
var total = 0L
val nextProblemVerticesEncountered =
(problemVerticesEncountered.first || vertex == problemVertices.first) to (problemVerticesEncountered.second || vertex == problemVertices.second)
for (otherVertex in otherVertices) {
val key = otherVertex to nextProblemVerticesEncountered
if (cache.contains(key)) {
total += cache[key]!!
} else if (otherVertex == end) {
if (nextProblemVerticesEncountered.first && nextProblemVerticesEncountered.second) {
total++
}
} else {
total += countPaths2(otherVertex, nextProblemVerticesEncountered).first
}
}
cache[vertex to nextProblemVerticesEncountered] = total
return total to nextProblemVerticesEncountered
}
fun parseInput1(input: String): Map<String, List<String>> = input.lines()
.filter { it.isNotBlank() }
.associate {
val split = it.split(": ")
split[0] to split[1].split(" ").toList()
}


My solution can’t be very good because it took over a minute to run part 2. I used a ray casting algorithm to determine if a point was in the main polygon. I did that for the 2 corners of each rectangle that weren’t given as input. If both of those corners were in the polygon, then I checked each point of each edge of that rectangle.
My first stab at this didn’t consider the edge points of the rectangle, and after looking at some visualizations of the polygon, I could see where I was going wrong. I wouldn’t have considered a rectangle with all 4 corners in the polygon could have an edge that goes outside the polygon without looking at a visualization.
Part 2 code:
fun main() {
val input = getInput(9)
val polygon = parseInput1(input)
val rectangles: MutableList<Rectangle> = mutableListOf()
for (i in polygon.indices) {
for (j in i + 1..<polygon.size) {
rectangles.add(Rectangle(polygon[i], polygon[j]))
}
}
rectangles.sortByDescending { it.area }
var answer: Rectangle? = null
for (rectangle in rectangles) {
if (isInsidePolygon(rectangle.corner3, polygon)
&& isInsidePolygon(rectangle.corner4, polygon)
) {
val edgePoints: MutableList<Pair<Long, Long>> = mutableListOf()
edgePoints.addAll(getAllPointsBetween(rectangle.corner1, rectangle.corner3))
edgePoints.addAll(getAllPointsBetween(rectangle.corner1, rectangle.corner4))
edgePoints.addAll(getAllPointsBetween(rectangle.corner2, rectangle.corner3))
edgePoints.addAll(getAllPointsBetween(rectangle.corner2, rectangle.corner4))
var isInside = true
for (edgePoint in edgePoints) {
if (!isInsidePolygon(edgePoint, polygon)) {
isInside = false
break
}
}
if (isInside) {
answer = rectangle
break
}
}
}
println(answer?.area ?: "Whoops")
}
fun parseInput1(input: String): List<Pair<Long, Long>> = input.lines()
.filter { it.isNotBlank() }
.map {
val split = it.split(",")
split[0].toLong() to split[1].toLong()
}
data class Rectangle(val corner1: Pair<Long, Long>, val corner2: Pair<Long, Long>) {
val corner3: Pair<Long, Long> = corner1.first to corner2.second
val corner4: Pair<Long, Long> = corner2.first to corner1.second
val area: Long = (1 + abs(corner1.first - corner2.first)) * (1 + abs(corner1.second - corner2.second))
}
fun isInsidePolygon(point: Pair<Long, Long>, polygon: List<Pair<Long, Long>>): Boolean {
val x = point.first
val y = point.second
var intersectionCount = 0L
for (i in polygon.indices) {
val p1 = polygon[i]
val p2 = polygon[(i + 1) % polygon.size]
if (point == p1 || point == p2 || isOnEdge(point, p1, p2)) {
return true
}
val y1 = p1.second
val y2 = p2.second
val crossesRay = (y in y1..<y2) || (y in y2..<y1)
if (crossesRay) {
val x1 = p1.first
val x2 = p2.first
val xIntersect = (x2 - x1) * (y - y1).toDouble() / (y2 - y1) + x1
if (x < xIntersect) {
intersectionCount++
}
}
}
return intersectionCount % 2L != 0L
}
fun isOnEdge(p: Pair<Long, Long>, p1: Pair<Long, Long>, p2: Pair<Long, Long>): Boolean {
val crossProduct = (p.second - p1.second) * (p2.first - p1.first) -
(p.first - p1.first) * (p2.second - p1.second)
if (crossProduct != 0L) {
return false
}
val isBetweenX = p.first in minOf(p1.first, p2.first)..maxOf(p1.first, p2.first)
val isBetweenY = p.second in minOf(p1.second, p2.second)..maxOf(p1.second, p2.second)
return isBetweenX && isBetweenY
}
fun getAllPointsBetween(left: Pair<Long, Long>, right: Pair<Long, Long>): List<Pair<Long, Long>> {
if (right.first == left.first) {
val max = maxOf(left.second, right.second)
val min = minOf(left.second, right.second)
return (min + 1..<max).toList().map { right.first to it }
} else if (right.second == left.second) {
val max = maxOf(left.first, right.first)
val min = minOf(left.first, right.first)
return (min + 1..<max).toList().map { it to right.second }
} else {
throw Exception("Whoops")
}
}


It can be confusing, for sure. But I really like Pairs and Triples.
answer = distance.first.first.first * distance.first.second.first is not very elegant, lol.


That’s interesting. I found part 1 to be the hard part and part 2 was just simplifying part 1. They ran in about the same time for me, too.
Part 1 run time:
Input IO: 0m 0s 10ms
Input Parse: 0m 0s 21ms
Algorithm: 0m 0s 250ms
Total: 0m 0s 281ms
Part 2 run time:
Input IO: 0m 0s 11ms
Input Parse: 0m 0s 22ms
Algorithm: 0m 0s 255ms
Total: 0m 0s 288ms
Code:
const val connectionsLimit = 1000
const val circuitsLimit = 3
fun part1() {
val input = getInput(8)
val circuits: MutableList<MutableSet<Triple<Int, Int, Int>>> = parseInput1(input)
val distances: MutableList<Pair<Pair<Triple<Int, Int, Int>, Triple<Int, Int, Int>>, Double>> = mutableListOf()
val tempList = circuits.toList()
for (i in tempList.indices) {
val left = tempList[i].first()
for (j in i + 1..<tempList.size) {
val right = tempList[j].first()
val distance = calculateDistance(left, right)
val coordinates = left to right
distances.add(coordinates to distance)
}
}
distances.sortBy { it.second }
// Parts 1 and 2 are the same until here
for (i in 0..<connectionsLimit) {
val distance = distances[i]
val leftCircuit = circuits.first { it.contains(distance.first.first) }
val rightCircuit = circuits.first { it.contains(distance.first.second) }
if (leftCircuit != rightCircuit) {
leftCircuit.addAll(rightCircuit)
circuits.remove(rightCircuit)
}
}
val sizes = circuits.map { it.size.toLong() }.sortedDescending().slice(0..<circuitsLimit)
val total = sizes.reduce { acc, i -> acc * i }
println(total)
}
fun part2() {
val input = getInput(8)
val circuits: MutableList<MutableSet<Triple<Int, Int, Int>>> = parseInput1(input)
val distances: MutableList<Pair<Pair<Triple<Int, Int, Int>, Triple<Int, Int, Int>>, Double>> = mutableListOf()
val tempList = circuits.toList()
for (i in tempList.indices) {
val left = tempList[i].first()
for (j in i + 1..<tempList.size) {
val right = tempList[j].first()
val distance = calculateDistance(left, right)
val coordinates = left to right
distances.add(coordinates to distance)
}
}
distances.sortBy { it.second }
// Part 2 differs starting here
var answer = 0
for (distance in distances) {
val leftCircuit = circuits.first { it.contains(distance.first.first) }
val rightCircuit = circuits.first { it.contains(distance.first.second) }
if (leftCircuit != rightCircuit) {
leftCircuit.addAll(rightCircuit)
circuits.remove(rightCircuit)
if (circuits.size == 1) {
answer = distance.first.first.first * distance.first.second.first
break
}
}
}
println(answer)
}
fun parseInput1(input: String): MutableList<MutableSet<Triple<Int, Int, Int>>> {
return input.lines()
.filter { it.isNotBlank() }
.map {
val split = it.split(",")
mutableSetOf(Triple(split[0].toInt(), split[1].toInt(), split[2].toInt()))
}
.toMutableList()
}
fun calculateDistance(left: Triple<Int, Int, Int>, right: Triple<Int, Int, Int>): Double {
val dx = (left.first - right.first).toDouble()
val dy = (left.second - right.second).toDouble()
val dz = (left.third - right.third).toDouble()
val distanceSquared = dx.pow(2) + dy.pow(2) + dz.pow(2)
return sqrt(distanceSquared)
}


I didn’t do recursion on part 1, so my part 1 and 2 were fairly different.
const val start = 'S'
const val empty = '.'
const val splitter = '^'
const val beam = '|'
var width: IntRange = IntRange(0, 0)
var height: IntRange = IntRange(0, 0)
val cache: MutableMap<Pair<Int, Int>, Long> = mutableMapOf()
var map: List<List<Char>> = listOf()
fun main() {
val input = getInput(7)
map = parseInput1(input)
height = map.indices
width = map[0].indices
val startLocation = map[0].indexOf(start) to 0
val splits = moveBeam(startLocation) + 1
println(splits)
}
fun parseInput1(input: String): List<List<Char>> = input.lines()
.filter { it.isNotBlank() }
.map { it.toCharArray().toList() }
fun moveBeam(beamLocation: Pair<Int, Int>): Long {
if (cache.containsKey(beamLocation)) {
return cache[beamLocation]!!
}
val belowLocation = beamLocation.first to beamLocation.second + 1
if (belowLocation.second !in height) {
return 0L
}
if (cache.containsKey(belowLocation)) {
return cache[belowLocation]!!
}
val below = map[belowLocation.second][belowLocation.first]
var splits = 0L
if (below == empty) {
splits = moveBeam(belowLocation)
} else if (below == splitter) {
splits++
val leftLocation = belowLocation.first - 1 to belowLocation.second
val left = if (leftLocation.first in width) map[leftLocation.second][leftLocation.first] else '!'
if (left == empty || left == splitter) {
splits += moveBeam(leftLocation)
}
val rightLocation = belowLocation.first + 1 to belowLocation.second
val right = if (rightLocation.first in width) map[rightLocation.second][rightLocation.first] else '!'
if (right == empty || right == splitter) {
splits += moveBeam(rightLocation)
}
}
cache[beamLocation] = splits
return splits
}


I also thought about trying to rotate, but not for very long. Mine would be a bit simpler if I’d done what you did and build the number string and then check if it’s blank.
fun main() {
val input = getInput(6)
val output = parseInput2(input)
var total = 0L
for ((numbers, operator) in output) {
when (operator) {
'+' -> { total += numbers.sum() }
'*' -> { total += numbers.reduce { acc, number -> acc * number }}
}
}
println(getElapsedTime())
println(total)
}
fun parseInput2(input: String): List<Pair<List<Long>, Char>> {
val rows = input.lines()
.filter { it.isNotBlank() }
.map { it.toCharArray() }
val output: MutableList<Pair<List<Long>, Char>> = mutableListOf()
val numberRowCount = rows.size - 1
var isNewProblem = true
var currentNumbers: MutableList<Long> = mutableListOf()
var operator = ' '
for (column in rows[0].indices) {
if (!isNewProblem && isColumnEmpty(rows, column)) {
isNewProblem = true
output.add(currentNumbers to operator)
continue
}
if (isNewProblem) {
isNewProblem = false
currentNumbers = mutableListOf()
operator = rows.last()[column]
}
var number = ""
for (row in 0..<numberRowCount) {
if (rows[row][column] != ' ') {
number += rows[row][column]
}
}
currentNumbers.add(number.toLong())
}
if (!isNewProblem) {
output.add(currentNumbers to operator)
}
return output
}
fun isColumnEmpty(rows: List<CharArray>, column: Int): Boolean {
for (i in rows.indices) {
if (rows[i][column] != ' ') {
return false
}
}
return true
}
I forgot, though. I was flying in the morning while there had been a lot of low level fog. There were no thermals at the time. The earth hadn’t started heating up yet.