mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-19 03:23:27 +02:00
2021-24, yay a whole day staring at assembly and reverse engineering it to hard code it -_-
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
//go:embed input.txt
|
||||
var input string
|
||||
|
||||
func init() {
|
||||
// do this in init (not main) so test file has same input
|
||||
input = strings.TrimRight(input, "\n")
|
||||
if len(input) == 0 {
|
||||
panic("empty input.txt file")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
ans := aluDay24(input, part)
|
||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
|
||||
func aluDay24(input string, part int) int64 {
|
||||
instLines := strings.Split(input, "\n")
|
||||
|
||||
/*
|
||||
"Compiled" the assembly to understand that it was 14 separate "programs"
|
||||
one for each input character.
|
||||
z is the only stateful variable, doing some algebra shows that this is
|
||||
basically a stack pairing a=1 and a=26
|
||||
|
||||
determining the relationships between the input characters reduces the
|
||||
search size, so it can be brute forced
|
||||
*/
|
||||
|
||||
/*
|
||||
NOTE: this does not need to be looped to find the largest and smallest
|
||||
because the differences are all known so it's just a matter of finding
|
||||
the largest or smallest digits from the start
|
||||
w0 + 4 = w13 -> max w0 is 5, so w13 = 9
|
||||
*/
|
||||
|
||||
// w10 + 8 = w11
|
||||
// w5 - 8 = w6
|
||||
// do not need to loop these bc they are 8 apart so only one possibility
|
||||
w11, w10 := 9, 1
|
||||
w5, w6 := 9, 1
|
||||
|
||||
var largest, smallest int64 = 0, 99999999999999
|
||||
// w0 + 4 = w13
|
||||
for w13, w0 := 9, 5; w0 >= 1; w13, w0 = w13-1, w0-1 {
|
||||
// w1 - 6 = w12
|
||||
for w12, w1 := 3, 9; w12 >= 1; w12, w1 = w12-1, w1-1 {
|
||||
// w3 - 3 = w4
|
||||
for w3, w4 := 9, 6; w4 >= 1; w3, w4 = w3-1, w4-1 {
|
||||
// w2 - 1 = w9
|
||||
for w2, w9 := 9, 8; w9 >= 1; w2, w9 = w2-1, w9-1 {
|
||||
// w7 + 7 = w8
|
||||
for w7, w8 := 2, 9; w7 >= 1; w7, w8 = w7-1, w8-1 {
|
||||
str := fmt.Sprintf("%d%d%d%d%d%d%d%d%d%d%d%d%d%d",
|
||||
w0, w1, w2, w3, w4, w5, w6,
|
||||
w7, w8, w9, w10, w11, w12, w13,
|
||||
)
|
||||
num, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
panic("parseint" + err.Error())
|
||||
}
|
||||
|
||||
// check against hardcoded alu and actual alu implementation
|
||||
lastZ := hardcodedALU(num)
|
||||
lastZ2 := runALU(num, instLines)
|
||||
|
||||
if lastZ == 0 && lastZ2 == 0 {
|
||||
if num > largest {
|
||||
largest = num
|
||||
}
|
||||
if num < smallest {
|
||||
smallest = num
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if part == 1 {
|
||||
return largest
|
||||
}
|
||||
return smallest
|
||||
}
|
||||
|
||||
// a literal implementation of the ALU, useful for checking the final answer?
|
||||
func runALU(model int64, instLines []string) (z int64) {
|
||||
registers := map[string]int64{}
|
||||
// ALU will only have w,x,y,z keys
|
||||
// sub model numbers to test are 14 digit numbers (1-9 ONLY)
|
||||
// uses 14 separate inp instructions, each a single digit of the model number (starting from left)
|
||||
// 1357... has inputs 1, 3, 5, 7...
|
||||
// model number is valid IF variable z == 0
|
||||
modelNumberIndex := 0
|
||||
modelString := fmt.Sprint(model)
|
||||
if len(modelString) != 14 {
|
||||
panic("model string should be 14 characters: " + modelString)
|
||||
}
|
||||
|
||||
for _, line := range instLines {
|
||||
parts := strings.Split(line, " ")
|
||||
|
||||
var bVal int64
|
||||
if len(parts) == 3 {
|
||||
maybeInt64, err := strconv.Atoi(parts[2]) // could use a regexp too...
|
||||
if err != nil {
|
||||
// parts[2] is not a number, look up its register
|
||||
bVal = registers[parts[2]]
|
||||
} else {
|
||||
bVal = int64(maybeInt64)
|
||||
}
|
||||
}
|
||||
switch parts[0] {
|
||||
case "inp":
|
||||
val := cast.ToInt(modelString[modelNumberIndex : modelNumberIndex+1])
|
||||
modelNumberIndex++
|
||||
registers[parts[1]] = int64(val)
|
||||
case "add":
|
||||
a := parts[1]
|
||||
registers[a] += bVal
|
||||
case "mul":
|
||||
a := parts[1]
|
||||
registers[a] *= bVal
|
||||
case "div":
|
||||
a := parts[1]
|
||||
registers[a] /= bVal
|
||||
case "mod":
|
||||
a := parts[1]
|
||||
registers[a] %= bVal
|
||||
case "eql":
|
||||
a := parts[1]
|
||||
if registers[a] == bVal {
|
||||
registers[a] = 1
|
||||
} else {
|
||||
registers[a] = 0
|
||||
}
|
||||
default:
|
||||
panic("unexpected command type " + parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
return registers["z"]
|
||||
}
|
||||
|
||||
func hardcodedALU(model int64) (z int64) {
|
||||
registers := map[string]int64{}
|
||||
modelChars := strings.Split(fmt.Sprint(model), "")
|
||||
if len(modelChars) != 14 {
|
||||
panic(fmt.Sprintf("expected 14 digit number, got %d", len(modelChars)))
|
||||
}
|
||||
|
||||
// all 14 steps are the same except for 3 values
|
||||
/*
|
||||
inp w
|
||||
mul x 0
|
||||
add x z
|
||||
mod x 26
|
||||
div z 1 <--
|
||||
add x 14 <--
|
||||
eql x w
|
||||
eql x 0
|
||||
mul y 0
|
||||
add y 25
|
||||
mul y x
|
||||
add y 1
|
||||
mul z y
|
||||
mul y 0
|
||||
add y w
|
||||
add y 16 <--
|
||||
mul y x
|
||||
add z y
|
||||
*/
|
||||
// div z, add x, add y
|
||||
differentVals := [][3]int{
|
||||
{1, 14, 16}, // 0
|
||||
{1, 11, 3}, // 1
|
||||
{1, 12, 2}, // 2
|
||||
{1, 11, 7}, // 3
|
||||
{26, -10, 13}, // 4
|
||||
{1, 15, 6}, // 5
|
||||
{26, -14, 10}, // 6
|
||||
{1, 10, 11}, // 7
|
||||
{26, -4, 6}, // 8
|
||||
{26, -3, 5}, // 9
|
||||
{1, 13, 11}, // 10
|
||||
{26, -3, 4}, // 11
|
||||
{26, -9, 4}, // 12
|
||||
{26, -12, 6}, // 13
|
||||
}
|
||||
for i, char := range modelChars {
|
||||
inp := int64(cast.ToInt(char))
|
||||
|
||||
x := registers["z"]%26 + int64(differentVals[i][1])
|
||||
|
||||
// always divided by 1 or 26... 1 is a no-op
|
||||
registers["z"] /= int64(differentVals[i][0])
|
||||
|
||||
if x != inp {
|
||||
registers["z"] = 26*registers["z"] + (inp + int64(differentVals[i][2]))
|
||||
}
|
||||
}
|
||||
|
||||
return registers["z"]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_aluDay24(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
part int
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
part: 1,
|
||||
want: 59996912981939,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
part: 2,
|
||||
want: 17241911811915,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := aluDay24(tt.input, tt.part); got != tt.want {
|
||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user