From July of 2025 until very recently, I have done no programming at all. I couldn't make myself, I tried. It's not that I didn't want to, I just couldn't. It's not something you can just "force". Recently, however, I seem to have gotten back into the swing of things.
Here's a video demonstrating what I've put together:
I can't remember what put me onto it, but I thought I was finally ready to try and make a simple text editor. I'm not. So, I almost got back into the swing of things but stopped. A few days later, my mind remembered Solitaire for some reason. And it clicked. That is the perfect thing to try next. You know how ELO matchmaking works? Like in Counter-Strike? Well, we just need the basic concept here. The best way to improve at something, or for oneself to have possibly the most fun, there needs to be some level of confidence and some level of challenge. Too difficult? You get stomped, learn nothing, no confidence. Too easy? You stomp the noobs, to the point of it being boring since there's no challenge.
The ideal goldilocks ratio is "a little challenging, enough that you have to put in some effort and overcome, but not too much or too little." I'd been searching for some ideas, well, searching is not the right word. I'm now ashamed to admit this, but I was so desperate I was flat out asking ChatGPT™️(one of the many contributing factors to our upcoming downfall 😍) for an old game to rewrite. It kept suggesting SimTower which was not too bad of an idea, it just had more friction than I could deal with. No way to even remotely easily extract the assets. That alone created enough friction that I couldn't be bothered. So, I stopped for a while, until Solitaire came to mind.
That felt right, more human. I myself knew better about what I'm capable of and my interests than some clanker, of course. The initial commit for this project was on 31 Jan 2026.
Yup, more Odin talk. Odin is awesome, what else can I say? I think were it not for Odin, I'd still be in the drought of doing nothing. C# is my natural first pick since I know it so well, but, starting this project with Odin seemed more attractive. I say "some reason" even though I think I've now elucidated why for myself. Odin is very pragmatic, very simple. There's usually one way to do the thing you need to do, and that's great.
Why is that great? Well, with C#, I have the power of abstraction. Power that I get drunk off of and abuse. I spend more time abusing this power, creating abstractions, helpers, extensions, interfaces, "smart" or "neat" solutions. I like to spend time working on everything but the problem at hand. C# lets me, Odin does not (to a reasonable extent). Without infinite abstraction in my reach, I tend to just work towards doing the thing. This is also combined with intentional effort on my part to let go of trying to make things perfect or the way I would if I were in C# land.
That doesn't mean intentionally write terrible code, just get it working at a good balance of foresight and "cut the bullshit." Don't invent problems for myself. Don't think of every possible fucking outcome when writing code in the current moment. Add something when I need it, don't plan ahead of or predict needs. This advice is definitely tangentially related to that of a particular group of popular programmers who I haven't given a name yet. It's a bit complicated, but I think I'm going to write a post about them soon. I have many thoughts on them and the things they talk about. Despite my issues with some of what they say and their behaviors, it's helpful advice.
I don't think I've fully expressed my point, but that should be good enough for the sake of cutting down on paragraphs. So, for Solitaire. I found myself working towards the goal rather quickly. I had written a blackjack engine, in Odin actually, a while back in like 2024. I grabbed that and imported it into the new project. The only limitation I gave myself in regards to the cards is that there should be one flat array of 52 cards (in the case of solitaire) and the game code should just act on that array of cards, the deck. The card structs shouldn't know anything about the game. This is the one thing I wanted to do that could be considered getting in the way of getting the game to just work as quickly as possible. In the end, I'm glad I went this route, I think it ended up being much more straight forward this way.
One thing I enjoyed is that, for this project, there was very little looking up how to do things or referencing an existing game's code. I did not look at any other game's code for this actually. Most solutions came to my mind rather quickly, and I got practice for stopping myself, asking what the problem I'm trying to solve is, and defining the parameters and requirements. Narrowing things down from a messy tangled up black ball of "the unknown" and "fear" into what it actually requires. Concrete example time: the undo system.
I had done basically all the logic: stacks, card suit and face rules, converting the stack system and interaction system to handle multiple cards at once, etc. Then I made a TODO list of the things the game needed to actually be playable, undo system was the one I was looking forward to the least. It's actually very funny in retrospect, because so much of it is mental instead of actual complexity. I didn't think about the problem, I assumed so much. "Oh, it needs to have undo AND redo!" (it literally didn't need redo), "Should it be a queue or an array? Should I make a custom Stack concept?" and more foggy hallucinations of gaps which needed to be filled out with my implementation. My first implementation was non-functional. An "Undoable" had a pointer to the stack that the card was moving from, a pointer to the stack it was moving to, and a pointer to the card that moved. I can't recall exactly why this failed, and in the moment I knew the rough idea of why it wasn't working, but didn't investigate in any detail. Instead, I gave up.
My first implementation didn't work, so it's impossible. I can't do it, it's out of my depth. That's my first instinct. I had a different problem on the TODO list, and in order to not let myself slip back into a drought, I pushed towards it instead of working on the undo system. It's important to note I'm using SDL3 for this project. SDL is awesome and widely used. I like it. However, a simple issue arose that lead me down a rabbit hole. I wasn't lying earlier, I did have a better attitude about just getting the game done, but I slipped up here. Let's get into it.
With SDL, you can either render in software mode to "Surfaces" (images) or you can use the SDL Renderer API to accelerate rendering with the GPU. Great! I've used it before and it's pretty good. It's similar to Raylib's render functions, or I should say that Raylib's is similar to SDL since SDL came first. This worked great at first. For each card in deck, SDL_RenderTexturedRect at this position, with this scale, and this rotation, etc. Cool, that's most certainly not getting batched but, whatever, it's 52 cards.
This is where that other problem on the TODO list comes in. I wanted to have a funky background shader effect like Balatro. I like Balatro despite my jealousy for the great execution of a great idea, so I figured if I'm making a game with cards I want to do something similar to it for fun. So, using a custom fragment shader alongside the SDL Renderer API, that should work, Raylib does it. Nope. It seems that since then, there is added functionality for something like that when using a "render state". However, from my small amount of time spent trying to use it, it doesn't seem like you get any useful information from the built-in vertex shader. I think I just specifically needed UVs, which weren't being provided to my custom fragment shader.
This is where the journey really begins. The only way to have custom vert and frag shaders is to use the GPU API. I haven't gotten around to actually writing a Vulkan renderer yet, but I can appreciate that they are pretty difficult and bulky to get going. Often being around 3000 LOC (not a super useful metric of course) to just do "Hello Triangle". However, I feel much more prepared to do so after having to learn SDL3's GPU API. Now, I'm going to cut them a lot of slack. They are basically wrapping and abstracting 3 different complicated rendering APIs: DirectX12, Vulkan, and Metal. That's a lot of work I imagine, so it's probably not gonna be super easy for client code, but damn they sure did their best.
When using the GPU API, you forfeit all the default SDL Renderer stuff. You have to setup the pipelines yourself. This is where it got kind of annoying. I'll end up needing about 3 pipelines due to the simple nature of the game. 1 pipeline for the background, 1 for the playing cards, and 1 for UI. Why? Well each pipeline can only have 1 set of vert/frag shaders bound to it. Pipelines are somewhat static, you can't swap out to different vert/frag shaders on the fly. You need a unique one for each shader. This is where I realized the value of "Ubershaders" for bigger games. Makes sense when pipelines aren't exactly cheap and swapping them while rendering isn't either. You just pack a lot of the responsibility in the shader, as much as possible. So, instead of having a player shader and an enemy shader (maybe you want the enemies to have a red outline and players to have a blue one), you would use the same shader and just give it flags about what is currently being rendered, instead of having unique shaders for each.
It, for good reason, reminded me of learning OpenGL. It's just way more verbose and complicated. Also, for the shaders, you can't just write good old fashion GLSL. Now you gotta compile them to SPIR-V. It's not too bad, but it is annoying to add another step to that process. I automated it of course, but I think Windows or VS Code made it slower than it needed to be.
I don't exclusively use Windows. I've used Linux distros a lot honestly. I'm comfortable with them, that's not an issue. Every day that I have to endure Windows 11 pushes me closer to daily driving a Linux distro instead. This is coming from someone who actually didn't jump on the Windows 11 hate bandwagon immediately. I liked the way it looked and so I upgraded. Simple as.
Windows 10 had a really nasty annoying bug with having additional language keyboard layouts. For years. Even if you removed the secondary keyboard layout, it would just add it back the next time you started the OS. You'd be playing a game and want to go prone, but z is swapped with y (guess the language) and you don't prone and you die. That sucks.
This pales in comparison to what is the nightmare that has been using Windows 11 after the first year or so of it's life. I constantly have problems. The crashes, god, the crashes. CPU hanging for no reason (thanks Windows Telemetry and or Microsoft Defender which you can't turn off). Microsoft Defender actually just causes 100% disk usage on my drives, SSDs included. It is so powerful that it will disrupt or outright crash my games or other software. Speak of the devil!

I am not joking. Literally as I am writing this section of the post, Windows Module Installer Worker also wanted to take it's turn thrashing my disk too. Fortunately, this was a mere fraction of the duration that these programs usually take. We have vibe-coded, ad-ridden, AI slopware to look forward to in the future as well. This will just get worse.
I bring up Windows because I originally tried Zed when they released their Windows version. I wanted to make it clear that I understand it has had a Linux/MacOS version for a while. I think the C# LSP support was subpar or something, so I quickly uninstalled it and moved on.
I saw someone mention it again recently and specifically how well it works with Odin, including the built-in debugger. I decided to give it a try again. It was a little painful getting the LSP to work, but it ended up being a good thing since I cleaned up how ols was installed on my system anyway.
I tried Zed again because VS Code has, like Windows 11, become unbearable. The startup is slow. Waiting for extensions (I don't even have many installed) is slow. Waiting for all the "participants" to be run, such as the 'File Create' ones, is slow. I literally cannot type text into the program when I start it. I have to wait until it lets me. A text editor... waiting. It's just getting old. I'm not even a VS Code hater. It's been great and reliable for years. It's possible there's some overlap between what's happening to Windows 11 and VS Code. I dunno, but after having used Zed for a bit now, I haven't needed to use VS Code anymore for a while. Thank god.
Configuring the vim keys was a bit annoying but once I got the hang of how their config system works, it wasn't so bad. For Odin, it's been great. The debugger actually works, pretty well too. I'll be having to open up raddbg less often now when I need to debug. Less friction and getting to debugging faster is nice!
It's pretty late at the moment. I was luckily able to happily pluck away at working on and finishing up the static site generator today. I'm staying up to write these posts and push the new site. I've got more things I want to write about. I'm trying to split them up better so the blogs aren't too huge. If I get around to adding a table of contents and jump-to-header links, maybe it'll be better. I also need to fix links that are created from markdown not having "target" set to blank so they open up in a new tab. Also need to re add the blog title header in the actual blog pages. Oh, and I need to implement the automatic RSS feed builder again.
Things to do, not enough time. 🥲
Thanks for reading!