diff --git a/functions/src/day17.ts b/functions/src/day17.ts index f5d93d6..2c31e8e 100644 --- a/functions/src/day17.ts +++ b/functions/src/day17.ts @@ -23,14 +23,6 @@ class Day17 implements Day { // steps = 5 -> 0-1-2-3-4 = -10 // steps = 6 -> 0-1-2-3-4-5 = -15 - // v = -1 * x - - // Theoretically, the displacement should be: - // s(t) = s_0 + v_0 t + 1/2 a t^2 - // Filling that in for s_0 = 0 and a = -1, we get: - // s(t) = v_0 t - 1/2 t ^ 2 - // But that gives us non-integer values, for example when t = 3, we get: v_0 * 3 - 4.5 - part1(input: string[]): number | string { const targetArea = TargetArea.parse(input[0]); @@ -41,20 +33,21 @@ class Day17 implements Day { // Note that the triangle value of x must be at least the minimal x of the target area, otherwise the probe won't // reach the target area before it's velocity reaches zero: - const vx0_min = this.triangleInvCeil(targetArea.minX); + const v_x0_min = Day17.triangleInvCeil(targetArea.minX); // Calculate the x-position we reach after v_x0 steps: - const x_min = this.triangle(vx0_min); + const x_min = Day17.triangle(v_x0_min); if (targetArea.isInX(x_min)) { // We have an x-speed that reaches the target area in x_min steps and stays there (e.g. reaches velocity zero in the target area), // so we can just find the biggest y that at some point ends up in the target area. // We already know that the y speed when the "probe" comes down is: // -v_y0 - 1 - // So if we choose as starting y that is one higher than the minimal y value, we end up in the bottom of the target area, while reaching the maximum height: - const vy0_max = Math.abs(targetArea.minY) - 1; - return this.triangle(vy0_max); + // So if we choose as starting y that is one higher than the minimal y value, we end up in the bottom of the target area, while + // reaching the maximum height: + const v_y0_max = Math.abs(targetArea.minY) - 1; + return Day17.triangle(v_y0_max); } else { - throw Error("fuck :)"); + throw Error("Unsupported target area, only a target area with an x values that is a triangle number is supported"); } } @@ -66,33 +59,144 @@ class Day17 implements Day { const targetArea = TargetArea.parse(input[0]); + const v_x0_min = Day17.triangleInvCeil(targetArea.minX); + const v_x0_max_static = Day17.triangleInvFloor(targetArea.maxX); + // const maxYSteps0 = Day17.triangleInvCeil(Math.abs(targetArea.minY)); - // TODO implement - return 0; - } - simulateY(initialSpeedY: number, steps: number): { maxY: number, finalY: number} { + let solutionCount = 0; - let curSpeed = initialSpeedY; - let y = 0; - let maxY = 0; - for (let step = 0; step < steps; step++) { - y += curSpeed; - curSpeed--; - if (y > maxY) { - maxY = y; + for (let v_x0 = v_x0_min; v_x0 <= targetArea.maxX; v_x0++) { + const maxSteps = v_x0 <= v_x0_max_static ? 1000 : v_x0_max_static; + for (let v_y0 = targetArea.minY; v_y0 < Math.abs(targetArea.minY); v_y0++) { + for (let steps = 1; steps < maxSteps; steps++) { + const xRangeResult = this.isInXAfterSteps(v_x0, steps, targetArea); + if (xRangeResult === RangeResult.TOO_HIGH) { + break; + } if (xRangeResult === RangeResult.YES) { + // Check what y values work for this x and step count: + const yRangeResult = this.isInYAfterSteps(v_y0, steps, targetArea); + if (yRangeResult === RangeResult.YES) { + // console.log(`${v_x0}, ${v_y0}`) + solutionCount++; + break; + } + } + } } } - return { finalY: y, maxY: maxY }; + return solutionCount; + + // // We gonna have to semi-brute-force anyway, + // + // // But first, let's find all x-es that perpetually stay inside the target area + // const v_x0_min = Day17.triangleInvCeil(targetArea.minX); + // const v_x0_max_static = Day17.triangleInvFloor(targetArea.maxX); + // + // // const vxStaticCount = (v_x0_max_static - v_x0_min) + 1; + // // + // // Then we count all y's that end up in the target area after v_x0_min steps + // // Note that any + // // if (v_x0_min * 2 + 1 > Math.abs(targetArea.maxY) + 1); + // let vyStaticCount = 0; + // // With a start y speed of 0, we can stay in the target area for at most this many steps: + // const maxYSteps0 = Day17.triangleInvCeil(Math.abs(targetArea.minY)); + // + // for (let vy_0 = targetArea.minY; vy_0 < Math.abs(targetArea.minY); vy_0++) { + // const minSteps = vy_0 > 0 ? Math.max(v_x0_min, 2 * vy_0 + 1) : v_x0_min; + // const maxSteps = vy_0 > 0 ? minSteps + maxYSteps0 : maxYSteps0; + // for (let steps = minSteps; steps < maxSteps; steps++) { + // const rangeResult = this.isInYAfterSteps(vy_0, steps, targetArea); + // if (rangeResult === RangeResult.YES) { + // vyStaticCount++; + // break; + // } else if (rangeResult === RangeResult.TOO_LOW) { + // // We've fallen below the target area, no need to scan this range any further + // break; + // } + // } + // } + // + // const staticSolutions = (v_x0_max_static - v_x0_min + 1) * vyStaticCount; + // + // // Any more numbers of steps will overshoot the x direction + // let dynamicSolutions = 0; + // for (let steps = 1; steps <= v_x0_max_static; steps++) { + // for (let vx_0 = v_x0_min; vx_0 < targetArea.maxX + 1; vx_0++) { + // if (this.isInXAfterSteps(vx_0, steps, targetArea) !== RangeResult.YES) continue; + // for (let vy_0 = targetArea.minY; vy_0 < Math.abs(targetArea.minY) - 1; vy_0++) { + // const rangeResult = this.isInYAfterSteps(vy_0, steps, targetArea); + // if (rangeResult === RangeResult.YES) { + // dynamicSolutions++; + // } else if (rangeResult === RangeResult.TOO_HIGH) { + // // We've fallen below the target area + // break; + // } + // } + // } + // } + // return dynamicSolutions + staticSolutions; } - triangle(n: number): number { + isInXAfterSteps(v_x0: number, steps: number, targetArea: TargetArea): RangeResult { + + if (steps >= v_x0) { + // reached final position, which is the triangle number of the initial speed: + return targetArea.isInX(Day17.triangle(v_x0)); + } else { + // On our way to the final position: + const x = Day17.triangle(v_x0) - Day17.triangle(v_x0 - steps); + return targetArea.isInX(x); + } + + } + + isInYAfterSteps(v_y0: number, steps: number, targetArea: TargetArea): RangeResult { + + if (targetArea.maxY >= 0) throw Error("Only target area's below the starting point are supported") + + if (v_y0 > 0) { + if (steps < 2 * v_y0) { + // We haven't gone below the + return RangeResult.TOO_HIGH; + } else { + // This is equivalent to firing with a negative y speed after v_y0 * 2 + 1 steps (so the steps left = steps - 2 * v_y0 - 1) + return this.isInYAfterSteps(-v_y0 - 1, steps - 2 * v_y0 - 1, targetArea); + } + } else { + // Firing downward: + const y = - Day17.calcPositionAfterIncreasingVelocity(Math.abs(v_y0), steps); + return targetArea.isInY(y); + } + } + + static calcPositionAfterIncreasingVelocity(v: number, steps: number): number { + if (v < 0) throw Error("Velocity must be positive"); + + return Day17.triangle(v - 1 + steps) - Day17.triangle(v - 1); + } + + static triangle(n: number): number { return (n*(n + 1)) / 2; } - triangleInvCeil(x: number): number { - return Math.ceil((Math.sqrt(8 * x + 1) - 1) / 2); + static triangleInvCeil(x: number): number { + return Math.ceil(this.triangleInv(x)); } + static triangleInvFloor(x: number): number { + return Math.floor(this.triangleInv(x)); + } + + static triangleInv(x: number): number { + return (Math.sqrt(8 * x + 1) - 1) / 2; + } + +} + +enum RangeResult { + TOO_LOW = -1, + TOO_HIGH = 0, + YES = 1 } class TargetArea { @@ -126,16 +230,20 @@ class TargetArea { ) } - isInX(x: number): boolean { - return x >= this.minX && x <= this.maxX; + isInX(x: number): RangeResult { + if (x < this.minX) return RangeResult.TOO_LOW; + else if (x > this.maxX) return RangeResult.TOO_HIGH; + else return RangeResult.YES; } - isInY(y: number): boolean { - return y >= this.minY && y <= this.maxY; + isInY(y: number): RangeResult { + if (y < this.minY) return RangeResult.TOO_LOW; + else if (y > this.maxY) return RangeResult.TOO_HIGH; + else return RangeResult.YES; } isIn(x: number, y: number): boolean { - return this.isInX(x) && this.isInY(y); + return this.isInX(x) === RangeResult.YES && this.isInY(y) === RangeResult.YES; } }