Dice Math
This math quiz program rolls two to six dice whose sides you must add up as quickly as possible. But this program operates as more than just automated flash cards; it draws the faces of the dice to random places on the screen. The ASCII-art aspect adds a fun twist while you practice arithmetic.
dice_math.py
1
2"""Dice Math, by Al Sweigart al@inventwithpython.com
3A flash card addition game where you sum the total on random dice rolls.
4View this code at https://nostarch.com/big-book-small-python-projects
5Tags: large, artistic, game, math"""
6
7import random, time
8
9# Set up the constants:
10DICE_WIDTH = 9
11DICE_HEIGHT = 5
12CANVAS_WIDTH = 79
13CANVAS_HEIGHT = 24 - 3 # -3 for room to enter the sum at the bottom.
14
15# The duration is in seconds:
16QUIZ_DURATION = 30 # (!) Try changing this to 10 or 60.
17MIN_DICE = 2 # (!) Try changing this to 1 or 5.
18MAX_DICE = 6 # (!) Try changing this to 14.
19
20# (!) Try changing these to different numbers:
21REWARD = 4 # (!) Points awarded for correct answers.
22PENALTY = 1 # (!) Points removed for incorrect answers.
23# (!) Try setting PENALTY to a negative number to give points for
24# wrong answers!
25
26# The program hangs if all of the dice can't fit on the screen:
27assert MAX_DICE <= 14
28
29D1 = (['+-------+',
30 '| |',
31 '| O |',
32 '| |',
33 '+-------+'], 1)
34
35D2a = (['+-------+',
36 '| O |',
37 '| |',
38 '| O |',
39 '+-------+'], 2)
40
41D2b = (['+-------+',
42 '| O |',
43 '| |',
44 '| O |',
45 '+-------+'], 2)
46
47D3a = (['+-------+',
48 '| O |',
49 '| O |',
50 '| O |',
51 '+-------+'], 3)
52
53D3b = (['+-------+',
54 '| O |',
55 '| O |',
56 '| O |',
57 '+-------+'], 3)
58
59D4 = (['+-------+',
60 '| O O |',
61 '| |',
62 '| O O |',
63 '+-------+'], 4)
64
65D5 = (['+-------+',
66 '| O O |',
67 '| O |',
68 '| O O |',
69 '+-------+'], 5)
70
71D6a = (['+-------+',
72 '| O O |',
73 '| O O |',
74 '| O O |',
75 '+-------+'], 6)
76
77D6b = (['+-------+',
78 '| O O O |',
79 '| |',
80 '| O O O |',
81 '+-------+'], 6)
82
83ALL_DICE = [D1, D2a, D2b, D3a, D3b, D4, D5, D6a, D6b]
84
85print('''Dice Math, by Al Sweigart al@inventwithpython.com
86
87Add up the sides of all the dice displayed on the screen. You have
88{} seconds to answer as many as possible. You get {} points for each
89correct answer and lose {} point for each incorrect answer.
90'''.format(QUIZ_DURATION, REWARD, PENALTY))
91input('Press Enter to begin...')
92
93# Keep track of how many answers were correct and incorrect:
94correctAnswers = 0
95incorrectAnswers = 0
96startTime = time.time()
97while time.time() < startTime + QUIZ_DURATION: # Main game loop.
98 # Come up with the dice to display:
99 sumAnswer = 0
100 diceFaces = []
101 for i in range(random.randint(MIN_DICE, MAX_DICE)):
102 die = random.choice(ALL_DICE)
103 # die[0] contains the list of strings of the die face:
104 diceFaces.append(die[0])
105 # die[1] contains the integer number of pips on the face:
106 sumAnswer += die[1]
107
108 # Contains (x, y) tuples of the top-left corner of each die.
109 topLeftDiceCorners = []
110
111 # Figure out where dice should go:
112 for i in range(len(diceFaces)):
113 while True:
114 # Find a random place on the canvas to put the die:
115 left = random.randint(0, CANVAS_WIDTH - 1 - DICE_WIDTH)
116 top = random.randint(0, CANVAS_HEIGHT - 1 - DICE_HEIGHT)
117
118 # Get the x, y coordinates for all four corners:
119 # left
120 # v
121 #top > +-------+ ^
122 # | O | |
123 # | O | DICE_HEIGHT (5)
124 # | O | |
125 # +-------+ v
126 # <------->
127 # DICE_WIDTH (9)
128 topLeftX = left
129 topLeftY = top
130 topRightX = left + DICE_WIDTH
131 topRightY = top
132 bottomLeftX = left
133 bottomLeftY = top + DICE_HEIGHT
134 bottomRightX = left + DICE_WIDTH
135 bottomRightY = top + DICE_HEIGHT
136
137 # Check if this die overlaps with previous dice.
138 overlaps = False
139 for prevDieLeft, prevDieTop in topLeftDiceCorners:
140 prevDieRight = prevDieLeft + DICE_WIDTH
141 prevDieBottom = prevDieTop + DICE_HEIGHT
142 # Check each corner of this die to see if it is inside
143 # of the area the previous die:
144 for cornerX, cornerY in ((topLeftX, topLeftY),
145 (topRightX, topRightY),
146 (bottomLeftX, bottomLeftY),
147 (bottomRightX, bottomRightY)):
148 if (prevDieLeft <= cornerX < prevDieRight
149 and prevDieTop <= cornerY < prevDieBottom):
150 overlaps = True
151 if not overlaps:
152 # It doesn't overlap, so we can put it here:
153 topLeftDiceCorners.append((left, top))
154 break
155
156 # Draw the dice on the canvas:
157
158 # Keys are (x, y) tuples of ints, values the character at that
159 # position on the canvas:
160 canvas = {}
161 # Loop over each die:
162 for i, (dieLeft, dieTop) in enumerate(topLeftDiceCorners):
163 # Loop over each character in the die's face:
164 dieFace = diceFaces[i]
165 for dx in range(DICE_WIDTH):
166 for dy in range(DICE_HEIGHT):
167 # Copy this character to the correct place on the canvas:
168 canvasX = dieLeft + dx
169 canvasY = dieTop + dy
170 # Note that in dieFace, a list of strings, the x and y
171 # are swapped:
172 canvas[(canvasX, canvasY)] = dieFace[dy][dx]
173
174 # Display the canvas on the screen:
175 for cy in range(CANVAS_HEIGHT):
176 for cx in range(CANVAS_WIDTH):
177 print(canvas.get((cx, cy), ' '), end='')
178 print() # Print a newline.
179
180 # Let the player enter their answer:
181 response = input('Enter the sum: ').strip()
182 if response.isdecimal() and int(response) == sumAnswer:
183 correctAnswers += 1
184 else:
185 print('Incorrect, the answer is', sumAnswer)
186 time.sleep(2)
187 incorrectAnswers += 1
188
189# Display the final score:
190score = (correctAnswers * REWARD) - (incorrectAnswers * PENALTY)
191print('Correct: ', correctAnswers)
192print('Incorrect:', incorrectAnswers)
193print('Score: ', score)