C# Blackjack: An AI Assisted Journey

Last updated on April 15, 2025


So I recently spent about 25-30 hours of my free time developing a multiplayer Blackjack game, and I used AI to assist me on my journey. The models I used were ChatGPT's 4o, and Anthropic's Claude 3.7. This was not my first foray into AI-assisted development, as I used ChatGPT to help get the backend for this blog site running. Comparatively, however, this blog was far simpler, and I used far less AI assistance than I did on the Blackjack game.

When I started, I already had the nuts and bolts of a character-based Blackjack game written- things like eliminating PRNG bias, classes for handling cards (rank, suits & values), support for a configurable number of decks in a shoe, and of course handling multple hands per player for splits. I also had the wagering and payout system in place, as well as the vast majority of the scoring logic (there were some edge cases related to integrating voice). What I didn't have in place was an overall architecture, meaning no framework or system for moving such a game to a graphical presentation with artwork & sound. This meant timing & threading, and while I have done a fair bit of multithreaded development before (so I wasn't scared), the type of multithreading in a blackjack game is just different enough from what I've done in the past that it took me a bit to get my head wrapped around what the correct flow should be. The main issue is that everything is driven by timeouts- both player timeouts and player group timeouts; timing out causes an action or actions, but player input also causes actions too. The arhitecture I ultimately settled on was a router component that handled the calls to and from the game logic regardless of whether the action was triggered via keys or via timer expirations. It came out pretty clean.

When I was ready to move the project to an actual graphical game, my first consideration was which UI framework (or game engine) to use. Since the game isn't doing any real animation, I chose to skip the tried-and-true Unity, and the always improving Godot for something (that I assumed would be) far simpler: Avalonia. For those not familiar, Avalonia is a cross-platform graphical framework modeled on Windows WPF, which uses the latest cross-platform .NET (so C#- surprise, surprise!). Avalonia does not render native controls, but rather uses Google's Skia library for graphics and Harfbuzz for font rendering. I was initially quite torn on this; I really wanted native platform controls, and I had skipped over the WPF generation entirely on Windows (I'm still a WinForms guy at heart).

I did my best not to let Avalonia get in the way, and it was pretty good about that. Getting the markup for the layout I needed was relatively straightforward, and I just used a couple of canvas elements for drawing the cards. What little I did interact with seemed easy enough to navigate (and even easier to have ChatGPT assist with). Note- Avalonia offers two frameworks which differ in their approach quite a bit with respect to data binding and event handling. They offer their newer ReactiveUI, which from the name I assume people familiar with React would understand intuitively (but don't quote me here), and then there's Microsoft's Community Toolkit, which is a so-called MVVM toolkit, which stands for Model/View/ViewModel, where the model is your data, the view is the presentation layer, and the view-model is essentially what the controller would be in MVC, but not in quite the same way (the view-model handles the bindings between the view and the model, whereas a controller handles everything and drives the views in MVC). Anyway, I was able to lay out my views and create a UserControl for my Blackjack table, so all was fine and good on that front.

Before I continue much further, I want to say that having a tool that can be as powerful as these AIs can be (when they behave) is both a blessing and a curse. They are a blessing, because they can take detailed instructions and whip out mostly-correct code so fast that I wouldn't even have a method signature finished, let alone a function body; but that is also their curse, especially when doing something interactive like a Blackjack game. Why? Well, I wanted to play it! But you can't really play Blackjack until the game is basically feature complete. So, what happened, naturally, on my first iteration was that I just plowed through requesting functions and classes from ChatGPT until I got the game to a playable state. But that's where the reality set in: the codebase sucked. It was functional, but was filled with little random errors and was hardly extensible at all. That was (probab^H^H definitely) my fault, but I'm going to place at least some of the blame on the AI. It knew what I was writing, lol.

After the first version, I took a step back and decided I wanted to encapsulate all of the timers into a single class that could rule them all (and track them). This was actually a so-so decision in the end. The upshot was that my OneShotTimer class did wind up being a big part of version 3 (and yes, it's exactly what it sounds like), but its wrapper class (i.e. the one to rule them all) saw more limited usage in the end. I'd say it was a 60/40 win, maybe 70/30. Anyway, this version, while better, still succumbed to my haste and wound up in essentially the same place v1 did. Spaghettified by timers.

I took a couple days off and spent more time thinking about how I wanted everything to flow together. I sat back down and wrote a couple of classes I felt could tame the beast- a router class and a UI controller. Note that I did not use Avalonia's MVVM to its fullest, but this was a game damnit, and ChatGPT agreed that sometimes a game has to bend the rules; it even gave me some confidence that I was on the right path with a short (and unprompted) pep talk.

After thinking with my brain and not my question-asking, code-demanding fingers, and after shuffling around a ton of code into and to make use of the new classes, I went back to ChatGPT and laid down the law. The new way to handle all of the timers and callbacks would all go through the router, all actual UI work would be handled by the UiController, all input would go through the KeyHandler class, etc. It gave me a glowing code review! Honestly, it was quite nice. Maybe it does that for everyone, but it really boosted my confidence.

Now back to V3, this time when I asked for the code I needed at the moment (and only the code I needed at the moment), ChatGPT was great. We went to work and within a few hours had a much more maintainable codebase, with clear separation of concerns, and a happy-path pattern that didn't get its wires crossed. Exactly what I wanted. Only it still didn't work correctly in all cases. It is challenging to test functions driven by cancelable/restartable timers that also have key-based initiators and timeouts themselves in some cases. I went round and round with ChatGPT with it implementing code patterns we'd already tried, then removed, then tried again. Truth be told, I had about reached my limit when I remembered Claude. I had read a fair bit about Claude and how it was very good with coding and in many cases superior to ChatGPT. So I whipped out the old debit card and signed up for a Pro subscription. It was probably the smartest thing I've done all year (but it is only April, lol). Claude immediately spotted an inconsistent state in the game and offered three 4-6 line fixes, and BOOM! Blackjack yo! It really was that easy. I even went back and rubbed it in ChatGPT's face. It was humble, but complemented the code and asked if we were going to start on animations. Not this week buddy.

So here comes the bad news: the code is not public! I'm sorry about that, but I promise that the vast majority of my code on this blog will be public, but this one just ain't it. Maybe one day in the future, but not now.

And for the record, I did write quite a bit of the code. This was not an AI written game by any stretch of the imagination. What I have found is that these tools work best when you give them something to work with (code, not words). They are very good at figuring out what you're doing and working within that framework. I achieved the best results by laying down some foundations and only then letting the AI fill in small blanks at a time. That's the real way to tame these beasts. Well that and being very specific with your prompts.

And for those wondering at home, YES it runs on Linux just fine :)

-padawan, 2025