Commits

Arne Babenhauserheide  committed b4807d0 Merge

merge in performance improvements, but don’t change fungus_advection (ignore the changes).

  • Participants
  • Parent commits 6257e7a, 2ddda70

Comments (0)

Files changed (6)

File code_swarm.py

 RANDOM_MOVEMENT = 0.5
 
 #: Speed towards the partner. They move this part of their distance together each step. 
-SPEED_TOWARDS_PARTNER = 0.02
+SPEED_TOWARDS_PARTNER = 0.01
 
 # Speed of partner repulsion; multiplied with the INVERSE distance. 
 SPEED_AWAY_FROM_PARTNER = 10
 
 #: Repulsion between active files - they move this part of their INVERSE distance away from each other each step. 
-SPEED_OF_PARTNER_REPULSION = 2
+SPEED_OF_PARTNER_REPULSION = 8
 
 #: The square root of the ratio between the speed of movement for files and for coders (file speed / coder speed)
 SPEED_RATIO_AUTHOR_FILE_SQRT = 4.0
 SAFE_CIRCLE_FILL = 0.6
 
 #: The number of frames for which a file is being associated to an author after an edit.
-TIME_OF_PARTNERSHIP = 30
+TIME_OF_PARTNERSHIP = 60
 
 #: The time a partner is considered active
 TIME_OF_PARTNER_ACTIVITY = 10
 SIMULATE_MINOR_FILE_REPULSION_IF_CHEAP = True
 SIMULATE_MINOR_FILE_REPULSION_IF_CHEAP_THRESHOLD = 5
 
+#: Should all active authors repulse each other? The speed is given by SPEED_OF_PARTNER_REPULSION
+SIMULATE_MINOR_ACTIVE_AUTHOR_REPULSION = False
+#: Do we want repulsion if we have few partners? 
+SIMULATE_MINOR_ACTIVE_AUTHOR_REPULSION_IF_CHEAP = True
+SIMULATE_MINOR_ACTIVE_AUTHOR_REPULSION_IF_CHEAP_THRESHOLD = 5
+
 #: How much the opacity gets reduced when a file no longer active.
 INACTIVITY_OPACITY_REDUCTION = 100
 
 #: How much teh blobs should fade out per frame
-OPACITY_REDUCTION_PER_FRAME = 6
+OPACITY_REDUCTION_PER_FRAME = 3
 
 #### Imports ####
 
 from time import time
 # And ctime to show time as localtime
 from time import ctime
+# mktime to parse time strings into seconds since epoch
+from time import mktime
+# and scheduling from pyglet
+from pyglet import clock
+
+
+# for identi.ca swarms: urlopen
+from urllib2 import urlopen, HTTPError
+
+# for xml parsing
+from xml.dom import minidom
+
+# for maildirs
+from os import listdir
+from os.path import join, isfile
+# the general parser
+from email.parser import Parser
+# date handling
+from email.utils import parsedate, parseaddr, getaddresses
+
 
 #### An example scene ####
 
     """
     def __init__(self, track = []): 
 	#: Internal data with time to play. Source for self.get_data()
-	self._data = None # [(time_to_play, item, related_item)]
+	self._data = None # [(time_to_play, date, item, related_item)]
 	# for codeswarms: item = author, related_item = file. 
 	#: The time when the data property was last accessed. 
 	self.last_access_time = None
 	# Set the maximum time
 	self.max_time = time_to_play
 	
-	from xml.dom import minidom
 	xml = minidom.parse(path)
 	data = [] # [(date, author, filename)]
 	struct = xml.childNodes[0].childNodes
 	
 	# Store the data
 	self._data = data
-	
+
+    def read_hg_log(self, path_to_repo, time_to_play = 180):
+        """Read the log directly from a hg repo. Requires Mercurial."""
+        from mercurial import ui, hg
+        repo = hg.repository(ui.ui(), path_to_repo)
+        data = []
+        rev = 0
+        while repo[rev] != repo["tip"]:
+            if not rev%1000: print rev
+            ctx = repo[rev]
+            date = ctx.date()
+            date = date[0] + date[1]
+            # user without email host
+            user = ctx.user().split("@")[0]
+            for f in ctx.files():
+                data.append((date, user, f))
+            rev += 1
+
+        self._data = self.clean_data(data, time_to_play)
+        
 	
     def read_maildir(self, path, time_to_play = 180): 
 	"""Read the data from all mails in a maildir folder and turn it into a code_swarm.
 	@param time_to_play: Duration of the feed (seconds)
 	@type time_to_play: number
 	@return: None (saves data in self._data with time_to_play)
-	"""
-	from os import listdir
-	from os.path import join, isfile
-	# the general parser
-	from email.parser import Parser
-	# date handling
-	from email.utils import parsedate, parseaddr, getaddresses
-	from time import mktime
-	
+	"""	
 	# Set the maximum time
 	self.max_time = time_to_play
 	
 		# only the mail addresses
 		all_recipients = [addr for name, addr in getaddresses(tos + ccs + resent_tos + resent_ccs)]
 		
+                subject = mail.get("Subject")
+                
 		# turn the mail into a set of simply tuples and append these to the data
 		for to in all_recipients: 
 		    if EMAIL_WITHOUT_HOST: 
 	multiplier = time_to_play / (self.max_timestamp - first_date)
 	# Normalize all dates, so they begin at 0 and extend for "time" seconds. 
 	data = [((date - first_date)*multiplier, date, fro, to) for date, fro, to in data]
-	# and sort the data in place
-	data.sort()
+        # and sort the data in place
+        data.sort()
 	
 	# If we want to track only one, we split the communication - and if we want to be recursive, we also track all contacts. 
 	if self.track: 
 	"""@return: current changes which should be displayed at once."""
 	if self._data is None: 
 	    return self.random_data()
-	else: 
+	else:
 	    # If data was never accessed before, set the current time for last access. 
 	    if self.last_access_time is None: 
 		self.last_access_time = time()
 	    # increase the time up to which we want to show the data
 	    self.time_delta += time() - self.last_access_time
 	    # Update the last access time
-	    # get all datapoints up to the 
+	    self.last_access_time = time()
+
+	    # get all datapoints up to the time delta
 	    data = [(date, fro, to) for reltime, date, fro, to in self._data if reltime <= self.time_delta]
 	    # remove all these datapoints from the private data. 
 	    self._data = [(reltime, date, fro, to) for reltime, date, fro, to in self._data if reltime > self.time_delta]
-	    
-	    self.last_access_time = time()
+
 	    return data
 	    
 	    
 	return [(time(), author, f) for author, f in data]
     
     data = property(get_data)
-    
+
+
+from threading import Thread
+
+class Urlgrabber(Thread):
+    def __init__(self, results, url, scene = None):
+        """@param results: a list to which to append the url content.
+        @param url: the url to grab repeatedly."""
+        super(Urlgrabber, self).__init__()
+        self.results = results
+        self.url = url
+        self.keep_grabbing = True
+        self.scene = scene
+
+    def run(self):
+        """repeatedly grab the url and append the results to self.results."""
+        while self.keep_grabbing:
+            try: 
+                f = urlopen(self.url)
+                d = f.read()
+                f.close()
+                self.results.append(d)
+            except HTTPError: pass
+            if self.scene and self.scene.core.win.has_exit:
+                return
+            
+
+class IdenticaFeeder(object):
+    def __init__(self, url="http://identi.ca/api/statuses/public_timeline.xml", time_to_play = 60, scene = None):
+        self.url = url
+        self.last_access_time = time()
+        self.max_timestamp = time() + time_to_play
+        self.scene = scene
+        self.latest_known_status_date = 0
+        self.username_split_chars = "(){}[]<>\\/,. "
+        #: the delay between items to show a continuous flow in between the slow accesses. TODO: move the url access into a thread.
+        self.delay_between_items = 0
+        self.last_access_time = time()
+        self._data = []
+        self.downloads = []
+        self.grabber = Urlgrabber(results = self.downloads, url = self.url, scene = scene)
+        self.grabber.start()
+        
+    def get_data(self):
+        """@return [(date, user, message_url), ...]"""
+        # cleanly shut down the grabber
+        if time() >= self.max_timestamp or (self.scene and self.scene.core.win.has_exit):
+            try:
+                self.grabber.join()
+            except: sleep(0.1)
+            return []
+
+        if self._data:
+            # add a delay between items to get a continuous flow
+            if time() - self.last_access_time < self.delay_between_items:
+                return []
+            self.last_access_time = time()
+            d = self._data[0:1]
+            self._data = self._data[1:]
+            print d
+            return [(time(), d[0][1], d[0][2])]
+        # if we have no data left, build new data.
+
+        # adjust the timing for elements.
+        if not self.downloads:
+            return []
+        self.delay_between_items += time() - self.last_access_time
+        if self.downloads[1:]:
+            self.delay_between_items /= 2
+
+        #: the return data to build
+        data = []
+
+        # get the identica data
+        d = self.downloads[0]
+        self.downloads.remove(d)
+
+        # parse it as xml
+        xml = minidom.parseString(d)
+        struct = xml.childNodes[0].childNodes
+        # read all elements
+        while struct.length:
+            status = struct.pop()
+            try:
+                # time in seconds since epoch
+                date = mktime(parsedate(status.getElementsByTagName("created_at")[0].firstChild.wholeText))
+                # only include newer status messages
+                if date <= self.latest_known_status_date:
+                    continue
+                # text for receivers
+                text = status.getElementsByTagName("text").pop().firstChild.wholeText
+                status_id = status.getElementsByTagName("id").pop().firstChild.wholeText
+                user = status.getElementsByTagName("user").pop().getElementsByTagName("name").pop().firstChild.wholeText
+            except: # no text
+                continue
+            # check for receivers
+            receivers = text.split("@")[1:]
+            for re in receivers:
+                for c in self.username_split_chars:
+                    re = re.split(c)[0]
+                data.append((date, user, "@"+re))
+            data.append((date, user, text))
+
+        if data: 
+            self._data = data[1:]
+            self.latest_known_status_date = max([date for date, user, re in data])
+            return data[0:1]
+        else: return []
+
+    data = property(fget=get_data)
+
 
 class Swarmable(object): 
 	"""Any object which is part of the code_swarm.
 	
 	It has to be inherited together with something which offers movement, especially self.x and self.y!
 	"""
-	def __init__(self, name=None, *args, **kwds): 
+	def __init__(self, scene=None, name=None, *args, **kwds): 
 	    
 	    try: 
 		super(Swarmable, self).__init__(*args, **kwds)
 		# Lable doesn't carry on *args and **kwds :(
 		pyglet_label.__init__(self, *args, **kwds)
 		
+            #: the scene
+            self.scene = scene
 	    #: The name of the blob. Author name or filename
 	    self.name = name 
 	    if self.name is None: 
 		self.name = random()
-	    
+
+	    # be actively managed, till we disappear
+            if self.scene is not None:
+                self.scene.active[self.name] = self
+
+
 	    #: The blobs to close in towards: [[blob, steps]], remove if steps == 0. 
 	    self.partners = []
 	    
 		# First add minor repulsion between all partnered files to make them align in a nice ring, if we want it. 
 		if SIMULATE_MINOR_FILE_REPULSION: 
 		    self.minor_repulsion_master()
-		elif SIMULATE_MINOR_FILE_REPULSION_IF_CHEAP and len(self.partners) < SIMULATE_MINOR_FILE_REPULSION_IF_CHEAP_THRESHOLD: 
+		elif SIMULATE_MINOR_FILE_REPULSION_IF_CHEAP and not self.partners[SIMULATE_MINOR_FILE_REPULSION_IF_CHEAP_THRESHOLD:]: 
 		    self.minor_repulsion_master()
 		    
 		# Then pull the partner towards the safe distance (by doing this as second step, they are always closer to the safe_distance. 
 		super(Blob, self).__init__(image_path=image_path, *args, **kwds)
 		
 		# Start with very small scale
-		self.scale = 0.1
+		self.scale = 0.01
 	
 	def update(self): 
 		"""Update the Blobs data and position."""
 		    self.opacity -= OPACITY_REDUCTION_PER_FRAME
 		elif self.opacity > 0: 
 		    self.opacity = 0
+                    # remove the swarmable from the actively updated group, till it gets touched again. 
+                    try:
+                        del self.scene.active[self.name]
+                    except: pass
 	
 	def touched(self): 
 	    """React to being touched: Grow and become solid."""
 	    self.scale = sqrt(self.scale)
 	    # and appear solid
 	    self.opacity = 255
+            # and become active
+            self.scene.active[self.name] = self
 		
 
 class Text(Swarmable, pyglet_label): 
 	
 	opacity = property(fget=get_opacity, fset=set_opacity)
 
+        def repulse_active_authors(self):
+            """Move away a bit from all active authors, so they get more nicely spaced."""
+            if SIMULATE_MINOR_ACTIVE_AUTHOR_REPULSION or SIMULATE_MINOR_ACTIVE_AUTHOR_REPULSION_IF_CHEAP and not self.scene.active_authors.keys()[SIMULATE_MINOR_ACTIVE_AUTHOR_REPULSION_IF_CHEAP_THRESHOLD:]: 
+                for author in self.scene.active_authors.values():
+                    if not author is self:
+                        self.minor_repulsion(author)
+
 	def update(self): 
 		"""Update the Blobs data and position."""
 		self.get_partners_nearer()
+                self.repulse_active_authors()
 		# Also become a bit less solid. 
 		if self.opacity > OPACITY_REDUCTION_PER_FRAME: 
 		    self.opacity -= OPACITY_REDUCTION_PER_FRAME
 		elif self.opacity > 0: 
 		    self.opacity = 0
+                    # remove the swarmable from the actively updated group, till it gets touched again.
+                    try: 
+                        del self.scene.active[self.name]
+                        del self.scene.active_authors[self.name]
+                    except: pass
 	
 	def touched(self): 
 	    """React to being touched: Grow and become solid."""
-	    # and appear solid
+	    # appear solid
 	    self.opacity = 255
+            # and become active
+            self.scene.active[self.name] = self
+            self.scene.active_authors[self.name] = self
 	
 
 class Author(Text): 
 	self.path = None
 	
 	#: The default time in seconds we want a code_swarm to last
-	self.time = 180
+	self.time = 60
 	
 	#: Some pre-shutdown wait time. The number of frames the scene will still run. 
 	self.countdown = None
 	    args[0].remove("--maildir")
 	    args[0].remove(self.path)
 	
+	if "--identica" in args[0]: 
+	    self.swarm_type = "identica"
+	    args[0].remove("--identica")
+
+        if "--twitter" in args[0]: 
+	    self.swarm_type = "twitter"
+	    args[0].remove("--twitter")
+	
 	if "--activity" in args[0]: 
 	    self.swarm_type = "activity"
 	    self.path = args[0][args[0].index("--activity") + 1]
 	    args[0].remove("--activity")
 	    args[0].remove(self.path)
 	
+	if "--mercurial" in args[0]: 
+	    self.swarm_type = "mercurial"
+	    self.path = args[0][args[0].index("--mercurial") + 1]
+	    args[0].remove("--mercurial")
+	    args[0].remove(self.path)
+	
 	while "--track" in args[0]: 
 	    track = args[0][args[0].index("--track") + 1]
 	    args[0].remove("--track")
 	    self.feeder = Feeder(track=self.track)
 	    # add the actitivity file to the data
 	    self.feeder.read_codeswarm_activity(self.path, time_to_play = self.time)
+	elif self.swarm_type == "mercurial": 
+	    self.feeder = Feeder(track=self.track)
+	    # add the actitivity file to the data
+	    self.feeder.read_hg_log(self.path, time_to_play = self.time)
+        elif self.swarm_type == "identica":
+            identica = "http://identi.ca/api/statuses/public_timeline.xml"
+            self.feeder = IdenticaFeeder(url = identica, time_to_play = self.time, scene = self)
+        elif self.swarm_type == "twitter":
+            twitter = "http://api.twitter.com/statuses/public_timeline.xml"
+            self.feeder = IdenticaFeeder(url = twitter, time_to_play = self.time, scene = self)
 	else: 
 	    self.feeder = Feeder()
-	
+
         ## The blobs
-        #: All authors
-	self.blobs = []
+        #: All things: name: blob
+	self.blobs = {}
+        #: The active things: name: blob
+        self.active = {}
+        #: The active authors
+        self.active_authors = {}
 	
 	#: The batch for updating all files together. 
 	self.file_batch = self.core.batch()
 	self.reltime = self.core.load_text("rel time", font_size = 10.0, x=10, y=10)
 	self.visible.append(self.reltime)
 
+        # limit the FPS to 50 via pyglet internals
+        clock.set_fps_limit(50)
+ 
+
     def show_change(self, author_name, filepath): 
 	"""Show a change by an author to a file."""
 	# If we don't yet have the called ones, add them. 
 	# TODO: This is prone to race conditions! Use a lock!
-	
-	if not author_name in [blob.name for blob in self.blobs]: 
+
+	if not author_name in self.blobs: 
 	    x, y = self.get_starting_position()
-	    author = Author(name=author_name, x=x, y=y, batch=self.author_batch)
-	    self.blobs.append(author)
-	
+	    author = Author(scene=self, name=author_name, x=x, y=y)#, batch=self.author_batch)
+	    self.blobs[author_name] = author
+
+        # for maildirs we also add all recipients as names (filepath)
 	if self.swarm_type == "maildir": 
-	    if not filepath in [blob.name for blob in self.blobs]: 
+            if not filepath in self.blobs: 
 		x, y = self.get_starting_position()
-		author = Author(name=filepath, x=x, y=y, batch=self.author_batch)
-		self.blobs.append(author)
+		author = Author(scene=self, name=filepath, x=x, y=y)#, batch=self.author_batch)
+		self.blobs[filepath] = author
+        elif self.swarm_type == "identica" or self.swarm_type == "twitter":
+            if not filepath in self.blobs:
+		x, y = self.get_starting_position()
+                if filepath.startswith("@") and not " " in filepath: # heuristic to recognize a name
+                    author = Author(scene=self, name=filepath, x=x, y=y)#, batch=self.author_batch)
+                    author.color = (200, 100, 100, author.color[3])
+                    self.blobs[filepath] = author
+                else:
+                    f = File(scene=self, name=filepath, x=x, y=y)#, batch=self.file_batch)
+                    self.blobs[filepath] = f
+
+        # otherwise we add the files as blobs
 	else: 
-	    if not filepath in [blob.name for blob in self.blobs]: 
+	    if not filepath in self.blobs: 
 		x, y = self.get_starting_position()
-		f = File(name=filepath, x=x, y=y, batch=self.file_batch)
-		self.blobs.append(f)
+		f = File(scene=self, name=filepath, x=x, y=y)#, batch=self.file_batch)
+		self.blobs[filepath] = f
 	
 	# Now scale them up a bit. This throws a value error 
 	# if the blob isn't in the list, 
 	# we don't catch it, because now the Blob has to be in the list. 
 	# First the file
-	f = self.find_blob_by(filepath)
+	f = self.blobs[filepath]
 	# Increase the scale by 0.5 TODO: replace with logarithmic scale. 
 	f.touched()
 	# Then the author
-	author = self.find_blob_by(author_name)
+	author = self.blobs[author_name]
 	# Increase the scale by 0.5 TODO: replace with logarithmic scale. 
 	author.touched()
 	
 	
     def find_blob_by(self, name): 
 	"""Locate a blob by its name."""
-	blob_names = [blob.name for blob in self.blobs]
-	return self.blobs[blob_names.index(name)]
+	return self.blobs[name]
 	    
      
     def keep_on_screen(self, blob): 
 		return x, y
 		
 
+    def get_new_changes(self):
+        """Get and process new change data."""
+        try: 
+            data = self.feeder.data
+        except:
+            return
+	
+	for date, author_name, filename in data: 
+	    if author_name and filename: 
+		self.show_change(author_name, filename)
+	    self.time.text = ctime(date)
+	    # Shut down once we reach the maximum timestamp. 
+	    if date == self.feeder.max_timestamp: 
+		self.countdown = COUNTDOWN
+        
+
     def update(self): 
         """Update the stats of all scene objects. 
 
 To add a collider, add it to the self.colliding list. 
 To add an overlay sprite, add it to the self.overlay list. 
 """
-	### Just Testing
-	
 	if self.countdown is not None: 
 	    if self.countdown <= 0: 
 		self.core.win.has_exit = True
 	    else: 
 		self.countdown -= 1
-	
-	data = self.feeder.data
-	
-	for date, author_name, filename in data: 
-	    if author_name and filename: 
-		self.show_change(author_name, filename)
-	    self.time.text = ctime(date)
-	    # Shut down once we reach the maximum timestamp. 
-	    if date == self.feeder.max_timestamp: 
-		self.countdown = COUNTDOWN
+
+        self.get_new_changes()
 	
 	self.reltime.text = ctime(time())
 	
 	# Update each blob
-        [blob.update() for blob in self.blobs if blob.opacity > 0]
+        [blob.update() for blob in self.active.values()]
+        [blob.draw() for blob in self.active.values()]
 	
 	# sleep for a blink, so we don't always max out the CPU
-	sleep(0.01)
+        # unneeded, because we set the fps limit in the __init__.
+	#sleep(0.01)

File fungus_advection.py

File contents unchanged.

File fungus_game.py

 from time import sleep
 
 # Load necessary pyglet classes
+# first disable GL debugging (massive speed penalty).
+import pyglet
+pyglet.options['debug_gl'] = False
+# now import the rest
 from pyglet import window
 from pyglet.gl import *
+        
+
 
 # load the fungus core
 from fungus_core import Core
         # And activate tranparency for pngs
         glEnable(GL_BLEND)
         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
-        
+
         # Then load a core object with basic functionality for the scenes
         self.core = Core(graphics_dir = graphics_dir)
         # Pass the core a rference to the window, so the scenes can access it
     @game.win.event
     def on_key_release(symbol, modifiers): 
         game.scene.on_key_release(symbol, modifiers)
+
+    # mouse motion
+    @game.win.event
+    def on_mouse_motion(x, y, dx, dy):
+        game.scene.on_mouse_motion(x, y, dx, dy)
     
     # mouse press
     @game.win.event
     @game.win.event
     def on_mouse_release(x, y, buttons, modifiers): 
         game.scene.on_mouse_release(x, y, buttons, modifiers)
-    
+
+    @game.win.event
+    def on_mouse_enter(x, y):
+        game.scene.on_mouse_enter(x, y)
+
+    @game.win.event
+    def on_mouse_leave(x, y):
+        game.scene.on_mouse_leave(x, y)
+
+    @game.win.event
+    def on_mouse_scroll(x, y, scroll_x, scroll_y):
+        game.scene.on_mouse_scroll(x, y, scroll_x, scroll_y)
+
     # output all events
     #game.win.push_handlers(pyglet.window.event.WindowEventLogger())
     

File fungus_hexbattle.py

+#!/usr/bin/env python
+# encoding: utf-8
+
+"""The hexbattle game scene.
+
+Plan:
+
+- Get mouse position
+- Add hexgrid. hex over which the mouse is gets highlighted.
+- Add a charakter on the hexgrid.
+- Beam the Charakter (click, then click new position).
+- Let two charakters fight (click, then click other char). Who dies disappears  (→ to “died” list)
+- Add several charakters and cycle through them in each round. All player controlled.
+- Add movement on the grid with forbidden fields (different for final position and intermediate: Final not on anyone, intermediate not adjadent to an enemy).
+- Add basic AI. Guess maximum effect per round up to the units next attack (single step, no complex tactics → all attack the weakest point). 
+"""
+
+#### Call the correct fungus_game when called from the command line or clicked in a GUI ####
+
+if __name__ == "__main__": 
+    # Call this Scene via fungus_game
+    # For this to work, the main scene inside the module MUST be the class with the name "Scene"
+    from fungus_core import call_this_scene
+    # pass the commandline args
+    from sys import argv
+    call_this_scene(__file__, argv)
+
+
+#### Imports ####
+
+from fungus_scene import BaseScene, key, MethodNotImplemented
+
+
+#### The Hexbattle Scene  ####
+
+### Things needed for the scene
+
+IMAGE_BLOB = "blob.png"
+
+### The Scene itself. 
+
+class Scene(BaseScene): 
+    """The Hexbattle Scene."""
+    def __init__(self, core, *args, **kwds): 
+        """Initialize the scene with a core object for basic functions."""
+        super(Scene, self).__init__(core, *args, **kwds)
+	
+        self.blob = self.core.sprite(IMAGE_BLOB, x=212, y=208)
+        self.visible.append(self.blob)
+
+	
+    def update(self): 
+        """Update the stats of all scene objects. 
+
+Don't blit them, though. That's done by the Game itself.
+
+To show something, add it to the self.visible list. 
+To add a collider, add it to the self.colliding list. 
+To add an overlay sprite, add it to the self.overlay list. 
+"""	
+        self.blob.update()
+
+    # mouse control
+    def on_mouse_motion(self, x, y, dx, dy):
+        self.blob.x = x
+        self.blob.y = y
+
+    def on_mouse_press(self, x, y, buttons, modifiers): 
+        """Forwarded mouse input."""
+        pass
+    
+    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): 
+        """Forwarded mouse input."""
+        pass
+    
+    def on_mouse_release(self, x, y, buttons, modifiers): 
+        """Forwarded mouse input."""
+        pass
+
+    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
+        pass
+
+    def on_mouse_enter(self, x, y):
+        pass
+
+    def on_mouse_leave(self, x, y):
+        pass

File fungus_scene.py

 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 # encoding: utf-8
 
 """An example for a fungus scene definition. 
         """Forwarded keyboard input."""
         pass
     
+    def on_mouse_motion(self, x, y, dx, dy):
+        """The basic mouse event."""
+        pass
+    
     def on_mouse_press(self, x, y, buttons, modifiers): 
         """Forwarded keyboard input."""
         pass
         """Forwarded keyboard input."""
         pass
 
+    def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
+        pass
+
+    def on_mouse_enter(self, x, y):
+        pass
+
+    def on_mouse_leave(self, x, y):
+        pass
+
 
 #### An example scene ####
 

File fungus_swarm.py

 
 from random import random, choice, randint
 
-# Reduce CPU usage: sleep
-from time import sleep
+# Reduce CPU usage: clock
+from pyglet import clock
 
 #### Constants ####
 
-NUMBER_OF_BLOBS = 50
+NUMBER_OF_BLOBS = 100
 
 SEARCH_DISTANCE = 20
 MAX_SEARCH_DISTANCE = 2**32 # 32 bit integer - avoids massive slowdowns
 #: Do we want to never choose the last partner as next partner? 
 NEVER_SELECT_LAST_AS_NEXT = True
 # The distribution of sexes among the blobs. 
-#SEX_DISTRIBUTION = ["male", "male", "female", "female", None]
+SEX_DISTRIBUTION = ["male", "male", "female", "female", None]
 #SEX_DISTRIBUTION = ["male", None]
-SEX_DISTRIBUTION = ["male", "female"]
+#SEX_DISTRIBUTION = ["male", "female"]
 #SEX_DISTRIBUTION = ["male", "female", None, None, None]
 #SEX_DISTRIBUTION = ["male", "male", "male", "male", "male", "female", None]
 #: Strengths of random movement
 	self.blob_batch = self.core.batch()
 	# Show the batch. If we now add a sprite with this batch, it is shown automatically. 
 	self.visible.append(self.blob_batch)
+
+        # limit the FPS to 50 via pyglet internals
+        clock.set_fps_limit(50)
+
 	
         self.blobs = []
         for i in range(NUMBER_OF_BLOBS):
         	blob.update()
 		self.keep_on_screen(blob)
 	# sleep for a blink, so we don't always max out the CPU
-	sleep(0.01)
+	#sleep(0.001)