Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Random dots, with Histogram 1.1
- # By CA7746
- # Based on Random Dots 1.0 by Jason Thibeault
- # http://freethoughtblogs.com/lousycanuck/2014/10/09/busy-busy-worker-bee/
- # Usage
- #
- # A wave of random dots appears. Then they fall. As they do, the average
- # X position among dots in that wave is added to a bar chart, a histogram,
- # showing how often waves with each average have appeared. As more waves are
- # independently measured, the frequency of their averages evens out into a
- # bell curve: a demo of the Central Limit Theorem.
- #
- # The chart gets re-scaled to fit the tallest bar on the screen.
- #
- # Mouse click - Spawn a new wave of dots.
- # You can accumulate them with gravity off, or make it rain.
- # Click Click Click Click...
- #
- # Caps lock - Toggle gravity for current and future waves.
- #
- # Shift - Tap to make only the current dots fall.
- #
- # Space - Reset everything and add a fresh wave of dots.
- #
- # Escape - Exit
- # Requires
- #
- # PyGame - http://www.pygame.org
- # Settable variables
- dots_per_wave = 5 # Small waves start off more interesting
- gravity = 0.5 # Continually added to falling dots' velocity
- histogram_divisions = 20
- # Imports
- import pygame
- from pygame.locals import *
- import random
- import time
- # Defines
- bg = None
- clock = None
- def init_game():
- global bg, clock
- pygame.init()
- display_info = pygame.display.Info()
- screen_width,screen_height = display_info.current_w, display_info.current_h
- bg = pygame.display.set_mode((screen_width,screen_height), FULLSCREEN, 32)
- clock = pygame.time.Clock()
- pygame.mouse.set_visible(False)
- def run_game():
- display_info = pygame.display.Info()
- screen_width,screen_height = display_info.current_w, display_info.current_h
- bg_color1 = (255, 255, 255)
- label_color = (240, 100, 0) # Ehh, a different color might be more readable
- # And/or draw a filled rect behind the text for contrast
- # Wasn't too awful when grayscaling a screenshot to check
- # Create a cached bitmap to share among all dots
- dot_image = pygame.Surface((8, 8), pygame.SRCALPHA, 32)
- pygame.draw.circle(dot_image, (0, 0, 128), [4, 4], 4, 0)
- myfont = pygame.font.SysFont("monospace", 30, bold=True)
- x_axis_label = myfont.render("Mean X position from each wave", True, label_color)
- y_axis_label = myfont.render("# of waves that had a given mean", True, label_color)
- y_axis_label = pygame.transform.rotate(y_axis_label, -90)
- waves = []
- histogram = Histogram(pygame.Rect(0, 0, screen_width, screen_height), histogram_divisions)
- # Loop forever
- running = True
- while (running):
- pygame.event.pump()
- # Look for quit and mouseclick
- for e in pygame.event.get():
- if (e.type == pygame.QUIT):
- running = False
- break
- elif (e.type == pygame.KEYDOWN):
- if (e.key == pygame.K_ESCAPE):
- running = False
- break
- elif (e.key == pygame.K_SPACE):
- # Clear all existing dots and add a fresh wave
- waves[:] = []
- histogram.reset()
- dots = generate_dots(dot_image, dots_per_wave, screen_width, screen_height)
- if (pygame.key.get_mods() & pygame.KMOD_CAPS):
- for dot in dots:
- if (dot.state is Dot.STATE_HOVER):
- dot.state = Dot.STATE_FALL
- waves.append(Wave(dots))
- elif (e.key in (pygame.K_CAPSLOCK, pygame.K_LSHIFT, pygame.K_RSHIFT)):
- # Drop any existing dots that are hovering
- if (pygame.key.get_mods() & pygame.KMOD_CAPS or pygame.key.get_mods() & pygame.KMOD_SHIFT):
- for wave in waves:
- for dot in wave.dots:
- if (dot.state is Dot.STATE_HOVER):
- dot.state = Dot.STATE_FALL
- elif (e.type == pygame.MOUSEBUTTONDOWN):
- # Add another wave of dots
- new_dots = generate_dots(dot_image, dots_per_wave, screen_width, screen_height)
- if (pygame.key.get_mods() & pygame.KMOD_CAPS):
- # Gravity's on; drop the new dots
- for dot in new_dots:
- dot.state = Dot.STATE_FALL
- waves.append(Wave(new_dots))
- # Update entities in the world
- for wave in waves:
- for dot in wave.dots:
- if (dot.state is Dot.STATE_FALL):
- dot.velocity_y += gravity # Accelerate gradually
- dot.y += dot.velocity_y
- # The line above always moves a fixed amount regardless of how
- # long ago the loop last executed.
- # Tips online suggest tracking "time since the last world update"
- # separately from "time since a frame was drawn depicting the world",
- # so low FPS rendering wouldn't lag the game itself, and vice versa.
- # Overkill here. Custom Clock classes exist on the net for that, but
- # not stock in pygame. :/
- if (dot.y+dot.height > screen_height):
- dot.velocity_y = 0
- dot.y = screen_height - dot.height
- dot.state = Dot.STATE_GROUNDED
- # Newly grounded dot; bump the histogram?
- if (wave.grounded()):
- wave_mean_x = wave.get_mean_x()
- histogram.add_wave_mean(wave_mean_x)
- # Prune the grounded dots to reduce clutter
- for wave in waves:
- if (wave.grounded()):
- wave.remove_dots()
- # Paint the screen
- # Fill background
- bg.fill(bg_color1, (0, 0, screen_width, screen_height))
- # Draw histogram
- histogram.draw(bg)
- # Draw dots
- for wave in waves:
- for dot in wave.dots:
- dot.draw(bg)
- # Draw labels
- bg.blit(x_axis_label, (screen_width/2 - x_axis_label.get_width()/2, screen_height - x_axis_label.get_height()*2))
- bg.blit(y_axis_label, (y_axis_label.get_width()*2, screen_height/2 - y_axis_label.get_height()/2))
- pygame.display.update()
- clock.tick(40) # Limit FPS.
- def generate_dots(dot_image, num, range_x, range_y):
- dot_list = [None]*num
- for i in range(0, num):
- dot = Dot(dot_image)
- dot.x,dot.y = random_coords(range_x, range_y)
- dot_list[i] = dot
- return dot_list
- def random_coords(range_x, range_y):
- x,y = random.randint(1,range_x), random.randint(1,range_y)
- return [x,y]
- def terminate_game():
- pygame.mouse.set_visible(True)
- pygame.quit()
- def main():
- init_game()
- run_game()
- terminate_game()
- class Dot(object):
- STATE_HOVER = "Hover"
- STATE_FALL = "Fall"
- STATE_GROUNDED = "Grounded"
- def __init__(self, image):
- self._image = image
- self.width, self.height = image.get_width(), image.get_height()
- self.x, self.y = 0, 0
- self.velocity_x, self.velocity_y = 0, 0
- self.state = Dot.STATE_HOVER
- def draw(self, surface):
- surface.blit(self._image, (self.x, self.y))
- class Wave(object):
- def __init__(self, existing_dots=None):
- if (existing_dots is not None):
- self.dots = existing_dots
- else:
- self.dots = []
- self._cached_mean_x = None
- def add_dot(self, dot):
- self.dots.append(dot)
- def add_dots(self, new_dots):
- self.dots.extend(new_dots)
- def grounded(self):
- """Tests whether all dots in this wave are grounded."""
- if (len(self.dots) == 0):
- return False
- for dot in self.dots:
- if (dot.state is not Dot.STATE_GROUNDED):
- return False
- return True
- def get_mean_x(self, recalculate=False):
- """Get the average x position among dots in this wave."""
- if (self._cached_mean_x is None or recalculate):
- values = [dot.x for dot in self.dots]
- self._cached_mean_x = float(sum(values)) / len(values) if (len(values) > 0) else float('nan')
- return self._cached_mean_x
- def remove_dots(self):
- """Removes all dot objects from this wave, while remembering the mean."""
- self.get_mean_x()
- self.dots[:] = []
- class Histogram(object):
- def __init__(self, screen_rect, divisions):
- self._screen_rect = screen_rect
- self.divisions = divisions
- self.bar_color = (200, 200, 230)
- self.cols = [None]*self.divisions
- col_width = self._screen_rect.width / self.divisions
- for n in range(0, self.divisions):
- region = pygame.Rect(n*col_width, 0, col_width, self._screen_rect.height)
- bar = pygame.Rect(n*col_width, self._screen_rect.height, col_width, 0)
- self.cols[n] = {"region":region, "count":0, "bar":bar}
- def reset(self):
- for col in self.cols:
- col["count"] = 0
- col["bar"].y = self._screen_rect.height
- col["bar"].height = 0
- def add_wave_mean(self, wave_mean_x):
- for col in self.cols:
- if (col["region"].collidepoint(wave_mean_x, 0)):
- col["count"] += 1
- break
- # Re-scale visible bars to fit the screen
- highest_count = max(self.cols, key=lambda x: x["count"])["count"]
- unit = float(self._screen_rect.height) / highest_count
- for col in self.cols:
- col["bar"].y = int(self._screen_rect.height - unit*col["count"])
- col["bar"].height = int(self._screen_rect.height - col["bar"].y)
- def draw(self, surface):
- for col in self.cols:
- pygame.draw.rect(surface, self.bar_color, col["bar"], 0)
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement