Programming Update: Aug


August was a programming-filled month for me. It focused entirely on Python and I mostly continued working on established projects. Let’s jump in!

Amortization

I wanted to re-calculate the amortization table for my home loan for the first time in about a year. As a refresher, I created this program (vs using Excel or an online form) because we are not consistent in the amount of extra principal payments we make. For example, if I get a bonus at work, I might throw all of that bonus into the loan payment. So this program takes variable extra payments into account when creating the amortization table. 

Since I hadn’t worked on it in a year, I had a new Python version and needed to recreate my virtual environment. This led me to learn that numpy had removed their financial methods from their module. I use the numpy financial methods to calculate a straight-ahead amortization table in order to calculate the interest saved by paying extra. So I had to change my imports to fix that.

I also finally used the rich module to create a new, pretty table for the CLI output. It really is MUCH nicer than my previous attempt at using the “\t” escape to make a table.

Finally, I made a few minor refactors suggested by the Sourcery plugin.

Advent of Code 2016 Day 12

I skipped ahead to the Aoc Day 12 problem and solved it with Python. I usually do very well with these assembly language emulator problems. This one was slightly more difficult than usual. At first I tried to use functions (better for testing) but the way it was setup made it easier to just do it all in main. I did get some practice with Python 3.10’s new match-case statement, making it much nicer to read than an if/else chain. 

"""Solution to Advent of Code 2016 Day 12: Leonardo's Monorail."""


def input_per_line(file: str):
    """This is for when each line is an input to the puzzle. The newline character is stripped."""
    with open(file, 'r') as input_file:
        return [line.rstrip() for line in input_file.readlines()]


if __name__ == "__main__":
    our_input = input_per_line('../input.txt')
    a, b, c, d, = (0, 0, 0, 0)
    counter = 0
    while counter < len(our_input):
        # print(f"{counter=}")
        # print(f"{a=}, {b=}, {c=}, {d=}, ")
        components = our_input[counter].split()
        # print(components)
        instruction = components[0]
        x = components[1]
        y = 0
        if len(components) == 3:
            y = components[2]
        match instruction:
            case "cpy":
                match x:
                    case "a":
                        left_side = a
                    case "b":
                        left_side = b
                    case "c":
                        left_side = c
                    case "d":
                        left_side = d
                    case _:
                        left_side = int(x)
                match y:
                    case "a":
                        a = left_side
                    case "b":
                        b = left_side
                    case "c":
                        c = left_side
                    case "d":
                        d = left_side
                counter += 1
            case "inc":
                match x:
                    case "a":
                        a += 1
                    case "b":
                        b += 1
                    case "c":
                        c += 1
                    case "d":
                        d += 1
                counter += 1
            case "dec":
                match x:
                    case "a":
                        a -= 1
                    case "b":
                        b -= 1
                    case "c":
                        c -= 1
                    case "d":
                        d -= 1
                counter += 1
            case "jnz":
                match x:
                    case "a":
                        number = a
                    case "b":
                        number = b
                    case "c":
                        number = c
                    case "d":
                        number = d
                    case _:
                        number = int(x)
                if number != 0:
                    counter += int(y)
                else:
                    counter += 1
    print("Part 1:")
    print(f"{a=}, {b=}, {c=}, {d=}")
    a, b, c, d, = (0, 0, 1, 0)
    counter = 0
    while counter < len(our_input):
        # print(f"{counter=}")
        # print(f"{a=}, {b=}, {c=}, {d=}, ")
        components = our_input[counter].split()
        # print(components)
        instruction = components[0]
        x = components[1]
        y = 0
        if len(components) == 3:
            y = components[2]
        match instruction:
            case "cpy":
                match x:
                    case "a":
                        left_side = a
                    case "b":
                        left_side = b
                    case "c":
                        left_side = c
                    case "d":
                        left_side = d
                    case _:
                        left_side = int(x)
                match y:
                    case "a":
                        a = left_side
                    case "b":
                        b = left_side
                    case "c":
                        c = left_side
                    case "d":
                        d = left_side
                counter += 1
            case "inc":
                match x:
                    case "a":
                        a += 1
                    case "b":
                        b += 1
                    case "c":
                        c += 1
                    case "d":
                        d += 1
                counter += 1
            case "dec":
                match x:
                    case "a":
                        a -= 1
                    case "b":
                        b -= 1
                    case "c":
                        c -= 1
                    case "d":
                        d -= 1
                counter += 1
            case "jnz":
                match x:
                    case "a":
                        number = a
                    case "b":
                        number = b
                    case "c":
                        number = c
                    case "d":
                        number = d
                    case _:
                        number = int(x)
                if number != 0:
                    counter += int(y)
                else:
                    counter += 1
    print("Part 2:")
    print(f"{a=}, {b=}, {c=}, {d=}")

ELDonationTracker

A while back I broke out the Donor Drive API code in case someone wanted to access the Donor Drive API without the burden of my Extra Life code or GUI. I ran into a dependency hell issue between the two projects and it took me a few weekends to get thing working correctly. Now I can finally continue innovating on the project.

ClanGen

My oldest child got obsessed with the Warriors series after her teacher introduced her to it near the end of the last school year. From what she’s described, it’s basically Game of Thrones, but cats and written for a Middle Grade reading level. On YouTube she found out about the ClanGen program which is basically a simulator that takes place in the Warriors universe. I wanted to make some improvements to the way the data is stored – moving it from CSV to JSON-based. The project is fast-moving which makes it hard to get that PR request in, but I’m trying to get these updates in.

FastAPI and MongoDB Class

I took an awesome class (and wrote up a review) where I learned a lot about how to work with MongoDB and the beanie ODM.

Civ VI Play by Cloud Webhook 

The greatest amount of work and the most passion I had for coding was with this project. First, I wrote code to sort the table on the index page so that it would go from oldest to newest games, making it easy to see which games were the most delinquent. Then I decided it would be fun to also calculate the average turn length and display this on the index page. I suspect it will be fun to see the contrast there when Kaira goes back to school and Dan and Dave potentially play dozens of turns per game per day as they were doing last year. As I continued to think about interesting things I could do with the user-facing aspect of the page, I decided to make it so that a winner could be set. 

As part of the user-facing pages and trying to make them more AJAX-y, I learned a lot about HTMX. I like it a lot as a way to do interactive pages without needing to learn Javascript or a JS Framework. It allows me to just focus on Python for all the logic and a little Jinja2 and HTMX for interactivity. 

I also decided, as I was thinking about my endpoints more critically, that I would change the Matrix blame command to only consider active games, so I made the changes to implement that. 

Finally, as a consequence of the class I took this month, I moved the data storage from a few JSON files on disk to MongoDB via the free Atlas Cloud MongoDB tier. I had a lot to learn about how MongoDB works in relation (no database pun intended) to what I was doing before. In a lot of ways it has simplified my endpoint code. And, via the database services I’ve written, has helped make a lot of the organization of data more explicit. I’m having some slight growing pains around the fact that changing fields is a little more problematic than when it’s just JSON following whatever random structure I want to use, but I think it will lead to a more responsive site, particularly if the number of games grows. As a result of using beanie for MongoDB, I also started using async all over my code. I’m not sure if I’m being inefficient with it, but it’s making the Matrix code make a bit more sense. Also, the library I’m planning on using for Discord is async, so it’s good to get used to it now.