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, I wrote a short simulation (using the Python language) to analyze the game. The results surprised me. I noticed two weaknesses of the game. Horses #2 and #12 had a huge advantage, and the game took way too many rolls on average to complete. I devised a new game layout that I think is much improved.
Here’s the actual 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 to be scratched (removed from the race) 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 of the original game:
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 ========== 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
The game takes way too long — about 40 rolls of the dice on average. And the chances that each horse wins are wildly different. Horses #2 and #12 each have a 20% chance of winning and poor horse #7 has only a 2.5% chance of winning a race. When the four horses are scratched before every race, horses #6, #7, #8 are often tossed out and don’t ever get a chance to win. Additionally, the game was already far too heavily skewed towards horses #2, #3, #11, and #12 by having too few steps required for them to win.

My improved version (in my opinion anyway).
Now in the original game, this doesn’t matter too much 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.
I modified my simulation to accept a game configuration of the number of steps required for each horse. After a bit of manual exploration I found a much better configuration:
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 || 4 wins ========== ========== game # 1 scratched: [8, 7, 5, 11] 9 10 9 10 6 9 9 12 3 9 3 6 9 6 2 6 9 10 2 4 4 9 || 9 wins ========== ========== game # 2 scratched: [11, 6, 10, 9] 8 2 7 8 8 3 8 8 7 3 4 4 7 7 7 12 5 5 8 4 8 8 12 8 || 8 wins ========== Game configuration: [-1 -1 3 5 6 8 9 10 9 8 6 5 3] Playing 100000 races Wins for each horse: 2 10326 0.10 3 7592 0.08 4 11070 0.11 5 7600 0.08 6 8657 0.09 7 9178 0.09 8 8573 0.09 9 7602 0.08 10 11343 0.11 11 7732 0.08 12 10327 0.10 End horse race analysis
Horses #2 and #12 require 3 steps to win Horses #3 and #11 require 5 steps. Horses #4 and #10 require 6 steps. Horses #5 and #9 require 8 steps. Horses #6 and #8 require 9 steps. And horse #7 requires 10 steps.
Each game is now about 20 rolls on average (much more fun) and no horse has a huge advantage. All 12 horses have very close to a 10% chance of winning. This would allow the game to include horse trading before each race. For example, a player might swap to acquire 100% ownership of a single horse, or he might swap to get partial ownership of as many horses as possible.
Good fun.
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 original
# -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, required, verbose=True):
positions = np.zeros(13, 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
# required = np.array([-1, -1, 3, 6, 8, 11,
# 14, 17, 14, 11, 8, 6, 3], dtype=np.int64) # original game
required = np.array([-1, -1, 3, 5, 6, 8, 9, 10, 9, 8,
6, 5, 3], dtype=np.int64) # improved game
print("\nPlaying 3 races with messages: ")
for g in range(3):
print("==========")
print("game # " + str(g))
play_game(rnd, required, verbose=True)
print("==========")
# simulation
wins = np.zeros(13, dtype=np.int64)
n_games = 100000
# number steps required for each horse to win
# required = np.array([-1, -1, 3, 6, 8, 11, 14, 17, 14,
# 11, 8, 6, 3], dtype=np.int64) # original .02 to .20
# required = np.array([-1, -1, 3, 4, 5, 6, 7, 8, 7, 6,
# 5, 4, 3], dtype=np.int64) # .06 to .10
# required = np.array([-1, -1, 3, 5, 6, 7, 8, 9, 8, 7,
# 6, 5, 3], dtype=np.int64) # .06 to .11
# required = np.array([-1, -1, 3, 4, 6, 7, 8, 9, 8, 7,
# 6, 4, 3], dtype=np.int64) # .08 to .13
# required = np.array([-1, -1, 3, 5, 7, 8, 9, 10, 9, 8,
# 7, 5, 3], dtype=np.int64) # .07 to .11
# required = np.array([-1, -1, 3, 5, 6, 8, 9, 10, 9, 8,
# 6, 5, 3], dtype=np.int64) # .08 to .11 *** best ***
# required = np.array([-1, -1, 3, 5, 6, 7, 9, 10, 9, 7,
# 6, 5, 3], dtype=np.int64) # .07 to .12
print("\nGame configuration: "); print(required)
print("\nPlaying " + str(n_games) + " races ")
for g in range(n_games):
h = play_game(rnd, required, verbose=False)
wins[h] += 1
print("\nWins for each horse: ")
for i in range(2,13):
pct = wins[i] / n_games
print(str(i).ljust(4, ' ') + " " + \
str(wins[i]).ljust(8, ' ') + "%0.2f " % 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.