Source

fungus / fungus_core.py

The default branch has multiple heads

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

"""Fungus - Live and die in a world of mold

core functions: 
- scrolling text.

Ideas: 
- maybe replace .blit() everywhere with .draw()
- core: Offer a lightweight sprite and an actor sprite inszead of only the normal sprite to avoid unecessary overhead. The actor sprite contains the functions for keyboard control, the lightweight sprite only has basic movement (dx, dy, x, y). 
"""

__copyright__ = """ 
  Fungus - Live and die in a world of mold
----------------------------------------------------------------- 
© 2008 - 2009 Copyright by Arne Babenhauserheide

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
  MA 02110-1301 USA

""" 

from pyglet import image
from pyglet.sprite import Sprite as pyglet_sprite
from pyglet.text import Label as pyglet_label
# batches for drawing many sprites together. 
from pyglet.graphics import Batch
from os.path import dirname, join
from pyglet.window import key
#from pyglet import clock
from pyglet import font
# Media playing stuff (video and audio)
from pyglet.media import Player
from pyglet.media import load, StaticSource

### The Core Object ###

"""The Core object offers several convenience functions to speed up game development."""

## Audio player which knows looping
class Player(Player): 
    """A player which knows if it should loop."""
    def __init__(self, loop=False, *args, **kwds):
	super(Player, self).__init__(*args, **kwds)
	self.loop = loop
	if self.loop: 
	    self.eos_action = self.EOS_LOOP
	else: 
	    self.eos_action = self.EOS_PAUSE


## Sprites ##

class Sprite(pyglet_sprite):
    def __init__(self, image_path, x = 212, y = 208, update_func=None, *args, **kwds):
        """Create a simple sprite. 
	
It is a more conveniently callable version of the pyglet_sprite with some extra functiosn for continuous movement. 
-> For pyglet sprite usage see: http://www.pyglet.org/doc/api/pyglet.sprite.Sprite-class.html

@param image_path: the path to the image, relative to the graphics folder. 
@param x: horizontal position. 
@param y: vertical position. 
@param update_func: A function to update the Sprite position. If defined, it has to take x, y and return new x, y. 
"""	
	## Sprite image
	img = image.load(image_path)
	super(Sprite, self).__init__(img, x=x, y=y, *args, **kwds)

	## Sprite positions
	#: Continuous movement into x direction
	self.dx = 0
	#: Continuous movement into y direction
	self.dy = 0
	
	## Keyboard control
        #: Continuous movement into x direction for movement when holding down a key
        self.d_left = 0
	#: Continuous movement into x direction for movement when holding down a key
	self.d_right = 0
        #: Continuous movement into y direction for movement when holding down a key
        self.d_up = 0
	#: Continuous movement into y direction for movement when holding down a key
	self.d_down = 0
	#: Delayed continuous movement into x direction, beginning only with the update cycle after the next.
	self.dd_left = 0
	#: Delayed continuous movement into x direction, beginning only with the update cycle after the next.
	self.dd_right = 0
	#: Delayed continuous movement into y direction, beginning only with the update cycle after the next.
	self.dd_up = 0
	#: Delayed continuous movement into y direction, beginning only with the update cycle after the next.
	self.dd_down = 0
	
	## The update function
        self.update_func = update_func
        
    def blit(self): 
        """Draw the sprite at its coordinates."""
        self.draw()

    def update(self): 
        """Update the sprite."""
        # First resolve the keyboard control
        if self.d_left: 
            self.x -= self.d_left
        if self.d_right: 
            self.x += self.d_right
        if self.d_up: 
            self.y += self.d_up
        if self.d_down: 
            self.y -= self.d_down
	# Then prepare delayed continuous movement
	if self.dd_right: 
            self.d_right += self.dd_right
	    self.dd_right = 0
	if self.dd_left: 
            self.d_left += self.dd_left
	    self.dd_left = 0
        if self.dd_up: 
            self.d_up += self.dd_up
	    self.dd_up = 0
        if self.dd_down: 
            self.d_down += self.dd_down
	    self.dd_down = 0
	# Now do the basic continuous movement
	self.x += self.dx
	self.y += self.dy
	# And call the update function of the sprite. 
        if self.update_func is not None: 
            self.x, self.y, self.dx, self.dy = self.update_func(self.x, self.y, self.dx, self.dy)
        else: 
            pass


class Core(object): 
    """Basic functions for scenes.

Basics: 
- The core provides a core object which gets passed to every new scene. This core 
object gives the scenes basic functions like moving, collision detection and 
similar (to avoid having to load them for every new scene). 
- Also it holds some basic configuration settings needed by the scenes (like the 
graphics path). 

The core can contain nothing which needs access to the game window or event loop. 

"""
    def __init__(self, graphics_dir = "graphics", audio_dir = "audio", *args, **kwds):
        # And define the folder to use for graphics. 
        self.image_base_folder = join(dirname(__file__), graphics_dir)
	self.audio_base_folder = join(dirname(__file__), audio_dir)
        # TODO: Write core. 

    def sprite(self, image_path, x = 0, y = 0, update_func=None, *args, **kwds): 
        """Create a sprite which can blit itself and has x and y coordinates."""
        sprite = Sprite(join(self.image_base_folder, image_path), x=x, y=y, update_func=update_func, *args, **kwds)
        return sprite
    
    def batch(self): 
	"""Create a pyglet batch which can be used to draw many sprites together. Just call the sprite with batch=<your batch>.
	
	Then you can add the batch to the visible list and all sprites which were called with that batch are drawn with it automatically, so they no longer have to be added to the visible list. 
	
	Using a batch seems most useful when we have really many sprites on screen where the drawing costs most time - especially when the sprites overlap. 
	"""
	batch = Batch()
	# Give the batch the standard blit we use here. TODO: Rewrite all to use draw instead of blit. 
	batch.blit = batch.draw
	return batch
    
    def load_image(self, img_path):
        """Create an image object which can be blitted.

        This is just a wrapper for a pyglet function.
        """
        return image.load(join(self.image_base_folder, img_path))

    def load_text(self, text, font_type = "Arial", font_size=14, x=10, y=75, *args, **kwds): 
        """Create a text object which can be blitted.

        This is just a wrapper for a pyglet function.
        """
	label = pyglet_label(text,
                          font_name=font_type,
                          font_size=font_size,
                          x=x, y=y, *args, **kwds)
        # Give the label the same ability to blit itself as any other sprite.
        label.blit = label.draw
        return label
        
    def preload_scene(self, scene_class, scene_object): 
        """Preload a scene in a seperate thread to avoid interrupting the game flow, since loading a scene can take longer than one frame. 
        
        @return: An instance of the scene_class.
        """
        raise NotImplementedException("TODO")
    
    def load_player(self, source_file=None, loop=False, streaming = False): 
	"""@return: a player which can play audio files."""
	player = Player(loop=loop)
	if source_file is not None: 
	    source = self.load_media_source(source_file, streaming=streaming)
	    player.queue(source)
	return player
    
    def load_media_source(self, source_file, streaming = False): 
	"""@return: A static pyglet source which can be attached to an arbitaary number of players. 
	
	It is decoded on creation and stored in memory.."""
	source = load(join(self.audio_base_folder, source_file))
	if streaming: 
	    return source
	return StaticSource(source)

    def keyboard_movement_key_press(self, actor, symbol, modifiers): 
        """Basic keyboard movement.


        Movement from keypad. 
        We use direct change as well as continuous movement to make the actor react quickly. 
        Note: ddx and ddy allow for delayed continuous movement. The first step is always done."""
        if symbol == key.LEFT:
            actor.x -= 10 
            actor.dd_left = 10
        elif symbol == key.RIGHT: 
            actor.x += 10
            actor.dd_right = 10
        elif symbol == key.DOWN: 
            actor.y -= 10
            actor.dd_down = 10
        elif symbol == key.UP: 
            actor.y += 10
            actor.dd_up = 10

    def keyboard_movement_key_release(self, actor, symbol, modifiers): 
        """Basic keyboard movement.

	Note: ddx = ddy = 0 stop delayed continuous movement. 
	
	As safety: If we move in one direction and the key in that direction is released, the movement into that direction always stops. 
        """
        if symbol == key.LEFT: 
            actor.dd_left = 0
	    actor.d_left = 0
        elif symbol == key.RIGHT: 
            actor.dd_right = 0
	    actor.d_right = 0
        elif symbol == key.DOWN: 
	    actor.dd_down = 0
	    actor.d_down = 0
        elif symbol == key.UP: 
            actor.dd_up = 0
	    actor.d_up = 0
          
    def point_is_inside(self, area, x, y): 
        """Check if some point is inside an area. 
        
        @param area: Something which has x, y, width and height.
        """
        if x > area.x and x < area.x + area.width and y > area.y and y < area.y + area.height: 
            return True
        else: 
            return False

    def overlaps_rectangle(self, area1, area2):
        """Check if area1 overlaps or touches another area. Only checks the edges and the center of the sides.
        
        @param area1: Something which has x, y, width and height.
        """
        ins = self.point_is_inside
        if ins(area2, area1.x, area1.y) or ins(area2, area1.x, area1.y + area1.height) or ins(area2, area1.x + area1.width, area1.y) or ins(area2, area1.x + area1.width, area1.y + area1.height) or ins(area2, area1.x + area1.width/2.0, area1.y) or ins(area2, area1.x, area1.y + area1.height/2.0) or ins(area2, area1.x + area1.width, area1.y + area1.height/2.0) or ins(area2, area1.x + area1.width/2.0, area1.y + area1.height):
            return True
        else: 
            return False


    def touches_rectangle(self, area1, area2):
        """Check if area1 overlaps or touches another area. 
        
        @param area1: Something which has x, y, width and height.
        """
        ins = self.point_is_inside
        if (area1.x <= area2.x + area2.width and
            area1.y <= area2.y + area2.height and
            area2.x <= area1.x + area1.width and
            area2.y <= area2.y + area2.height): 
            return True
        else: 
            return False


#### Useful functions ####

def call_this_scene(_file_, args=[]): 
    """Call a scene when given its __file__. args can take command line arguments passed to the scene."""
    from subprocess import call
    from os import name as os_name
    from os import chdir, curdir
    from os.path import dirname, abspath, basename
    # The time for logging
    from time import strftime

    # Call the fungus_game.py script with the correct file as parameter. 
    # switch to the right dir
    DIRPATH = abspath(dirname(_file_))
    chdir(DIRPATH)
    # Use a log file
    log = open("log", "w")
    # write the date as first line
    log.write(strftime("%Y-%m-%d %H:%M:%S") + "\n")
    # Also write the commandline args
    log.write("commandline arguments: " + str(args) + "\n")
    # If called with ./, we remove the ./ - we won't need __file__ anywhere else, so we can change it. 
    if _file_.startswith("./"): 
	_file_ = _file_[2:]
    # Strip the _file_ down to the filename without the folder part. 
    _file_ = basename(_file_)
    # create the line to call
    line = ["fungus_game.py", _file_[:-2] + "Scene"]
    # add all commandline arguments
    if args[1:]: 
	line.extend(args[1:])
    # Windows has the files in the path
    if os_name in ["dos", "nt", "ce"]: 
	log.write("line to call: " + str(line) + "\n")
	call(line)
    # Others don't
    else: 
	line[0] = "./" + line[0]
	log.write("line to call: " + str(line) + "\n")
	call(line)
    # The game ended, so we exit, too. 
    log.close()
    exit()

#### Self-Test ####

if __name__ == "__main__": 
    pass