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