Phpizza Blog


April 25, 2022

I finally got my Playdate! I had placed my preorder within less than a minute of them opening up orders, and while it was supposed to ship last December, things happened and it was delayed a bit. They started shipping last week though, and I have mine now! Thanks, Panic ❤️

Playdate console in its yellow box, with a charging cable visible on a solid purple background.

I love the aesthetic of this thing. It’s tiny, bright yellow, has a purple cover accessory, and is just the cutest thing ever. I’m definitely getting a Stereo Dock when it’s available, but there’s no date or anything for that yet.

The form factor is a little rough to play on. The display is tiny and doesn’t have a backlight, and the crank requires more force than I was expecting which can be tricky for some games that require quick or precise movement. Overall though it’s a fun toy that I’d recommend to anyone

Let’s get to the real fun though.

The Games

New Playdate owners get a collection of games included with the purchase, rolling out with two games a week, known as Season One. I’ll be updating this with reviews of any of the games that stand out to me throughout the season. You can also easily sideload games or make them yourself!

Overall I’ve found the Season One games to be fine but not anything I’ve immediately loved. They’re unique, but not especially fun or engaging. That said there are 24 games rolling out in the coming weeks and I’ve only tried the first few so far, so I’m sure there are some that I’ll love.


My favorite game on Playdate so far isn’t part of the Season One package, but is instead sold independently on I love casual games around playing for a few minutes here and there, which the Playdate seems perfect for, and I also love simple narrative-driven games like visual novels. Bloom combines those elements excellently, with a relaxed, cozy gameplay around running a flower shop and talking with friends. This game is wonderful and I love it.


This is another fun game available on A simple but very creative puzzle game with a good selection of levels, with very unique mechanics. Definitely recommended for anyone with a Playdate, and if you don’t there’s also a web version of the original game.

Casual Birder

One of the early Season One games, Casual Birder is an interesting one. You play as someone in a small town, tasked with taking photos of birds. Use the crank to focus your camera. I like a lot of things about it, but the overall gameplay feels clunky, and generally off in a way that’s hard to describe. I like a lot of the writing and visual design, but it feels like an idea that needed a bit more polish to really be fun and engaging.

Lost Your Marbles

This is a fun, silly visual novel mixed with a simple physics game played with the crank. It’s relatively short, but has a lot of branching from the marble minigame that offers decent re-playability. I like the art style and characters, and the gameplay is fun, but it sometimes plays a bit janky with frame rate dips and difficult crank control.

Pick Pack Pup

A matching puzzle game about being a dog working in shipping, this one’s fun and engaging. Very creative expansion on the typical match-3 mechanics, with some interesting and challenging levels. Super cute cartoon dogs.

Flipper Lifter

This game relies on the Playdate’s crank to move a lift up and down floors, helping penguins reach the floors they’re heading to. It’s very simple, but quickly becomes challenging when the number of penguins and floors increases. A fun game worth a bit of play, but not something I’d come back to as often as some of the others.


As a software developer, the thing that made me most excited for Playdate was the idea of making my own stuff for it. I’m not exactly a competent dev when it comes to making games, but I loved the idea of a very simplified form factor that limited the scope of a potential game to something more manageable.

The Playdate SDK is really, really good. It has solid documentation, a great selection of example projects, and is incredibly approachable. I’ve done a bit of work in lower level game dev stuff in the past, but Panic has done an excellent job of making input handling, file IO, audio playback, sprite rendering, and a lot more quite painless.

I started off by digging through their example projects, as I always find it way easier to learn something in development by seeing actual implementations of something rather than a list of available functions without any real context. I got rickrolled, learned how text and sprite rendering worked, and started on the easiest type of game I could build: a visual novel.

If you’re building a visual novel for PC or mobile, you should basically only ever choose one engine: Ren’Py. It’s been around an incredibly long time, it’s stupidly simple to implement games in, and there’re loads of games built in it to use as examples of how to do unique gameplay. I wanted to implement a simple subset of Ren’Py’s core functionality as a way to learn the SDK, so I started on a generic VN engine.

I have an original character named Ren, so I drew him in 1-bit pixel art and coded some stuff. It’s a really mediocre “game” that doesn’t really do anything other than test that conversations are somewhat possible to script in the engine, but I was able to quickly learn font rendering and editing, how to dynamically load in game objects, animating sprites, and a bunch of other stuff.

I also built a simple Ren’Py conversion script in PHP, and I want to try porting a full existing game to the Playdate at some point, but for now I’m going to focus on some less dialog-driven games. My current plan is to implement something similar to the Pizzatron 3000 minigame from Club Penguin, complete with using the crank to spread cheese and toss dough.

Tailwind 3 Redesign!

December 29, 2021

Tailwind CSS v3 was recently released, and it’s a pretty simple upgrade process for most v2-based sites. Since I like redesigning things and playing around with Tailwind though, I took the opportunity to update this site again!

This blog probably doesn’t even have an actual audience, but it’s been a good thing to maintain over the years as a way to learn new tools and play around with ideas. This new visual style is the first in what may be a series of changes that are pushing for a more distinct look, rather than looking like a generic site.

I still want to adjust things more, moving components around in a more fun way, adding more colors, etc. I like this as it is, but would love to see where I could take it with a more artistic perspective. So much of my front-end development work centers around simple repetitive UIs, and I really want to do more creative things with the design.

If you do anything with web design and haven’t tried Tailwind yet, you totally should. It’s pretty great!

Beat Saber

December 01, 2021

I’ve played… a lot of Beat Saber. If it was a typical video game I’d consider it about at the limit of what I should reasonably play, especially considering it hasn’t been around as long as Skyrim, Terraria, Minecraft, Half-Life, etc. but I’ve spent about as much time in it.

It’s a very, very good game. It’s incredibly simple, but very polished. It’s the most intuitive game I’ve played, but mastering it takes a lot of practice. I’m at a point now where I can pretty consistently finish ~7 note per second “Expert+” songs, and I’m usually in the top few hundred players score-wise on the songs I play often, but there are definitely plenty of official and fan-made beat maps that I’m nowhere near able to play.

Beat Saber has been an interesting game to follow. I started playing when it was in early access on Steam, with only a handful of official songs. The modding community immediately took off, with loads of excellent “beat maps” being made for songs, and a good selection of quality of life mods adding features and fixes to the base game. The first official paid DLC seemed like an obvious choice, a Monstercat music pack, which is easy to license and fits the gameplay.

From there, the game had grown more popular and Beat Games could more easily license music in a more traditional way for DLC. The occasional first-party song would get added as a free update, but the majority of music that’s officially available these days for Beat Saber is paid DLC of licensed popular music. By the end of 2019, the game was doing so well that Beat Games was acquired by Facebook, as part of their Oculus division.

I’ve played on Oculus Rift, Oculus Quest, and Valve Index. The modding process on the Quest is definitely a lot more painful as it’s running a weird Android OS and doesn’t officially support custom songs, but is still possible. Facebook’s continuous restrictions being added to their Oculus software over time has made me question how much longer the Quest version will be moddable at all, but for now it’s still possible despite the launcher constantly whining about how you’re running unofficial software and they may ban you.

Facebook needs to stop existing. Meta doesn’t count.

Playing on SteamVR is great. The Index is especially great for Beat Saber, with the 144hz mode and bright, high-res displays genuinely feeling like a significant improvement over other hardware. Mods are super simple to install and use, and custom songs are natively supported. Excellent projects and communities like the now-defunct ModSaber, BSMG, ModAssistant, BeastSaber, and many more keep the game constantly improving, with an endless collection of good beat maps, new features, and integrations.

I particularly like color mods, custom sabers, and avatars. For whatever reason I’ve currently settled on playing as a particular Hatsune Miku avatar, when I have that mod enabled. If for some reason you want to watch me play Beat Saber, I occasionally (like 2-3 times ever so far) stream on Twitch, and also have a playlist with some of the maps I’ve enjoyed playing.

I’ve really grown to love the game. It’s consistently the most fun I have in any game, and I love that I can spend an hour playing a great game and burn a few hundred calories doing it.

If you have a VR headset, you should play Beat Saber.

Applying SOLID Principles to Modern PHP

August 31, 2021

The SOLID principles are one of a few groups of software design principles that are often and easily applied to most modern software development. I work primarily in PHP development on the back-end, where object oriented programming is still the usual standard and SOLID is of particular relevancy.

In the modern PHP world, most new development is done in one of a few major frameworks. My framework of choice is Laravel, but each include some level of implied and explicit structure to your application which can determine a lot about how you plan the specifics of new feature implementations.

The SOLID principles include:

  • Single responsibility — Each class should have one responsibility
  • Open/closed — Objects should be open to extension, but closed for modification (private attributes, immutability, etc.)
  • Liskov substitution — An extended object should behave the same as the object it extends for the behavior the original object exposes
  • Interface segregation — Functionality should be split into small logical objects
  • Dependency inversion — Rely on abstractions and interfaces when building common implementations, but don’t couple abstractions to an implementation

I personally find that these principles feel somewhat obvious once you’ve worked in OOP for a while, and that the SOLID acronym does little to help remember or educate (unlike, for example DRY). However they’re still important to learn and truly understand, particularly when designing an application from early stages, to avoid running into major problems when trying to scale the application later.

Let’s take a look at how each one affects how I architect a PHP application.

Single responsibility

One of the most common approaches to structuring a modern web application is the MVC pattern. Many variations of this pattern exist, but the basic concept is that you break an application into at least three major areas:

  • Models, which represent the data objects (e.g. a record in a database table)
  • Views, which represent the renderable representations of data
  • Controllers, which connect the models and views for a given request

Within this simple architecture, there are a lot of things left open. Choices like where to put business logic, how to handle authorization, and many other common realities of an application are not predetermined.

To keep classes responsible for only their relevant behaviors, it is necessary to supplement MVC with additional areas. Common additions include Middleware and View-Models, which offer greater control over the different layers of the application.

In particular when building in a modern framework, splitting code into smaller logical areas within each MVC area is important to keeping things clean. As an example, putting everything related to a user login process in a single controller class may make sense when it only includes one or two routes to handle showing a login page and processing the POST action, but can quickly become too complex when you start including validation, two-factor flows, password resets, etc.

Instead of putting everything involved in a user logging in in a single controller class, splitting the behavior into several classes could include:

  • A controller for each action (showing the form, handling the POST, etc.)
  • A data transfer object for representing the form data, which performs validation
  • A view model that ties the DTO values to the view

Each of those classes have one clearly defined role within the action the user is performing, and make it easy to find and change that behavior without affecting anything unexpected.


When designing a class, it’s important to pay attention to which attributes are handled by the class internally, and which should be accessible to both outside accessors and child classes that extend the class. Failure to correctly enforce these conventions can have huge impacts on the quality of a codebase, and cause classes and behaviors to become entangled in very unpredictable and unmaintainable ways.

Various conventions and language constructs exist to both imply and enforce the open and closed nature of properties and methods on a class, including a very useful readonly keyword coming to class properties in PHP 8.1 later this year. I typically find that I make most class properties protected, and most methods public.

In cases where a class needs to perform some operation as an internal effect of a public method call, it often makes sense to put that behavior in a protected function within the class, especially if it is used in multiple places within the class.

When you do need to share a property value with a class, most applications currently implement this indirectly through the use of getter functions, rather than exposing the property publicly. PHP 8.1’s addition of the readonly keyword will change this in many cases, as it will be safe to set a value once within a class constructor, and make it accessible publicly without allowing external behavior to mutate the property.

Using protected instead of private when declaring properties and methods is particularly helpful down the road when you need to extend a class to modify or wrap some portion of its original behavior. It is possible to override private class members, but doing so often requires completely reimplementing a large amount of the original class’s behavior, which can be error-prone and introduce many side effects, as well as making it more difficult to maintain.

Liskov substitution

When extending classes, it’s important to keep in mind that the class may be used in a variety of ways, especially when building a library that’s used in multiple projects. To avoid breaking implementations, keeping the public behavior consistent is critical.

A common point of concern is method signatures. Given a method that expects a certain list of parameters, extending that class should not change the order, type, or structure of the parameters. When it is necessary to supplement the parameters, ensure that behavior when called with parameters designed for the original implementation still behave as the parent class does.

Here is a simple example of extending a class while preserving the initial behavior:

class ObjectA
    public function updateCount(int $count): void

class NotifiableObjectA extends ObjectA
    public function updateCount(int $count, bool $notify = false): void

The default value on $notify is required to prevent callers from

One of the best ways to avoid accidentally changing method signatures is to ensure that strict types are used for arguments and return values whenever possible. PHP will enforce types being identical between implementations.

Without defined types, we could easily break this behavior without the language enforcing anything:

class ObjectB
    public function updateCount($count)

class NotifiableObjectB
    public function updateCount($notify, $count)

PHP 8 introduced named arguments which, when used, mean that the name of each argument in your method definition must also stay unchanged in new implementations. It’s also a good idea to avoid changing argument names entirely for any public methods when using PHP 8, as this can easily break functionality anywhere in an application.

There are many more complex things to be aware of to safely extend a class, but they are often more implementation-specific, and a result of poor design of the initial class. In those cases, it’s often necessary to find where the class is being used and manually verify that the behavior is not changed unexpectedly.

Interface segregation

Segregation of code goes along well with both the Single Responsibility and Dependency Inversion principles. In particular, it’s important to keep classes and interfaces small, splitting common behavior into separate interfaces.

This is particularly helpful to avoid requiring an implementer to add definitions for behavior that is not supported or is unrelated to an implementation of an interface.

For example, let’s define an interface of common features of a YouTube channel:

interface Channel
    public string $id;
    public string $name;

    public function listVideos(): array;
    public function listPlaylists(): array;
    public function listPlaylistItems(string $playlistId): array;

This works fine for YouTube, but later on if it’s necessary to support another video platform that doesn’t include playlists for example, the expected behavior of the playlist-related methods becomes a lot more complicated.

You could simply return an empty array when listPlaylists is called, but what about listPlaylistItems? Do you throw a generic exception? Do you introduce a new exception type and hope other implementations also throw the same exception? All of these options have negative side effects and can break the application and result in widely varied implementations, which is exactly what we’re trying to avoid by using an interface!

Instead, it’s best to define interfaces in a way that is more flexible for future implementations:

interface Channel
    public string $id;
    public string $name;

    public function listVideos(): array;

interface ChannelWithPlaylists extends Channel
    public function listPlaylists(): array;
    public function listPlaylistItems(string $playlistId): array;

If you architect the application this way from the beginning, code that’s aware of the playlist functionality and needs to work with playlists can easily check if it’s supported by the platform via $channel instanceof ChannelWithPlaylists.

This principle can be hard to implement effectively since you never really know what’s going to happen to your application in the long-term, but doing what you can to get this right early on can be very impactful.

Dependency inversion

Correctly structuring the layers of an application is important to avoid making things too tightly coupled and dangerous to change. For example, it is common to have a Controller that connects a DTO to a View-Model.

A View should not be accessing an HTTP request directly, for many reasons including security, validation, and extensibility, so additional layers to facilitate that flow are necessary to ensure all aspects of the HTTP request can be handled in an intuitive and maintainable way.

It is also important to use interfaces and abstract classes whenever there is a chance that an object will need to be implemented differently in another context. Doing this before you have a need for multiple implementations is important, as it’s harder to restructure an existing application than it is to design with abstractions to begin with.

Having multiple implementations is particularly common in unit testing, as testing directly against an implementation of a class may require additional things like a database, web server, or external API access. For example if a a typical implementation of a class requires accessing a third-party API, it can be undesirable for many reasons (rate limits, credential sharing, performance) to trigger those same API calls during automated testing.

Instead, it’s best to make a mock implementation of the interface, matching the behavior of the external API with hard-coded or procedurally-generated responses, and handling data in a similar way to the actual API. Changing which implementation is used in the testing context can depend on how you’re running tests and which framework you use, but if the choice is between mocking API responses and not testing API integrations at all, it’s definitely best to test them.

Games on Linux

July 21, 2021

It’s been a while since I’ve tried to play games on Linux, I ended up back on Windows as my only gaming PC a few years ago particularly because of Oculus’s lack of Linux support for VR. With Windows 11 doing some… interesting things, I think it’s time I try it again.

Steam’s built-in Proton compatibility layer can be enabled for all non-native games in Settings > Steam Play > Advanced, and basically just does its best to run whatever you want. It mostly just works.

My usual Linux PC

I only have one dedicated Linux PC that’s not a server right now, it’s a relatively low-end HP EliteDesk with an Intel Core i5-6500T with HD 530 graphics. Not suitable for modern 3D games for sure, but perfectly usable for something like Terraria, Minecraft, or Celeste.

Native games via Steam

  • Celeste has a native Linux build that works perfectly, apart from the Steam overlay which doesn’t render any text/images.
  • Psychonauts mostly works. Switching to fullscreen broke HDMI audio, but fixed mouse capture issues in windowed mode.
  • Stardew Valley works great natively. Drops to 40 FPS at 4K, but this PC isn’t really meant for that anyway. Modding will probably be a bit more complicated on Linux than Windows but definitely still possible.

Playing via Proton (Steam’s custom Wine)

  • A Hat in Time took quite a while to process shaders and install dependencies on first launch. Fully saturated 3 CPU threads for maybe 20 minutes processing shaders, which is skippable but probably ideal to let run. I also have 7 GiB of Workshop mods installed, which probably doesn’t help stability or performance, but after a while it did launch the game. Since this is running integrated graphics, it gave me a warning, but running it at 720p low settings gets a somewhat usable 30-60 FPS (very inconsistent, lots of stutters). No major input mapping/delay issues like Psychonauts had, even in windowed mode.
  • Sonic Mania runs perfectly at 4K fullscreen/windowed, solid 60 FPS. Turning on screen filters drops the framerate a bit at 4K. No noticeable input lag or glitchiness.
  • Psychonauts under Proton has much better input handling, but still breaks mouse input when switching apps/resolutions. Also has a persistent “important but non-fatal problem” error text rendered all the time, with no additional detail. Other than that, it seems to run perfectly, with less issues and better performance than the native build.

An actual gaming PC

I also have a proper gaming PC running Windows 10. It’s mostly actually used for YouTube.

3D via Proton, but with an actual GPU this time

  • A Hat in Time - This runs very well on my gaming PC via Proton. There are some occasional hitches when it loads in new assets for the first time, presumably because it’s transpiling things to Vulkan or something, but once you’ve been on a map for a bit, it runs fairly smooth. It’s not quite the Windows experience, but it’s very close. Easily gets >60 FPS at 4K on this PC.
  • Skyrim - Works perfectly via Proton. Loads fast, runs on maxed out settings, mods work (with some effort), it’s great.

Non-Steam games

  • GOG doesn’t have an official client, and the most popular third-party client doesn’t recognize Proton as a usable Wine install yet. Since GOG doesn’t use DRM though, most games work well under Proton when added as “non-Steam” games in the Steam UI, and should work when launched manually under Proton via the command-line. Not a perfect experience, but very usable.
  • has Linux support natively for their client, but it limits downloads to games with Linux builds available. I tried a few of those and they work great. Windows builds can still be downloaded by accessing the Itch site in a browser, and adding them to Steam or running manually should work for most simpler engines. Ren’Py and GameMaker-based games (which both have native Linux support as a build target to the devs) work basically perfectly under Proton.


For me to ever fully leave Windows behind, I’ll have to have decent VR performance. Luckily Valve has official support for the Index on Linux, so I’ll be trying that out with a variety of games.

  • SteamVR itself works okay. It detected my Index hardware, which was left connected the same way its been on Windows, and walked through the initial room setup without any issues. Once in the headset, a few problems started:
    1. The base stations, which I had configured to turn off automatically on the Windows setup, did not automatically turn on and had to be manually power cycled.
    2. The SteamVR UI dropped lots of frames and seemed to not properly match the headset motion sometimes. This could be due to running at 120 Hz, and may have worked better at 90 Hz, but it’s an Index and I’m not using a $1000 headset at lower than its supported specs.
    3. The audio was unusable on the latest Nvidia driver. It “worked” in that it detected the device over DisplayPort, and played back audio to it, but it sounded like it was coming over a phone line that was being repeatedly used to short a high-voltage AC circuit. Adjusting settings a bit completely broke playback on the device until a reboot, with no improvements.
  • Beat Saber via Proton was finnicky to get to launch, as it involved clicking “OK” in a dialog box that’s shown in a window that is only visible in the SteamVR overlay, and didn’t auto-open (I had to manually enter the SteamVR menu while waiting for the game to load). Once it launched, it seemed to work basically perfectly, which was very surprising. Sadly because of the audio not working at the OS level, I was unable to actually play it.

And that ended my attempt at VR on Linux. If the weird UI lag and the audio driver problem were fixed, I could see myself actually moving to Linux for VR games, which is honestly not something I thought would be this close to usable. I’m particularly impressed that the connection between Beat Saber running under Wine/Proton and the SteamVR runtime worked perfectly out of the box, which clearly shows that this is something Valve has been focused on.