# Histogram_Dots_1.1.py

Oct 11th, 2014
204
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
1. # Random dots, with Histogram 1.1
2. # By CA7746
3. # Based on Random Dots 1.0 by Jason Thibeault
4. #     http://freethoughtblogs.com/lousycanuck/2014/10/09/busy-busy-worker-bee/
5.
6. # Usage
7. #
8. # A wave of random dots appears. Then they fall. As they do, the average
9. # X position among dots in that wave is added to a bar chart, a histogram,
10. # showing how often waves with each average have appeared. As more waves are
11. # independently measured, the frequency of their averages evens out into a
12. # bell curve: a demo of the Central Limit Theorem.
13. #
14. # The chart gets re-scaled to fit the tallest bar on the screen.
15. #
16. # Mouse click - Spawn a new wave of dots.
17. #               You can accumulate them with gravity off, or make it rain.
18. #               Click Click Click Click...
19. #
20. # Caps lock - Toggle gravity for current and future waves.
21. #
22. # Shift - Tap to make only the current dots fall.
23. #
24. # Space - Reset everything and add a fresh wave of dots.
25. #
26. # Escape - Exit
27.
28. # Requires
29. #
30. # PyGame - http://www.pygame.org
31.
32.
33. # Settable variables
34. dots_per_wave = 5         # Small waves start off more interesting
35. gravity = 0.5             # Continually added to falling dots' velocity
36. histogram_divisions = 20
37.
38.
39. # Imports
40. import pygame
41. from pygame.locals import *
42. import random
43. import time
44.
45. # Defines
46. bg = None
47. clock = None
48.
49.
50. def init_game():
51.     global bg, clock
52.     pygame.init()
53.
54.     display_info = pygame.display.Info()
55.     screen_width,screen_height = display_info.current_w, display_info.current_h
56.
57.     bg = pygame.display.set_mode((screen_width,screen_height), FULLSCREEN, 32)
58.     clock = pygame.time.Clock()
59.
60.     pygame.mouse.set_visible(False)
61.
62.
63. def run_game():
64.     display_info = pygame.display.Info()
65.     screen_width,screen_height = display_info.current_w, display_info.current_h
66.
67.     bg_color1 = (255, 255, 255)
68.     label_color = (240, 100, 0)  # Ehh, a different color might be more readable
69.                                  # And/or draw a filled rect behind the text for contrast
70.                                  # Wasn't too awful when grayscaling a screenshot to check
71.
72.     # Create a cached bitmap to share among all dots
73.     dot_image = pygame.Surface((8, 8), pygame.SRCALPHA, 32)
74.     pygame.draw.circle(dot_image, (0, 0, 128), [4, 4], 4, 0)
75.
76.     myfont = pygame.font.SysFont("monospace", 30, bold=True)
77.     x_axis_label = myfont.render("Mean X position from each wave", True, label_color)
78.     y_axis_label = myfont.render("# of waves that had a given mean", True, label_color)
79.     y_axis_label = pygame.transform.rotate(y_axis_label, -90)
80.
81.     waves = []
82.
83.     histogram = Histogram(pygame.Rect(0, 0, screen_width, screen_height), histogram_divisions)
84.
85.     # Loop forever
86.     running = True
87.     while (running):
88.         pygame.event.pump()
89.
90.         # Look for quit and mouseclick
91.         for e in pygame.event.get():
92.             if (e.type == pygame.QUIT):
93.                 running = False
94.                 break
95.             elif (e.type == pygame.KEYDOWN):
96.                 if (e.key == pygame.K_ESCAPE):
97.                     running = False
98.                     break
99.
100.                 elif (e.key == pygame.K_SPACE):
101.                     # Clear all existing dots and add a fresh wave
102.                     waves[:] = []
103.                     histogram.reset()
104.                     dots = generate_dots(dot_image, dots_per_wave, screen_width, screen_height)
105.
106.                     if (pygame.key.get_mods() & pygame.KMOD_CAPS):
107.                         for dot in dots:
108.                             if (dot.state is Dot.STATE_HOVER):
109.                                 dot.state = Dot.STATE_FALL
110.
111.                     waves.append(Wave(dots))
112.
113.                 elif (e.key in (pygame.K_CAPSLOCK, pygame.K_LSHIFT, pygame.K_RSHIFT)):
114.                     # Drop any existing dots that are hovering
115.                     if (pygame.key.get_mods() & pygame.KMOD_CAPS or pygame.key.get_mods() & pygame.KMOD_SHIFT):
116.                         for wave in waves:
117.                             for dot in wave.dots:
118.                                 if (dot.state is Dot.STATE_HOVER):
119.                                     dot.state = Dot.STATE_FALL
120.
121.             elif (e.type == pygame.MOUSEBUTTONDOWN):
122.                 # Add another wave of dots
123.                 new_dots = generate_dots(dot_image, dots_per_wave, screen_width, screen_height)
124.
125.                 if (pygame.key.get_mods() & pygame.KMOD_CAPS):
126.                     # Gravity's on; drop the new dots
127.                     for dot in new_dots:
128.                         dot.state = Dot.STATE_FALL
129.
130.                 waves.append(Wave(new_dots))
131.
132.
133.         # Update entities in the world
134.         for wave in waves:
135.             for dot in wave.dots:
136.                 if (dot.state is Dot.STATE_FALL):
137.                     dot.velocity_y += gravity   # Accelerate gradually
138.                     dot.y += dot.velocity_y
139.
140.                     # The line above always moves a fixed amount regardless of how
141.                     # long ago the loop last executed.
142.
143.                     # Tips online suggest tracking "time since the last world update"
144.                     # separately from "time since a frame was drawn depicting the world",
145.                     # so low FPS rendering wouldn't lag the game itself, and vice versa.
146.                     # Overkill here. Custom Clock classes exist on the net for that, but
147.                     # not stock in pygame. :/
148.
149.                     if (dot.y+dot.height > screen_height):
150.                         dot.velocity_y = 0
151.                         dot.y = screen_height - dot.height
152.                         dot.state = Dot.STATE_GROUNDED
153.
154.                         # Newly grounded dot; bump the histogram?
155.                         if (wave.grounded()):
156.                             wave_mean_x = wave.get_mean_x()
158.
159.         # Prune the grounded dots to reduce clutter
160.         for wave in waves:
161.             if (wave.grounded()):
162.                 wave.remove_dots()
163.
164.         # Paint the screen
165.         # Fill background
166.         bg.fill(bg_color1, (0, 0, screen_width, screen_height))
167.
168.         # Draw histogram
169.         histogram.draw(bg)
170.
171.         # Draw dots
172.         for wave in waves:
173.             for dot in wave.dots:
174.                 dot.draw(bg)
175.
176.         # Draw labels
177.         bg.blit(x_axis_label, (screen_width/2 - x_axis_label.get_width()/2, screen_height - x_axis_label.get_height()*2))
178.         bg.blit(y_axis_label, (y_axis_label.get_width()*2, screen_height/2 - y_axis_label.get_height()/2))
179.
180.         pygame.display.update()
181.
182.     clock.tick(40)  # Limit FPS.
183.
184.
185. def generate_dots(dot_image, num, range_x, range_y):
186.     dot_list = [None]*num
187.
188.     for i in range(0, num):
189.         dot = Dot(dot_image)
190.         dot.x,dot.y = random_coords(range_x, range_y)
191.         dot_list[i] = dot
192.
193.     return dot_list
194.
195.
196. def random_coords(range_x, range_y):
197.     x,y = random.randint(1,range_x), random.randint(1,range_y)
198.     return [x,y]
199.
200.
201. def terminate_game():
202.     pygame.mouse.set_visible(True)
203.     pygame.quit()
204.
205.
206. def main():
207.     init_game()
208.     run_game()
209.     terminate_game()
210.
211.
212.
213. class Dot(object):
214.     STATE_HOVER = "Hover"
215.     STATE_FALL = "Fall"
216.     STATE_GROUNDED = "Grounded"
217.
218.     def __init__(self, image):
219.         self._image = image
220.         self.width, self.height = image.get_width(), image.get_height()
221.         self.x, self.y = 0, 0
222.         self.velocity_x, self.velocity_y = 0, 0
223.         self.state = Dot.STATE_HOVER
224.
225.     def draw(self, surface):
226.         surface.blit(self._image, (self.x, self.y))
227.
228.
229.
230. class Wave(object):
231.     def __init__(self, existing_dots=None):
232.         if (existing_dots is not None):
233.             self.dots = existing_dots
234.         else:
235.             self.dots = []
236.         self._cached_mean_x = None
237.
239.         self.dots.append(dot)
240.
242.         self.dots.extend(new_dots)
243.
244.     def grounded(self):
245.         """Tests whether all dots in this wave are grounded."""
246.         if (len(self.dots) == 0):
247.             return False
248.
249.         for dot in self.dots:
250.             if (dot.state is not Dot.STATE_GROUNDED):
251.                 return False
252.         return True
253.
254.     def get_mean_x(self, recalculate=False):
255.         """Get the average x position among dots in this wave."""
256.         if (self._cached_mean_x is None or recalculate):
257.             values = [dot.x for dot in self.dots]
258.             self._cached_mean_x = float(sum(values)) / len(values) if (len(values) > 0) else float('nan')
259.         return self._cached_mean_x
260.
261.     def remove_dots(self):
262.         """Removes all dot objects from this wave, while remembering the mean."""
263.         self.get_mean_x()
264.         self.dots[:] = []
265.
266.
267.
268. class Histogram(object):
269.     def __init__(self, screen_rect, divisions):
270.         self._screen_rect = screen_rect
271.         self.divisions = divisions
272.         self.bar_color = (200, 200, 230)
273.
274.         self.cols = [None]*self.divisions
275.         col_width = self._screen_rect.width / self.divisions
276.         for n in range(0, self.divisions):
277.             region = pygame.Rect(n*col_width, 0, col_width, self._screen_rect.height)
278.             bar = pygame.Rect(n*col_width, self._screen_rect.height, col_width, 0)
279.             self.cols[n] = {"region":region, "count":0, "bar":bar}
280.
281.     def reset(self):
282.         for col in self.cols:
283.             col["count"] = 0
284.             col["bar"].y = self._screen_rect.height
285.             col["bar"].height = 0
286.
288.         for col in self.cols:
289.             if (col["region"].collidepoint(wave_mean_x, 0)):
290.                 col["count"] += 1
291.                 break
292.
293.         # Re-scale visible bars to fit the screen
294.         highest_count = max(self.cols, key=lambda x: x["count"])["count"]
295.         unit = float(self._screen_rect.height) / highest_count
296.         for col in self.cols:
297.             col["bar"].y = int(self._screen_rect.height - unit*col["count"])
298.             col["bar"].height = int(self._screen_rect.height - col["bar"].y)
299.
300.     def draw(self, surface):
301.         for col in self.cols:
302.             pygame.draw.rect(surface, self.bar_color, col["bar"], 0)
303.
304.
305.
306. main()
RAW Paste Data