Tabletop Dice Baseball

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()
This entry was posted in Miscellaneous. Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s