[ about | work | projects | writing | contact | resume ]
Reverse engineering every casino game in Gamble With Your Friends

TL;DR:

I decompiled Gamble With Your Friends to work out the odds of every minigame in the casino. The table below shows the games by approximate edge, sorted by “Return To Player” (RTP). Bigger RTP means the player has better odds. 100% here means essentially “perfectly fair 50/50 odds”.

Game Bet RTP Favors
Craps N/A 137.75% Player
Money Wheel Orange 135.14% Player
Ducks Faruk ~134.80% Player
Video Poker Strategy Table 128.00% Player
Slots N/A ~115.70% Player
Ducks Erlaf ~109.60% Player
Money Wheel Red 108.11% Player
Blackjack Strategy Table ~102.00% Player
Coin Flip Heads / Tails 100.00% Fair
Penguins Any step 100.00% Fair
Prize Wheel Any segment 100.00% Fair
Hi-Lo Any threshold 100.00% Fair
Dragon Tower Any difficulty 100.00% Fair
Keno Any pick count 100.00% Fair
Minesweeper Any mine count 100.00% Fair
Roulette Any bet 97.30% House
Baccarat Player / Banker 90.10% House
Crash Any cashout 85.00% House
Ducks Yarl ~81.60% House
Money Wheel Blue 81.08% House
Baccarat Tie 79.20% House
Plinko Single Ball 78.00% House
Money Wheel Green 75.68% House
Ducks Jabin ~74.40% House

Why I did this

Gamble with your Friends is the latest entry in a sub-genre of indie titles that has become known as Friendslop. While I don’t agree with how pejorative this name is, I won’t deny that it is a product of our current moment. Gamble with your Friends (GWYF) pits you, and up to five of your degen friends, against violent loan sharks to whom you owe an immense debt.

We had a lot of fun playing this game, and it made us wonder: are these games fair? While playing we got the distinct feeling that some of the games seemed to favor the player, such as the slots and the ducks, while other games seemed unwinnable, like Crash. I was curious if the developers had juiced the odds one way or another. Fortunately, GWYF is a Unity game, and it does not ship itself AOT compiled. In simple terms, that means we can use reverse engineering tools like dnSpy to recover the game logic.

What this document is not intending to do is show you exploits. This post is only about statistics and odds. For example there is a known exploit for Craps by crouching in a specific location before throwing the dice. Likewise, it is possible to exploit Plinko by shoving baseball bats into the machine to form “ramps” for the balls to ride. I’m sure there are more exploits like this that are possible, but we are not interested in them here.

Caveats

I did all of this analysis based on game build 1.0.11. If the game has updated since then it is possible that the developers have retuned the games to change their RTP entirely. In my analysis I found that there is a single tunable “profitability” slider that the devs can easily change at a later date in response to player feedback. Take these results with a grain of salt.

Just like we are not covering game exploits, we are also not covering the use of items. There are a number of items that directly affect EV, by negating losses or increasing profit. These items are very interesting, and a key part of a successful strategy to actually beat the game. However, discussing them is out of scope for this already very-very-long blog post.

AI disclaimer

I used AI assistance to aid in reverse engineering the game and to write various helper scripts. The analysis itself, and the prose you are reading are human made, however many of the diagrams, charts, and tables in this post were constructed with AI assistance.

Ducks

The Duck Race game in Gamble With Your Friends, four lanes of ducks racing to a finish line

Everyone loves the ducks so let’s start there. On first glance, this game appears to be fair. When the race starts, time starts ticking forward, and on every tick all of the ducks advance forward by a uniformly sampled random amount.

private IEnumerator DuckRaceRoutine(Random rng)
{
    this.RpcSetRunningAnimation(true);
    while (!this.duckRace.hasEnded)
    {
        float num = Mathf.Lerp(this.minStepDistance, this.maxStepDistance, (float)rng.NextDouble());
        float targetZ = Mathf.Min(base.transform.localPosition.z + num, this.duckRace.endPoint.localPosition.z);
        this.RpcStep(targetZ);
        yield return new WaitForSeconds(this.stepTweenDuration);
        if (Mathf.Approximately(targetZ, this.duckRace.endPoint.localPosition.z))
        {
            if (this.duckRace.OnDuckFinish(this))
            {
                this.RpcWinFeedback();
            }
            this.RpcSetRunningAnimation(false);
            yield break;
        }
        float seconds = Mathf.Lerp(this.minStepDelay, this.maxStepDelay, (float)rng.NextDouble());
        yield return new WaitForSeconds(seconds);
    }
    this.RpcSetRunningAnimation(false);
    yield break;
}

At first glance this looks perfectly symmetric. Every duck is identical and independently seeded, so you would expect each one to cross first exactly one quarter of the time, paying a fair 4x:

\[E[\text{net}] = \tfrac{1}{4}(+3) + \tfrac{3}{4}(-1) = 0 \implies \text{RTP} = 100\%\]

This is what the math tells us, but when we dig deeper we find a big flaw: It turns out that multiple ducks can cross the finish line on the same tick. When this happens, the winner is determined by which Duck coroutine (which is basically like a thread) fires first. It turns out that Unity schedules these coroutines in creation order. The coroutines are started in index order, meaning that the ducks on the left side of the board are more likely to win.

I have to come clean here, I did not figure out this bug on my own. I was assisted by AI, which suggested this was possible. I didn’t believe the AI, so I had it write a simple Unity program to simulate these dynamics as close as possible to what the game logic does. I simulated 4,000 duck races, so random variation should be minimal:

Duck Lane Win % RTP
Faruk 1 33.7% 135%
Erlaf 2 27.4% 110%
Yarl 3 20.4% 81%
Jabin 4 18.6% 74%

Faruk (lane 1) wins nearly twice as often as Jabin (lane 4). The chi-square against a uniform 25% split is 231, where anything above 7.815 already rejects “all ducks equally likely” at the 95% confidence level. This is largely because the track they race on is relatively short. If they were on a longer track, ties would be less likely, and this effect becomes less relevant:

Track (steps) Faruk Erlaf Yarl Jabin Chi-square
~21 (in game) 33.7% 27.4% 20.4% 18.6% 231
80 30.2% 26.2% 23.0% 20.6% 84
160 28.4% 26.2% 24.1% 21.3% 45

With this bug, the RTP of betting on Faruk is an eye watering 135%. I suspect that the developers did not realize the implications of their tie breaking behavior. I hardly blame them, on first glance I also was ready to just write this off as “perfectly fair.” Of course the correct way to fix this problem would be to use a fair tie-breaker, instead of biasing to certain ducks.

One caveat of this result is that while I did simulate the races inside of a toy Unity game, I did not run this same experiment in the original game itself. It is possible that would change the results. Future work here would require writing a GWYF mod to run the experiments in the game directly.

Slots

The Mummy's Chamber slot machines in Gamble With Your Friends

In real life, slot machines are tuned to be very close to 100% RTP. Some machines advertise 99% RTP, for example. Since modern digital slots need less maintenance than their mechanical predecessors, and don’t require a dealer, these are the money printers for casinos. At 99% RTP most players will not be able to discern that they are losing money on net, even though they slowly are being drained.

What we had noticed while playing the game was that playing the slots felt like it was slightly positive EV. We had managed to rescue more than one run by spamming slot machines. Unlike the duck race above, this game is not very interesting from a technical perspective. On each cell of the 3x3 grid, there is a “roller” which can display one of four symbols. Looking at the game code, these rollers seem unbiased, so all of the rollers are equally likely. It basically does this:

def spinSlotMachine(playfield):
    for roller in playfield:
        roller.symbol = random.randint(1, 4)

You win when these rollers all contain the same symbol. The payout for various patterns is listed below, as extracted from game assets:

Payline Payout Win Probability EV
Top row 2x 6.25% 0.125
Middle row 2x 6.25% 0.125
Bottom row 2x 6.25% 0.125
Left column 2x 6.25% 0.125
Middle column 2x 6.25% 0.125
Right column 2x 6.25% 0.125
Diagonal \ 2x 6.25% 0.125
Diagonal / 2x 6.25% 0.125
Diamond 5x 1.5625% 0.078
X (corners + center) 20x 0.390625% 0.078
Full board 59x 0.001526% 0.0009

Summing that column gives the base RTP:

\[E[\text{return}] = 8(0.125) + 0.078 + 0.078 + 0.0009 \approx \mathbf{1.157}\]

Additionally, the win is scaled by which of the four symbols you matched with.

Symbol Index Factor
Lotus 0 1.75x
Beetle 1 1.25x
Staff 2 0.75x
Eye 3 0.25x

Since these scaling factors average to 1.0 our EV is still 1.157.

Craps

The Street Craps table in Gamble With Your Friends with dice on the green felt

As we mentioned before, craps is exploitable but let’s focus instead on playing the game correctly. When you toss the dice to scramble it, that action is random, and not weighted. When you throw the dice, they become physics objects and are simulated using Unity’s physics engine. While we could in principle simulate these rolls, there is extra randomness introduced by player input since the starting velocity of the die is influenced by the players position and mouse cursor when they roll.

Instead, let’s just do some classic statistics here. The first, “come-out”, roll works like real craps: a 7 or 11 wins immediately paying out 2x. A 2, 3, or 12 loses immediately. Any other roll sets a target score, called a “point”, that you need to attempt to roll for. If you hit your point you get paid out 4x, but if you instead roll a 7 you lose. Where this game differs from traditional casino craps is that after 3 attempts to hit your point, you get your ante refunded. In a normal casino you lose at this point, and this difference is why the player has an edge in GWYF’s variant.

It is worth briefly reminding ourselves of the probabilities of the outcomes of rolling two dice:

Each “tower” of dice in this diagram represents all of the possible rolls of two dice, and what value they sum to. This makes it a bit more intuitive that 7 is the most likely dice roll, and why. From here, we just need to consider the game as a sort of flow chart of dice rolls.

In the graph below we show what the rules say you do in the case of every dice roll, and that nodes contribution to EV. To make it explicit, EV is just “probability x reward”.

This diagram makes the math much more intuitive to me, since to calculate the EV we need to just traverse this whole decision tree. We can then construct a simplified table like this to see the overall EV contribution of each node:

Outcome Probability Multiplier EV contribution
Come-out 7 / 11 22.22% 2x 0.4444
Come-out 2 / 3 / 12 11.11% 0x 0.0000
Point hit (within 3 rolls) 17.15% 4x 0.6860
Seven-out 24.81% 0x 0.0000
3-roll refund 24.71% 1x 0.2471
Total 100%   1.3775

This result was not intuitive to my group, since we felt like we were always losing at Craps, but the overall EV of 1.3775 is one of the best player edges in the casino.

Prize wheels

There are three wheel based games in the casino, that are fundamentally the same game.

Wheel of Fortune

The Prize Wheel in Gamble With Your Friends, a segmented wheel with a Spin button in the center

The simplest of the three, this wheel has 20 segments that are equally likely. What is interesting is that the “spin” of the wheel is just for show. The result of the spin is predetermined, and then the wheel is simply animated towards the predetermined result:

public virtual void SpinTheWheel(Random rng)
{
    if (this._isSpinning) return;
    this._isSpinning = true;

    // 1. Pick the resting position FIRST: a uniform angle in [0, 360).
    float angle = (float)(rng.NextDouble() * 360.0);
    // Add a few whole turns purely for show, so it looks like a real spin.
    float finalAngle = this.minTurnAmount * 360f + angle;
    if (this.spinDirection) finalAngle *= -1f;

    // 2. Animate toward that predetermined angle (DOTween, fixed easing).
    this.RpcSpinWheel(finalAngle, this.spinDuration);
    base.StartCoroutine(this.WaitAndStop());
}

Since the segments are all equal width, they are therefore equally likely. By adding up the multipliers on the wheel segments we can straightforwardly get the EV:

Payout Wedges Probability EV contribution
5x 1 5% 0.250
3x 2 10% 0.300
2x 2 10% 0.200
0.5x 3 15% 0.075
0.25x 4 20% 0.050
0.1x 5 25% 0.025
0x 1 5% 0.000
Spin Again 2 10% 0.100
Total 20 100% 1.000

The only subtle row is Spin Again. Since they spin the wheel for us a second time, it is equivalent to refunding us and us playing again, so we can count it as the same as a 1x payout. Summing the EV contributions we find that means this game has an EV of 1.0, making it perfectly fair.

Money Wheel

The Money Wheel in Gamble With Your Friends, with 2X, 3X, 5X, and 10X color bets below the wheel

The Money Wheel is governed by the same logic as the Wheel of Fortune, in that it also predetermines the result and then drives the animation towards it. What makes this game different is that it asks you to predict the outcome of the spin, Green, Blue, Red, or Orange. These colors payout at different rates, 2x, 3x, 5x, and 10x respectively. You may be tempted to think that surely this game will also be fair, but it emphatically is not.

Adding up the number of squares per color we get this:

Color Segments Share of wheel Payout RTP (share × payout)
Green 14 14/37 = 37.84% 2x 75.68%
Blue 10 10/37 = 27.03% 3x 81.08%
Red 8 8/37 = 21.62% 5x 108.11%
Orange 5 5/37 = 13.51% 10x 135.14%

If you are playing this game, you should ONLY play Red or Orange bets, never Green or Blue.

Roulette

The roulette wheel and betting layout in Gamble With Your Friends

Roulette is a textbook casino-style roulette wheel, with a single green square. This game is very similar to the other two wheel games, and as such the ball landing on a square is simply animated towards a predetermined target. For this game we don’t even need to do our own math, Wikipedia has done it for us. Regardless of your bet or betting patterns your RTP will be 97.30%, just like at a real casino.

The Martingales

Hi-Lo, Penguins, Minesweeper, Dragon Tower, Keno, and Crash look like six different games, but they are really the same game mathematically. Each one secretly picks a survival probability \(P\), lets you climb for a rising multiplier, and pays out exactly \(1/P\) if you make it to where you stop. In short, the more risk you take on, the higher the payout, but weighted such that the EV is always 1.

\[E[\text{return}] = P \times \frac{1}{P} = 1 \implies \text{RTP} = 100\%\]

I think to make this more intuitive, it’s helpful to start with Hi-Lo. If you set the slider in Hi-Lo to the 50% position, your odds of winning are exactly 50% regardless of if you pick “Hi” or “Low”. Because your odds are 50% or \(1/2\) the game pays out the reciprocal of your odds, which is 2x. Likewise, if you set the slider to the 90% position and bet “Hi” your odds of winning are \(1/10\), so the game will payout 10x if you win.

// HiLoGame.cs -- roll is uniform on [0, 1); hiLoSlider.currentValue is the threshold t
float roll = (float)base.GetSeededRandom(0).NextDouble();
bool win = this._isOver
    ? roll >= this.hiLoSlider.currentValue    // "Hi":  win when the roll clears t
    : roll <= this.hiLoSlider.currentValue;   // "Low": win when the roll is under t

// P(win) is exactly the slider position, and the multiplier is E / P(win)
double num = this._isOver
    ? (1.0 - (double)this.hiLoSlider.currentValue)   // P(win) for "Hi"
    : ((double)this.hiLoSlider.currentValue);        // P(win) for "Low"
double multiplier = 1 / num;

The other five games follow the same pattern. For example in Minesweeper your payout is calculated based on the number of mines you added to the board, and the number of tiles you have revealed so far. The math looks like this:

\[P(\text{survive } r) = \prod_{i=0}^{r-1} \frac{N - m - i}{N - i}\]

Where \(N\) is the total tiles, \(m\) is the number of mines, and \(r\) is the number of tiles safely revealed.

// Minesweeper.cs
private double CalculateCurrentMultiplier()
{
    if (this._revealedTiles.Count == 0) return 1.0;
    int count = this.tiles.Count;                 // N: total tiles (25)
    double num = 1.0;
    int num2 = count - this._currentMineCount;    // S: safe tiles = N - m
    for (int i = 0; i < this._revealedTiles.Count; i++)
    {
        // (S - i)/(N - i) is P(the i-th reveal is safe), sampling without replacement
        double num3 = (double)(num2 - i) / (double)(count - i);
        num *= 1.0 / num3;                         // accumulate 1 / P(survive so far)
    }
    return num;
}

The payout that the game gives you for winning is therefore \(1/P(\text{survive})\) just like Hi-Lo. It turns out that games of this flavor are called “martingales”. I won’t pretend to understand all of the math here, but the optional stopping theorem proves that for games like this there is no cash-out strategy that improves your expected value. I won’t bore you with going through these games one by one, but they all have this flavor to them, and are tuned to be exactly fair in the code with one notable exception.

Crash

The Crash game in Gamble With Your Friends, with the multiplier curve climbing past 2x

Crash is the only one of these martingale games that is not tuned to be fair. From the game code we see the following:

Random seededRandom = base.GetSeededRandom(0);
float num = (float)seededRandom.NextDouble();
float crashPoint = 1.01f;
if (num > this.instantCrashChance)
{
    crashPoint = this.GetRandomCrashPoint((float)seededRandom.NextDouble());
}
...
private float GetRandomCrashPoint(float r)
{
    r = Mathf.Max(r, 0.001f);
    return Mathf.Clamp(1f / r, 1.001f, this.maxPoint);
}

Note the variable “instantCrashChance.” Unlike the other games, Crash has a tunable parameter for how likely the player should be to lose instantly. “instantCrashChance” is set to 15% in the shipping version of the game. I presume this is to tune down the likelihood of the potentially enormous wins that are possible here. It’s hard to say exactly why this was done, but nonetheless, this caps the EV for this game at 0.85.

Physics-driven games

Two of the games in the casino are actually driven by the Unity physics engine. While we can reason about them analytically, that analysis might not match the real game logic for a number of reasons.

Coin Flip

The winning side of the Coin Flip in Gamble With Your Friends, a green coin with a money symbol
The losing side of the Coin Flip in Gamble With Your Friends, a red coin with X eyes

Let’s start with the very first game you are presented with when you start the game: the Coin Flip. If you aren’t familiar with GWYF, if you win the coinflip you are allowed to play the game. If you fail, the game exits and you have to try again. It’s a good teaser for the vibe of the rest of the game.

In the Unity scene that drives the coin flip, the coin starts face down. The game then applies a random upward force, and a torque along a random axis to the coin. The coin is simulated with the default Unity physics engine, but the physics constants have been tuned for a more dramatic looking coin flip. For example, the rest of the game uses 15 m/s² of gravity, but the coin only experiences 2 m/s² of gravity.

Now every middle schooler can tell you that a coin flip is roughly 50-50, but even in real life it is possible for coins to not be fair. To this end, we will need to do the same trick we did with the duck game and simulate a couple thousand runs by recreating the game code as faithfully as we can in our own Unity scene.

Flips Win Lose P(win) RTP
10,000 4,995 5,005 0.4995 99.9%

This is a pretty good result, and shows that this coin is damn near fair. One fun caveat of these results is that “tails” and “heads” are not the only possibilities. It turns out that the coin is being flipped inside an invisible “cup” to prevent it from flying off screen. In some rare circumstances, it is possible for the coin to come to rest leaning on the walls of this cup. Likewise, under extreme circumstances it is possible for the coin to land precisely on its edge. These quirks don’t influence the outcome of the experiments much, they are just fun.

Heads · loseF 4.95 · τ 6.53 · θ 5°
Heads · loseF 4.93 · τ 6.44 · θ 133°
Heads · loseF 4.21 · τ 7.89 · θ 117°
Heads · loseF 4.77 · τ 5.31 · θ 191°
Tails · winF 4.18 · τ 8.96 · θ 140°
Tails · winF 4.33 · τ 6.78 · θ 315°
Tails · winF 4.48 · τ 9.67 · θ 102°
Tails · winF 4.61 · τ 6.78 · θ 174°
On edgeF 4.84 · τ 5.12 · θ 214°
On edgeF 4.95 · τ 6.36 · θ 338°
On edgeF 4.25 · τ 5.77 · θ 349°
On edgeF 4.62 · τ 9.66 · θ 258°
Wall leanF 4.05 · τ 7.50 · θ 289°
Wall leanF 4.62 · τ 8.08 · θ 43°
Wall leanF 4.80 · τ 9.19 · θ 242°
Wall leanF 4.72 · τ 8.83 · θ 322°

Plinko

The Plinko machine in Gamble With Your Friends, a triangular peg board with multiplier buckets from 24x down to 0.2x

Plinko is an interesting case, because on first glance it might appear impossible to model the probability directly. However, the Plinko machine is a straightforward application of the binomial distribution. In a simplified view of the Plinko machine, imagine that at each vertical level of the board the ball asks a question “should I go right, or should I go left?” and flips a coin to determine its path. Assuming that the coin is fair, you should see probabilities like this:

Galton board probability tree for an 11-row Plinko machine; each node is labeled with the binomial probability of a ball reaching it

This type of model is also called a Galton board. Notice on the third row that the probabilities for each node goes, from left to right, 25%, 50%, 25%. That is because there are 2 paths to reach the middle node, but there is only one path to reach the left and right nodes. This simple idea, iterated out for all 11 rows, gets us our expected probabilities for each of the bottom nodes. Now reminding ourselves of the \(EV = probability * reward\) math we can use this table to compute the “theoretical” EV of this game.

Bin 0 1 2 3 4 5 6 7 8 9 10 11
Mult 24x 6x 2.8x 1.2x 0.5x 0.2x 0.2x 0.5x 1.2x 2.8x 6x 24x
Probability 0.05% 0.54% 2.69% 8.06% 16.11% 22.56% 22.56% 16.11% 8.06% 2.69% 0.54% 0.05%
EV 0.012 0.032 0.075 0.097 0.081 0.045 0.045 0.081 0.097 0.075 0.032 0.012
\[\begin{aligned} \text{EV} &= 0.012 + 0.032 + 0.075 + 0.097 + 0.081 + 0.045 \\ &\quad + 0.045 + 0.081 + 0.097 + 0.075 + 0.032 + 0.012 \\ &= 0.683 \end{aligned}\]

You can also compute this directly with the binomial distribution formula like such, where \(m_k\) is the payout multiplier for bucket \(k\):

\[\text{EV} = \sum_{k=0}^{11}\frac{\binom{11}{k}}{2^{11}}\,m_k = \frac{1398.8}{2048} = 0.683\]

The naive model says 68.3% RTP which is a good starting point, but is ultimately wrong. The balls in this game do not behave like an idealized Galton board. For one they are bouncy, which the idealized balls are not. Also, the game code has some tunings that manually changes the speed of any ball after a collision, presumably just for visual appeal. To really make sure that we capture the real odds, we need to rebuild the in-game Plinko board in Unity and simulate it. I ran 100,000 balls through the simulated board and got these results:

Bin mult 24x 6x 2.8x 1.2x 0.5x 0.2x 0.2x 0.5x 1.2x 2.8x 6x 24x
Measured P 0.59% 0.97% 1.53% 2.94% 8.82% 34.44% 34.70% 8.99% 2.98% 1.43% 0.92% 0.62%

The twelve pockets account for 98.92% of drops. The remaining 1.08% are balls that got stuck on one of the pegs. If the ball gets stuck, the game has a 20s timeout, after which the ball is deleted and the player loses their ante. Doing the same math we did above, we get an EV of 0.78, which is 10 points higher than our theoretical guess. Looking at a histogram of where the balls land, this becomes a bit more obvious:

Bar chart comparing the binomial (Galton board) pocket probabilities against the measured PhysX simulation over 100,000 drops, showing much fatter 24x edges in the measured data

Even though the center 0.2x bins are way more likely than the math predicted, which should drag our EV down, the 24x buckets on the edges are 10 times more likely than we predicted. These edge buckets account for almost the entire EV improvement above the theoretical model.

1,000 independent samples superimposed as a sample of the behavior of this system

Card games

The card-based games (Blackjack, Video Poker, Baccarat) all deal cards from a single shuffled deck. The deck is not shuffled in-between rounds until the deck is empty. This means card counting is trivial. I also think that card counting is way too high effort for most players. With that said, we will be analyzing these games without counting for simplicity.

Blackjack

A blackjack hand in Gamble With Your Friends with Double, Stand, Hit, and Play buttons

Blackjack in this casino bends the rules in the player’s favor. Blackjack pays 2:1 instead of the usual 3:2, and a player natural beats a dealer natural instead of pushing. Also unlike many casinos you cannot split. The game actually has splitting implemented in the code, but there is no button for it on the Blackjack table. Presumably they intended on adding this, but removed it late during development for some reason. Due to these rule changes, the normal house edge of ~2% is in question. (NB: that is the rough house edge for tables with 6 decks in shoe, like most real casinos)

Result Return Net
Player blackjack 3x +2
Player win 2x +1
Push 1x (refund) 0
Player lose 0x -1

Computing the exact RTP

To determine both the house edge and the optimal play charts, we used Eric Farmer’s blackjack analyzer, an engine that computes the optimal blackjack play for any given rule set.

We ran his code two ways: once with the GWYF ruleset and once with the same rules, but with the normal 3:2 payout scheme that casinos normally use as a smoketest.

Rules Blackjack payoff Overall EV RTP
Single deck, S17, double any two, no split, no surrender 3:2 -0.333% 99.667%
Single deck, S17, double any two, no split, no surrender 2:1 +1.991% 101.991%

So EV is 1.02 which means we are just barely positive under perfect play.

One quirk of the game falls outside what the engine can express: the dealer never peeks for blackjack. This rule shouldn’t have a big influence on EV, so we are choosing to ignore it here.

Optimal strategy

The analyzer also outputs the optimal strategy chart given our ruleset. This chart is very similar to the canonical chart, so no big surprises here.

To use the chart, find the row that matches your hand and read across to the column for the dealer’s card. The cell where they meet is the action you should take. Hard totals (no ace, or an ace that can only count as 1) are the “H” rows, and soft totals that contain a flexible ace are the “A,” rows. Check the soft rows first. If you hold an ace counted as 11, use the matching soft row; otherwise fall back to the hard total.

Video Poker

The Video Poker machine in Gamble With Your Friends showing a five-card hand and a Play button

Like Blackjack, Poker is also very different from most casino incarnations of the game. In this game the Ace is always low, and pairs pay. Normal video poker machines will require face card pairs to pay out.

I’m going to be honest: for this game I surrendered entirely to our AI overlords. All of the other analysis was driven by me, with AI doing the boring stuff. In this case I could not find a ready-made tool to simulate the odds of this game, since it uses a custom rule set. “No worries”, said the LLM du jour, “I can just spit that out for you”. It generated some of the densest C++ code I have ever seen. I tried to understand it, and failed. To that end, take these results with a grain of salt: the poker solver that the AI wrote says that with perfect play this game has an EV of 1.28.

The solver enumerated all 2,598,960 possible five-card deals. Scan your dealt hand from the top of the table down and take the first action that matches what you are holding. The “avg return” column is the average payout (in multiples of your bet) you can expect from playing that situation optimally.

Dealt hand Frequency Best action Avg return
Straight flush 0.0014% Keep all five 50.00x
Four of a kind 0.0240% Hold the four, draw 1 25.00x
Full house 0.1441% Keep all five 9.00x
Flush 0.1967% Keep all five 6.00x
Three of a kind 2.1128% Hold the three, draw 2 4.30x
Straight 0.3532% Keep all five 4.00x
Two pair 4.7539% Hold both pairs, draw 1 2.60x
One pair 42.26% Hold the pair, draw 3 1.54x
Four to a flush (no pair) 2.95% Hold the four suited, draw 1 1.47x
Four to a straight (no pair) 9.20% Hold any 2 suited, else a high card, draw 3 0.75x
Nothing 38.01% Hold one high card (or 2-3 suited), draw the rest 0.68x

Baccarat

The Baccarat table in Gamble With Your Friends with Banker, Tie, and Player bet buttons

Baccarat in GWYF is a stripped-down version of the game: two cards are dealt to each side, the totals are compared mod 10, and the higher one wins. Because a single deck only has 1,624,350 possible two-card-each deals, we can just enumerate all of them and tally the results.

\[\binom{52}{2}\binom{50}{2} = 1{,}624{,}350\]
Outcome Probability Bet payout EV
Player wins 45.05% 2x 0.901
Banker wins 45.05% 2x 0.901
Tie 9.90% 8x 0.792

The Player and Banker win counts are identical, so the house edge on those bets is from ties pushing to a loss. If ties were refunded, this game would be perfectly fair.

Conclusion

Thanks for reading this far! Not much else to say other than go buy Gamble With Your Friends if you haven’t tried it yet. It’s great fun :)

Corrections


← all posts

Comments