zerosleeps

Since 2010

Powered by Django

zerosleeps.com is now served by my very own Django application 🥳

Since first mentioning it 14 months ago, and posting about it several times since, it turns out I needed to actually sit down for a few hours and build the friggin’ thing. Who knew?!

Still a lot to do - I need to re-upload images for posts that have them. You’ll see there’s no favicon, and page titles are non-existent. I haven’t build my reading log yet, which I’m personally most excited about. And the database isn’t being automatically backed up yet, but that’s no big deal as so far it’s only this post that would be lost in a disaster.

Plus RSS/JSON feeds, maybe a site map, and site search. And the ability for me to create draft posts. And maybe different post “types”, like quick posts that don’t have a title, or image-only posts.

But at least the barrier of deploying this thing is now behind me.

Site rebuild update

A little update on my rebuild of zerosleeps.com:

  • Post migration was sorted a while ago. Python-Markdown is super easy to work with
  • Code blocks are handled by the migration as well. Pygments is also a delight to work with.
  • My migration script also takes care of Jekyll image tags, converting them to plain Markdown image tags.
    • The image files themselves are going to be trickier because the current site uses a mishmash of image sizes and thumbnails. I’ve got things working for the happy-path, and will probably just have to manually update the markdown for anything else.
    • Not sure how I’m going to model post images in Django either. Should I bother with a model for them? I suppose so, or I won’t be able to leverage Django’s tools for uploading files. But do they belong to a post, or are they their own thing?
  • Post URLs… I said I wanted to change the structure of post URLs, but that I also want to keep the existing structure for compatibility. Well if I’m going to do the latter why bother with the former?
    • I didn’t think about post slugs though, which are just URL-safe copies of the post title. I have toyed with creating a custom Transform in Django to avoid storing post title and slug, but safer (and more flexible) to just add a slug field to my Post model.
  • “Static” pages, i.e. CV and About took 30 seconds each - they’re both just using TemplateView.

Oh and I’m using Bulma for the whole lot - I am not a designer!

Once I get post URLs sorted I reckon I’ll “go live” as they say in the business. If I don’t then I’ll never deploy the thing. Everything else can follow later: search, tags and categories (maybe?), visitor statistics, JSON and XML feeds, maybe a sitemap. There’s more to a stupid blog than you’d think.

Advent of Code 2022: I think I'm done

I think I’m done with Advent of Code for the year. I enjoyed the first couple of weeks: the problems were all distinct from each other and had well defined solutions so - for my taste/abilities - were largely about crafting pleasant code that is totally different to the kind of stuff I work on 09:00–17:00.

I could solve these puzzles in less than an hour, too. Perfect little brain-tickers.

But days 15 (for which I have a solution that works for the example but not my actual input), 16, and 17 are all way too computer science/mathematics for me. Pattern matching and algorithms and path-finding - they’re just not my thing.

And that’s okay! Of course I could teach myself the tools and tricks Advent of Code is so great at hinting at, I just don’t want to! I’ve got enough life experience and introspection to know what I enjoy and what I don’t enjoy. This is something I chose to do in my spare time, it stopped making me happy, and it wasn’t helping me to improve the personal traits I care about.

Let it be said, whatever your reasons for partaking, Advent of Code is magnificently well done. The guy behind it is a legend.

Now… I should probably set aside an hour each day for “Advent of fixing my damned website” 😬

Advent of Code 2022 day 14

Completed day 14 today as well. It’s hideously inefficient but the code fits my brain, so adapting it for part 2 was nice and quick and worked first time 🥳

from itertools import pairwise
from unittest import TestCase
from utils import get_raw_input


class Cave:
    def __init__(self, raw_input):
        self.build(raw_input)
        self.sand = set()
        self.overflowing = False

    def plot_rocks(self, x1, y1, x2, y2):
        if y1 == y2:
            return [(i, y1) for i in range(min([x1, x2]), max([x1, x2]) + 1)]
        else:
            return [(x1, i) for i in range(min([y1, y2]), max([y1, y2]) + 1)]

    def build(self, raw_input):
        self.rocks = set()
        for line in raw_input:
            for pair in pairwise(line.split(" -> ")):
                self.rocks.update(
                    self.plot_rocks(
                        int(pair[0].split(",")[0]),
                        int(pair[0].split(",")[1]),
                        int(pair[1].split(",")[0]),
                        int(pair[1].split(",")[1]),
                    )
                )

    @property
    def boundaries(self):
        # y_min is highest rock, y_max is lowest rock
        return {
            "x_min": min([rock[0] for rock in self.rocks]),
            "x_max": max([rock[0] for rock in self.rocks]),
            "y_min": min([rock[1] for rock in self.rocks]),
            "y_max": max([rock[1] for rock in self.rocks]),
        }

    def drop_sand(self, floor=False):
        sand_position = (500, 0)
        while True:
            if (
                not floor
                and (
                    sand_position[1] > self.boundaries["y_max"]
                    or sand_position[0] < self.boundaries["x_min"]
                    or sand_position[0] > self.boundaries["x_max"]
                )
            ) or (floor and (500, 0) in self.sand):
                # Out of bounds
                self.overflowing = True
                return
            elif (
                (sand_position[0], sand_position[1] + 1) not in self.rocks
                and (sand_position[0], sand_position[1] + 1) not in self.sand
                and (
                    not floor
                    or (floor and sand_position[1] + 1 < self.boundaries["y_max"] + 2)
                )
            ):
                # Can fall straight down
                sand_position = (sand_position[0], sand_position[1] + 1)
            elif (
                (sand_position[0] - 1, sand_position[1] + 1) not in self.rocks
                and (sand_position[0] - 1, sand_position[1] + 1) not in self.sand
                and (
                    (not floor)
                    or (floor and sand_position[1] + 1 < self.boundaries["y_max"] + 2)
                )
            ):
                # Can fall down-and-left
                sand_position = (sand_position[0] - 1, sand_position[1] + 1)
            elif (
                (sand_position[0] + 1, sand_position[1] + 1) not in self.rocks
                and (sand_position[0] + 1, sand_position[1] + 1) not in self.sand
                and (
                    (not floor)
                    or (floor and sand_position[1] + 1 < self.boundaries["y_max"] + 2)
                )
            ):
                # Can fall down-and-right
                sand_position = (sand_position[0] + 1, sand_position[1] + 1)
            else:
                # Can't move
                self.sand.add(sand_position)
                return


class TestExamples(TestCase):
    def setUp(self):
        self.example_input = [
            "498,4 -> 498,6 -> 496,6",
            "503,4 -> 502,4 -> 502,9 -> 494,9",
        ]
        self.cave = Cave(self.example_input)

    def test_part_one(self):
        self.assertEqual(run(self.cave), 24)

    def test_part_two(self):
        self.assertEqual(run(self.cave, True), 93)


def run(cave, floor=False):
    while not cave.overflowing:
        cave.drop_sand(floor)
    return len(cave.sand)


if __name__ == "__main__":
    print(f"Part one: {run(Cave(get_raw_input('day_14_input.txt')))}")
    print(f"Part two: {run(Cave(get_raw_input('day_14_input.txt')), True)}")