This is documented other places on the web, but I had fun with it, so I figured I'd write it up.


I've been replaying Mass Effect 1 recently. I noticed that in a club called Flux on the Citadel, there's a casino game called "Quasar". It's essentially a blackjack style game; you want to get as close to 20 points as possible without going over, and each round you can either "stay" and cash out for a known cash prize, or you can "hit" to add a random number of points to your score, but then you risk busting. The minor novelty to Quasar is that when you hit you can choose to add either 1-8 points or 4-7 points to your score.

To be more concrete, the game costs 200 credits to play, and has the following payouts:

0-14: 0
15:  50
16: 100
18: 250
17: 200
19: 300
20: 400
21+:  0


I played a few rounds and lost, when I realized that I'm a programmer! I can make the compute figure out the optimal strategy, and whether it's worth playing!

I whipped up a little dynamic programming algorithm, and it turns out casino owners in Mass Effect aren't very savvy businessmen. Assuming all the dice rolls are uniform, here's what the optimal strategy looks like:

0: hit 4-7 244.60
1: hit 4-7 244.61
2: hit 4-7 238.44
3: hit 1-8 235.73
4: hit 1-8 237.23
5: hit 1-8 242.82
6: hit 4-7 247.19
7: hit 4-7 251.15
8: hit 4-7 237.30
9: hit 1-8 218.13
10: hit 1-8 221.67
11: hit 1-8 230.37
12: hit 1-8 249.22
13: hit 4-7 287.50
14: hit 4-7 237.50
15: hit 4-7 175.00
16: hit 1-8 143.75
17: stay 200.00
18: stay 250.00
19: stay 300.00
20: stay 400.00


The first column is your score, the middle is the action you should take, and the last is your expected return. As you can see, the odds are pretty freaking good. You generally start out at somewhere between zero and five points, where the expected profit is greater than 30 credits per round.

Following this strategy, I went back to the machines and quickly won 1000 credits. Finally that programming knowledge is paying off!

Anyways, here's the code for the curious:

def payout(n):
    if n <= 14 or n > 20:
        return 0
    return {
        15: 50,
        16: 100,
        17: 200,
        18: 250,
        19: 300,
        20: 400
        }[n]

def avg(x):
    return float(sum(x)) / len(x)

res = dict()

def expected(n):
    if n > 20:
        return ('stay', 0)

    if n in res:
        return res[n]

    stay = payout(n)

    hit1 = avg([expected(n + x)[1] for x in range(1, 9)])
    hit2 = avg([expected(n + x)[1] for x in range(4, 8)])

    m = max([stay, hit1, hit2])

    if m == stay:
        res[n] = ('stay', m)
    elif m == hit1:
        res[n] = ('hit 1-8', m)
    elif m == hit2:
        res[n] = ('hit 4-7', m)

    return res[n]

expected(0)

for k, (a, e) in res.iteritems():
    print '%d:\t%s\t\t%0.2f' % (k, a, e)