Part 1

Pretty unremarkable. I finally added a length fact to the database directly for the length of the winning scratchoffs that I needed, rather than computing it awkwardly. I got to reuse most of the awkward “turn this input into a json blob that you can deal with” code from Day 2.

Here’s the program on dusa.rocks

Here’s the program on StackBlitz

Part 2

Here’s the program on dusa.rocks.%20You%20always%20have%20zero%0A%23%20copies%20of%20card%20C%20due%20to%20cards%20%5B1...1).%0AnumCopiesDueToLtThan%201%20Card%20is%200%20%3A-%20%0A%20%20%20%20card%20Card.%0A%0A%23%20If%20you%20know%20how%20many%20copies%20of%20card%20C%20you%20have%20due%20to%0A%23%20cards%20%5B1...C)%2C%20then%20your%20total%20number%20of%20cards%20is%20one%0A%23%20more%20than%20that!%0AnumCopiesOf%20Card%20is%20(plus%20Count%201)%20%3A-%0A%20%20%20%20numCopiesDueToLtThan%20Card%20Card%20is%20Count.%0A%0A%23%20If%20card%20Prev%20gives%20you%20a%20copy%20of%20card%20Card%2C%20then%0A%23%20your%20number%20of%20copies%20of%20card%20Card%20has%20a%20contribution%0A%23%20from%20however%20many%20copies%20of%20card%20Prev%20there%20are%0AnumCopiesDueToLtThan%20(plus%20Prev%201)%20Card%20is%20(plus%20Count%20Copies)%20%3A-%0A%20%20%20%20numCopiesDueToLtThan%20Prev%20Card%20is%20Count%2C%0A%20%20%20%20cardCopy%20Prev%20Card%20is%20true%2C%0A%20%20%20%20numCopiesOf%20Prev%20is%20Copies.%0A%0A%23%20Sometimes%20card%20Prev%20won't%20contribute%20anything%20to%20the%0A%23%20number%20of%20copies%20of%20card%20Card.%0AnumCopiesDueToLtThan%20(plus%20Prev%201)%20Card%20is%20Count%20%3A-%0A%20%20%20%20numCopiesDueToLtThan%20Prev%20Card%20is%20Count%2C%0A%20%20%20%20cardCopy%20Prev%20Card%20is%20false.%0A%20%20%20%20%0A%23%20Sum%20up%20the%20result%0A%0Agoal%201%20is%200.%0Agoal%20(plus%20N%201)%20is%20(plus%20Accum%20V)%20%3A-%0A%20%20%20%20goal%20N%20is%20Accum%2C%0A%20%20%20%20numCopiesOf%20N%20is%20V.%0A%0A%23%20Buncha%20facts%20representing%20the%20sample%20solution%0A%0Alength%20is%208.%0Agames%20is%206.%0Afield%20ref25%200%20is%2074.%0Afield%20ref25%201%20is%2077.%0Afield%20ref25%202%20is%2010.%0Afield%20ref25%203%20is%2023.%0Afield%20ref25%204%20is%2035.%0Afield%20ref25%205%20is%2067.%0Afield%20ref25%206%20is%2036.%0Afield%20ref25%207%20is%2011.%0Afield%20ref24%200%20is%2031.%0Afield%20ref24%201%20is%2018.%0Afield%20ref24%202%20is%2013.%0Afield%20ref24%203%20is%2056.%0Afield%20ref24%204%20is%2072.%0Afield%20ref23%200%20is%20ref24.%0Afield%20ref23%201%20is%20ref25.%0Afield%20ref22%20%22card%22%20is%206.%0Afield%20ref22%20%22data%22%20is%20ref23.%0Afield%20ref21%200%20is%2088.%0Afield%20ref21%201%20is%2030.%0Afield%20ref21%202%20is%2070.%0Afield%20ref21%203%20is%2012.%0Afield%20ref21%204%20is%2093.%0Afield%20ref21%205%20is%2022.%0Afield%20ref21%206%20is%2082.%0Afield%20ref21%207%20is%2036.%0Afield%20ref20%200%20is%2087.%0Afield%20ref20%201%20is%2083.%0Afield%20ref20%202%20is%2026.%0Afield%20ref20%203%20is%2028.%0Afield%20ref20%204%20is%2032.%0Afield%20ref19%200%20is%20ref20.%0Afield%20ref19%201%20is%20ref21.%0Afield%20ref18%20%22card%22%20is%205.%0Afield%20ref18%20%22data%22%20is%20ref19.%0Afield%20ref17%200%20is%2059.%0Afield%20ref17%201%20is%2084.%0Afield%20ref17%202%20is%2076.%0Afield%20ref17%203%20is%2051.%0Afield%20ref17%204%20is%2058.%0Afield%20ref17%205%20is%205.%0Afield%20ref17%206%20is%2054.%0Afield%20ref17%207%20is%2083.%0Afield%20ref16%200%20is%2041.%0Afield%20ref16%201%20is%2092.%0Afield%20ref16%202%20is%2073.%0Afield%20ref16%203%20is%2084.%0Afield%20ref16%204%20is%2069.%0Afield%20ref15%200%20is%20ref16.%0Afield%20ref15%201%20is%20ref17.%0Afield%20ref14%20%22card%22%20is%204.%0Afield%20ref14%20%22data%22%20is%20ref15.%0Afield%20ref13%200%20is%2069.%0Afield%20ref13%201%20is%2082.%0Afield%20ref13%202%20is%2063.%0Afield%20ref13%203%20is%2072.%0Afield%20ref13%204%20is%2016.%0Afield%20ref13%205%20is%2021.%0Afield%20ref13%206%20is%2014.%0Afield%20ref13%207%20is%201.%0Afield%20ref12%200%20is%201.%0Afield%20ref12%201%20is%2021.%0Afield%20ref12%202%20is%2053.%0Afield%20ref12%203%20is%2059.%0Afield%20ref12%204%20is%2044.%0Afield%20ref11%200%20is%20ref12.%0Afield%20ref11%201%20is%20ref13.%0Afield%20ref10%20%22card%22%20is%203.%0Afield%20ref10%20%22data%22%20is%20ref11.%0Afield%20ref9%200%20is%2061.%0Afield%20ref9%201%20is%2030.%0Afield%20ref9%202%20is%2068.%0Afield%20ref9%203%20is%2082.%0Afield%20ref9%204%20is%2017.%0Afield%20ref9%205%20is%2032.%0Afield%20ref9%206%20is%2024.%0Afield%20ref9%207%20is%2019.%0Afield%20ref8%200%20is%2013.%0Afield%20ref8%201%20is%2032.%0Afield%20ref8%202%20is%2020.%0Afield%20ref8%203%20is%2016.%0Afield%20ref8%204%20is%2061.%0Afield%20ref7%200%20is%20ref8.%0Afield%20ref7%201%20is%20ref9.%0Afield%20ref6%20%22card%22%20is%202.%0Afield%20ref6%20%22data%22%20is%20ref7.%0Afield%20ref5%200%20is%2083.%0Afield%20ref5%201%20is%2086.%0Afield%20ref5%202%20is%206.%0Afield%20ref5%203%20is%2031.%0Afield%20ref5%204%20is%2017.%0Afield%20ref5%205%20is%209.%0Afield%20ref5%206%20is%2048.%0Afield%20ref5%207%20is%2053.%0Afield%20ref4%200%20is%2041.%0Afield%20ref4%201%20is%2048.%0Afield%20ref4%202%20is%2083.%0Afield%20ref4%203%20is%2086.%0Afield%20ref4%204%20is%2017.%0Afield%20ref3%200%20is%20ref4.%0Afield%20ref3%201%20is%20ref5.%0Afield%20ref2%20%22card%22%20is%201.%0Afield%20ref2%20%22data%22%20is%20ref3.%0Afield%20ref1%200%20is%20ref2.%0Afield%20ref1%201%20is%20ref6.%0Afield%20ref1%202%20is%20ref10.%0Afield%20ref1%203%20is%20ref14.%0Afield%20ref1%204%20is%20ref18.%0Afield%20ref1%205%20is%20ref22.%0A)

Here’s the program on StackBlitz

Two interesting things about this solution.

The core of this solution

I felt like I was writing for-loops back in the course I taught at CMU, principles of imperative computation, except that instead of writing both loop invariants and the loop contents, I was mostly writing the loop invariants, somehow.

We're going to calculate how many copies of card C you have due to cards [1...N), for all N less than C. You always have zero copies of card C due to cards [1...1), since that’s an empty set.

**numCopiesDueToLtThan 1 Card is 0 :- 
    card Card.**

If you know how many copies of card C you have due to cards [1...C), then your total number of cards is one more than that!

**numCopiesOf Card is (plus Count 1) :-
    numCopiesDueToLtThan Card Card is Count.**

If card Prev gives you a copy of card Card, then your number of copies of card Card has a contribution from however many copies of card Prev there are.

**numCopiesDueToLtThan (plus Prev 1) Card is (plus Count Copies) :-
    numCopiesDueToLtThan Prev Card is Count,
    cardCopy Prev Card is true,
    numCopiesOf Prev is Copies.

numCopiesDueToLtThan (plus Prev 1) Card is Count :-
    numCopiesDueToLtThan Prev Card is Count,
    cardCopy Prev Card is false.**

I probably could have factored this more clearly into a separate relation that said specifically how many cards of Card were contributed by copies won on card Prev, but I did not.

The terrible hack