Initial commit

This commit is contained in:
Frank DeMarco 2012-07-05 17:21:49 +09:00
commit 8fbfb84115
15 changed files with 776 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pyc

51
README Normal file
View File

@ -0,0 +1,51 @@
----------------
Pygame Framework
----------------
Set of classes that facilitate the creation of Pygame projects and/or their
features.
Example
=======
This script creates a project which draws a square every second:
from time import sleep
from random import randint
from pgfw.Game import Game
# inheriting from Game allows you to customize your project
class SampleGame(Game):
square_width = 30
# update runs every frame, you can think of it as the mainloop
def update(self):
sleep(1)
screen = self.get_screen()
bounds = screen.get_size()
screen.fill((0, 0, 0))
screen.fill((255, 255, 255),
(randint(0, bounds[0]), randint(0, bounds[1]),
self.square_width, self.square_width))
if __name__ == '__main__':
# the play method begins the project's animation
SampleGame().play()
License
=======
This software is dedicated to the public domain
http://creativecommons.org/publicdomain/zero/1.0/
Contact
=======
frank dot s dot demarco at gmail
http://usethematrixze.us

61
pgfw/Animation.py Normal file
View File

@ -0,0 +1,61 @@
import pygame
class Animation:
def __init__(self, frame_duration, skip_frames=False):
self.reset_ticks()
self.updates_this_cycle = 1
self.overflow = 0
self.update_count = 1
self.actual_frame_duration = 0
self.target_frame_duration = frame_duration
self.skip_frames = skip_frames
self.stopping = False
def reset_ticks(self):
self.last_ticks = self.get_ticks()
def play(self):
while not self.stopping:
self.advance_frame()
self.update_frame_duration()
self.update_overflow()
self.stopping = False
def advance_frame(self):
while self.update_count > 0:
self.sequence()
self.update_count -= 1
if not self.skip_frames:
break
def update_frame_duration(self):
last_ticks = self.last_ticks
actual_frame_duration = self.get_ticks() - last_ticks
last_ticks = self.get_ticks()
wait_duration = self.get_configuration().get("display", "wait-duration")
while actual_frame_duration < self.target_frame_duration:
pygame.time.wait(wait_duration)
actual_frame_duration += self.get_ticks() - last_ticks
last_ticks = self.get_ticks()
self.actual_frame_duration = actual_frame_duration
self.last_ticks = last_ticks
def get_ticks(self):
return pygame.time.get_ticks()
def update_overflow(self):
self.update_count = 1
target_frame_duration = self.target_frame_duration
overflow = self.overflow
overflow += self.actual_frame_duration - target_frame_duration
while overflow > target_frame_duration:
self.update_count += 1
overflow -= target_frame_duration
overflow = self.overflow
def stop(self):
self.stopping = True
def clear_queue(self):
self.update_count = 1

70
pgfw/Audio.py Normal file
View File

@ -0,0 +1,70 @@
from os import listdir
from os.path import join
from pygame.mixer import Channel, Sound, music
from GameChild import *
from Input import *
class Audio(GameChild):
current_channel = None
paused = False
muted = False
def __init__(self, game):
GameChild.__init__(self, game)
self.load_fx()
self.subscribe_to(Input.command_event, self.mute)
def load_fx(self):
fx = {}
if self.get_configuration().has_option("audio", "sfx-path"):
root = self.get_resource("audio", "sfx-path")
for name in listdir(root):
fx[name.split(".")[0]] = Sound(join(root, name))
self.fx = fx
def mute(self, event):
if event.command == "mute":
self.muted = not self.muted
self.set_volume()
def set_volume(self):
volume = int(not self.muted)
music.set_volume(volume)
if self.current_channel:
self.current_channel.set_volume(volume)
def play_bgm(self, path, stream=False):
self.stop_current_channel()
if stream:
music.load(path)
music.play(-1)
else:
self.current_channel = Sound(path).play(-1)
self.set_volume()
def stop_current_channel(self):
music.stop()
if self.current_channel:
self.current_channel.stop()
self.current_channel = None
self.paused = False
def play_fx(self, name):
if not self.muted:
self.fx[name].play()
def pause(self):
channel = self.current_channel
paused = self.paused
if paused:
music.unpause()
if channel:
channel.unpause()
else:
music.pause()
if channel:
channel.pause()
self.paused = not paused

127
pgfw/Configuration.py Normal file
View File

@ -0,0 +1,127 @@
from os import sep
from os.path import join, exists
from sys import argv
from pprint import pformat
from ConfigParser import RawConfigParser
class Configuration(RawConfigParser):
default_rel_path = "config"
def __init__(self, installed_resources_path=None, rel_path=None,
type_declarations=None):
RawConfigParser.__init__(self)
self.set_type_declarations(type_declarations)
self.set_defaults()
self.installed_resources_path = installed_resources_path
self.rel_path = rel_path
self.read()
def set_type_declarations(self, type_declarations):
if type_declarations is None:
type_declarations = TypeDeclarations()
self.type_declarations = type_declarations
def set_defaults(self):
self.add_section("setup")
self.set("setup", "title", "")
self.add_section("display")
self.set("display", "dimensions", "480, 320")
self.set("display", "frame-duration", "33")
self.set("display", "wait-duration", "2")
self.set("display", "caption", None)
self.add_section("resources")
self.set("resources", "installation-path", ".")
self.add_section("screen-captures")
self.set("screen-captures", "path", "caps")
self.add_section("keys")
def set(self, section, option, value):
value = self.cast_value(section, option, value)
RawConfigParser.set(self, section, option, value)
def cast_value(self, section, option, value):
pair = section, option
types = self.type_declarations
if type(value) == str:
if pair in types["bool"]:
return True if value == "T" else False
elif pair in types["int"]:
return int(value)
elif pair in types["float"]:
return float(value)
elif pair in types["path"]:
return join(*value.split(sep))
elif pair in types["list"]:
return map(str.strip, value.split(types.list_member_sep))
elif pair in types["int-list"]:
print value
return map(int, value.split(types.list_member_sep))
return value
def read(self):
path = self.locate_file()
if path:
RawConfigParser.read(self, path)
for section in self.sections():
for option, value in self.items(section):
self.set(section, option, value)
else:
if self.is_debug_mode():
print "No configuration file found"
def is_debug_mode(self):
return "-d" in argv
def locate_file(self):
rel_path = self.rel_path if self.rel_path else self.default_rel_path
if not self.is_local_mode():
installed_path = join(self.installed_resources_path, rel_path)
if exists(installed_path):
return installed_path
if exists(rel_path):
return rel_path
def is_local_mode(self):
return "-l" in argv
def get(self, section, option):
value = RawConfigParser.get(self, section, option)
if value is None:
value = self.get_substitute(section, option)
return value
def get_substitute(self, section, option):
if section == "display":
if option == "caption":
return self.get("setup", "title")
def get_section(self, section):
assignments = {}
for option in self.options(section):
assignments[option] = self.get(section, option)
return assignments
def __repr__(self):
config = {}
for section in self.sections():
config[section] = self.get_section(section)
return pformat(config, 2, 1)
class TypeDeclarations(dict):
list_member_sep = ','
def __init__(self):
dict.__init__(self, {"bool": [], "int": [], "float": [], "path": [],
"list": [], "int-list": []})
self.add("int", "display", "frame-duration")
self.add("int", "display", "wait-duration")
self.add("int-list", "display", "dimensions")
self.add("path", "resources", "installation-path")
self.add("path", "screen-captures", "path")
self.add("list", "keys", "up")
def add(self, type, section, option):
self[type].append((section, option))

29
pgfw/Display.py Normal file
View File

@ -0,0 +1,29 @@
from pygame import display, image
from GameChild import *
class Display(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
self.config = self.get_configuration().get_section("display")
self.set_screen()
self.set_caption()
self.set_icon()
def set_screen(self):
self.screen = display.set_mode(self.config["dimensions"])
def set_caption(self):
display.set_caption(self.config["caption"])
def set_icon(self):
if self.get_configuration().has_option("display", "icon-path"):
path = self.get_resource("display", "icon-path")
display.set_icon(image.load(path).convert_alpha())
def get_screen(self):
return self.screen
def get_size(self):
return self.screen.get_size()

43
pgfw/EventDelegate.py Normal file
View File

@ -0,0 +1,43 @@
from pygame import event
from pygame.locals import *
from GameChild import *
class EventDelegate(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
self.subscribers = dict()
self.disable()
if self.is_debug_mode():
print "Event ID range: %i - %i" % (NOEVENT, NUMEVENTS)
def enable(self):
self.enabled = True
def disable(self):
self.enabled = False
def dispatch_events(self):
if self.enabled:
subscribers = self.subscribers
for evt in event.get():
kind = evt.type
if kind in subscribers:
for subscriber in subscribers[kind]:
if self.is_debug_mode():
print "Passing %s to %s" % (evt, subscriber)
subscriber(evt)
else:
event.pump()
def add_subscriber(self, kind, callback):
if self.is_debug_mode():
print "Subscribing %s to %i" % (callback, kind)
subscribers = self.subscribers
if kind not in subscribers:
subscribers[kind] = list()
subscribers[kind].append(callback)
def remove_subscriber(self, kind, callback):
self.subscribers[kind].remove(callback)

9
pgfw/Font.py Normal file
View File

@ -0,0 +1,9 @@
from pygame import font
from GameChild import *
class Font(font.Font, GameChild):
def __init__(self, parent, size):
GameChild.__init__(self, parent)
font.Font.__init__(self, self.get_resource("font-path"), size)

74
pgfw/Game.py Normal file
View File

@ -0,0 +1,74 @@
import sys
import pygame
from pygame import display
from pygame.locals import *
from GameChild import *
from Animation import *
from Audio import *
from Display import *
from Configuration import *
from EventDelegate import *
from Input import *
from ScreenGrabber import *
class Game(GameChild, Animation):
def __init__(self, installed_resources_path=".", config_rel_path=None):
self.init_gamechild()
self.installed_resources_path = installed_resources_path
self.config_rel_path = config_rel_path
self.set_configuration()
self.init_animation()
pygame.init()
self.set_children()
self.subscribe_to(QUIT, self.end)
self.subscribe_to(Input.command_event, self.end)
self.clear_queue()
self.delegate.enable()
def init_gamechild(self):
GameChild.__init__(self)
def set_configuration(self):
self.configuration = Configuration(self.installed_resources_path,
self.config_rel_path)
def init_animation(self):
Animation.__init__(self,
self.configuration.get("display", "frame-duration"))
def set_children(self):
self.set_display()
self.set_delegate()
self.set_input()
self.set_audio()
self.set_screen_grabber()
def set_display(self):
self.display = Display(self)
def set_delegate(self):
self.delegate = EventDelegate(self)
def set_input(self):
self.input = Input(self)
def set_audio(self):
self.audio = Audio(self)
def set_screen_grabber(self):
self.screen_grabber = ScreenGrabber(self)
def sequence(self):
self.delegate.dispatch_events()
self.update()
display.update()
def update(self):
pass
def end(self, evt):
if evt.type == QUIT or evt.command == "quit":
self.stop()

56
pgfw/GameChild.py Normal file
View File

@ -0,0 +1,56 @@
from os.path import exists, join, basename
from sys import argv
from pygame import mixer
import Game
class GameChild:
def __init__(self, parent=None):
self.parent = parent
def get_game(self):
current = self
while not isinstance(current, Game.Game):
current = current.parent
return current
def get_configuration(self):
return self.get_game().configuration
def get_input(self):
return self.get_game().input
def get_screen(self):
return self.get_game().display.get_screen()
def get_audio(self):
return self.get_game().audio
def get_delegate(self):
return self.get_game().delegate
def get_resource(self, section, option):
config = self.get_configuration()
path = config.get(section, option)
if not self.is_local_mode():
installation_root = config.get("resources", "installation-path")
installed_path = join(installation_root, path)
if exists(installed_path):
return installed_path
elif exists(path):
return path
return None
def is_local_mode(self):
return "-l" in argv
def subscribe_to(self, kind, callback):
self.get_game().delegate.add_subscriber(kind, callback)
def unsubscribe_from(self, kind, callback):
self.get_game().delegate.remove_subscriber(kind, callback)
def is_debug_mode(self):
return "-d" in argv

127
pgfw/Input.py Normal file
View File

@ -0,0 +1,127 @@
from pygame import event, joystick as joy, key as keyboard
from pygame.locals import *
from GameChild import *
class Input(GameChild):
command_event = USEREVENT + 7
def __init__(self, game):
GameChild.__init__(self, game)
self.joystick = Joystick()
self.unsuppress()
self.subscribe_to_events()
self.build_key_map()
def subscribe_to_events(self):
self.subscribe_to(KEYDOWN, self.translate_key_press)
self.subscribe_to(JOYBUTTONDOWN, self.translate_joy_button)
self.subscribe_to(JOYAXISMOTION, self.translate_axis_motion)
def suppress(self):
self.suppressed = True
def unsuppress(self):
self.suppressed = False
def build_key_map(self):
key_map = {}
for command, keys in self.get_configuration().items("keys"):
key_map[command] = []
for key in keys:
key_map[command].append(globals()[key])
self.key_map = key_map
def translate_key_press(self, evt):
if self.is_debug_mode():
print "You pressed %i, suppressed => %s" % (evt.key, self.suppressed)
if not self.suppressed:
key = evt.key
for command, keys in self.key_map.iteritems():
if key in keys:
self.post_command(command)
def post_command(self, name):
if self.is_debug_mode():
print "Posting %s command with id %i" % (name, self.command_event)
event.post(event.Event(self.command_event, command=name))
def translate_joy_button(self, evt):
if not self.suppressed:
button = evt.button
config = self.get_configuration()
code = self.command_event
if button == config["joy-advance"]:
self.post_command("advance")
if button == config["joy-pause"]:
self.post_command("pause")
def translate_axis_motion(self, evt):
if not self.suppressed:
axis = evt.axis
value = evt.value
code = self.command_event
if axis == 1:
if value < 0:
self.post_command("up")
elif value > 0:
self.post_command("down")
else:
if value > 0:
self.post_command("right")
elif value < 0:
self.post_command("left")
def is_command_active(self, command):
if not self.suppressed:
joystick = self.joystick
if self.is_key_pressed(command):
return True
if command == "up":
return joystick.is_direction_pressed(Joystick.up)
elif command == "right":
return joystick.is_direction_pressed(Joystick.right)
elif command == "down":
return joystick.is_direction_pressed(Joystick.down)
elif command == "left":
return joystick.is_direction_pressed(Joystick.left)
else:
return False
def is_key_pressed(self, command):
poll = keyboard.get_pressed()
for key in self.key_map[command]:
if poll[key]:
return True
def get_axes(self):
axes = {}
for direction in "up", "right", "down", "left":
axes[direction] = self.is_command_active(direction)
return axes
class Joystick:
(up, right, down, left) = range(4)
def __init__(self):
js = None
if joy.get_count() > 0:
js = joy.Joystick(0)
js.init()
self.js = js
def is_direction_pressed(self, direction):
js = self.js
if not js or direction > 4:
return False
if direction == 0:
return js.get_axis(1) < 0
elif direction == 1:
return js.get_axis(0) > 0
elif direction == 2:
return js.get_axis(1) > 0
elif direction == 3:
return js.get_axis(0) < 0

35
pgfw/ScreenGrabber.py Normal file
View File

@ -0,0 +1,35 @@
from os import mkdir
from os.path import exists, join
from sys import exc_info
from time import strftime
from pygame import image
from GameChild import *
from Input import *
class ScreenGrabber(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
self.subscribe_to(Input.command_event, self.save_display)
def save_display(self, event):
if event.command == "capture-screen":
directory = self.get_resource("capture-path")
try:
if not exists(directory):
mkdir(directory)
name = self.build_name()
path = join(directory, name)
capture = image.save(self.get_screen(), path)
print "Saved screen capture to %s" % directory + name
except:
print "Couldn't save screen capture to %s, %s" % \
(directory, exc_info()[1])
def build_name(self):
config = self.get_configuration()
prefix = config["capture-file-name-format"]
extension = config["capture-extension"]
return strftime(prefix) + extension

69
pgfw/Setup.py Normal file
View File

@ -0,0 +1,69 @@
from os import walk, remove
from os.path import sep, join, exists, realpath, relpath
from re import findall
from Configuration import *
class Setup:
config = Configuration(local=True)
@classmethod
def remove_old_mainfest(self):
path = "MANIFEST"
if exists(path):
remove(path)
@classmethod
def build_package_list(self):
packages = []
for root, dirs, files in walk("esp_hadouken"):
packages.append(root.replace(sep, "."))
return packages
@classmethod
def build_data_map(self):
install_root = self.config["resources-install-path"]
include = [(install_root, ["config", "Basic.ttf", "hi-scores"])]
exclude = map(realpath,
[".git", "esp_hadouken", "vid", "aud/uncompressed", "aud/mod",
"img/local", "dist", "build"])
for root, dirs, files in walk("."):
removal = []
for directory in dirs:
if realpath(join(root, directory)) in exclude:
removal.append(directory)
for directory in removal:
dirs.remove(directory)
if root != ".":
destination = join(install_root, relpath(root))
listing = []
for file_name in files:
listing.append(join(root, file_name))
include.append((destination, listing))
return include
@classmethod
def translate_title(self):
return self.config["game-title"].replace(" ", "-")
@classmethod
def build_description(self):
return "\n%s\n%s\n%s" % (file("description").read(),
"Changelog\n=========",
self.translate_changelog())
@classmethod
def translate_changelog(self):
translation = ""
for line in file("changelog"):
line = line.strip()
if line.startswith("esp-hadouken"):
version = findall("\((.*)\)", line)[0]
translation += "\n%s\n%s\n" % (version, "-" * len(version))
elif line and not line.startswith("--"):
if line.startswith("*"):
translation += line + "\n"
else:
translation += " " + line + "\n"
return translation

0
pgfw/__init__.py Normal file
View File

24
sample.py Normal file
View File

@ -0,0 +1,24 @@
from time import sleep
from random import randint
from pgfw.Game import Game
# inheriting from Game allows you to customize your project
class SampleGame(Game):
square_width = 30
# update runs every frame, you can think of it as the mainloop
def update(self):
sleep(1)
screen = self.get_screen()
bounds = screen.get_size()
screen.fill((0, 0, 0))
screen.fill((255, 255, 255),
(randint(0, bounds[0]), randint(0, bounds[1]),
self.square_width, self.square_width))
if __name__ == '__main__':
# the play method begins the project's animation
SampleGame().play()