Source

fungus / fungus_matter.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# encoding: utf-8

"""A set of matter points which only adhere retarded momentum and energy conservation. 

Energy and momentum conservation is always violated by a bit (the amount it is violated by could for example be in a magnetic or electric field), but never too much. 

Ideas: 
- Use a lightweight sprite instead of the normal core sprite to avoid unecessary overhead. 

"""

#### Call the correct fungus_game when called from the command line ####

if __name__ == "__main__": 
    # Call this Scene via fungus_game
    from fungus_core import call_this_scene
    # pass the commandline args
    from sys import argv
    call_this_scene(__file__, argv)


#### Imports ####

from fungus_core import Sprite
from fungus_scene import BaseScene

from os.path import join, dirname

from random import random, choice, randint

# Reduce CPU usage: sleep
from time import sleep

# Square root for energy to speed conversion
from math import sqrt

#### Constants ####

NUMBER_OF_BLOBS = 30
#: Where Blobs start. "random", None (None is the default position for Sprites)
STARTING_POSITIONS = "random"
#: Strengths of random movement
RANDOM_MOVEMENT = 0.5
#: Strengths of the momentum and energy correction. 1.0 / ... means all the other blobs compensate a momentum violation in one update cycle - for energy violation we don't make such promises, though. 
CORRECTION_STRENGTH = 5.0 / NUMBER_OF_BLOBS

#### An example scene ####

### Things needed for the scene

IMAGE_BASE_PATH = join(dirname(__file__), "graphics")

class Matter(Sprite): 
	"""One of the moving blobs.
	
	Plans: 
	    - Conservation of momentum: if _conservation["momentum_x"] != 0: self.dx -= 0.01 * _conservation["momentum_x"] / self.m
	    - Conservation of energy: if _conservation["current_energy"] > _energy: self.dx += 0.99
	    - 0.01 = 1.0/NUMBER_OF_BLOBS
		"""
	#: conervation values
	_conservation = {
	    "momentum_x": 0, 
	    "momentum_y": 0, 
	    "current_energy": 0, 
	    "energy": 1.0
	    }
	#: placeholder for the mass
	mass = 0
	
	def __init__(self, mass=1.0, image_path=join(IMAGE_BASE_PATH, "blobn.png"), *args, **kwds): 
		super(Matter, self).__init__(image_path=image_path, *args, **kwds)
		
		#: mass
		self.mass = mass
		
		#: x speed
		self._dx = 0.0
		#: y speed
		self._dy = 0.0
		
		#: Heat energy (inner energy)
		self._heat_energy = 277.0
		
		# increase the full energy by 1
		self._conservation["energy"] += 277.0
		self._conservation["current_energy"] += self._heat_energy
		
		# Blobs never start compeltely unmoving
		self.move_random()
	
	# Use a property for dx and dy
	def get_dx(self): 
	    # catch the first initialization, where we don't yet have a _dx
	    if hasattr(self, "_dx"): 
		return self._dx
	    else: 
		return 0.0
	    
	
	def get_dy(self): 
	    # catch the first initialization, where we don't yet have a _dy
	    if hasattr(self, "_dy"): 
		return self._dy
	    else: 
		return 0.0
	    
	def set_dx(self, dx):
	    # If something changed
	    if dx != self.dx: 
		# Update momentum - only when we already have it (Download Borg Data)
		if hasattr(self, "_conservation"):
		    self._conservation["momentum_x"] += self.mass * (dx - self.dx)
		    # And update current energy; + new_energy - old_energy
		    self._conservation["current_energy"] += self.get_energy(dx, self.dy) - self.get_energy()
		# Now update _dx
		self._dx = dx
	    
	def set_dy(self, dy): 
	    if dy != self.dy: 
		# Update momentum
		if hasattr(self, "_conservation"):
		    self._conservation["momentum_y"] += self.mass * (dy - self.dy)
		    # And update current energy; + new_energy - old_energy
		    self._conservation["current_energy"] += self.get_energy(self.dx, dy) - self.get_energy()
		# Now update _dy
		self._dy = dy

	def get_energy(self, dx=None, dy=None): 
	    """Get the current (kinetic) energy."""
	    if dx is None and dy is None: 
		return 0.5 * self.mass * (self.dx**2 + self.dy**2) + self._heat_energy
	    elif dx is not None and dy is not None: 
		return 0.5 * self.mass * (dx**2 + dy**2)  + self._heat_energy
	    else: 
		raise Exception("get_energy always has to get either both dx and dy or None")
	
	def get_temperature(self): 
	    """Return the current temperature of the Blob."""
	    if hasattr(self, "_heat_energy"): 
		return self._heat_energy
	    else: 
		return 0.0
	
	def set_temperature(self, temp): 
	    """Set the temperature of the Blob, also changing the total energy, if required."""
	    if temp != self._heat_energy: 
		self._conservation["current_energy"] += temp - self._heat_energy
		self._heat_energy = temp
	
	# Use a property for dx and dy, so they always keep momentum and energy up to date
	dx = property(fget=get_dx, fset=set_dx)
	dy = property(fget=get_dy, fset=set_dy)
	temperature = property(fget=get_temperature, fset=set_temperature)
	
	def update(self): 
		"""Update the position."""
		# First add random speeds
		#self.move_random()
		# Afterwards strive to conserve momentum and energy
		#self.conserve_momentum()
		#self.conserve_energy()
		# Finally move according to dx and dy
		super(Matter, self).update()
		#if self._conservation["momentum_x"] or self._conservation["momentum_y"]: 
		    #print "momentum violation:" ,self._conservation["momentum_x"], self._conservation["momentum_y"]
		#if self._conservation["current_energy"] - self._conservation["energy"]: 
		    #print "energy violation:", self._conservation["current_energy"],  self._conservation["energy"]
		    #print "my total and termal energy:", self.get_energy(), self.temperature
		
	def move_random(self): 
		"""Random movement."""
		self.dx += (2*random() - 1.0) * RANDOM_MOVEMENT * self.temperature / 277.0
		self.dy += (2*random() - 1.0) * RANDOM_MOVEMENT * self.temperature / 277.0
	
	def conserve_energy(self): 
	    """Adjust the speed to get closer to conservation of enery."""
	    if self._conservation["energy"] != 0: 
		#if sqrt_energy_ratio > 10: 
		    #sqrt_energy_ratio = 10.0
		#elif sqrt_energy_ratio < 0.1: 
		    #sqrt_energy_ratio = 0.1
		#if energy_ratio > 100: 
		    #energy_ratio = 100.0
		#elif energy_ratio < 0.01: 
		    #energy_ratio = 0.01
		#: speed ratio between x and (x+y)
		# correction 
		#: current / energy to have, E = 0.5 m*v²
		#energy_ratio = self._conservation["current_energy"] / self._conservation["energy"]
		# clean out extreme values
		if self._conservation["current_energy"] and self._conservation["current_energy"] != self._conservation["energy"]:
		    # First correct using speed
		    # choose between starting with x and y at random
		    if random() < 0.5: 
			# correct dx
			self.dx += 5*CORRECTION_STRENGTH * ((self.dx / sqrt(self._conservation["current_energy"] / self._conservation["energy"])) - self.dx)
			# correct dy
			self.dy += 5*CORRECTION_STRENGTH * ((self.dy / sqrt(self._conservation["current_energy"] / self._conservation["energy"])) - self.dy)
		    else: 
			# correct dy
			self.dy += 5*CORRECTION_STRENGTH * ((self.dy / sqrt(self._conservation["current_energy"] / self._conservation["energy"])) - self.dy)
			# correct dx
			self.dx += 5*CORRECTION_STRENGTH * ((self.dx / sqrt(self._conservation["current_energy"] / self._conservation["energy"])) - self.dx)
		    # then correct using inner temperature
		    self.temperature += CORRECTION_STRENGTH * ((self.temperature / (self._conservation["current_energy"] / self._conservation["energy"])) - self.temperature)
		    
	
	def conserve_momentum(self): 
	    """Adjust the speed to get closer to conservation of momentum."""
	    if self._conservation["momentum_x"] != 0: 
		self.dx -= CORRECTION_STRENGTH * self._conservation["momentum_x"] / self.mass
	    if self._conservation["momentum_y"] != 0: 
		self.dy -= CORRECTION_STRENGTH * self._conservation["momentum_y"] / self.mass
		
		
class HeavyBlob(Matter): 
    """A Blob with high mass."""
    def __init__(self, mass=10.0, *args, **kwds): 
		super(HeavyBlob, self).__init__(mass=mass, image_path=join(IMAGE_BASE_PATH, "blob.png"), *args, **kwds)
### The Scene itself. 

class Scene(BaseScene): 
    """A dummy scene - mostly just the Scene API."""
    def __init__(self, core, *args, **kwds): 
        """Initialize the scene with a core object for basic functions."""
        
        ## Get the necessary attributes for any scene. 
        # This gets the 'visible', 'colliding' and 'overlay' lists 
        # as well as the scene switch 'switch_to_scene' 
        # which can be assigned a scene to switch to. 
        super(Scene, self).__init__(core, *args, **kwds)
        
        ## Blobs
        # Add blobs and show them
        self.blobs = []
        for i in range(NUMBER_OF_BLOBS):
		x, y = self.get_starting_position()
        	blob = Matter(x=x, y=y)
        	self.blobs.append(blob)
        	self.visible.append(blob)
	# also add one superblob
	x, y = self.get_starting_position()
	blob = HeavyBlob(x=x, y=y)
	self.blobs.append(blob)
	self.visible.append(blob)
	
	## Overlay
	# Also add a mean temperature marker. 
	self.energy_violation = self.core.load_text("Energy violation", y=30.0)
	self.momentum_violation = self.core.load_text("Momentum violation", y=10.0)
	self.mean_temp = self.core.load_text("Mean Temperature", y=50.0)
	self.visible.append(self.mean_temp)
	self.visible.append(self.energy_violation)
	self.visible.append(self.momentum_violation)
     
    def keep_on_screen(self, blob): 
	"""Keep the blobs from leaving the screen. """
	
	# First part: soft repellent: 
	#if blob.x < 10: 
		#blob.dx += 10 - blob.x
		    
	#if blob.y < 10: 
		#blob.dy += 10 - blob.y
		    
	#if blob.x + blob.width > self.core.win.width - 10: 
		#blob.dx += (self.core.win.width - 10) - (blob.x + blob.width)
		    
	#if blob.y + blob.height > self.core.win.height - 10: 
		#blob.dy += (self.core.win.height - 10) - (blob.y + blob.height)
	
	# Second part: hard placement
	
	if blob.x < 0: 
		blob.x = 0
		blob.dx = 0
		    
	if blob.y < 0: 
		blob.y = 0
		blob.dy = 0
		    
	if blob.x + blob.width > self.core.win.width: 
		blob.x = self.core.win.width - blob.width
		blob.dx = 0
		
		    
	if blob.y + blob.height > self.core.win.height: 
		blob.y = self.core.win.height - blob.height
		blob.dy = 0
		


		
    def get_starting_position(self): 
		"""Select a starting position based on the config parameters."""
		# Start at a fixed position
		if STARTING_POSITIONS is None: 
			x = 0.5 * self.core.win.width
			y = 0.5 * self.core.win.height
		# Or start at random positions
		elif STARTING_POSITIONS == "random": 
			x = random() * self.core.win.width
			y = random() * self.core.win.height
		return x, y
		

    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. 
"""
        for blob in self.blobs: 
		#blob.move_random()
		blob.conserve_momentum()
		blob.conserve_energy()
        	blob.update()
		self.keep_on_screen(blob)
	# only let the last move at random
	self.blobs[-1].move_random()
	self.keep_on_screen(self.blobs[-1])
	# update the displays for mean temperature, momentum and energy violation
	self.mean_temp.text = "Mean Temperature: " + str(sum([blob.temperature for blob in self.blobs]) / len(self.blobs))
	self.energy_violation.text = "Energy Violation: " + str(self.blobs[0]._conservation["current_energy"] - self.blobs[0]._conservation["energy"])
	self.momentum_violation.text = "Momentum Violation: x: " + str(self.blobs[0]._conservation["momentum_x"]) + " y: " + str(self.blobs[0]._conservation["momentum_y"])
	
	# sleep for a blink, so we don't always max out the CPU
	sleep(0.02)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.