Computer and video games can be very sophisticated. But simple games can be fun too. Various versions of tabletop dice baseball have been around for well over 100 years. There are many variations, but in most you roll one or two dice which determine the result of a play, for example, “single”, or “ground out”, or “walk”.

One rainy weekend evening in the Pacific Northwest where I live, I decided to write a simulation of tabletop dice baseball. The version I picked uses two dice and has the following gameplay:

1/1 home run 3/3 single 1/2 double 3/4 strikeout 1/3 single 3/5 ground out 1/4 pop out 3/6 fly out 1/5 ground out* 4/4 walk 1/6 strikeout 4/5 fly out 2/2 single 4/6 fly out 2/3 pop out 5/5 base on error 2/4 ground out 5/6 single 2/5 strikeout 6/6 triple 2/6 ground out * double play if a force available

It took me about two hours to code up a simulation. I used Python but I could have used any of my usual languages such as C/C++, C#, or JavaScript.

*A demo run. Click to enlarge.*

The program look me a bit longer than I expected. Some of the logic was tricky. For example, I originally implemented a function advance_one_base() and then a function advance_n_bases() for doubles, triples, and homers that called advance_one_base() in a loop. But that logic isn’t correct because a batter hitting a double is not the same as two players hitting consecutive singles.

I was also a bit surprised that there were a lot more potential customization points than I thought there’d be. For example, my simulation did take into account the possibility of a double play on a sharp ground out. But there were many other scenarios, such as runners advancing on a fly out with less than two outs, and extending the game to extra innings if the game is tied after nine innings. Simple fun. Good fun!

*Left: “Our National Game” from about 1885 used two dice. Right: “Peg Base Ball” from about 1906 used a single dice (OK, a single “die”).*

Code below (long).

```
# dice_baseball.py
import random as R
def advance_single(runners):
runs_scored = 0
# 1,2,3 - 1,2,3 run
if runners[1] == True and runners[2] == True and runners[3] == True:
runners[1] = True; runners[2] = True; runners[3] = True; runs_scored = 1
# 1,2 - 1,2,3 no run
elif runners[1] == True and runners[2] == True and runners[3] == False:
runners[1] = True; runners[2] = True; runners[3] = True; runs_scored = 0
# 2,3 - 1,3 run
elif runners[1] == False and runners[2] == True and runners[3] == True:
runners[1] = True; runners[2] = False; runners[3] = True; runs_scored = 1
# 1,3 - 1,2 run
elif runners[1] == True and runners[2] == False and runners[3] == True:
runners[1] = True; runners[2] = True; runners[3] = False; runs_scored = 1
# 1 - 1,2 no run
elif runners[1] == True and runners[2] == False and runners[3] == False:
runners[1] = True; runners[2] = True; runners[3] = False; runs_scored = 0
# 2 - 1,3 no run
elif runners[1] == False and runners[2] == True and runners[3] == False:
runners[1] = True; runners[2] = False; runners[3] = True; runs_scored = 0
# 3 - 1 run
elif runners[1] == False and runners[2] == False and runners[3] == True:
runners[1] = True; runners[2] = False; runners[3] = False; runs_scored = 1
# X - no run
elif runners[1] == False and runners[2] == False and runners[3] == False:
runners[1] = True; runners[2] = False; runners[3] = False; runs_scored = 0
return runs_scored
def advance_double(runners):
runs_scored = 0
# 1,2,3 - 2,3 2 runs
if runners[1] == True and runners[2] == True and runners[3] == True:
runners[1] = False; runners[2] = True; runners[3] = True; runs_scored = 2
# 1,2 - 2,3 1 run
elif runners[1] == True and runners[2] == True and runners[3] == False:
runners[1] = False; runners[2] = True; runners[3] = True; runs_scored = 1
# 2,3 - 2 2 runs
elif runners[1] == False and runners[2] == True and runners[3] == True:
runners[1] = False; runners[2] = True; runners[3] = False; runs_scored = 2
# 1,3 - 2,3 1 run
elif runners[1] == True and runners[2] == False and runners[3] == True:
runners[1] = False; runners[2] = True; runners[3] = True; runs_scored = 1
# 1 - 1,3 no run
elif runners[1] == True and runners[2] == False and runners[3] == False:
runners[1] = True; runners[2] = False; runners[3] = True; runs_scored = 0
# 2 - 2 1 run
elif runners[1] == False and runners[2] == True and runners[3] == False:
runners[1] = False; runners[2] = True; runners[3] = False; runs_scored = 1
# 3 - 2 1 run
elif runners[1] == False and runners[2] == False and runners[3] == True:
runners[1] = False; runners[2] = True; runners[3] = False; runs_scored = 1
# X - 2 no run
elif runners[1] == False and runners[2] == False and runners[3] == False:
runners[1] = False; runners[2] = True; runners[3] = False; runs_scored = 0
return runs_scored
def advance_triple(runners):
# any - 3
runs_scored = 0
# 1,2,3 - 3 runs
if runners[1] == True and runners[2] == True and runners[3] == True:
runs_scored = 3
# 1,2 2,3 1,3 - 2 runs
elif runners[1] == True and runners[2] == True and runners[3] == False \
or runners[1] == False and runners[2] == True and runners[3] == True \
or runners[1] == True and runners[2] == False and runners[3] == True:
runs_scored = 2
# 1 2 3 - 1 run
elif runners[1] == True and runners[2] == False and runners[3] == False \
or runners[1] == False and runners[2] == True and runners[3] == False \
or runners[1] == False and runners[2] == False and runners[3] == True:
runs_scored = 1
elif runners[1] == False and runners[2] == False and runners[3] == False:
runs_scored = 0
runners[1] = False; runners[2] = False; runners[3] = True
return runs_scored
def advance_homerun(runners):
# any - x
runs_scored = 0
# 1,2,3 - 4 runs
if runners[1] == True and runners[2] == True and runners[3] == True:
runs_scored = 4
# 1,2 2,3 1,3 - 3 runs
elif runners[1] == True and runners[2] == True and runners[3] == False \
or runners[1] == False and runners[2] == True and runners[3] == True \
or runners[1] == True and runners[2] == False and runners[3] == True:
runs_scored = 3
# 1 2 3 - 2 runs
elif runners[1] == True and runners[2] == False and runners[3] == False \
or runners[1] == False and runners[2] == True and runners[3] == False \
or runners[1] == False and runners[2] == False and runners[3] == True:
runs_scored = 2
elif runners[1] == False and runners[2] == False and runners[3] == False:
runs_scored = 1
runners[1] = False; runners[2] = False; runners[3] = False
return runs_scored
def advance_walk(runners):
runs_scored = 0
# 1,2,3 - 1,2,3 run
if runners[1] == True and runners[2] == True and runners[3] == True:
runners[1] = True; runners[2] = True; runners[3] = True; runs_scored = 1
# 1,2 - 1,2,3 no run
elif runners[1] == True and runners[2] == True and runners[3] == False:
runners[1] = True; runners[2] = True; runners[3] = True; runs_scored = 0
# 2,3 - 1,2,3 no run
elif runners[1] == False and runners[2] == True and runners[3] == True:
runners[1] = True; runners[2] = True; runners[3] = True; runs_scored = 0
# 1,3 - 1,2,3 run
elif runners[1] == True and runners[2] == False and runners[3] == True:
runners[1] = True; runners[2] = True; runners[3] = True; runs_scored = 0
# 1 - 1,2 no run
elif runners[1] == True and runners[2] == False and runners[3] == False:
runners[1] = True; runners[2] = True; runners[3] = False; runs_scored = 0
# 2 - 1,2 no run
elif runners[1] == False and runners[2] == True and runners[3] == False:
runners[1] = True; runners[2] = True; runners[3] = False; runs_scored = 0
# 3 - 1,3 no run
elif runners[1] == False and runners[2] == False and runners[3] == True:
runners[1] = True; runners[2] = False; runners[3] = True; runs_scored = 0
# X - 1 no run
elif runners[1] == False and runners[2] == False and runners[3] == False:
runners[1] = True; runners[2] = False; runners[3] = False; runs_scored = 0
return runs_scored
def advance_error(runners):
return advance_single(runners)
def possible_dp(runners):
# double play on 1,2,3 1,2 1
num_outs = 0
if runners[1] == True and runners[2] == True and runners[3] == True:
runners[1] = True; runners[2] = True; runners[3] = False; num_outs = 2
elif runners[1] == True and runners[2] == True and runners[3] == False:
runners[1] = True; runners[2] = False; runners[3] = False; num_outs = 2
elif runners[1] == True and runners[2] == False and runners[3] == False:
runners[1] = False; runners[2] = False; runners[3] = False; num_outs = 2
else:
num_outs = 1 # no change to runners
return num_outs
def show_bases(runners):
if runners[2] == True:
print(" X ")
else:
print(" _ ")
if runners[1] == True and runners[3] == True:
print("X X")
elif runners[3] == True and runners[1] == False:
print("X _")
elif runners[3] == False and runners[1] == True:
print("_ X")
elif runners[3] == False and runners[1] == False:
print("_ _")
print(" X ")
print("")
def play_inning(inning, tb="top"):
print("=============================")
print(tb + " of inning " + str(inning))
outs = 0
runs = 0
hits = 0
errors = 0
runners = [True, False, False, False] # [home, 1st, 2nd, 3rd]
while outs != 3:
# print("runners: " + str(runners))
print("---------------------------")
show_bases(runners)
d1 = R.randint(1,6) # smaller
d2 = R.randint(1,6) # larger
if d1 > d2:
tmp = d1; d1 = d2; d2 = tmp
print("roll: " + str(d1) + " " + str(d2))
if (d1,d2) == (1,3) or \
(d1,d2) == (2,2) or \
(d1,d2) == (3,3) or \
(d1,d2) == (5,6):
print("single")
hits += 1
runs_scored = advance_single(runners)
if runs_scored == 1:
print("run scores!")
runs += 1
elif (d1,d2) == (1,2):
print("double")
hits += 1
runs_scored = advance_double(runners)
if runs_scored != 0:
print(str(runs_scored) + " run(s) score!")
runs += runs_scored
elif (d1,d2) == (6,6):
print("triple")
hits += 1
runs_scored = advance_triple(runners)
if runs_scored != 0:
print(str(runs_scored) + " run(s) score!")
runs += runs_scored
elif (d1,d2) == (1,1):
print("home run!")
hits += 1
runs_scored = advance_homerun(runners)
if runs_scored != 0:
print(str(runs_scored) + " run(s) score!")
runs += runs_scored
if runs_scored == 4:
print("GRAND SLAM!!")
elif (d1,d2) == (4,4):
print("walk")
runs_scored = advance_walk(runners)
if runs_scored != 0:
print(str(runs_scored) + " a run scores!")
runs += 1
elif (d1,d2) == (5,5):
print("base on error")
runs_scored = advance_error(runners)
if runs_scored > 0:
print(str(runs_scored) + " run(s) score!")
runs += 1
elif (d1,d2) == (1,4) or (d1,d2) == (2,3):
print("pop out")
outs += 1
print(str(outs) + " outs")
elif (d1,d2) == (3,6) or (d1,d2) == (4,5) or \
(d1,d2) == (4,6):
print("fly out")
outs += 1
print(str(outs) + " outs")
elif (d1,d2) == (2,4) or (d1,d2) == (2,6) or \
(d1,d2) == (3,5):
print("ground out")
outs += 1
print(str(outs) + " outs")
elif (d1,d2) == (1,5):
print("sharp grounder")
tmp = possible_dp(runners)
if (outs == 0 or outs == 1) and tmp == 2:
print("* double play *")
outs += 2
else:
outs += 1
print(str(outs) + " outs")
elif (d1,d2) == (1,6) or (d1,d2) == (2,5) or \
(d1,d2) == (3,4):
print("strikeout")
outs += 1
print(str(outs) + " outs")
else:
print("Unhandled roll")
input()
print("---------------------------")
# print("runners: " + str(runners))
# show_bases(runners)
if outs == 3:
print("\nEnd " + tb + " of inning " + str(inning))
print("runs = " + str(runs) + " hits = " + \
str(hits) + " errors = " + str(errors))
input()
print("=============================")
return (runs, hits, errors)
def play_game():
visitor_r_h_e = [0, 0, 0]
home_r_h_e = [0, 0, 0]
for i in range(1,10): # 1 to 9
rhe = play_inning(i, "top")
visitor_r_h_e[0] += rhe[0]
visitor_r_h_e[1] += rhe[1]
home_r_h_e[2] += rhe[2]
# could check to see if home team is ahead
# after top of 9th inning -- if so, bottom
# of the 9th is not played.
rhe = play_inning(i, "bottom")
home_r_h_e[0] += rhe[0]
home_r_h_e[1] += rhe[1]
visitor_r_h_e[2] += rhe[2]
print("\n**************************\n")
print(" Runs Hits Errors")
print("Home: " + str(visitor_r_h_e))
print("Visitor: " + str(home_r_h_e))
print("\n**************************\n")
def main():
R.seed(1)
play_game()
if __name__ == "__main__":
main()
```