![]() |
![]() |
See full code here.
This repo is designed to answer at least the following questions:
I will also write this in Kotlin, because I'm trying to learn the language.
Because this may be a large project, I plan to have a few milestones: 1. A black jack playing program. 2. Add counters for each card remaining in the shoe (total distribution). 3. Add strategy card and autoplay. 4. Update strategy card and EV as we go. Allow autoplay until strategy card changes. 5. Build traditional high-low card count and display.
Then switch to a backend program. 5. Simulate perfect strategy to see if long-term EV is positive. 6. Build a training set with high-low card counts and associated distributions. Use decision tree algorithm to decide rule-based strategy cards. Compute long-term EV. 7. Repeat with othe variables (counts).
We'll establish some black jack rules: - Pays 3:2 on black jack - Can double any hand - Dealer hits on soft 17 - Can split any face cards - Dealer+player black jacks are considered a push - Black jacks on splits are considered regular 21s
One way I will deviate from reality in a non-material way is: If the dealer has a black jack, the player can still hit or stand, but they will lose regardless of their outcome. This is done for programmming ease.
It turns out that java applets haven't been used for about a decade, so I created a swing app, using ChatGPT for a Hello, World.
I created a Glyph pattern for drawing:
open class Glyph(val x: Int, val y: Int) {
val children: MutableList<Glyph> = mutableListOf()
fun addChild(child: Glyph) {
children.add(child)
}
// Method to draw the glyph and its children
fun draw(frame: JFrame, off_x: Int = 0, off_y: Int = 0) {
draw_this(frame, off_x + x, off_y + y)
for (child in children) {
child.draw(frame, off_x + x, off_y + y)
}
}
// Draw method for children with adjusted positions
open fun draw_this(frame: JFrame, off_x: Int, off_y: Int) {}
}
This allows me to abstract components easily. For example, I have a Card class with 20x20 cards, and I can make a Hand class like this:
class Hand(x: Int, y: Int) : Glyph(x, y) {
fun addCard(char: String) {
children.add(Card(25*children.size, 0, char))
}
}
After adding a few glyphs, this looks like this:
To organize the UI together with the backend components, I'm gonna try a Model-View-Controller pattern. I haven't used this pattern before, but I do know that it's usually a little messy to handle both UI and logically components.
This should look roughly like:
view = View() // Contains glyphs
model = Model() // Contains shoe
controller = Controller(view, model)
view.add_callbacks(["hit", "stand", ...], controller.dispatcher)
This shows an example of how a Hit would operate:
A few things to note about design principles here:
Notice that "Draw card" in the above diagram violates principle 2. We should think of "draw card" as a helper function that does, "get card" and "set card" in one step. This is allowable, but to keep things readable, we'll always put "Update" in such calls. So that snippet of code would look like:
Example 1
card = model.drawCardUpdateHand()
view.updateHand(card)
Later we'll want to include the distribution in the View. We'll do this like:
Example 2
card, distribution = model.drawCardUpdateDistribution()
view.updateDistribution(distribution)
model.updateHand(card)
view.updateHand(card)
A few more design principles introduced here:
drawCardUpdateHandUpdateDistribution
, but we'll avoid this.drawCardUpdateHand
, then later read distribution = model.getDistribution()
followed by view.updateDistribution(distribution)
. If we ever find ourselves doing this, then we've violated the contract that Controller should be in charge of setting state. (Of course, the Controller can have a helper function to do a few of these steps together.)However, rules were made to be broken. I found myself calling, view.displayStatus(model.dealerShowing(), model.humanShowing())
.
Adding a distribution of cards, a status box, and a profit-tracker were all pretty easy with the design patterns I decided.
I wanna get a fully functional black jack game before moving forward. Some steps I need to do next are:
As I proceed I find myself fudging more of the above principles: