I played an interesting horse race dice game for the first time recently. The mathematics of the game intrigued me and so one evening while on a dreadfully long flight from Seattle to Boston, I wrote a short simulation (using the Python language). The results surprised me.
Here’s the game layout:
A pair of dice is rolled and the corresponding horse is moved one step closer to the finish line. The outside horses, #2 and #12 only have to move 3 steps to win because rolling a 2 or a 12 is rare. The #3 and #11 horses must move 6 steps to win. The #4 and #10 horses must move 8 steps. The #5 and #9 horses must move 11 steps. The #6 and #8 horses must move 14 steps. The #7 horse must move 17 steps to win.
It would be relatively simple to compute directly, or use a simulation, to determine the probability that each horse would win. But the horse race game has an additional nuance. Before each game starts, four horses are selected by rolling the pair of dice. Therefore, horse #7 is most likely to be scratched, and horse #2 and #12 are least likely to be scratched. How does this affect the chances for each horse to win?
The output of my simulation starts by showing three races in detail:
Playing 3 races with messages: ========== game # 0 scratched: [11, 5, 8, 6] 9 3 7 7 10 4 10 4 3 4 12 7 4 4 4 7 9 10 9 10 9 9 12 3 7 7 7 9 7 3 7 7 7 9 7 2 7 7 9 7 10 2 4 4 || 4 wins ========== ========== game # 1 scratched: [9, 11, 6, 10] 8 2 7 8 8 3 8 8 7 3 4 4 7 7 7 12 5 5 8 4 8 8 12 8 4 5 12 || 12 wins ========== ========== game # 2 scratched: [7, 6, 3, 12] 2 8 5 5 10 9 8 5 8 11 9 11 11 9 10 8 8 4 4 4 9 4 4 4 9 5 5 10 5 5 5 10 8 8 9 2 10 10 9 8 8 5 9 10 10 || 10 wins ==========
The next part of my demo shows the results of playing 100,000 simulated games:
Playing 100000 races Wins for each horse: 2 20235 0.2024 3 9495 0.0950 4 9501 0.0950 5 5946 0.0595 6 3701 0.0370 7 2483 0.0248 8 3569 0.0357 9 5624 0.0562 10 9565 0.0956 11 9625 0.0963 12 20256 0.2026
Interesting! Horses #2 and #12 have a combined chance of winning of over 40% and poor horse #7 has only a 2.5% chance of winning a race. Apparently, when the four horses are scratched before every race, horses #6, #7, #8 are often tossed out and don’t get a chance. Additionally, the game was already too heavily skewed towards horses #2, #3, #11, and #12 by having too few steps required for them to win.
Now in the game, this doesn’t matter because players are assigned ownership to each of the horses randomly by dealing out playing cards. If a player is dealt the 5 of clubs, 5 of diamonds, and 5 of spades, that player has 75% ownership of horse #5. But if the game were to be modified so that players could trade cards before a race, for example to get 100% ownership of a particular horse, knowing the probabilities would help you trade away ownership in horses #5 though #9 to get ownership of horses #2, #3, #11, #12.
Here are the rules:
From the deck of cards, discard the aces, kings, and jokers, leaving you with 44 cards. Shuffle and deal these cards. Each player will not necessarily receive the same amount of cards.
Place the horses at the starting gate. Four horses must be eliminated, or Scratched before the race starts. To do this, the player to the left of the dealer rolls the dice, and the total on the dice is the first horse to be scratched. That horse is moved back to the first line. For example, if a three and a five were thrown with the dice, a total of eight, then horse number eight would be moved back to the first line. All players would then check their cards and pay five cents for each number eight card in their hand. The eights are then discarded.
The next player then rolls the dice for the second scratch. That horse moves back to the second line and costs ten cents for each of those that you have in your hand. Continue with the third and fourth scratch, moving to the fifteen and twenty cent lines, respectively. Paying the designated amounts, and discarding the scratched cards accordingly. If you shake the number of a horse that is already scratched, you pay the amount on that horse’s line, and pass the dice to the next player.
The horses now remaining at the gate are ready for the race to start. The next person to roll the dice totals them, and moves that horse forward one space. However, if the total on the dice is that of a scratched horse, then that player must pay the amount which that horse is standing on. Play continues around the table, until one horse reaches the finish line!
Your cards are your shares of each horse in the race. If horse number nine should win the race, and you have no nines in your hand, then you’re just plain out of luck. On the other hand, if you were to have two nines in your hand, you would win half the pot!
Each card with the winning horse’s number wins 25% of the pot. If you had all four of the one number, and that horse was to win then you would take the entire pot. After the pot has been divided, the cards are passed to the next player, the horses are all moved to the starting gate, and you’re ready to begin the next race!
Poker chips can be substituted for money. A pair of ones on the dice would be a #2 horse. A pair of sixes would be a Queen. A five and a six would be a Jack.
Demo program:
# horse_race.py
import numpy as np
rnd = np.random.RandomState(0)
# steps required to win for each horse
# -1, -1, 3, 6, 8, 11, 14, 17, 14, 11, 8, 6, 3
# 0 1 2 3 4 5 6 7 8 9 10 11 12
def pick_scratched(rnd):
# pick four horses
result = []
while len(result) != 4:
d1 = rnd.randint(1, 7)
d2 = rnd.randint(1, 7)
d = d1 + d2
if d not in result:
result.append(d)
return result
def horse_to_move(rnd, scratched):
d1 = rnd.randint(1, 7)
d2 = rnd.randint(1, 7)
d = d1 + d2
while d in scratched:
d1 = rnd.randint(1, 7)
d2 = rnd.randint(1, 7)
d = d1 + d2
return d
def play_game(rnd, verbose=True):
positions = np.zeros(13, dtype=np.int64)
required = np.array([-1, -1, 3, 6, 8, 11, 14, 17, \
14, 11, 8, 6, 3], dtype=np.int64)
scratched = pick_scratched(rnd) # four horses out
# scratched = [0, 1] what if no scratches?
if verbose == True:
print("scratched: ", end=""); print(scratched)
while (True):
h = horse_to_move(rnd, scratched)
if verbose == True:
print(str(h) + " ", end="")
positions[h] += 1
if positions[h] == required[h]:
if verbose == True:
print(" || " + str(h) + " wins ")
return h
print("\nPlaying 3 races with messages: ")
for g in range(3):
print("==========")
print("game # " + str(g))
play_game(rnd, verbose=True)
print("==========")
wins = np.zeros(13, dtype=np.int64)
print("\nPlaying 100000 races ")
for g in range(100000):
h = play_game(rnd, verbose=False)
wins[h] += 1
print("\nWins for each horse: ")
for i in range(2,13):
pct = wins[i] / 100000
print(str(i).ljust(4, ' ') + " " + \
str(wins[i]).ljust(8, ' ') + "%0.4f " % pct)
print("\nEnd horse race analysis ")


.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
2026 Visual Studio Live
2025 Summer MLADS Conference
2026 DevIntersection Conference
2025 Machine Learning Week
2025 Ai4 Conference
2026 G2E Conference
2026 iSC West Conference
You must be logged in to post a comment.