Advertisement
CA7746

Histogram_Dots_1.1.py

Oct 11th, 2014
237
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.16 KB | None | 0 0
  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()
  157.                             histogram.add_wave_mean(wave_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.  
  238.     def add_dot(self, dot):
  239.         self.dots.append(dot)
  240.  
  241.     def add_dots(self, new_dots):
  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.  
  287.     def add_wave_mean(self, wave_mean_x):
  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()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement