Compare commits

..

1 Commits
main ... casts

Author SHA1 Message Date
Frank DeMarco c54ebd914d Configuration options stored as text, cast on retrieval 2012-07-16 17:23:51 +09:00
36 changed files with 673 additions and 4974 deletions

3
.gitignore vendored
View File

@ -1,4 +1 @@
*.pyc
build/
MANIFEST
*~

View File

@ -1,19 +0,0 @@
Copyright (c) 2022 shampoo.ooo <mailbox@shampoo.ooo>
This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use of
this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@ -1 +0,0 @@
include sample.py

50
README Normal file
View File

@ -0,0 +1,50 @@
----------------
Pygame Framework
----------------
Classes that facilitate the creation of Pygame projects
Example
=======
Create 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. See
http://creativecommons.org/publicdomain/zero/1.0/ for details.
Contact
=======
frank dot s dot demarco at gmail
http://usethematrixze.us

View File

@ -1,60 +0,0 @@
Pygame Framework
================
PGFW is a game framework that facilitates the creation and development of [Pygame][] projects. It contains a class `Game` which can be inherited and used as a project skeleton. There are classes for features such as sprites, animations, audio, b-splines, geometry, and screen capture. It is a collection of code used in previous projects with a focus on creating 2D games. Some examples of games using it are [Picture Processing][], [Scrapeboard][], and [Cakefoot][].
Requirements
------------
* Python 3+
* [Pygame][] 1.9+
Start a project
---------------
Clone the repository (or [download](https://open.shampoo.ooo/shampoo/pgfw/archive/main.zip) and unzip it)
git clone https://open.shampoo.ooo/shampoo/pgfw
Save the following script at the root of the repository to create a project that redraws a square at a random location every second. The project can be run with `python3 [SCRIPT]`. Maybe some cats may even like this game. This script is also available in [sample.py][].
from time import sleep
from random import randint
from pgfw.Game import Game
class SampleGame(Game):
square_width = 30
# instructions in the update method automatically run once every frame
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__':
SampleGame().run()
To further build this example, the [Pygame API][] and classes in [pgfw/][] could be imported and used directly in the update function.
License
-------
Unrestricted use, under the zlib license, see [LICENSE.txt][]
![Pygame powered](https://www.pygame.org/docs/_static/pygame_powered.png)
[Pygame]: https://pygame.org
[Pygame API]: https://www.pygame.org/docs
[Picture Processing]: https://ohsqueezy.itch.io/ppu
[Scrapeboard]: https://scrape.nugget.fun
[Cakefoot]: https://ohsqueezy.itch.io/cakefoot
[sample.py]: sample.py
[pgfw/]: pgfw/
[LICENSE.txt]: LICENSE.txt

View File

@ -1,151 +1,62 @@
import collections
import pygame
from .GameChild import GameChild
class Animation:
class Animation(GameChild):
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 __init__(self, parent, method=None, interval=None, unfiltered=False):
GameChild.__init__(self, parent)
self.unfiltered = unfiltered
self.default_method = method or self.build_frame
self.accounts = collections.OrderedDict()
self.register(self.default_method, interval=interval)
self.last_update = 0
def build_frame(self):
pass
def register(self, *args, **kwargs):
interval = None
if "interval" in kwargs:
interval = kwargs["interval"]
for method in args:
if method not in self.accounts:
self.accounts[method] = Account(interval, self)
else:
self.accounts[method].set_interval(interval)
def play(self, method=None, interval=None, delay=0, play_once=False, **kwargs):
account = self.accounts[self.get_default(method)]
account.set_delay(delay)
account.set_args(kwargs)
account.set_play_once(play_once)
if interval:
account.set_interval(interval)
account.play()
def get_default(self, method=None):
if not method:
method = self.default_method
elif isinstance(method, str):
return getattr(self, method)
return method
def halt(self, method=None):
if not method:
for account in self.accounts.values():
account.halt()
else:
if method in self.accounts:
self.accounts[method].halt()
def is_playing(self, method=None, check_all=False, include_delay=False):
if check_all:
return any(self.is_account_playing(account, include_delay) for method, account in self.accounts.items())
return self.is_account_playing(self.accounts[self.get_default(method)], include_delay)
def is_account_playing(self, account, include_delay):
return account.playing and (include_delay or not account.delay)
def reset_timer(self, method=None):
if not method:
for account in self.accounts.values():
account.reset_timer()
else:
self.accounts[method].reset_timer()
def update(self):
for method, account in self.accounts.items():
if account.update():
method(**account.args)
class Account:
def __init__(self, interval, animation):
self.animation = animation
self.time_filter = animation.get_game().time_filter
self.set_interval(interval)
self.set_delay(0)
self.set_play_once(False)
self.interval_index = 0
self.last_frame = 0
self.halt()
def set_interval(self, interval):
if isinstance(interval, int) or isinstance(interval, str):
interval = [interval]
self.interval = interval
def set_delay(self, delay):
self.delay = delay
def set_play_once(self, play_once):
self.play_once = play_once
def set_args(self, args):
self.args = args
def reset_ticks(self):
self.last_ticks = self.get_ticks()
def play(self):
self.playing = True
while not self.stopping:
self.advance_frame()
self.update_frame_duration()
self.update_overflow()
self.stopping = False
def halt(self):
self.last_update = None
self.playing = False
def advance_frame(self):
while self.update_count > 0:
self.sequence()
self.update_count -= 1
if not self.skip_frames:
break
def update(self):
if self.playing:
if self.animation.unfiltered:
ticks = self.time_filter.get_unfiltered_ticks()
else:
ticks = self.time_filter.get_ticks()
self.update_delay(ticks)
if not self.delay:
interval = self.interval
if interval:
if ticks - self.last_frame < self.get_current_interval():
return False
self.last_frame = ticks
self.increment_interval_index()
if self.play_once:
self.halt()
return True
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",
"int")
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_current_interval(self):
return self.interval[self.interval_index]
def get_ticks(self):
return pygame.time.get_ticks()
def increment_interval_index(self, increment=1):
index = self.interval_index + increment
while index >= len(self.interval):
index -= len(self.interval)
self.interval_index = index
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 reset_interval(self):
self.interval_index = 0
def stop(self):
self.stopping = True
def reset_timer(self):
if self.animation.unfiltered:
ticks = self.time_filter.get_unfiltered_ticks()
else:
ticks = self.time_filter.get_ticks()
self.last_frame = ticks
def update_delay(self, ticks):
delay = self.delay
if delay > 0:
last_update = self.last_update or ticks
delay -= ticks - last_update
if delay < 0:
delay = 0
self.last_update = ticks
self.delay = delay
def clear_queue(self):
self.update_count = 1

View File

@ -1,911 +1,70 @@
# -*- coding: utf-8 -*-
from os import listdir
from os.path import join
import os, re, shutil, pygame, sys, collections
from pygame.mixer import Channel, Sound, music
from .GameChild import *
from .Sprite import *
from .Input import *
from .Animation import *
from .extension import *
from GameChild import *
from Input import *
class Audio(Animation):
class Audio(GameChild):
UP, DOWN = .1, -.1
CONFIG_SEPARATOR = ","
current_channel = None
paused = False
muted = False
def __init__(self, game):
Animation.__init__(self, game)
self.current_bgm = None
self.volume = 1.0
self.pre_muted_volume = 1.0
if self.check_command_line("-mute"):
self.get_configuration().set("audio", "volume", 0)
self.register(self.play_sfx)
if self.get_configuration("audio", "panel-enabled"):
self.audio_panel = AudioPanel(self)
GameChild.__init__(self, game)
self.load_fx()
self.subscribe_to(self.get_user_event_id(), 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 self.is_command(event, "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.audio_panel = None
self.subscribe(self.respond)
self.sfx = {}
self.bgm = {}
if self.get_configuration("audio", "auto-load"):
self.load_sfx()
self.load_bgm()
self.set_volume(self.get_configuration("audio", "volume"))
self.current_channel = Sound(path).play(-1)
self.set_volume()
def set_volume(self, volume=None, increment=None, mute=False, unmute=False):
if mute:
self.pre_muted_volume = self.volume
self.volume = 0
elif unmute and self.pre_muted_volume is not None:
self.volume = self.pre_muted_volume
self.pre_muted_volume = None
elif increment:
self.volume = clamp(self.volume + increment, 0, 1.0)
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:
self.volume = volume
self.get_configuration().set("audio", "volume", self.volume)
if pygame.mixer.music.get_busy():
pygame.mixer.music.set_volume(self.current_bgm.volume * self.volume)
for ii in range(pygame.mixer.get_num_channels()):
channel = pygame.mixer.Channel(ii)
if channel.get_busy():
channel.set_volume(channel.get_sound().get_volume() * self.volume)
def get_volume(self):
return self.volume
def set_channel_volume(self, channel, *args):
'''
Set channel volume taking global volume into account. One or two values can be passed into *args.
A single value will affect both left and right speakers. Two values will be used as the left and right
speakers separately. This is the behavior of pygame's Channel.set_volume method
'''
for ii in range(len(args)):
args[ii] *= self.volume
channel.set_volume(*args)
def respond(self, event):
compare = self.get_game().delegate.compare
if compare(event, "volume-mute"):
if self.volume > 0:
self.set_volume(mute=True)
else:
self.set_volume(unmute=True)
elif compare(event, "volume-up"):
self.set_volume(increment=self.UP)
elif compare(event, "volume-down"):
self.set_volume(increment=self.DOWN)
def load_sfx(self, sfx_location=None):
"""
Load SFX from paths defined in config. This can be run without arguments, and it will attempt to auto find
SFX following the below procedure. If sfx_location is set, paths for SFX files in the config are overridden.
Auto-loading SFX procedure:
* load config file name/path definitions at init
* check project specific sfx paths at init, load any that don't conflict
* check default sfx paths at init, load any that don't conflict
* repository paths are not loaded at init but can replace loaded paths
and get written to config file
"""
for name, sfx_definition in self.get_configuration("sfx").items():
sfx_definition_members = sfx_definition.split(self.CONFIG_SEPARATOR)
# default values for everything besides path in case those aren't included in the config definition
path, volume, fade_out, loops, maxtime = sfx_definition_members[0], 1.0, 0, 0, 0
# format for an SFX defintion in config is: "name = path[, volume][, fade out][, loops][, maxtime]"
for ii, member in enumerate(sfx_definition_members[1:]):
if ii == 0:
volume = float(member)
elif ii == 1:
fade_out = float(member)
elif ii == 2:
loops = int(member)
elif ii == 3:
maxtime = float(member)
self.load_sfx_file(
path, name, True, volume=volume, fade_out=fade_out, loops=loops, maxtime=maxtime)
# override config definitions of SFX paths
if sfx_location is None:
sfx_location = self.get_configuration("audio", "sfx-project-path") + \
self.get_configuration("audio", "sfx-default-path")
if isinstance(sfx_location, str):
sfx_location = [sfx_location]
for root in sfx_location:
prefix = ""
root = self.get_resource(root)
if root:
print("checking {} for sound effects".format(root))
if os.path.isfile(root):
self.load_sfx_file(root)
else:
for node, branches, leaves in os.walk(root, followlinks=True):
for leaf in leaves:
# use path elements to prepend subdirectories to the SFX name
print(r"{}".format(re.escape(root)), r"{}".format(re.escape(node)), rf"{root}", rf"{node}")
prefix = re.sub(r"{}".format(re.escape(root)), r"", r"{}".format(node))
print(prefix)
prefix = re.sub(rf"^\{os.path.sep}", r"", prefix)
if prefix:
prefix = re.sub(rf"\{os.path.sep}", r"_", prefix) + "_"
self.load_sfx_file(os.path.join(node, leaf,), prefix=prefix)
def load_sfx_file(self, path, name=None, replace=False, prefix="", volume=1.0, fade_out=0, loops=0, maxtime=0):
path = self.get_resource(path)
if path and self.is_loadable(path):
if name is None:
name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path))
if not replace and name in self.sfx:
print("skipping existing sound effect for {}: {}".format(name, path))
else:
print("loading sound effect {} into {}".format(path, name))
self.sfx[name] = SoundEffect(self, path, volume, loops, fade_out, maxtime=maxtime)
return True
else:
print("couldn't load sound effect, path is not loadable {}".format(path))
return False
def get_sfx(self, name):
'''
Get a SoundEffect object (which inherits pygame's Sound) from this object's dictonary of loaded sfx
'''
return self.sfx[name]
def play_sfx(self, name, loops=None, maxtime=None, fade_ms=None, position=None, x=None):
return self.sfx[name].play(loops, maxtime, fade_ms, position, x)
def load_bgm(self):
"""
Loading BGM procedure:
- Check project specific BGM paths and load files found in those paths.
- Load config file name/path definitions, overwriting existing. This means the config file
definitions have precedence over the automatic loading of files placed in folders.
Further editing of BGM while the game is running can be done through the AudioPanel object.
"""
# First load BGM files found in the BGM path set in the configuration
for root in self.get_configuration("audio", "bgm-project-path"):
# look for path in resource folders
root = self.get_resource(root)
if root is not None and os.path.exists(root):
print("checking {} for background music files".format(root))
if os.path.isfile(root):
self.set_bgm(root)
else:
for node, branches, leaves in os.walk(root, followlinks=True):
for leaf in leaves:
prefix = re.sub(root, "", node)
prefix = re.sub("^/", "", prefix)
if prefix:
prefix = re.sub("/", "_", prefix) + "_"
self.set_bgm(os.path.join(node, leaf), prefix=prefix)
# Next load BGM paths defined in the configuration. If any of these have the same name as
# BGM loaded by the previous code block, they will be overwritten to give the config file
# precedence over automatic BGM detection.
print("checking configuration for background music definitions".format(root))
for name, bgm_definition in self.get_configuration("bgm").items():
bgm_definition_members = bgm_definition.split(self.CONFIG_SEPARATOR)
path, volume = bgm_definition_members[0], 1.0
for ii, member in enumerate(bgm_definition_members[1:]):
if ii == 0:
volume = float(member)
self.set_bgm(path, name, volume=volume)
def set_bgm(self, path, name=None, prefix="", volume=1.0):
path = self.get_resource(path)
try:
pygame.mixer.music.load(path)
except:
print("can't load {} as music".format(path))
return False
if name is None:
name = os.path.basename(path).split(".")[0]
print("setting {} background music to {}".format(name, path))
self.bgm[prefix + name] = BGM(self, path, volume)
if self.current_bgm is None:
self.current_bgm = self.bgm[prefix + name]
return True
def play_bgm(self, name=None, store_as_current=True, start=0):
if name is None:
bgm = self.current_bgm
else:
bgm = self.bgm[name]
pygame.mixer.music.load(bgm.get_path())
try:
pygame.mixer.music.play(-1, start)
except pygame.error:
pygame.mixer.music.play(-1)
pygame.mixer.music.set_volume(bgm.get_volume() * self.get_configuration("audio", "volume"))
if store_as_current:
self.current_bgm = bgm
def is_sound_file(self, path):
return path.split(".")[-1] in self.get_configuration("audio", "sfx-extensions")
def is_loadable(self, path):
try:
pygame.mixer.Sound(path)
except:
return False
return True
def is_streamable(self, path):
try:
pygame.mixer.music.load(path)
except:
return False
return True
def is_audio_panel_active(self):
return self.audio_panel and self.audio_panel.active
def update(self):
Animation.update(self)
if self.audio_panel:
self.audio_panel.update()
class BGM(GameChild):
def __init__(self, parent, path, volume=1.0):
GameChild.__init__(self, parent)
self.path = path
self.volume = volume
def get_path(self):
return self.path
def adjust_volume(self, increment):
self.volume = clamp(self.volume + increment, 0, 1.0)
if self.parent.current_bgm == self:
pygame.mixer.music.set_volume(self.volume)
return self.volume
def get_volume(self):
return self.volume
def __eq__(self, other):
return self.path == other.path
class SoundEffect(GameChild, pygame.mixer.Sound):
def __init__(self, parent, path, volume=1.0, loops=0, fade_out_length=0, fade_in_length=0, maxtime=0):
self.path = path
GameChild.__init__(self, parent)
pygame.mixer.Sound.__init__(self, path)
self.display_surface = self.get_display_surface()
self.local_volume = volume
self.loops = loops
self.fade_out_length = fade_out_length
self.fade_in_length = fade_in_length
self.maxtime = maxtime
def play(self, loops=None, maxtime=None, fade_ms=None, position=None, x=None):
self.set_volume(self.local_volume * self.get_configuration("audio", "volume"))
if loops is None:
loops = self.loops
if maxtime is None:
maxtime = int(self.maxtime * 1000)
if fade_ms is None:
fade_ms = int(self.fade_in_length * 1000)
channel = pygame.mixer.Sound.play(self, loops, maxtime, fade_ms)
if x is not None:
position = float(x) / self.display_surface.get_width()
if position is not None and channel is not None:
channel.set_volume(*self.get_panning(position))
if self.fade_out_length > 0:
self.fadeout(int(self.fade_out_length * 1000))
return channel
def get_panning(self, position):
return 1 - max(0, ((position - .5) * 2)), 1 + min(0, ((position - .5) * 2))
def adjust_volume(self, increment):
self.local_volume += increment
if self.local_volume > 1.0:
self.local_volume = 1.0
elif self.local_volume < 0:
self.local_volume = 0
return self.local_volume
def adjust_loop_count(self, increment):
self.loops += increment
if self.loops < -1:
self.loops = -1
return self.loops
def adjust_fade_in_length(self, increment):
self.fade_in_length += increment
limit = self.get_length() * (self.loops + 1)
if self.fade_in_length < 0:
self.fade_in_length = 0
elif self.loops > -1 and self.fade_in_length > limit:
self.fade_in_length = limit
return self.fade_in_length
def adjust_fade_out_length(self, increment):
self.fade_out_length += increment
limit = self.get_length() * (self.loops + 1)
if self.fade_out_length < 0:
self.fade_out_length = 0
elif self.loops > -1 and self.fade_out_length > limit:
self.fade_out_length = limit
return self.fade_out_length
def adjust_maxtime(self, increment):
self.maxtime += increment
limit = self.get_length() * (self.loops + 1)
if self.maxtime < 0:
self.maxtime = 0
elif self.loops > -1 and self.maxtime > limit:
self.maxtime = limit
return self.maxtime
class AudioPanel(Animation):
MARGIN = 6
def __init__(self, parent):
Animation.__init__(self, parent)
self.rows = []
self.bgm_elapsed = None
font_path = self.get_resource(self.get_configuration("audio", "panel-font"))
self.font_large = pygame.font.Font(font_path, 15)
self.font_medium = pygame.font.Font(font_path, 12)
self.font_small = pygame.font.Font(font_path, 8)
self.file_browser = AudioPanelFileBrowser(self)
self.subscribe(self.respond)
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
self.reset()
def reset(self):
self.row_offset = 0
self.deactivate()
def get_selected(self):
for row in self.rows:
if row.selected:
return row
def activate(self):
pygame.mouse.set_visible(True)
self.active = True
if pygame.mixer.music.get_busy():
self.bgm_elapsed = pygame.mixer.music.get_pos() / 1000
pygame.mixer.music.stop()
pygame.mixer.stop()
# self.build()
def deactivate(self):
pygame.mouse.set_visible(self.get_configuration("mouse", "visible"))
self.active = False
if self.bgm_elapsed is not None:
self.get_audio().play_bgm(start=self.bgm_elapsed)
self.file_browser.hide()
def respond(self, event):
if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx:
if self.active:
self.deactivate()
else:
self.activate()
if not self.rows:
self.build()
elif self.active:
if event.type == pygame.MOUSEBUTTONDOWN and self.file_browser.is_hidden():
if event.button == 5:
self.row_offset += 1
elif event.button == 4:
self.row_offset -= 1
elif event.button == 3:
self.deactivate()
def build(self):
for row in self.rows:
row.unsubscribe()
del row
self.rows = []
for key in sorted(self.parent.bgm):
self.rows.append(AudioPanelRow(self, key, True))
for key in sorted(self.parent.sfx):
self.rows.append(AudioPanelRow(self, key))
def update(self):
if self.active:
Animation.update(self)
ds = self.get_display_surface()
dsr = ds.get_rect()
ds.fill((0, 0, 0))
corner = Vector(self.MARGIN, self.MARGIN)
index = self.row_offset
for row in self.rows:
row.location.bottom = 0
row.update()
while corner.y < dsr.height - self.MARGIN:
row = self.rows[index % len(self.rows)]
row.location.topleft = corner.copy()
row.update()
corner.y += row.location.height + self.MARGIN
index += 1
self.file_browser.update()
class AudioPanelRow(BlinkingSprite):
BACKGROUND = pygame.Color(128, 192, 255, 255)
FOREGROUND = pygame.Color(0, 0, 0, 255)
WIDTH = .5
HEIGHT = 30
INDENT = 4
MAX_NAME_WIDTH = .7
SLIDER_W = 60
BUTTON_W = 30
def __init__(self, parent, key, is_bgm=False):
BlinkingSprite.__init__(self, parent, 500)
self.key = key
self.selected = False
self.font = self.parent.font_large
self.is_bgm = is_bgm
self.build()
font_medium = self.parent.font_medium
font_small = self.parent.font_small
if self.is_bgm:
volume = self.get_bgm().volume
volume_function = self.get_bgm().adjust_volume
else:
volume = self.get_sound_effect().local_volume
volume_function = self.get_sound_effect().adjust_volume
self.volume_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .05,
volume, volume_function, self.FOREGROUND, self.BACKGROUND, 2, "vol")
if not self.is_bgm:
self.fade_out_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().fade_out_length,
self.get_sound_effect().adjust_fade_out_length, self.FOREGROUND,
self.BACKGROUND, 1, "fade")
self.loops_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, 1,
self.get_sound_effect().loops,
self.get_sound_effect().adjust_loop_count, self.FOREGROUND,
self.BACKGROUND, 0, "loops")
self.maxtime_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().maxtime,
self.get_sound_effect().adjust_maxtime, self.FOREGROUND,
self.BACKGROUND, 1, "cutoff")
if self.is_bgm:
callback, kwargs = self.get_game().get_audio().play_bgm, {"name": self.key, "store_as_current": False}
else:
callback, kwargs = self.get_sound_effect().play, {}
self.play_button = AudioPanelButton(self, callback, kwargs)
frame = pygame.Surface((self.BUTTON_W, self.location.h), SRCALPHA)
frame.fill(self.BACKGROUND)
stop_button_frame = frame.copy()
w, h = frame.get_size()
pygame.draw.polygon(frame, self.FOREGROUND, ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5)))
self.play_button.add_frame(frame)
if self.is_bgm:
callback = pygame.mixer.music.stop
else:
callback = self.get_sound_effect().stop
self.stop_button = AudioPanelButton(self, callback)
stop_button_frame.fill(self.FOREGROUND, (w * .25, h * .25, w * .5, h * .5))
self.stop_button.add_frame(stop_button_frame)
self.stop_blinking()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def respond(self, event):
if self.parent.active and event.button == 1:
if self.parent.file_browser.is_hidden() and self.location.collidepoint(event.pos):
if not self.selected:
self.parent.file_browser.visit(self.parent.file_browser.HOME)
self.selected = True
self.start_blinking()
self.parent.file_browser.unhide()
elif self.parent.file_browser.is_hidden():
if self.selected:
self.selected = False
self.stop_blinking()
def unsubscribe(self, callback=None, kind=None):
if callback is None:
callback = self.respond
kind = pygame.MOUSEBUTTONDOWN
GameChild.unsubscribe(self, self.respond, pygame.MOUSEBUTTONDOWN)
self.play_button.unsubscribe()
self.stop_button.unsubscribe()
if not self.is_bgm:
for spinner in self.volume_spinner, self.fade_out_spinner, self.loops_spinner, self.maxtime_spinner:
spinner.unsubscribe()
def build(self):
ds = self.get_display_surface()
dsr = ds.get_rect()
surface = pygame.Surface((dsr.w * self.WIDTH, self.HEIGHT), pygame.SRCALPHA)
surface.fill(self.BACKGROUND)
self.add_frame(surface)
name_sprite = Sprite(self)
name = self.font.render(self.key + ":", True, self.FOREGROUND)
if name.get_width() > int(self.location.w * self.MAX_NAME_WIDTH):
crop = pygame.Rect(0, 0, int(self.location.w * self.MAX_NAME_WIDTH), name.get_height())
crop.right = name.get_rect().right
name = name.subsurface(crop)
name_sprite.add_frame(name)
name_sprite.display_surface = surface
name_sprite.location.midleft = self.INDENT, self.location.centery
name_sprite.update()
file_sprite = Sprite(self)
box = get_boxed_surface(
pygame.Surface((self.location.w - name_sprite.location.w - self.INDENT * 3,
self.location.height - 4), pygame.SRCALPHA),
border=self.FOREGROUND)
file_sprite.add_frame(box)
file_sprite.location.midright = self.location.right - self.INDENT, self.location.centery
file_sprite.display_surface = surface
file_name_sprite = Sprite(self)
if self.is_bgm:
file_name = self.get_bgm().path
else:
file_name = self.get_sound_effect().path
file_name_text = self.font.render(file_name, True, self.FOREGROUND)
file_name_sprite.add_frame(file_name_text)
file_name_sprite.display_surface = box
file_name_sprite.location.midright = file_sprite.location.w - self.INDENT, file_sprite.location.h / 2
file_name_sprite.update()
file_sprite.update()
def get_sound_effect(self):
return self.get_game().get_audio().sfx[self.key]
def get_bgm(self):
return self.get_game().get_audio().bgm[self.key]
def update_config(self):
if self.is_bgm:
section_name = "bgm"
else:
section_name = "sfx"
if not self.get_configuration().has_section(section_name):
self.get_configuration().add_section(section_name)
if self.is_bgm:
bgm = self.get_bgm()
config_value = "{}, {:.2f}".format(bgm.path, bgm.volume)
else:
sound_effect = self.get_sound_effect()
config_value = "{}, {:.2f}, {:.2f}, {}, {:.2f}".format(
sound_effect.path, sound_effect.local_volume, sound_effect.fade_out_length,
sound_effect.loops, sound_effect.maxtime)
self.get_configuration().set(section_name, self.key, config_value)
config_path = self.get_configuration().locate_project_config_file()
backup_path = config_path + ".backup"
shutil.copyfile(config_path, backup_path)
self.get_configuration().write(open(config_path, "w"))
def set_clickable(self, clickable=True):
self.play_button.set_clickable(clickable)
self.stop_button.set_clickable(clickable)
self.volume_spinner.set_clickable(clickable)
if not self.is_bgm:
self.fade_out_spinner.set_clickable(clickable)
self.loops_spinner.set_clickable(clickable)
self.maxtime_spinner.set_clickable(clickable)
def update(self):
self.play_button.location.midleft = self.location.move(5, 0).midright
self.stop_button.location.midleft = self.play_button.location.midright
self.volume_spinner.location.midleft = self.stop_button.location.move(5, 0).midright
if not self.is_bgm:
self.fade_out_spinner.location.midleft = self.volume_spinner.location.midright
self.loops_spinner.location.midleft = self.fade_out_spinner.location.midright
self.maxtime_spinner.location.midleft = self.loops_spinner.location.midright
Sprite.update(self)
self.volume_spinner.update()
if not self.is_bgm:
self.fade_out_spinner.update()
self.loops_spinner.update()
self.maxtime_spinner.update()
self.play_button.update()
self.stop_button.update()
class AudioPanelFileBrowser(Sprite):
WIDTH = .75
HEIGHT = .75
COLORS = pygame.Color(255, 255, 255), pygame.Color(0, 0, 0)
HOME, UP = "[HOME]", "[UP]"
def __init__(self, parent):
Sprite.__init__(self, parent)
self.rows = []
self.font = self.parent.font_large
self.previewing_sound = None
self.previewing_sound_row = None
ds = self.get_display_surface()
dsr = ds.get_rect()
surface = pygame.Surface((dsr.w * self.WIDTH - 2, dsr.h * self.HEIGHT - 2), SRCALPHA)
surface.fill(self.COLORS[0])
self.background = get_boxed_surface(surface, self.COLORS[0], self.COLORS[1])
self.add_frame(self.background.copy())
self.location.center = dsr.center
self.reset()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def reset(self):
if self.previewing_sound is not None:
self.previewing_sound.stop()
# self.visit(self.HOME)
self.hide()
def respond(self, event):
if not self.is_hidden():
if event.button == 1:
if self.collide(event.pos):
for row in self.rows:
pos = Vector(*event.pos).get_moved(-self.location.left, -self.location.top)
if (not row.has_child("button") or pos.x < row.get_child("button").location.left) and row.collide(pos):
full_path = os.path.join(os.path.sep.join(self.trail), row.path)
if row.path == self.HOME or row.path == self.UP or \
os.path.isdir(full_path) and os.access(full_path, os.R_OK):
self.visit(row.path)
elif os.path.isfile(full_path) and os.access(full_path, os.R_OK):
loaded = False
selected = self.parent.get_selected()
if selected.is_bgm:
loaded = self.get_audio().set_bgm(full_path, selected.key)
else:
loaded = self.get_audio().load_sfx_file(full_path, selected.key, True)
if loaded:
selected.update_config()
self.hide()
self.get_delegate().cancel_propagation()
self.parent.build()
else:
self.hide()
self.get_delegate().cancel_propagation()
elif event.button == 4:
self.row_offset -= 1
elif event.button == 5:
self.row_offset += 1
def hide(self):
for row in self.parent.rows:
row.selected = False
row.stop_blinking()
row.set_clickable(True)
for row in self.rows:
if row.has_child("button"):
row.get_child("button").set_clickable(False)
if self.previewing_sound is not None:
self.previewing_sound.stop()
Sprite.hide(self)
def unhide(self):
for row in self.parent.rows:
row.set_clickable(False)
for row in self.rows:
if row.has_child("button"):
row.get_child("button").set_clickable()
Sprite.unhide(self)
def visit(self, path):
if path == self.UP and len(self.trail) > 1:
path = self.trail[-2]
self.trail = self.trail[:-2]
self.visit(path)
elif path != self.UP:
self.row_offset = 0
if path == self.HOME:
self.trail = []
self.paths = ["/"]
for option in "sfx-repository-path", "sfx-default-path", "sfx-project-path", \
"bgm-repository-path", "bgm-project-path":
for sfx_location in self.get_configuration("audio", option):
if self.get_resource(sfx_location):
self.paths.append(self.get_resource(sfx_location))
else:
self.paths = [self.HOME]
self.trail.append(path)
if len(self.trail) > 1:
self.paths.append(self.UP)
self.paths.extend(sorted(os.listdir(os.path.sep.join(self.trail))))
self.build()
def build(self):
for row in self.rows:
if row.has_child("button"):
row.get_child("button").unsubscribe()
del row
self.rows = []
for path in self.paths:
row = Sprite(self)
row.path = path
text = self.font.render(path, True, self.COLORS[1])
surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA)
surface.blit(text, (8, 0))
surface.fill(self.COLORS[1], (0, surface.get_height() - 1, self.location.w, 1))
row.add_frame(surface)
row.display_surface = self.get_current_frame()
row.location.bottom = 0
self.rows.append(row)
full_path = os.path.join(os.path.sep.join(self.trail), path)
if self.get_audio().is_sound_file(full_path):
button = AudioPanelButton(self, self.preview, {"path": full_path, "row": row}, [row, self])
row.set_child("button", button)
frame = pygame.Surface([text.get_height()] * 2, SRCALPHA)
w, h = frame.get_size()
pygame.draw.polygon(
frame, self.COLORS[1], ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5)))
button.add_frame(frame)
button.display_surface = row.get_current_frame()
button.location.right = self.location.w - 10
def preview(self, path, row):
is_bgm = self.parent.get_selected().is_bgm
audio = self.get_audio()
if is_bgm and audio.is_streamable(path) or not is_bgm and audio.is_loadable(path):
if self.previewing_sound is not None:
self.previewing_sound.stop()
pygame.mixer.music.stop()
if is_bgm:
pygame.mixer.music.load(path)
pygame.mixer.music.play(-1)
else:
self.previewing_sound = SoundEffect(self, path)
self.previewing_sound.play()
self.previewing_sound_row = row
def update(self):
self.get_current_frame().blit(self.background, (0, 0))
if not self.is_hidden():
corner = Vector(1, 1)
index = self.row_offset
for row in self.rows:
row.remove_locations()
row.location.bottom = 0
while corner.y < self.location.h:
row = self.rows[index % len(self.rows)]
if index - self.row_offset >= len(self.rows):
row.add_location(corner.copy())
else:
row.location.topleft = corner.copy()
corner.y += row.location.height
index += 1
for row in self.rows:
row.update()
for location in row.locations:
if location.collidepoint(*Vector(*pygame.mouse.get_pos()).get_moved(
-self.location.left, -self.location.top)) or \
row == self.previewing_sound_row:
self.get_current_frame().fill(self.COLORS[1], (
location.topleft, (6, location.h)))
self.get_current_frame().fill(self.COLORS[1], (
location.move(-8, 0).topright, (6, location.h)))
Sprite.update(self)
class AudioPanelSpinner(Sprite):
def __init__(self, parent, font, label_font, width=80, height=48,
magnitude=1, value=0, callback=None,
foreground=pygame.Color(0, 0, 0),
background=pygame.Color(255, 255, 255), precision=0,
label_text=""):
Sprite.__init__(self, parent)
self.magnitude, self.value = magnitude, value
self.background, self.foreground = background, foreground
self.precision = precision
self.callback = callback
self.font = font
self.label_font = label_font
surface = pygame.Surface((width, height), SRCALPHA)
surface.fill(background)
self.add_frame(surface)
self.label = Sprite(self)
self.label.add_frame(self.label_font.render(label_text, True, foreground))
self.label.display_surface = self.get_current_frame()
self.display = Sprite(self)
self.display.display_surface = self.get_current_frame()
self.update_display()
self.up_button = Sprite(self)
self.up_button.add_frame(render_box(
width=width - self.display.location.w - 2, height=int(height * .5) - 2,
color=foreground, border=foreground, font=self.font, text="+"))
self.up_button.location.left = self.display.location.right - 1
self.up_button.display_surface = self.get_current_frame()
self.down_button = Sprite(self)
self.down_button.add_frame(render_box(
width=self.up_button.location.w - 2, height=self.up_button.location.h - 1,
border=foreground, font=self.font, text="-"))
self.down_button.location.topleft = self.display.location.right - 1, \
self.up_button.location.bottom - 1
self.down_button.display_surface = self.get_current_frame()
self.set_clickable()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def unsubscribe(self, callback=None, kind=None):
if callback is None:
callback = self.respond
kind = pygame.MOUSEBUTTONDOWN
GameChild.unsubscribe(self, callback, kind)
def update_display(self):
self.display.clear_frames()
self.display.add_frame(render_box(
width=int(self.location.w * .7) - 2,
border=self.foreground, font=self.font, text="{:.{precision}f}".format(
self.value, precision=self.precision)))
self.display.location.bottomleft = 0, self.location.h
def increment(self, up=True):
step = self.magnitude * [-1, 1][up]
self.value += step
if self.callback is not None:
response = self.callback(step)
if response is not None:
self.value = response
self.update_display()
def respond(self, event):
if self.clickable and event.button == 1:
relative_position = Vector(*event.pos).get_moved(
-self.location.left, -self.location.top)
up_collides = self.up_button.collide(relative_position)
down_collides = self.down_button.collide(relative_position)
if up_collides or down_collides:
if up_collides:
self.increment()
else:
self.increment(False)
self.parent.update_config()
def set_clickable(self, clickable=True):
self.clickable = clickable
def update(self):
self.get_current_frame().fill(self.background)
self.label.update()
self.up_button.update()
self.down_button.update()
self.display.update()
Sprite.update(self)
class AudioPanelButton(Sprite):
def __init__(self, parent, callback, callback_kwargs={}, containers=[], pass_mods=False):
Sprite.__init__(self, parent)
self.callback = callback
self.callback_kwargs = callback_kwargs
self.containers = containers
self.pass_mods = pass_mods
self.set_clickable()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def unsubscribe(self, callback=None, kind=None):
if callback is None:
callback = self.respond
kind = pygame.MOUSEBUTTONDOWN
Sprite.unsubscribe(self, callback, kind)
def respond(self, event):
if self.get_audio().audio_panel.active and self.clickable and event.button == 1:
pos = Vector(*event.pos)
for container in self.containers:
pos.move(-container.location.left, -container.location.top)
if self.collide(pos):
if self.pass_mods:
kwargs = collections.ChainMap(self.callback_kwargs, {"mods": pygame.key.get_mods()})
else:
kwargs = self.callback_kwargs
self.callback(**kwargs)
def set_clickable(self, clickable=True):
self.clickable = clickable
music.pause()
if channel:
channel.pause()
self.paused = not paused

View File

@ -1,505 +0,0 @@
import argparse
from os import sep, getcwd
from os.path import join, exists, basename, dirname, expanduser
from sys import argv, version_info
from re import match
from pprint import pformat
if version_info[0] >= 3:
from configparser import RawConfigParser
else:
from ConfigParser import RawConfigParser
class Configuration(RawConfigParser):
default_project_file_rel_path = "config"
default_resource_paths = [".", "resource"]
def __init__(self, project_file_rel_path=None, resource_path=None, type_declarations=None):
RawConfigParser.__init__(self)
self.project_file_rel_path = project_file_rel_path
self.resource_path = resource_path
self.modifiable = {}
self.order = []
self.set_type_declarations(type_declarations)
self.set_defaults()
self.read_project_config_file()
self.merge_command_line()
self.modify_defaults()
self.print_debug(self)
def set_type_declarations(self, type_declarations):
if type_declarations is None:
type_declarations = TypeDeclarations()
self.type_declarations = type_declarations
def translate_path(self, path):
new = ""
if path and path[0] == sep:
new += sep
return expanduser("{0}{1}".format(new, join(*path.split(sep))))
def set_defaults(self):
add_section = self.add_section
set_option = self.set
section = "setup"
add_section(section)
set_option(section, "package-root", basename(getcwd()), False)
set_option(section, "additional-packages", "", False)
set_option(section, "title", "", False)
set_option(section, "classifiers", "", False)
set_option(section, "resource-search-path", "./, resource/", False)
set_option(section, "installation-dir", "/usr/local/share/games/", False)
set_option(section, "changelog", "changelog", False)
set_option(section, "description-file", "", False)
set_option(section, "init-script", "", False)
set_option(section, "version", "", False)
set_option(section, "summary", "", False)
set_option(section, "license", "", False)
set_option(section, "platforms", "", False)
set_option(section, "contact-name", "", False)
set_option(section, "contact-email", "", False)
set_option(section, "url", "", False)
set_option(section, "requirements", "", False)
set_option(section, "main-object", "pgfw/Game.py", False)
set_option(section, "resource-path-identifier", "resource_path", False)
set_option(section, "special-char-placeholder", "_", False)
set_option(section, "whitespace-placeholder", "-", False)
set_option(section, "windows-dist-path", "dist/win/", False)
set_option(section, "windows-icon-path", "", False)
set_option(section, "boolean-true-lowercase", "yes, true, t, 1", False)
set_option(section, "osx-includes", "", False)
section = "display"
add_section(section)
set_option(section, "dimensions", "480, 360", False)
set_option(section, "frame-duration", "40", False)
set_option(section, "wait-duration", "2", False)
set_option(section, "caption", "", False)
set_option(section, "centered", "yes", False)
set_option(section, "icon-path", "", False)
set_option(section, "skip-frames", "no", False)
set_option(section, "fullscreen", "no", False)
set_option(section, "windowed-flag", "wi", False)
set_option(section, "show-framerate", "no", False)
set_option(section, "framerate-display-flag", "fr", False)
set_option(section, "framerate-text-size", "16", False)
set_option(section, "framerate-text-color", "0, 0, 0", False)
set_option(section, "framerate-text-background", "255, 255, 255", False)
set_option(section, "framerate-position", "-1, 0", False)
set_option(section, "use-framebuffer", "no", False)
section = "input"
add_section(section)
set_option(section, "release-suffix", "-release", False)
set_option(section, "confirm-quit", "no", False)
section = "sprite"
add_section(section)
set_option(section, "transparent-color", "magenta", False)
section = "screen-captures"
add_section(section)
set_option(section, "rel-path", "caps", False)
set_option(section, "file-name-format", "%Y%m%d%H%M%S", False)
set_option(section, "file-extension", "png", False)
section = "video-recordings"
add_section(section)
set_option(section, "enable", "no", False)
set_option(section, "rel-path", "vids", False)
set_option(section, "directory-name-format", "%Y%m%d%H%M%S", False)
set_option(section, "file-extension", "png", False)
set_option(section, "frame-format", "RGB", False)
set_option(section, "framerate", "40", False)
set_option(section, "temp-directory", "", False)
set_option(section, "record-audio", "yes", False)
set_option(section, "filename-digits", "6", False)
section = "mouse"
add_section(section)
set_option(section, "visible", "yes", False)
set_option(section, "double-click-time-limit", ".5", False)
section = "keys"
add_section(section)
set_option(section, "up", "K_UP", False)
set_option(section, "right", "K_RIGHT", False)
set_option(section, "down", "K_DOWN", False)
set_option(section, "left", "K_LEFT", False)
set_option(section, "capture-screen", "K_F9", False)
set_option(section, "toggle-fullscreen", "K_F11", False)
set_option(section, "reset-game", "K_F8", False)
set_option(section, "record-video", "K_F10", False)
set_option(section, "volume-down", "K_F1", False)
set_option(section, "volume-up", "K_F2", False)
set_option(section, "volume-mute", "K_F3", False)
set_option(section, "toggle-interpolator", "K_F7", False)
set_option(section, "toggle-audio-panel", "K_F6", False)
section = "joy"
add_section(section)
set_option(section, "advance", "7", False)
set_option(section, "pause", "7", False)
set_option(section, "select", "6", False)
set_option(section, "single-xy", "no", False)
set_option(section, "delay-axis", "0", False)
set_option(section, "vertical-axis", "1", False)
set_option(section, "horizontal-axis", "0", False)
section = "event"
add_section(section)
set_option(section, "user-event-id", "USEREVENT", False)
set_option(section, "command-id-offset", "1", False)
set_option(section, "command-key", "command", False)
set_option(section, "cancel-flag-key", "cancel", False)
section = "audio"
add_section(section)
set_option(section, "sfx-default-path", "~/storage/audio/sfx/default", False)
set_option(section, "sfx-repository-path", "~/storage/audio/sfx/all", False)
set_option(section, "sfx-project-path", "sfx", False)
set_option(section, "sfx-extensions", "wav, ogg, mp3", False)
set_option(section, "bgm-repository-path", "~/storage/audio/bgm", False)
set_option(section, "bgm-project-path", "bgm", False)
set_option(section, "sfx-volume", "1.0", False)
set_option(section, "bgm-volume", "1.0", False)
set_option(section, "volume", "1.0", False)
set_option(section, "panel-enabled", "no", False)
set_option(section, "panel-font", None, False)
set_option(section, "auto-load", "yes", False)
section = "interpolator-gui"
add_section(section)
set_option(section, "margin", "80", False)
set_option(section, "marker-color", "255, 0, 0", False)
set_option(section, "marker-size", "11", False)
set_option(section, "curve-color", "0, 255, 0", False)
set_option(section, "label-size", "16", False)
set_option(section, "label-precision", "2", False)
set_option(section, "axis-label-count", "8", False)
set_option(section, "prompt-size", "380, 60", False)
set_option(section, "prompt-border-color", "255, 0, 0", False)
set_option(section, "prompt-border-width", "3", False)
set_option(section, "prompt-character-limit", "21", False)
set_option(section, "prompt-text-size", "42", False)
set_option(section, "template-nodeset", "L 0 0, 1000 1", False)
set_option(section, "template-nodeset-name", "template", False)
set_option(section, "flat-y-range", "1", False)
def add_section(self, name):
if name not in self.order:
self.order.append(name)
RawConfigParser.add_section(self, name)
def set(self, section, option, value, modifiable=True):
if modifiable:
if section not in self.order:
self.order.append(section)
if section not in self.modifiable:
self.modifiable[section] = []
if option not in self.modifiable[section]:
self.modifiable[section].append(option)
RawConfigParser.set(self, section, option, value)
def read_project_config_file(self):
path = self.locate_project_config_file()
if path:
fp = open(path)
self.set_modifiable(fp)
fp.seek(0)
self.readfp(fp)
fp.seek(0)
self.set_order(fp)
fp.close()
else:
self.print_debug("No configuration file found")
def locate_project_config_file(self):
rel_path = self.project_file_rel_path
if not rel_path:
rel_path = self.default_project_file_rel_path
if exists(rel_path) and not self.is_shared_mode():
return rel_path
if self.resource_path:
installed_path = join(self.resource_path, rel_path)
if exists(installed_path):
return installed_path
def set_order(self, fp):
self.order = order = []
for line in open(self.locate_project_config_file()):
result = match("^\s*\[(.*)\]\s*$", line)
if result:
order.append(result.group(1))
def set_modifiable(self, fp):
config = RawConfigParser()
config.readfp(fp)
modifiable = self.modifiable
for section in config._sections:
if section not in modifiable:
modifiable[section] = []
for option in config._sections[section]:
if option != "__name__" and option not in modifiable[section]:
modifiable[section].append(option)
def is_shared_mode(self):
return "-s" in argv
def print_debug(self, statement):
if self.is_debug_mode():
print(statement)
def is_debug_mode(self):
return "-d" in argv
def modify_defaults(self):
self.set_installation_path()
self.set_resource_search_path()
self.set_screen_captures_path()
self.set_video_recordings_path()
self.set_data_exclusion_list()
self.set_requirements()
def set_installation_path(self):
self.set("setup", "installation-path",
join(self.get("setup", "installation-dir"),
self.get("setup", "package-root")), False)
def set_resource_search_path(self):
section, option = "setup", "resource-search-path"
search_path = self.get(section, option)
if self.resource_path:
search_path.append(self.resource_path)
else:
search_path.append(self.get("setup", "installation-path"))
self.set(section, option, search_path, False)
def get(self, section, option):
value = RawConfigParser.get(self, section, option)
if value is None:
value = self.get_substitute(section, option)
return self.cast_value(section, option, value)
def get_substitute(self, section, option):
if section == "display":
if option == "caption":
return self.get("setup", "title")
def cast_value(self, section, option, value):
pair = section, option
types = self.type_declarations
# if type(value) == str or type(value) == unicode:
if type(value) == str:
if pair in types["bool"]:
return value.lower() in self.get("setup", "boolean-true-lowercase")
elif pair in types["int"]:
return int(value)
elif pair in types["float"]:
return float(value)
elif pair in types["path"]:
return self.translate_path(value)
elif pair in types["list"]:
if value == "":
return []
else:
return [member.strip() for member in value.split(types.list_member_sep)]
elif pair in types["int-list"]:
return [int(member) for member in value.split(types.list_member_sep)]
elif pair in types["float-list"]:
return [float(member) for member in value.split(types.list_member_sep)]
elif pair in types["path-list"]:
return [self.translate_path(member.strip()) for member in value.split(types.list_member_sep)]
return value
def set_screen_captures_path(self):
section, option = "screen-captures", "path"
if not self.has_option(section, option):
self.set(section, option, join(self.build_home_path(), self.get(section, "rel-path")), False)
def build_home_path(self):
return join("~", "." + self.get("setup", "package-root"))
def set_video_recordings_path(self):
section, option = "video-recordings", "path"
if not self.has_option(section, option):
self.set(section, option, join(self.build_home_path(), self.get(section, "rel-path")), False)
def set_data_exclusion_list(self):
section, option = "setup", "data-exclude"
exclude = []
if self.has_option(section, option):
exclude = self.get(section, option)
exclude += [".git*", "README", "build/", "dist/", "*.egg-info",
"*.py", "MANIFEST*", "PKG-INFO", "*.pyc", "*.swp", "*~",
self.get("setup", "changelog"),
self.get("setup", "package-root"),
self.get("setup", "init-script")]
for location in self.get("setup", "additional-packages"):
exclude.append(location)
self.set(section, option, exclude, False)
def set_requirements(self):
section, option = "setup", "requirements"
requirements = []
if self.has_option(section, option):
requirements = self.get(section, option)
if "pygame" not in requirements:
requirements.append("pygame")
self.set(section, option, requirements, False)
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)
def items(self, section):
items = []
for option in self.options(section):
items.append((option, self.get(section, option)))
return items
def write(self, fp=None):
modifiable = self.modifiable
use_main = fp is None
if use_main:
path = self.locate_project_config_file()
if not path:
path = join(self.resource_path or "", self.default_project_file_rel_path)
fp = open(path, "w")
break_line = False
for section in self.order:
if section in modifiable:
if break_line:
fp.write("\n")
fp.write("[%s]\n" % section)
for option in modifiable[section]:
if self.has_option(section, option):
raw_value = self.get_raw_value(self.get(section, option))
# handle multiline entries
if "\n" in raw_value:
lines = raw_value.split("\n")
fp.write("{} = {}\n".format(option, lines[0]))
for line in lines[1:]:
# have to indent lines after the first to be compatible with configparser
fp.write(" {}\n".format(line))
else:
fp.write("{} = {}\n".format(option, raw_value))
break_line = True
if use_main:
fp.close()
def get_raw_value(self, value):
if isinstance(value, list):
raw = ""
for ii, value in enumerate(value):
if ii:
raw += ", "
raw += str(value)
else:
raw = str(value)
return raw
def clear_section(self, section):
if self.has_section(section):
for option in self.options(section):
self.remove_option(section, option)
def merge_command_line(self, command_line=None):
"""
Instantiate an argument parser to parse the command line. Load all settings submitted through the `--config` flag. Each setting
is a string starting with the chosen delimiter, followed by the section name, option name, and option value each separated by the
chosen delimiter.
For example, set the screen dimensions using "|" as the delimiter and set the Q key to the quit command using "," as the
delimiter,
$ ./OPEN-GAME --config "|display|dimensions|960,540" ",keys,quit,K_q"
@param command_line A list of strings to use as the command line. If this is `None`, `sys.argv` is used instead.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--config", nargs="*", default=[])
# Loop over all assignment strings found
for assignment in parser.parse_known_args(command_line)[0].config:
# Split each assigment string by the first character in the string and pass along the parsed values as arguments
# to the set member function
self.set(*assignment.split(assignment[0])[1:])
class TypeDeclarations(dict):
list_member_sep = ','
defaults = {
"display": {"int": ["frame-duration", "wait-duration", "framerate-text-size"],
"bool": ["centered", "skip-frames", "fullscreen", "show-framerate", "use-framebuffer"],
"int-list": ["dimensions", "framerate-text-color", "framerate-text-background", "framerate-position"]},
"input": {"bool": "confirm-quit"},
"screen-captures": {"path": ["rel-path", "path"]},
"video-recordings": {"path": ["rel-path", "path"],
"int": ["framerate", "filename-digits"],
"bool": ["enable", "record-audio"]},
"setup": {"list": ["classifiers", "resource-search-path", "requirements", "data-exclude", "additional-packages", "osx-includes",
"boolean-true-lowercase"],
"path": ["installation-dir", "changelog", "description-file", "main-object", "icon-path", "windows-dist-path", "package-root"]},
"mouse": {"float": "double-click-time-limit",
"bool": "visible"},
"keys": {"list": ["up", "right", "down", "left"]},
"joy": {"int": ["advance", "pause", "select", "vertical-axis", "horizontal-axis"],
"float": "delay-axis",
"bool": "single-xy"},
"audio": {
"list": "sfx-extensions",
"path": "panel-font",
"path-list": ["sfx-default-path", "sfx-repository-path", "sfx-project-path", "bgm-repository-path", "bgm-project-path"],
"float": ["sfx-volume", "bgm-volume", "volume"],
"bool": ["panel-enabled", "auto-load"]
},
"event": {"int": "command-id-offset"},
"interpolator-gui": {"int": ["margin", "marker-size", "label-size", "axis-label-count", "label-precision", "prompt-border-width",
"prompt-character-limit", "prompt-text-size", "flat-y-range"],
"int-list": ["marker-color", "curve-color", "prompt-size", "prompt-border-color"]},
}
additional_defaults = {}
def __init__(self):
dict.__init__(self, {"bool": [], "int": [], "float": [], "path": [], "list": [], "int-list": [], "float-list": [], "path-list": []})
self.add_chart(self.defaults)
self.add_chart(self.additional_defaults)
def add(self, cast, section, option):
self[cast].append((section, option))
def add_chart(self, chart):
for section, declarations in chart.items():
for cast, options in declarations.items():
if type(options) != list:
options = [options]
for option in options:
self.add(cast, section, option)

View File

@ -1,92 +0,0 @@
import collections
from pygame.event import get, pump, Event, post
from pygame.locals import *
from .GameChild import GameChild
from .Input import Input
class Delegate(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
self.subscribers = collections.OrderedDict()
self.load_configuration()
self.disable()
self.cancelling_propagation = False
def load_configuration(self):
config = self.get_configuration("event")
self.cancel_flag_key = config["cancel-flag-key"]
self.command_key = config["command-key"]
self.command_event_id = config["command-id-offset"] + globals()[config["user-event-id"]]
def disable(self):
self.enabled = False
def enable(self):
self.enabled = True
self.interpolator = self.get_game().interpolator
def dispatch(self):
if self.enabled:
subscribers = self.subscribers
for evt in get():
kind = evt.type
if kind in subscribers:
for subscriber in subscribers[kind]:
if not self.interpolator.is_gui_active() or hasattr(subscriber, "im_class") and \
(subscriber.im_class == Input or subscriber.im_class == \
self.interpolator.gui.__class__):
self.print_debug("Passing %s to %s" % (evt, subscriber))
if not self.cancelling_propagation:
subscriber(evt)
else:
self.cancelling_propagation = False
break
else:
pump()
def cancel_propagation(self):
self.cancelling_propagation = True
def add_subscriber(self, callback, kind=None):
self.print_debug("Subscribing %s to %s" % (callback, kind))
if kind is None:
kind = self.command_event_id
subscribers = self.subscribers
if kind not in subscribers:
subscribers[kind] = list()
subscribers[kind].append(callback)
def is_command(self, event):
return event.type == self.command_event_id
def remove_subscriber(self, callback, kind=None):
if kind is None:
kind = self.command_event_id
self.subscribers[kind].remove(callback)
def compare(self, evt, commands=None, cancel=False, **attributes):
if self.is_command(evt):
self.add_cancel_flag_to_attributes(attributes, cancel)
if commands is not None and not isinstance(commands, list):
commands = [commands]
if commands is not None:
if not self.command_in_list(evt, commands):
return False
return all(key in evt.dict and evt.dict[key] == value for key, value in attributes.items())
def add_cancel_flag_to_attributes(self, attributes, cancel):
attributes[self.cancel_flag_key] = cancel
def command_in_list(self, evt, commands):
return self.get_command_attribute(evt) in commands
def get_command_attribute(self, evt):
return (self.command_key in evt.dict) and evt.dict[self.command_key]
def post(self, command=None, cancel=False, **attributes):
attributes[self.command_key] = command
self.add_cancel_flag_to_attributes(attributes, cancel)
post(Event(self.command_event_id, attributes))

View File

@ -1,112 +1,30 @@
from os import environ
from sys import maxsize, platform
from pygame import display, image
import pygame
from pygame import image, mouse
from pygame.locals import *
from .GameChild import *
from GameChild import *
class Display(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
print(f"available fullscreen modes: {pygame.display.list_modes()}")
self.delegate = self.get_delegate()
self.load_configuration()
self.align_window()
self.init_screen()
self.config = self.get_configuration()
self.set_screen()
self.set_caption()
self.set_icon()
self.set_mouse_visibility()
self.subscribe(self.toggle_fullscreen)
def load_configuration(self):
config = self.get_configuration("display")
self.centered = config["centered"]
self.fullscreen_enabled = config["fullscreen"]
self.caption = config["caption"]
self.windowed_flag = config["windowed-flag"]
self.icon_path = self.get_resource("display", "icon-path")
self.mouse_visibility = self.get_configuration("mouse", "visible")
def align_window(self):
if self.centered:
environ["SDL_VIDEO_CENTERED"] = "1"
def init_screen(self):
full = False
if self.fullscreen_requested():
full = True
# Added pygame.SCALED to avoid fullscreen bug
# <https://www.pygame.org/docs/ref/display.html#pygame.display.toggle_fullscreen>
try:
self.set_screen(pygame.SCALED, fs=full)
except AttributeError:
self.set_screen(fs=full)
def fullscreen_requested(self):
return not self.check_command_line(self.windowed_flag) and self.fullscreen_enabled
def set_screen(self, flags=0x0, dimensions=None, fs=None):
"""
Initialize the pygame display, passing along any flags, and assign the returned display surface to `self.screen`. If no
dimensions are passed, use the dimensions of the current screen surface if it exists, otherwise get dimensions from the
configuration. If `fs` is `None`, leave the fullscreen state as is, otherwise read it as a boolean and set fullscreen
accordingly.
@param flags Flags from https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode. To pass multiple flags,
join them with the OR operator.
@param dimensions Dimensions of the screen surface as (width, height)
@param fs Set to True or False or omit to keep current fullscreen state
"""
# Try to auto discover dimensions if not provided
if dimensions is None:
if pygame.display.get_surface():
dimensions = pygame.display.get_surface().get_size()
else:
dimensions = self.get_configuration("display", "dimensions")
if fs is None:
# Get the current fullscreen state
fs = bool(pygame.display.get_surface().get_flags() & pygame.FULLSCREEN)
# Get a display surface with specified fullscreen state
if fs:
self.screen = pygame.display.set_mode(dimensions, flags | pygame.FULLSCREEN)
else:
self.screen = pygame.display.set_mode(dimensions, flags)
# Redraw the spline interpolator interface if enabled
if self.get_game().interpolator.gui_enabled:
self.get_game().interpolator.gui.rearrange()
def rotate(self):
"""
Rotate the screen surface 90 degrees by resetting it with swapped W and H dimensions.
"""
current_width, current_height = self.get_display_surface().get_size()
self.set_screen(dimensions=(current_height, current_width))
def set_screen(self):
self.screen = display.set_mode(self.config.get("display", "dimensions",
"int-list"))
def set_caption(self):
pygame.display.set_caption(self.caption)
display.set_caption(self.config.get("display", "caption"))
def set_icon(self):
if self.icon_path:
pygame.display.set_icon(image.load(self.icon_path).convert_alpha())
def set_mouse_visibility(self, visibility=None):
if visibility is None:
visibility = self.mouse_visibility
return mouse.set_visible(visibility)
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()
def toggle_fullscreen(self, event):
if self.delegate.compare(event, "toggle-fullscreen"):
pygame.display.toggle_fullscreen()

41
pgfw/EventDelegate.py Normal file
View File

@ -0,0 +1,41 @@
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()
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]:
self.print_debug("Passing {0} to {1}".\
format(evt, subscriber))
subscriber(evt)
else:
event.pump()
def add_subscriber(self, kind, callback):
self.print_debug("Subscribing {0} to {1}".\
format(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)

View File

@ -1,129 +1,81 @@
import os, pygame, sys
from os import environ
import pygame
from pygame import display
from pygame.locals import *
from .Animation import Animation
from .Mainloop import Mainloop
from .Audio import Audio
from .Display import Display
from .Configuration import Configuration
from .Delegate import Delegate
from .Input import Input
from .ScreenGrabber import ScreenGrabber
from .Profile import Profile
from .VideoRecorder import VideoRecorder
from .Interpolator import Interpolator
from .TimeFilter import TimeFilter
from .Sprite import Sprite
from .confirm_quit_message import CONFIRM_QUIT_MESSAGE
from .extension import *
from GameChild import *
from Animation import *
from Audio import *
from Display import *
from configuration.Configuration import *
from EventDelegate import *
from Input import *
from ScreenGrabber import *
class Game(Animation):
class Game(GameChild, Animation):
resources_path = None
resource_path = None
def __init__(self, config_rel_path=None, type_declarations=None):
self.confirming_quit = False
self.profile = Profile(self)
self.time_filter = TimeFilter(self)
Animation.__init__(self, self)
self.print_debug(pygame.version.ver)
print(sys.version_info)
def __init__(self, config_rel_path=None):
self.init_gamechild()
self.config_rel_path = config_rel_path
self.type_declarations = type_declarations
self.set_configuration()
if self.get_configuration("display", "use-framebuffer"):
os.putenv("SDL_FBDEV", "/dev/fb0")
os.putenv("SDL_VIDEODRIVER", "fbcon")
os.putenv("SDL_NOMOUSE", "1")
self.init_animation()
self.align_window()
pygame.init()
self.set_children()
self.register(self.get_input().unsuppress)
self.subscribe_to(QUIT, self.end)
self.subscribe_to(self.get_user_event_id(), self.end)
self.clear_queue()
self.delegate.enable()
def init_gamechild(self):
GameChild.__init__(self)
def set_configuration(self):
self.configuration = Configuration(self.config_rel_path, self.resource_path, self.type_declarations)
self.configuration = Configuration(self.config_rel_path,
self.resources_path)
def init_animation(self):
Animation.__init__(self,
self.configuration.get("display", "frame-duration",
"int"))
def align_window(self):
if self.configuration.get("display", "centered", "bool"):
environ["SDL_VIDEO_CENTERED"] = "1"
def set_children(self):
"""
Create the child objects that encapsulate other parts of the framework. They're usually used as singletons or managers. Subscribe
to the pygame.QUIT event.
"""
self.delegate = Delegate(self)
self.subscribe(self.end, QUIT)
self.subscribe(self.end)
self.interpolator = Interpolator(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)
self.mainloop = Mainloop(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)
self.video_recorder = VideoRecorder(self)
# Call this separately because it needs the display to be initialized previously
self.interpolator.init_gui()
def frame(self):
self.time_filter.update()
self.delegate.dispatch()
if not self.interpolator.is_gui_active() and not self.get_audio().is_audio_panel_active():
Animation.update(self)
if self.confirming_quit:
self.time_filter.close()
self.draw_confirm_quit_dialog()
else:
self.time_filter.open()
self.update()
elif self.interpolator.is_gui_active():
self.interpolator.gui.update()
self.audio.update()
if self.video_recorder.requested:
self.video_recorder.update()
def draw_confirm_quit_dialog(self):
dialog = Sprite(self)
dialog.add_frame(self.get_confirm_quit_surface())
dialog.location.center = self.get_display_surface().get_rect().center
dialog.update()
def get_confirm_quit_surface(self):
lines = CONFIRM_QUIT_MESSAGE.strip().split("\n")
w, h = len(lines[0]), len(lines)
text = pygame.Surface((w, h), SRCALPHA)
for y, line in enumerate(lines):
for x, char in enumerate(line.strip()):
if char == "#":
text.set_at((x, y), (0, 0, 0))
text = pygame.transform.scale2x(text)
text = pygame.transform.scale2x(text)
return get_boxed_surface(
text, background=(255, 255, 255), border=(0, 0, 0), border_width=24, padding=12)
def run(self):
self.mainloop.run()
def sequence(self):
self.delegate.dispatch_events()
self.update()
display.update()
def update(self):
pass
def blit(self, source, destination, area=None, special_flags=0):
self.get_screen().blit(source, destination, area, special_flags)
def get_rect(self):
return self.get_screen().get_rect()
def suppress_input_temporarily(self, length=700):
self.get_input().suppress()
self.play(self.get_input().unsuppress, delay=length, play_once=True)
def end(self, event):
if event.type == QUIT or self.delegate.compare(event, "quit"):
if self.confirming_quit or not self.get_configuration("input", "confirm-quit"):
self.mainloop.stop()
self.profile.end()
else:
print("press quit again to confirm or press any other button to cancel...")
self.confirming_quit = True
elif self.delegate.compare(event, "any"):
if self.confirming_quit and self.delegate.compare(event, "any"):
print("cancelled quit...")
self.confirming_quit = False
self.delegate.cancel_propagation()
def end(self, evt):
if evt.type == QUIT or self.is_command(evt, "quit"):
self.stop()

View File

@ -1,19 +1,15 @@
from os.path import exists, join, basename, normpath, abspath, commonprefix, sep
from sys import argv, version_info
from os.path import exists, join, basename, normpath, abspath
from sys import argv
from pygame import mixer, event, time
from pygame import mixer
from pygame.locals import *
if version_info[0] >= 3:
from . import Game
else:
import Game
import Game
class GameChild:
def __init__(self, parent=None):
self.parent = parent
self.game = self.get_game()
def get_game(self):
current = self
@ -21,74 +17,57 @@ class GameChild:
current = current.parent
return current
def get_configuration(self, section=None, option=None, linebreaks=True):
config = self.game.configuration
if option is None and section is None:
return config
elif option and section:
if config.has_option(section, option):
rvalue = config.get(section, option)
if not linebreaks and isinstance(rvalue, str):
rvalue = rvalue.replace("\n", " ")
return rvalue
elif option is None:
if config.has_section(section):
return config.get_section(section)
else:
return {}
def get_configuration(self):
return self.get_game().configuration
def get_input(self):
return self.game.input
return self.get_game().input
def get_screen(self):
return self.game.display.get_screen()
def get_display_surface(self):
current = self
attribute = "display_surface"
while not isinstance(current, Game.Game):
if hasattr(current, attribute):
return getattr(current, attribute)
current = current.parent
return current.display.get_screen()
return self.get_game().display.get_screen()
def get_audio(self):
return self.game.audio
return self.get_game().audio
def get_delegate(self):
return self.game.delegate
return self.get_game().delegate
def get_resource(self, path_or_section, option=None):
def get_resource(self, section, option):
config = self.get_configuration()
rel_path = path_or_section
if option is not None:
rel_path = config.get(path_or_section, option)
if rel_path:
for root in config.get("setup", "resource-search-path"):
if self.is_shared_mode() and not self.is_absolute_path(root):
continue
path = join(root, rel_path)
if exists(path):
return normpath(path)
def is_shared_mode(self):
return self.check_command_line("s")
def check_command_line(self, flag):
return "-" + flag in argv
rel_path = config.get(section, option, "path")
for root in config.get("setup", "resources-search-path", "list"):
if self.is_shared_mode() and not self.is_absolute_path(root):
continue
path = join(root, rel_path)
if (exists(path)):
return path
self.print_debug("Couldn't find resource: {0}, {1}".\
format(section, option))
def print_debug(self, statement):
if self.is_debug_mode():
print(statement)
def is_debug_mode(self):
return self.check_command_line("d")
print statement
def is_absolute_path(self, path):
return normpath(path) == abspath(path)
def subscribe(self, callback, kind=None):
self.game.delegate.add_subscriber(callback, kind)
def is_shared_mode(self):
return "-s" in argv
def unsubscribe(self, callback, kind=None):
self.game.delegate.remove_subscriber(callback, kind)
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
def get_user_event_id(self):
return globals()[self.get_configuration().get("event",
"user-event-id")]
def is_command(self, evt, cmd):
name = self.get_configuration().get("event", "command-event-name")
return evt.type == self.get_user_event_id() and evt.name == name and \
evt.command == cmd

View File

@ -1,30 +0,0 @@
from .GameChild import GameChild
from .extension import get_random_hsla_color, get_range_steps, get_hsla_color
class Gradient(GameChild):
def __init__(self, parent, rect, granularity, start=None, end=None):
GameChild.__init__(self, parent)
if start is None:
start = get_random_hsla_color()
self.start = start
if end is None:
end = get_random_hsla_color()
self.end = end
self.rect = rect
self.granularity = granularity
def update(self):
ds = self.get_display_surface()
y = self.rect.top
hues = list(get_range_steps(self.start.hsla[0], self.end.hsla[0],
self.granularity))
saturations = list(get_range_steps(self.start.hsla[1], self.end.hsla[1],
self.granularity))
lightnesses = list(get_range_steps(self.start.hsla[2], self.end.hsla[2],
self.granularity))
for index in range(len(hues)):
h = self.rect.h // self.granularity
ds.fill(get_hsla_color(int(hues[index]), int(saturations[index]), int(lightnesses[index])),
(self.rect.left, y, self.rect.w, h))
y += h

View File

@ -1,40 +1,21 @@
from time import time as get_secs
from pygame import joystick as joy
from pygame.key import get_pressed, get_mods
from pygame import event, joystick as joy, key as keyboard
from pygame.locals import *
from .GameChild import *
from GameChild import *
class Input(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
self.last_mouse_down_left = None
self.axes_cancelled = {"up": True, "right": True, "down": True, "left": True}
self.last_command_post_time = get_secs()
self.joystick = Joystick()
self.delegate = self.get_delegate()
self.load_configuration()
self.set_any_press_ignore_list()
self.unsuppress()
self.unsuppress_any_on_mods()
self.subscribe_to_events()
self.build_key_map()
self.build_joy_button_map()
def load_configuration(self):
self.release_suffix = self.get_configuration("input", "release-suffix")
self.key_commands = self.get_configuration().items("keys")
self.double_click_time_limit = self.get_configuration(
"mouse", "double-click-time-limit")
def set_any_press_ignore_list(self):
self.any_press_ignored = set(["capture-screen", "toggle-fullscreen",
"reset-game", "record-video", "quit",
"toggle-interpolator", "toggle-audio-panel",
"volume-down", "volume-up", "volume-mute"])
self.any_press_ignored_keys = set()
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
@ -42,135 +23,55 @@ class Input(GameChild):
def unsuppress(self):
self.suppressed = False
def is_suppressed(self):
'''
Return True if input is suppressed
'''
return self.suppressed
def unsuppress_any_on_mods(self):
'''
Prevent modifier keys from triggering an any key event
'''
self.suppressed_any_key_on_mods = False
def suppress_any_key_on_mods(self):
'''
Allow modifier keys to trigger an any key event
'''
self.suppressed_any_key_on_mods = True
def subscribe_to_events(self):
'''
Tell Delegate to send keyboard, joystick and mouse buttons
'''
self.subscribe(self.translate_key, KEYDOWN)
self.subscribe(self.translate_key, KEYUP)
self.subscribe(self.translate_joy_button, JOYBUTTONDOWN)
self.subscribe(self.translate_joy_button, JOYBUTTONUP)
self.subscribe(self.translate_axis_motion, JOYAXISMOTION)
self.subscribe(self.translate_mouse_input, MOUSEBUTTONDOWN)
self.subscribe(self.translate_mouse_input, MOUSEBUTTONUP)
def build_key_map(self):
key_map = {}
for command, keys in self.key_commands:
config = self.get_configuration()
for command, keys in config.get_section("keys", "list").iteritems():
key_map[command] = []
if type(keys) == str:
keys = [keys]
for key in keys:
key_map[command].append(globals()[key])
self.key_map = key_map
def build_joy_button_map(self):
self.joy_button_map = self.get_configuration("joy")
def translate_key(self, event):
def translate_key_press(self, evt):
if not self.suppressed:
cancel = event.type == KEYUP
posted = None
key = event.key
for cmd, keys in self.key_map.items():
key = evt.key
for cmd, keys in self.key_map.iteritems():
if key in keys:
self.post_command(cmd, cancel=cancel)
posted = cmd
if (not posted or posted not in self.any_press_ignored) and \
key not in self.any_press_ignored_keys and \
(not self.suppressed_any_key_on_mods or \
(not get_mods() & KMOD_CTRL and not get_mods() & KMOD_ALT)):
self.post_any_command(key, cancel)
self.post_command(cmd)
def post_command(self, cmd, **attributes):
self.delegate.post(cmd, **attributes)
def post_command(self, cmd):
eid = self.get_user_event_id()
name = self.get_configuration().get("event", "command-event-name")
event.post(event.Event(eid, name=name, command=cmd))
def post_any_command(self, id, cancel=False):
self.post_command("any", id=id, cancel=cancel)
def translate_joy_button(self, event):
def translate_joy_button(self, evt):
if not self.suppressed:
cancel = event.type == JOYBUTTONUP
posted = None
for command, button in self.joy_button_map.items():
if int(button) == event.button:
self.post_command(command, cancel=cancel)
posted = command
if not posted or posted not in self.any_press_ignored:
self.post_any_command(event.button, cancel)
button = evt.button
config = self.get_configuration()
if button == config["joy-advance"]:
self.post_command("advance")
if button == config["joy-pause"]:
self.post_command("pause")
def translate_axis_motion(self, event):
if not self.suppressed and not self.check_command_line("-disable-joy-axis"):
axis = event.axis
value = event.value
single_xy = self.get_configuration("joy", "single-xy")
command = None
if -.01 < value < .01:
for direction in "up", "right", "down", "left":
if not self.axes_cancelled[direction]:
self.post_command(direction, cancel=True)
if direction not in self.any_press_ignored:
self.post_any_command(direction, True)
self.axes_cancelled[direction] = True
if single_xy:
if direction == "up" and self.joystick.is_direction_pressed(Joystick.down):
command = "down"
elif direction == "down" and self.joystick.is_direction_pressed(Joystick.up):
command = "up"
elif direction == "right" and self.joystick.is_direction_pressed(Joystick.left):
command = "left"
elif direction == "left" and self.joystick.is_direction_pressed(Joystick.right):
command = "right"
def translate_axis_motion(self, evt):
if not self.suppressed:
axis = evt.axis
value = evt.value
if axis == 1:
if value < 0:
self.post_command("up")
elif value > 0:
self.post_command("down")
else:
if axis == self.get_configuration("joy", "vertical-axis"):
if value < 0:
if not single_xy or not self.joystick.is_direction_pressed(Joystick.down):
command = "up"
elif value > 0:
if not single_xy or not self.joystick.is_direction_pressed(Joystick.up):
command = "down"
elif axis == self.get_configuration("joy", "horizontal-axis"):
if value > 0:
if not single_xy or not self.joystick.is_direction_pressed(Joystick.left):
command = "right"
elif value < 0:
if not single_xy or not self.joystick.is_direction_pressed(Joystick.right):
command = "left"
if command is not None:
delay = self.get_configuration("joy", "delay-axis")
secs = get_secs()
if not delay or secs - self.last_command_post_time > delay:
self.post_command(command)
if command not in self.any_press_ignored:
self.post_any_command(command)
self.axes_cancelled[command] = False
self.last_command_post_time = secs
if value > 0:
self.post_command("right")
elif value < 0:
self.post_command("left")
def is_command_active(self, command):
if not self.suppressed:
if self.is_key_pressed(command):
return True
joystick = self.joystick
joy_map = self.joy_button_map
if command in joy_map and joystick.get_button(int(joy_map[command])):
if self.is_key_pressed(command):
return True
if command == "up":
return joystick.is_direction_pressed(Joystick.up)
@ -180,55 +81,21 @@ class Input(GameChild):
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 = get_pressed()
poll = keyboard.get_pressed()
for key in self.key_map[command]:
if poll[key]:
return True
def translate_mouse_input(self, event):
button = event.button
pos = event.pos
post = self.post_command
if event.type == MOUSEBUTTONDOWN:
if button == 1:
last = self.last_mouse_down_left
if last:
limit = self.double_click_time_limit
if get_secs() - last < limit:
post("mouse-double-click-left", pos=pos)
last = get_secs()
self.last_mouse_down_left = last
if "mouse" not in self.any_press_ignored:
self.post_any_command(event.button)
if event.type == MOUSEBUTTONUP:
if "mouse" not in self.any_press_ignored:
self.post_any_command(event.button, True)
def get_axes(self):
axes = {}
for direction in "up", "right", "down", "left":
axes[direction] = self.is_command_active(direction)
return axes
def register_any_press_ignore(self, *args, **attributes):
self.any_press_ignored.update(args)
self.any_press_ignored_keys.update(self.extract_keys(attributes))
def extract_keys(self, attributes):
keys = []
if "keys" in attributes:
keys = attributes["keys"]
if type(keys) == int:
keys = [keys]
return keys
def unregister_any_press_ignore(self, *args, **attributes):
self.any_press_ignored.difference_update(args)
self.any_press_ignored_keys.difference_update(
self.extract_keys(attributes))
class Joystick:
@ -241,19 +108,15 @@ class Joystick:
js.init()
self.js = js
def is_direction_pressed(self, direction, margin=0.01):
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 - margin
return js.get_axis(1) < 0
elif direction == 1:
return js.get_axis(0) > 0 + margin
return js.get_axis(0) > 0
elif direction == 2:
return js.get_axis(1) > 0 + margin
return js.get_axis(1) > 0
elif direction == 3:
return js.get_axis(0) < 0 - margin
def get_button(self, id):
if self.js:
return self.js.get_button(id)
return js.get_axis(0) < 0

View File

@ -1,741 +0,0 @@
from re import match
from os.path import join
from tempfile import gettempdir
from pygame import Surface
from pygame.font import Font
from pygame.draw import aaline
from pygame.locals import *
from .GameChild import GameChild
from .Sprite import Sprite
from .Animation import Animation
class Interpolator(list, GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.set_nodesets()
self.gui_enabled = False
def init_gui(self):
"""
If the interpolator is requested on the command line, create a GUI object. This runs separately from `pgfw.Interpolator.__init__` because
the display is not available when the main `pgfw.Interpolator` is created by `pgfw.Game`.
"""
self.gui_enabled = self.check_command_line("-interpolator")
if self.gui_enabled:
self.gui = GUI(self)
def set_nodesets(self):
config = self.get_configuration()
if config.has_section("interpolate"):
for name, value in config.get_section("interpolate").items():
self.add_nodeset(name, value)
def add_nodeset(self, name, value, method=None):
self.append(Nodeset(name, value, method))
return len(self) - 1
def is_gui_active(self):
return self.gui_enabled and self.gui.active
def get_nodeset(self, name):
for nodeset in self:
if nodeset.name == name:
return nodeset
def remove(self, outgoing):
for ii, nodeset in enumerate(self):
if nodeset.name == outgoing.name:
self.pop(ii)
break
class Nodeset(list):
LINEAR, CUBIC = range(2)
def __init__(self, name, nodes, method=None):
list.__init__(self, [])
self.name = name
if isinstance(nodes, str):
self.parse_raw(nodes)
else:
self.interpolation_method = method
self.parse_list(nodes)
self.set_splines()
def parse_raw(self, raw):
raw = raw.strip()
if raw[0].upper() == "L":
self.set_interpolation_method(self.LINEAR, False)
else:
self.set_interpolation_method(self.CUBIC, False)
for node in raw[1:].strip().split(","):
self.add_node(list(map(float, node.strip().split())), False)
def set_interpolation_method(self, method, refresh=True):
self.interpolation_method = method
if refresh:
self.set_splines()
def add_node(self, coordinates, refresh=True):
x = coordinates[0]
inserted = False
index = 0
for ii, node in enumerate(self):
if x < node.x:
self.insert(ii, Node(coordinates))
inserted = True
index = ii
break
elif x == node.x:
return None
if not inserted:
self.append(Node(coordinates))
index = len(self) - 1
if refresh:
self.set_splines()
return index
def parse_list(self, nodes):
for node in nodes:
self.add_node(node)
def set_splines(self):
if self.interpolation_method == self.LINEAR:
self.set_linear_splines()
else:
self.set_cubic_splines()
def set_linear_splines(self):
self.splines = splines = []
for ii in range(len(self) - 1):
x1, y1, x2, y2 = self[ii] + self[ii + 1]
m = float(y2 - y1) / (x2 - x1)
splines.append(LinearSpline(x1, y1, m))
def set_cubic_splines(self):
n = len(self) - 1
a = [node.y for node in self]
b = [None] * n
d = [None] * n
h = [self[ii + 1].x - self[ii].x for ii in range(n)]
alpha = [None] + [(3.0 / h[ii]) * (a[ii + 1] - a[ii]) - \
(3.0 / h[ii - 1]) * (a[ii] - a[ii - 1]) \
for ii in range(1, n)]
c = [None] * (n + 1)
l = [None] * (n + 1)
u = [None] * (n + 1)
z = [None] * (n + 1)
l[0] = 1
u[0] = z[0] = 0
for ii in range(1, n):
l[ii] = 2 * (self[ii + 1].x - self[ii - 1].x) - \
h[ii - 1] * u[ii - 1]
u[ii] = h[ii] / l[ii]
z[ii] = (alpha[ii] - h[ii - 1] * z[ii - 1]) / l[ii]
l[n] = 1
z[n] = c[n] = 0
for jj in range(n - 1, -1, -1):
c[jj] = z[jj] - u[jj] * c[jj + 1]
b[jj] = (a[jj + 1] - a[jj]) / h[jj] - \
(h[jj] * (c[jj + 1] + 2 * c[jj])) / 3
d[jj] = (c[jj + 1] - c[jj]) / (3 * h[jj])
self.splines = [CubicSpline(self[ii].x, a[ii], b[ii], c[ii],
d[ii]) for ii in range(n)]
def get_y(self, t, loop=False, reverse=False, natural=False):
if loop or reverse:
if reverse and int(t) / int(self[-1].x) % 2:
t = self[-1].x - t
t %= self[-1].x
elif not natural:
if t < self[0].x:
t = self[0].x
elif t > self[-1].x:
t = self[-1].x
splines = self.splines
for ii in range(len(splines) - 1):
if t < splines[ii + 1].x:
return splines[ii].get_y(t)
return splines[-1].get_y(t)
def remove(self, node, refresh=True):
list.remove(self, node)
if refresh:
self.set_splines()
def resize(self, left, length, refresh=True):
old_left = self[0].x
old_length = self.get_length()
for node in self:
node.x = left + length * (node.x - old_left) / old_length
if refresh:
self.set_splines()
def get_length(self):
return self[-1].x - self[0].x
class Node(list):
def __init__(self, coordinates):
list.__init__(self, coordinates)
def __getattr__(self, name):
if name == "x":
return self[0]
elif name == "y":
return self[1]
return list.__get__(self, name)
def __setattr__(self, name, value):
if name == "x":
list.__setitem__(self, 0, value)
elif name == "y":
list.__setitem__(self, 1, value)
else:
list.__setattr__(self, name, value)
class Spline:
def __init__(self, x):
self.x = x
class CubicSpline(Spline):
def __init__(self, x, a, b, c, d):
Spline.__init__(self, x)
self.a = a
self.b = b
self.c = c
self.d = d
def get_y(self, t):
x = self.x
return self.a + self.b * (t - x) + self.c * (t - x) ** 2 + self.d * \
(t - x) ** 3
class LinearSpline(Spline):
def __init__(self, x, y, m):
Spline.__init__(self, x)
self.y = y
self.m = m
def get_y(self, t):
return self.m * (t - self.x) + self.y
class GUI(Animation):
B_DUPLICATE, B_WRITE, B_DELETE, B_LINEAR, B_CUBIC, B_SPLIT = range(6)
S_NONE, S_LEFT, S_RIGHT = range(3)
def __init__(self, parent):
Animation.__init__(self, parent, unfiltered=True)
self.audio = self.get_audio()
self.display = self.get_game().display
self.display_surface = self.get_display_surface()
self.time_filter = self.get_game().time_filter
self.delegate = self.get_delegate()
self.split = self.S_NONE
self.success_indicator_active = True
self.success_indicator_blink_count = 0
self.load_configuration()
self.font = Font(None, self.label_size)
self.prompt = Prompt(self)
self.set_temporary_file()
self.set_background()
self.set_success_indicator()
self.set_plot_rect()
self.set_marker_frame()
self.set_buttons()
self.active = False
self.set_nodeset_index()
self.set_y_range()
self.set_markers()
self.subscribe(self.respond_to_command)
self.subscribe(self.respond_to_mouse_down, MOUSEBUTTONDOWN)
self.subscribe(self.respond_to_key, KEYDOWN)
self.register(self.show_success_indicator, interval=100)
self.register(self.save_temporary_file, interval=10000)
self.play(self.save_temporary_file)
def load_configuration(self):
config = self.get_configuration("interpolator-gui")
self.label_size = config["label-size"]
self.axis_label_count = config["axis-label-count"]
self.margin = config["margin"]
self.curve_color = config["curve-color"]
self.marker_size = config["marker-size"]
self.marker_color = config["marker-color"]
self.label_precision = config["label-precision"]
self.template_nodeset = config["template-nodeset"]
self.template_nodeset_name = config["template-nodeset-name"]
self.flat_y_range = config["flat-y-range"]
def set_temporary_file(self):
self.temporary_file = open(join(gettempdir(), "pgfw-config"), "w")
def set_background(self):
surface = Surface(self.display_surface.get_size())
surface.fill((0, 0, 0))
self.background = surface
def set_success_indicator(self):
surface = Surface((10, 10))
surface.fill((0, 255, 0))
rect = surface.get_rect()
rect.topleft = self.display_surface.get_rect().topleft
self.success_indicator, self.success_indicator_rect = surface, rect
def set_plot_rect(self):
margin = self.margin
self.plot_rect = self.display_surface.get_rect().inflate(-margin,
-margin)
def set_marker_frame(self):
size = self.marker_size
surface = Surface((size, size))
transparent_color = (255, 0, 255)
surface.fill(transparent_color)
surface.set_colorkey(transparent_color)
line_color = self.marker_color
aaline(surface, line_color, (0, 0), (size - 1, size - 1))
aaline(surface, line_color, (0, size - 1), (size - 1, 0))
self.marker_frame = surface
def set_buttons(self):
self.buttons = buttons = []
text = "Duplicate", "Write", "Delete", "Linear", "Cubic", "Split: No"
x = 0
for instruction in text:
buttons.append(Button(self, instruction, x))
x += buttons[-1].location.w + 10
def set_nodeset_index(self, increment=None, index=None):
parent = self.parent
if index is None:
if not increment:
index = 0
else:
index = self.nodeset_index + increment
limit = len(parent) - 1
if index > limit:
index = 0
elif index < 0:
index = limit
self.nodeset_index = index
self.set_nodeset_label()
def set_nodeset_label(self):
surface = self.font.render(self.get_nodeset().name, True, (0, 0, 0),
(255, 255, 255))
rect = surface.get_rect()
rect.bottomright = self.display_surface.get_rect().bottomright
self.nodeset_label, self.nodeset_label_rect = surface, rect
def get_nodeset(self):
if not len(self.parent):
self.parent.add_nodeset(self.template_nodeset_name,
self.template_nodeset)
self.set_nodeset_index(0)
return self.parent[self.nodeset_index]
def set_y_range(self):
width = self.plot_rect.w
nodeset = self.get_nodeset()
self.y_range = y_range = [nodeset[0].y, nodeset[-1].y]
x = 0
while x < width:
y = nodeset.get_y(self.get_function_coordinates(x)[0])
if y < y_range[0]:
y_range[0] = y
elif y > y_range[1]:
y_range[1] = y
x += width * .01
if y_range[1] - y_range[0] == 0:
y_range[1] += self.flat_y_range
if self.split:
self.adjust_for_split(y_range, nodeset)
self.set_axis_labels()
def get_function_coordinates(self, xp=0, yp=0):
nodeset = self.get_nodeset()
x_min, x_max, (y_min, y_max) = nodeset[0].x, nodeset[-1].x, self.y_range
rect = self.plot_rect
x = float(xp) / (rect.right - rect.left) * (x_max - x_min) + x_min
y = float(yp) / (rect.bottom - rect.top) * (y_min - y_max) + y_max
return x, y
def adjust_for_split(self, y_range, nodeset):
middle = nodeset[0].y if self.split == self.S_LEFT else nodeset[-1].y
below, above = middle - y_range[0], y_range[1] - middle
if below > above:
y_range[1] += below - above
else:
y_range[0] -= above - below
def set_axis_labels(self):
self.axis_labels = labels = []
nodeset, formatted, render, rect, yr = (self.get_nodeset(),
self.get_formatted_measure,
self.font.render,
self.plot_rect, self.y_range)
for ii, node in enumerate(nodeset[0::len(nodeset) - 1]):
xs = render(formatted(node.x), True, (0, 0, 0), (255, 255, 255))
xsr = xs.get_rect()
xsr.top = rect.bottom
if not ii:
xsr.left = rect.left
else:
xsr.right = rect.right
ys = render(formatted(yr[ii]), True, (0, 0, 0), (255, 255, 255))
ysr = ys.get_rect()
ysr.right = rect.left
if not ii:
ysr.bottom = rect.bottom
else:
ysr.top = rect.top
labels.append(((xs, xsr), (ys, ysr)))
def get_formatted_measure(self, measure):
return "%s" % float(("%." + str(self.label_precision) + "g") % measure)
def deactivate(self):
self.active = False
self.time_filter.open()
# self.audio.muted = self.saved_mute_state
self.display.set_mouse_visibility(self.saved_mouse_state)
def respond_to_command(self, event):
compare = self.delegate.compare
if compare(event, "toggle-interpolator"):
self.toggle()
elif self.active:
if compare(event, "reset-game"):
self.deactivate()
elif compare(event, "quit"):
self.get_game().end(event)
def toggle(self):
if self.active:
self.deactivate()
self.get_game().delegate.post("refresh-nodesets")
else:
self.activate()
def activate(self):
self.active = True
self.time_filter.close()
# self.saved_mute_state = self.audio.muted
# self.audio.mute()
self.draw()
self.saved_mouse_state = self.display.set_mouse_visibility(True)
def respond_to_mouse_down(self, event):
redraw = False
if self.active and not self.prompt.active:
nodeset_rect = self.nodeset_label_rect
plot_rect = self.plot_rect
if event.button == 1:
pos = event.pos
if nodeset_rect.collidepoint(pos):
self.set_nodeset_index(1)
redraw = True
elif self.axis_labels[0][0][1].collidepoint(pos):
text = "{0} {1}".format(*map(self.get_formatted_measure,
self.get_nodeset()[0]))
self.prompt.activate(text, self.resize_nodeset, 0)
elif self.axis_labels[1][0][1].collidepoint(pos):
text = "{0} {1}".format(*map(self.get_formatted_measure,
self.get_nodeset()[-1]))
self.prompt.activate(text, self.resize_nodeset, -1)
else:
bi = self.collide_buttons(pos)
if bi is not None:
if bi == self.B_WRITE:
self.get_configuration().write()
self.play(self.show_success_indicator)
elif bi in (self.B_LINEAR, self.B_CUBIC):
nodeset = self.get_nodeset()
if bi == self.B_LINEAR:
nodeset.set_interpolation_method(Nodeset.LINEAR)
else:
nodeset.set_interpolation_method(Nodeset.CUBIC)
self.store_in_configuration()
redraw = True
elif bi == self.B_DUPLICATE:
self.prompt.activate("", self.add_nodeset)
elif bi == self.B_DELETE and len(self.parent) > 1:
self.parent.remove(self.get_nodeset())
self.set_nodeset_index(1)
self.store_in_configuration()
redraw = True
elif bi == self.B_SPLIT:
self.toggle_split()
redraw = True
elif plot_rect.collidepoint(pos) and \
not self.collide_markers(pos):
xp, yp = pos[0] - plot_rect.left, pos[1] - plot_rect.top
self.get_nodeset().add_node(
self.get_function_coordinates(xp, yp))
self.store_in_configuration()
redraw = True
elif event.button == 3:
pos = event.pos
if nodeset_rect.collidepoint(pos):
self.set_nodeset_index(-1)
redraw = True
elif plot_rect.collidepoint(pos):
marker = self.collide_markers(pos)
if marker:
self.get_nodeset().remove(marker.node)
self.store_in_configuration()
redraw = True
elif self.active and self.prompt.active and \
not self.prompt.rect.collidepoint(event.pos):
self.prompt.deactivate()
redraw = True
if redraw:
self.set_y_range()
self.set_markers()
self.draw()
def resize_nodeset(self, text, index):
result = match("^\s*(-{,1}\d*\.{,1}\d*)\s+(-{,1}\d*\.{,1}\d*)\s*$",
text)
if result:
try:
nodeset = self.get_nodeset()
x, y = map(float, result.group(1, 2))
if (index == -1 and x > nodeset[0].x) or \
(index == 0 and x < nodeset[-1].x):
nodeset[index].y = y
if index == -1:
nodeset.resize(nodeset[0].x, x - nodeset[0].x)
else:
nodeset.resize(x, nodeset[-1].x - x)
self.store_in_configuration()
self.set_y_range()
self.set_axis_labels()
self.set_markers()
self.draw()
return True
except ValueError:
return False
def collide_buttons(self, pos):
for ii, button in enumerate(self.buttons):
if button.location.collidepoint(pos):
return ii
def store_in_configuration(self):
config = self.get_configuration()
section = "interpolate"
config.clear_section(section)
for nodeset in self.parent:
code = "L" if nodeset.interpolation_method == Nodeset.LINEAR else \
"C"
for ii, node in enumerate(nodeset):
if ii > 0:
code += ","
code += " {0} {1}".format(*map(self.get_formatted_measure,
node))
if not config.has_section(section):
config.add_section(section)
config.set(section, nodeset.name, code)
def toggle_split(self):
self.split += 1
if self.split > self.S_RIGHT:
self.split = self.S_NONE
self.buttons[self.B_SPLIT].set_frame(["Split: No", "Split: L",
"Split: R"][self.split])
def add_nodeset(self, name):
nodeset = self.get_nodeset()
self.set_nodeset_index(index=self.parent.add_nodeset(\
name, nodeset, nodeset.interpolation_method))
self.store_in_configuration()
self.draw()
return True
def collide_markers(self, pos):
for marker in self.markers:
if marker.location.collidepoint(pos):
return marker
def set_markers(self):
self.markers = markers = []
for node in self.get_nodeset()[1:-1]:
markers.append(Marker(self, node))
markers[-1].location.center = self.get_plot_coordinates(*node)
def get_plot_coordinates(self, x=0, y=0):
nodeset = self.get_nodeset()
x_min, x_max, (y_min, y_max) = nodeset[0].x, nodeset[-1].x, self.y_range
x_ratio = float(x - x_min) / (x_max - x_min)
rect = self.plot_rect
xp = x_ratio * (rect.right - rect.left) + rect.left
y_ratio = float(y - y_min) / (y_max - y_min)
yp = rect.bottom - y_ratio * (rect.bottom - rect.top)
return xp, yp
def draw(self):
display_surface = self.display_surface
display_surface.blit(self.background, (0, 0))
display_surface.blit(self.nodeset_label, self.nodeset_label_rect)
self.draw_axes()
self.draw_function()
self.draw_markers()
self.draw_buttons()
def draw_axes(self):
display_surface = self.display_surface
for xl, yl in self.axis_labels:
display_surface.blit(*xl)
display_surface.blit(*yl)
def draw_function(self):
rect = self.plot_rect
surface = self.display_surface
nodeset = self.get_nodeset()
step = 1
for x in range(rect.left, rect.right + step, step):
ii = x - rect.left
fx = nodeset.get_y(self.get_function_coordinates(ii)[0])
y = self.get_plot_coordinates(y=fx)[1]
if ii > 0:
aaline(surface, self.curve_color, (x - step, last_y), (x, y))
last_y = y
def draw_markers(self):
for marker in self.markers:
marker.update()
def draw_buttons(self):
for button in self.buttons:
button.update()
def respond_to_key(self, event):
if self.prompt.active:
prompt = self.prompt
if event.key == K_RETURN:
if prompt.callback[0](prompt.text, *prompt.callback[1]):
prompt.deactivate()
elif event.key == K_BACKSPACE:
prompt.text = prompt.text[:-1]
prompt.update()
prompt.draw_text()
elif (event.unicode.isalnum() or event.unicode.isspace() or \
event.unicode in (".", "-", "_")) and len(prompt.text) < \
prompt.character_limit:
prompt.text += event.unicode
prompt.update()
prompt.draw_text()
def show_success_indicator(self):
self.draw()
if self.success_indicator_blink_count > 1:
self.success_indicator_blink_count = 0
self.halt(self.show_success_indicator)
else:
if self.success_indicator_active:
self.display_surface.blit(self.success_indicator,
self.success_indicator_rect)
if self.success_indicator_active:
self.success_indicator_blink_count += 1
self.success_indicator_active = not self.success_indicator_active
def save_temporary_file(self):
fp = self.temporary_file
fp.seek(0)
fp.truncate()
self.get_configuration().write(fp)
def rearrange(self):
self.set_background()
self.set_success_indicator()
self.set_plot_rect()
self.set_markers()
self.set_nodeset_label()
self.set_axis_labels()
self.set_buttons()
self.prompt.reset()
class Marker(Sprite):
def __init__(self, parent, node):
Sprite.__init__(self, parent)
self.add_frame(parent.marker_frame)
self.node = node
class Button(Sprite):
def __init__(self, parent, text, left):
Sprite.__init__(self, parent)
self.set_frame(text)
self.location.bottomleft = left, \
self.get_display_surface().get_rect().bottom
def set_frame(self, text):
self.clear_frames()
self.add_frame(self.parent.font.render(text, True, (0, 0, 0),
(255, 255, 255)))
class Prompt(Sprite):
def __init__(self, parent):
Sprite.__init__(self, parent)
self.load_configuration()
self.font = Font(None, self.text_size)
self.reset()
self.deactivate()
def deactivate(self):
self.active = False
def load_configuration(self):
config = self.get_configuration("interpolator-gui")
self.size = config["prompt-size"]
self.border_color = config["prompt-border-color"]
self.border_width = config["prompt-border-width"]
self.character_limit = config["prompt-character-limit"]
self.text_size = config["prompt-text-size"]
def reset(self):
self.set_frame()
self.place()
def set_frame(self):
self.clear_frames()
surface = Surface(self.size)
self.add_frame(surface)
surface.fill(self.border_color)
width = self.border_width * 2
surface.fill((0, 0, 0), surface.get_rect().inflate(-width, -width))
def place(self):
self.location.center = self.display_surface.get_rect().center
def activate(self, text, callback, *args):
self.active = True
self.text = str(text)
self.callback = callback, args
self.update()
self.draw_text()
def draw_text(self):
surface = self.font.render(self.text, True, (255, 255, 255), (0, 0, 0))
rect = surface.get_rect()
rect.center = self.location.center
self.display_surface.blit(surface, rect)

View File

@ -1,117 +0,0 @@
from pygame import display
from pygame.font import Font
from pygame.time import get_ticks, wait
from .GameChild import GameChild
class Mainloop(GameChild):
"""
Maintain and display framerate. Call the parent's (the main game object) frame method once per frame. If frame skipping is enabled and the frame rate
is below the requested framerate, the frame method will be called multiple times to try to keep pace (although this rarely makes sense, since calling
the frame method multiple times would most likely cause further slow down). It is missing some important game loop features. For example, game logic
is not separated from draw logic, and frame rate is expected to remain constant based on the definition in the config file. The time of each frame is
tracked by the TimeFilter class.
"""
def __init__(self, parent):
GameChild.__init__(self, parent)
self.overflow = 0
self.frame_count = 1
self.actual_frame_duration = 0
self.frames_this_second = 0
self.last_framerate_display = 0
self.frame_times = []
self.load_configuration()
self.init_framerate_display()
self.last_ticks = get_ticks()
self.stopping = False
def load_configuration(self):
config = self.get_configuration("display")
self.target_frame_duration = config["frame-duration"]
self.wait_duration = config["wait-duration"]
self.skip_frames = config["skip-frames"]
self.show_framerate = config["show-framerate"]
self.framerate_text_size = config["framerate-text-size"]
self.framerate_text_color = config["framerate-text-color"]
self.framerate_text_background = config["framerate-text-background"]
self.framerate_display_flag = config["framerate-display-flag"]
def init_framerate_display(self):
if self.framerate_display_active():
screen = self.get_screen()
self.last_framerate_count = 0
self.framerate_topright = self.get_configuration("display", "framerate-position")
if self.framerate_topright[0] == -1:
self.framerate_topright[0] = screen.get_rect().right
self.display_surface = screen
self.font = Font(None, self.framerate_text_size)
self.font.set_bold(True)
self.render_framerate()
def framerate_display_active(self):
return self.check_command_line(self.framerate_display_flag) or self.show_framerate
def render_framerate(self):
average_frame_time = sum(self.frame_times) / len(self.frame_times) if self.frame_times else 0
self.frame_times = []
text = self.font.render(f"{self.last_framerate_count} ({average_frame_time: .1f})", False, self.framerate_text_color, self.framerate_text_background)
rect = text.get_rect()
rect.topright = self.framerate_topright
self.framerate_text = text
self.framerate_text_rect = rect
def run(self):
while not self.stopping:
self.advance_frame()
self.update_frame_duration()
self.update_overflow()
self.stopping = False
def advance_frame(self):
refresh = False
while self.frame_count > 0:
refresh = True
self.parent.frame()
if self.framerate_display_active():
self.update_framerate()
self.frame_count -= 1
if not self.skip_frames:
break
if refresh:
display.update()
def update_frame_duration(self):
last_ticks = self.last_ticks
actual_frame_duration = get_ticks() - last_ticks
last_ticks = get_ticks()
self.frame_times.append(actual_frame_duration)
while actual_frame_duration < self.target_frame_duration:
wait(self.wait_duration)
actual_frame_duration += get_ticks() - last_ticks
last_ticks = get_ticks()
self.actual_frame_duration = actual_frame_duration
self.last_ticks = last_ticks
def update_overflow(self):
self.frame_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.frame_count += 1
overflow -= target_frame_duration
overflow = self.overflow
def update_framerate(self):
count = self.frames_this_second + 1
if get_ticks() - self.last_framerate_display > 1000:
self.last_framerate_count = count
self.render_framerate()
self.last_framerate_display = get_ticks()
count = 0
self.display_surface.blit(self.framerate_text, self.framerate_text_rect)
self.frames_this_second = count
def stop(self):
self.stopping = True

View File

@ -1,156 +0,0 @@
# adapted from Pythoven
# https://github.com/phiresky/pythoven
from random import randint
from math import sin, log, pi
from array import array
from pygame import version
from pygame.mixer import Sound, get_init
class Samples(Sound):
def __init__(self):
self.set_amplitude()
if version.vernum < (1, 9, 2):
Sound.__init__(self, self.build())
else:
Sound.__init__(self, buffer=self.build())
def set_amplitude(self):
self.amplitude = (1 << (self.get_sample_width() * 8 - 1)) - 1
def get_sample_width(self):
return abs(int(get_init()[1] / 8))
def build(self):
pass
def get_empty_array(self, length):
return array(self.get_array_typecode(), [0] * length)
def get_array_typecode(self):
return [None, "b", "h"][self.get_sample_width()]
class Note(Samples):
base_frequency = 440.0
base_octave = 4
base_name = "A"
names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
SQUARE, TRIANGLE, SAW, SINE, DIRTY = range(5)
def __init__(self, name=None, octave=4, frequency=None, shape=SQUARE, volume=1.0):
names = self.names
self.shape = shape
if frequency is None:
self.name = name
self.octave = octave
self.set_frequency()
elif name is None:
self.frequency = float(frequency)
self.set_name_and_octave()
Samples.__init__(self)
self.set_volume(volume)
def set_frequency(self):
name, octave = self.name, self.octave
names = self.names
octave_length = len(names)
offset = (octave - self.base_octave) * octave_length + \
names.index(name) - names.index(self.base_name)
self.frequency = self.base_frequency * 2 ** (offset / float(octave_length))
def set_name_and_octave(self):
names = self.names
octave_length = len(names)
offset = int(round(log(self.frequency / self.base_frequency, 2) * \
octave_length)) + names.index(self.base_name)
self.octave = self.base_octave + offset / octave_length
self.name = names[offset % octave_length]
def __repr__(self):
return "%s%i %.2f" % (self.name, self.octave, self.frequency)
def build(self):
period = int(round(get_init()[0] / self.frequency))
samples = self.get_empty_array(period)
shape = self.shape
if shape == self.TRIANGLE:
self.store_triangle_wave(samples, period)
elif shape == self.SAW:
self.store_saw_wave(samples, period)
elif shape == self.SINE:
self.store_sine_wave(samples, period)
elif shape == self.DIRTY:
self.store_dirty_wave(samples)
else:
self.store_square_wave(samples, period)
return samples
def store_triangle_wave(self, samples, period):
amplitude = self.amplitude
if period > 1:
coefficient = 4 * amplitude / float(period - 1)
for time in range(int(round(period / 2.0))):
y = int((coefficient * time) - amplitude)
samples[time] = y
samples[-time - 1] = y
def store_saw_wave(self, samples, period):
amplitude = self.amplitude
if period > 1:
for time in range(period):
samples[time] = int(2 * amplitude / float(period - 1) * time - \
amplitude)
def store_sine_wave(self, samples, period):
amplitude = self.amplitude
for time in range(period):
samples[time] = int(round(sin(time / (period / pi / 2)) * \
amplitude))
def store_dirty_wave(self, samples):
amplitude = self.amplitude
for time in range(len(samples)):
samples[time] = randint(-amplitude, amplitude)
def store_square_wave(self, samples, period):
amplitude = self.amplitude
for time in range(period):
if time < period / 2:
samples[time] = amplitude
else:
samples[time] = -amplitude
def play(self, maxtime=0, fadeout=None, panning=None, fade_in=0, position=None):
channel = Samples.play(self, -1, maxtime, fade_in)
if fadeout:
self.fadeout(fadeout)
if position is not None:
panning = self.get_panning(position)
if channel and panning:
channel.set_volume(*panning)
return channel
def get_panning(self, position):
return 1 - max(0, ((position - .5) * 2)), 1 + min(0, ((position - .5) * 2))
class Chord:
def __init__(self, *args):
self.notes = args
def play(self, maxtime=0, fadeout=[None], panning=None, fade_in=[0], position=None):
if isinstance(fadeout, int):
fadeout = [fadeout]
if isinstance(fade_in, int):
fade_in = [fade_in]
for ii, note in enumerate(self.notes):
note.play(maxtime, fadeout[ii % len(fadeout)], panning, fade_in[ii % len(fade_in)], position)
def stop(self):
for note in self.notes:
note.stop()

View File

@ -1,27 +0,0 @@
import cProfile
from time import strftime
from os import mkdir
from os.path import join, exists
from .GameChild import GameChild
class Profile(cProfile.Profile, GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
cProfile.Profile.__init__(self)
if self.requested():
self.enable()
def requested(self):
return self.check_command_line("p")
def end(self):
if self.requested():
root = "stat/"
if not exists(root):
mkdir(root)
self.disable()
self.create_stats()
self.print_stats(sort="cumtime")
self.dump_stats(join(root, strftime("%Y%m%d-%H%M_%S.stat")))

View File

@ -5,37 +5,32 @@ from time import strftime
from pygame import image
from .GameChild import *
from .Input import *
from GameChild import *
from Input import *
class ScreenGrabber(GameChild):
def __init__(self, game):
GameChild.__init__(self, game)
self.delegate = self.get_delegate()
self.load_configuration()
self.subscribe(self.save_display)
def load_configuration(self):
config = self.get_configuration("screen-captures")
self.save_path = config["path"]
self.file_name_format = config["file-name-format"]
self.file_extension = config["file-extension"]
self.subscribe_to(self.get_user_event_id(), self.save_display)
def save_display(self, event):
if self.delegate.compare(event, "capture-screen"):
directory = self.save_path
if self.is_command(event, "capture-screen"):
directory = self.get_configuration().get("screen-captures", "path",
"path")
try:
if not exists(directory):
makedirs(directory)
name = self.build_name()
path = join(directory, name)
capture = image.save(self.get_screen(), path)
self.print_debug("Saved screen capture to %s" % (path))
self.print_debug("Saved screen capture to {0}".format(path))
except:
self.print_debug("Couldn't save screen capture to %s, %s" %\
(directory, exc_info()[1]))
self.print_debug("Couldn't save screen capture to {0}, {1}".\
format(directory, exc_info()[1]))
def build_name(self):
return "{0}.{1}".format(strftime(self.file_name_format),
self.file_extension)
config = self.get_configuration().get_section("screen-captures")
prefix = config["file-name-format"]
extension = config["file-extension"]
return "{0}.{1}".format(strftime(prefix), extension)

View File

@ -1,82 +1,70 @@
from os import walk, remove
from os.path import sep, join, exists, normpath, basename
from re import findall, sub
from os.path import sep, join, exists, normpath
from re import findall
from distutils.core import setup
from distutils.command.install import install
from pprint import pprint
from fileinput import FileInput
from re import sub, match
from fnmatch import fnmatch
from .Configuration import *
from configuration.Configuration import *
class Setup:
config = Configuration()
manifest_path = "MANIFEST"
def __init__(self):
pass
def remove_old_mainfest(self):
path = self.manifest_path
path = "MANIFEST"
if exists(path):
remove(path)
def build_package_list(self):
packages = []
config = self.config.get_section("setup")
locations = [config["package-root"]] + config["additional-packages"]
for location in locations:
if exists(location):
for root, dirs, files in walk(location, followlinks=True):
if exists(join(root, "__init__.py")):
packages.append(root.replace(sep, "."))
package_root = self.config.get("setup", "package-root", "path")
if exists(package_root):
for root, dirs, files in walk(package_root, followlinks=True):
packages.append(root.replace(sep, "."))
return packages
def build_data_map(self):
include = []
config = self.config.get_section("setup")
exclude = list(map(normpath, config["data-exclude"]))
config = self.config
exclude = map(normpath, config.get("setup", "data-exclude", "list"))
for root, dirs, files in walk("."):
dirs = self.remove_excluded(dirs, root, exclude)
files = [join(root, f) for f in self.remove_excluded(files, root,
exclude)]
if files:
include.append((normpath(join(config["installation-path"],
root)), files))
include.append((normpath(join(config.get("setup",
"installation-path",
"path"), root)),
files))
return include
def remove_excluded(self, paths, root, exclude):
removal = []
for path in paths:
if self.contains_path(join(root, path), exclude):
if normpath(join(root, path)) in exclude:
removal.append(path)
for path in removal:
if path in paths:
paths.remove(path)
paths.remove(path)
return paths
def contains_path(self, path, container):
return any(fnmatch(path, rule) or fnmatch(basename(path), rule) for rule in container)
def translate_title(self):
config = self.config.get_section("setup")
title = config["title"].replace(" ", config["whitespace-placeholder"])
return sub("[^\w-]", config["special-char-placeholder"], title)
return self.config.get("setup", "title").replace(" ", "-")
def build_description(self):
description = ""
path = self.config.get("setup", "description-file")
path = self.config.get("setup", "description-file", "path")
if exists(path):
description = "\n%s\n%s\n%s" % (file(path).read(),
"Changelog\n=========",
self.translate_changelog())
description = "\n{0}\n{1}\n{2}".format(file(path).read(),
"Changelog\n=========",
self.translate_changelog())
return description
def translate_changelog(self):
translation = ""
path = self.config.get("setup", "changelog")
path = self.config.get("setup", "changelog", "path")
if exists(path):
lines = file(path).readlines()
package_name = lines[0].split()[0]
@ -92,48 +80,44 @@ class Setup:
translation += " " + line + "\n"
return translation
def setup(self, windows=[], options={}, name=None):
print("running setup...")
self.remove_old_mainfest()
config = self.config.get_section("setup")
scripts = []
if config["init-script"]:
scripts.append(config["init-script"])
if name is None:
name = self.translate_title()
setup(cmdclass={"install": insert_resource_path},
name=name,
def setup(self):
config = self.config
section = config.get_section("setup")
setup(cmdclass={"install": insert_resources_path},
name=self.translate_title(),
packages=self.build_package_list(),
scripts=scripts,
scripts=[config.get("setup", "init-script", "path")],
data_files=self.build_data_map(),
requires=config["requirements"],
version=config["version"],
description=config["summary"],
classifiers=config["classifiers"],
requires=config.get("setup", "requirements", "list"),
version=section["version"],
description=section["summary"],
classifiers=config.get("setup", "classifiers", "list"),
long_description=self.build_description(),
license=config["license"],
platforms=config["platforms"],
author=config["contact-name"],
author_email=config["contact-email"],
url=config["url"],
windows=windows,
options=options)
license=section["license"],
platforms=config.get("setup", "platforms", "list"),
author=section["contact-name"],
author_email=section["contact-email"],
url=section["url"])
class insert_resource_path(install):
class insert_resources_path(install):
def run(self):
install.run(self)
self.edit_game_object_file()
def edit_game_object_file(self):
config = Configuration().get_section("setup")
config = Configuration()
section = "setup"
for path in self.get_outputs():
if path.endswith(config["main-object"]):
if path.endswith(config.get(section, "main-object", "path")):
for line in FileInput(path, inplace=True):
pattern = "^ *{0} *=.*".\
format(config["resource-path-identifier"])
format(config.get(section,
"resources-path-identifier"))
if match(pattern, line):
line = sub("=.*$", "= \"{0}\"".\
format(config["installation-path"]), line)
print(line.strip("\n"))
format(config.get(section,
"installation-path",
"path"), line)
print line.strip("\n")

View File

@ -1,44 +0,0 @@
from os.path import exists
from re import match
from setuptools import setup, find_packages
from Configuration import Configuration
from Setup import Setup
class SetupOSX(Setup):
def __init__(self, launcher_path, data_file_paths,
config_file_path="config"):
Setup.__init__(self)
self.launcher_path = launcher_path
self.data_file_paths = data_file_paths
self.config_file_path = config_file_path
def setup(self):
config = Configuration()
setup_obj = Setup()
version = config.get_section("setup")["version"]
name = setup_obj.translate_title()
plist = dict(
CFBundleIconFile=name,
CFBundleName=name,
CFBundleShortVersionString=version,
CFBundleGetInfoString=' '.join([name, version]),
CFBundleExecutable=name,
CFBundleIdentifier='org.' + name.lower())
setup(name=name,
version=version,
app=[dict(script=self.launcher_path, plist=plist)],
setup_requires=["py2app"],
options=dict(py2app=dict(arch="i386",)),
data_files=self.data_file_paths)
config_path = "dist/%s.app/Contents/Resources/%s" % \
(name, self.config_file_path)
if exists(config_path):
lines = open(config_path).readlines()
fp = open(config_path, "w")
for line in lines:
if match("^\W*fullscreen\W*=\W*yes\W*", line):
fp.write("fullscreen = no\n")
else:
fp.write(line)

View File

@ -1,85 +0,0 @@
import sys
from os import makedirs, walk, sep, remove
from os.path import join, dirname, basename, exists
from shutil import rmtree, copy, rmtree
from itertools import chain
from zipfile import ZipFile
import py2exe
from .Setup import Setup
class SetupWin(Setup):
def __init__(self):
Setup.__init__(self)
if sys.version_info[0] < 3:
self.replace_isSystemDLL()
def replace_isSystemDLL(self):
origIsSystemDLL = py2exe.build_exe.isSystemDLL
def isSystemDLL(pathname):
if basename(pathname).lower() in ("libogg-0.dll", "sdl_ttf.dll"):
return 0
return origIsSystemDLL(pathname)
py2exe.build_exe.isSystemDLL = isSystemDLL
def setup(self):
if sys.version_info[0] < 3 or sys.version_info[1] >= 5:
self.numpy_dll_paths_fix()
config = self.config.get_section("setup")
windows = [{}]
if config["init-script"]:
windows[0]["script"] = config["init-script"]
if config["windows-icon-path"]:
windows[0]["icon-resources"] = [(1, config["windows-icon-path"])]
Setup.setup(self, windows,
{"py2exe": {"packages": self.build_package_list(),
"dist_dir": config["windows-dist-path"]}})
rmtree("build")
self.copy_data_files()
self.create_archive()
# https://stackoverflow.com/questions/36191770/py2exe-errno-2-no-such-file-or-directory-numpy-atlas-dll
def numpy_dll_paths_fix(self):
import numpy
import sys
paths = set()
np_path = numpy.__path__[0]
for dirpath, _, filenames in walk(np_path):
for item in filenames:
if item.endswith('.dll'):
paths.add(dirpath)
sys.path.append(*list(paths))
def copy_data_files(self):
root = self.config.get("setup", "windows-dist-path")
for path in chain(*list(zip(*self.build_data_map()))[1]):
dest = join(root, dirname(path))
if not exists(dest):
makedirs(dest)
copy(path, dest)
self.include_readme(root)
def include_readme(self, root):
name = "README"
if exists(name):
readme = open(name, "r")
reformatted = open(join(root, name + ".txt"), "w")
for line in open(name, "r"):
reformatted.write(line.rstrip() + "\r\n")
def create_archive(self):
config = self.config.get_section("setup")
title = self.translate_title() + "-" + config["version"] + "-win"
archive_name = title + ".zip"
archive = ZipFile(archive_name, "w")
destination = config["windows-dist-path"]
for root, dirs, names in walk(destination):
for name in names:
path = join(root, name)
archive.write(path, path.replace(destination, title + sep))
archive.close()
copy(archive_name, "dist")
remove(archive_name)
rmtree(destination)

View File

@ -1,607 +0,0 @@
import collections, pygame
from os import listdir
from os.path import isfile, join
from glob import glob
from pygame import Color, Rect, Surface, PixelArray, mask
from pygame.image import load
from pygame.transform import flip
from pygame.locals import *
from .Animation import Animation
from .Vector import Vector, EVector
from .extension import get_hue_shifted_surface, get_step, load_frames, get_blinds_rects
class Sprite(Animation):
def __init__(self, parent, framerate=None, draw_children_on_frame=True):
Animation.__init__(self, parent, self.shift_frame, framerate)
self.frames = []
self.mirrored = False
self.alpha = 255
self.locations = []
self.framesets = [Frameset(self, framerate=framerate)]
self.frameset_index = 0
self._step = EVector()
self.children = collections.OrderedDict()
self.draw_children_on_frame = draw_children_on_frame
self.set_frameset(0)
self.locations.append(Location(self))
self.motion_overflow = Vector()
self.display_surface = self.get_display_surface()
self.wipe_blinds = []
self.current_wipe_index = 0
self.areas = None
self.register(self.hide, self.unhide, self.toggle_hidden, self.wipe_out, self.set_frameset)
def reset(self):
self.halt(self.wipe_out)
self.halt(self.set_frameset)
self.current_wipe_index = 0
def __getattr__(self, name):
if name in ("location", "rect"):
return self.locations[0]
if hasattr(Animation, "__getattr__"):
return Animation.__getattr__(self, name)
raise AttributeError(name)
def add_frameset(self, order=[], framerate=None, name=None, switch=False):
if framerate is None:
framerate = self.accounts[self.default_method].interval
frameset = Frameset(self, order, framerate, name)
self.framesets.append(frameset)
if switch:
self.set_frameset(len(self.framesets) - 1)
return frameset
def set_frameset(self, identifier):
if isinstance(identifier, str):
for ii, frameset in enumerate(self.framesets):
if frameset.name == identifier:
identifier = ii
break
if self.frameset_index != identifier:
self.frameset_index = identifier
self.register_interval()
self.update_location_size()
if self.get_current_frameset().length() > 1:
self.play()
self.get_current_frameset().reset()
return self.get_current_frameset()
def register_interval(self):
self.register(self.shift_frame,
interval=self.get_current_frameset().framerate)
def get_current_frameset(self):
return self.framesets[self.frameset_index]
def update_location_size(self):
size = self.get_current_frameset().rect.size
for location in self.locations:
location.size = size
location.fader.init_surface()
def set_framerate(self, framerate):
self.get_current_frameset().set_framerate(framerate)
self.register_interval()
def load_from_path(self, path, transparency=False, ppa=True, key=None, query=None, omit=False):
for frame in load_frames(self.get_resource(path), transparency, ppa, key, query):
self.add_frame(frame, omit)
def fill_colorkey(img, key=None):
if not key:
key = (255, 0, 255)
img = img.convert_alpha()
frame = Surface(img.get_size())
frame.fill(key)
frame.set_colorkey(key)
frame.blit(img, (0, 0))
return frame
def add_frame(self, frame, omit=False):
self.frames.append(frame)
frame.set_alpha(self.alpha)
if not omit:
frameset = self.get_current_frameset()
frameset.add_index(self.frames.index(frame))
self.update_location_size()
self.wipe_blinds = get_blinds_rects(*self.location.size)
self.wipe_blinds.reverse()
if frameset.length() > 1:
self.play()
def add_frames(self, frames):
for frame in frames:
self.add_frame(frame)
def shift_frame(self):
self.get_current_frameset().shift()
def get_current_frame(self):
return self.frames[self.get_current_frameset().get_current_id()]
def move(self, dx=0, dy=0, rotated=False):
for location in self.locations:
if not rotated:
location.move_ip(dx, dy)
else:
location.move_ip(dy, -dx)
def reset_motion_overflow(self):
for location in self.locations:
location.reset_motion_overflow()
def collide(self, other):
if not isinstance(other, Rect):
if hasattr(other, "rect"):
other = other.rect
else:
other = Rect(other[0], other[1], 1, 1)
for location in self.locations:
if location.colliderect(other):
return location
def collide_mask(self, other):
location = self.collide(other)
if location:
current_mask = mask.from_surface(self.get_current_frame())
other_mask = mask.from_surface(other.get_current_frame())
offset = other.location.left - location.left, \
other.location.top - location.top
return current_mask.overlap(other_mask, offset) is not None
def mirror(self):
frames = self.frames
for ii, frame in enumerate(frames):
frames[ii] = flip(frame, True, False)
self.mirrored = not self.mirrored
def clear_frames(self):
self.frames = []
for frameset in self.framesets:
frameset.order = []
frameset.reset()
frameset.measure_rect()
def add_location(self, topleft=None, offset=(0, 0), count=1, base=0):
if topleft is not None:
for ii in range(count):
self.locations.append(Location(
self, Rect(topleft, self.locations[0].size)))
else:
base = self.locations[base]
current_offset = list(offset)
for ii in range(count):
self.locations.append(Location(self,
base.move(*current_offset)))
current_offset[0] += offset[0]
current_offset[1] += offset[1]
return self.locations[-1]
def fade(self, length=0, out=None, index=None):
if index is None:
for location in self.locations:
fader = location.fader
fader.reset()
fader.start(length, out)
else:
fader = self.locations[index].fader
fader.reset()
fader.start(length, out)
def set_alpha(self, alpha):
self.alpha = alpha
for frame in self.frames:
frame.set_alpha(alpha)
for location in self.locations:
location.fader.set_alpha()
def hide(self):
for location in self.locations:
location.hide()
for child in self.children.values():
child.hide()
def unhide(self):
for location in self.locations:
location.unhide()
for child in self.children.values():
child.unhide()
def toggle_hidden(self):
for location in self.locations:
location.toggle_hidden()
for child in self.children.values():
child.toggle_hidden()
def is_hidden(self):
return all(location.is_hidden() for location in self.locations)
def remove_locations(self, location=None):
if location:
self.locations.remove(location)
else:
self.locations = self.locations[:1]
def reverse(self, frameset=None):
if frameset:
frameset.reverse()
else:
for frameset in self.framesets:
frameset.reverse()
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):
self._step.set_step(dx, dy, magnitude, angle)
def cancel_step(self):
self._step.set_step(0, 0)
def is_stepping(self):
return bool(self._step)
def set_child(self, name, child):
self.children[name] = child
def get_child(self, name):
return self.children[name]
def has_child(self, name):
return name in self.children
def remove_child(self, name):
if self.has_child(name):
self.children.pop(name)
def wipe_out(self):
"""
Increase index of wipe animation, causing blinds frames to grow. To launch the animation use
sprite.play(sprite.wipe_out)
"""
for child in self.children.values():
if not child.is_playing(child.wipe_out):
child.play(child.wipe_out)
self.current_wipe_index += 1
if self.current_wipe_index == len(self.wipe_blinds):
self.current_wipe_index = 0
self.halt(self.wipe_out)
self.hide()
def get_current_blinds(self):
return self.wipe_blinds[self.current_wipe_index]
def set_areas(self, areas):
self.areas = areas
def update(self, areas=None, substitute=None, flags=0):
Animation.update(self)
if self.is_stepping():
self.move(self._step.dx, self._step.dy)
if self.get_current_frameset().length():
if self.is_playing(self.wipe_out):
areas = self.get_current_blinds()
elif not areas and self.areas:
areas = self.areas
self.draw(areas, substitute, flags)
for child in self.children.values():
if self.draw_children_on_frame:
child.display_surface = self.get_current_frame()
else:
child.display_surface = self.get_display_surface()
save_child_topleft = child.location.topleft
child.move(*self.location.topleft)
child.update()
if not self.draw_children_on_frame:
child.location.topleft = save_child_topleft
def draw(self, areas=None, substitute=None, flags=0):
for location in self.locations:
location.fader.draw(areas, substitute, flags)
def rotate(self):
"""
Rotate the sprite's frame surfaces and location rects by 90 degrees clockwise
"""
for ii, frame in enumerate(self.frames):
self.frames[ii] = pygame.transform.rotate(frame, 90)
for ii, location in enumerate(self.locations):
rotated = pygame.Rect(0, 0, 0, 0)
rotated.x = location.y
rotated.y = self.get_display_surface().get_height() - location.x + location.w
rotated.w = location.h
rotated.h = location.w
self.locations[ii] = Location(self, rect=rotated)
for child in self.children.values():
child.rotate()
class Location(Rect):
def __init__(self, sprite, rect=(0, 0, 0, 0)):
self.sprite = sprite
Rect.__init__(self, rect)
self.motion_overflow = Vector()
self.fader = Fader(self)
self.unhide()
def move_ip(self, dx, dy):
if isinstance(dx, float) or isinstance(dy, float):
excess = self.update_motion_overflow(dx, dy)
Rect.move_ip(self, int(dx) + excess[0], int(dy) + excess[1])
else:
Rect.move_ip(self, dx, dy)
def update_motion_overflow(self, dx, dy):
overflow = self.motion_overflow
overflow.move(dx - int(dx), dy - int(dy))
excess = [int(value) for value in overflow]
# excess = map(int, overflow)
overflow[0] -= int(overflow[0])
overflow[1] -= int(overflow[1])
return excess
def move_to(self, x, y, base=None):
ox, oy = self.apply_motion_overflow(base)
self.move_ip(x - ox, y - oy)
def reset_motion_overflow(self):
self.motion_overflow.place_at_origin()
def apply_motion_overflow(self, coordinates=None):
if coordinates is None:
coordinates = self.topleft
return self.motion_overflow + coordinates
def hide(self):
self.hidden = True
def unhide(self):
self.hidden = False
def toggle_hidden(self):
self.hidden = not self.hidden
def is_hidden(self):
return self.hidden
class Fader(Surface):
def __init__(self, location):
self.location = location
self.time_filter = location.sprite.get_game().time_filter
self.reset()
def reset(self):
self.init_surface()
self.fade_remaining = None
def init_surface(self):
Surface.__init__(self, self.location.size)
if self.location.sprite.get_current_frameset().length():
background = Surface(self.get_size())
sprite = self.location.sprite
key = sprite.get_current_frame().get_colorkey() or (255, 0, 255)
self.set_colorkey(key)
background.fill(key)
self.background = background
self.set_alpha()
def set_alpha(self, alpha=None):
if alpha is None:
alpha = self.location.sprite.alpha
Surface.set_alpha(self, alpha)
def start(self, length, out=None):
if self.fade_remaining <= 0:
alpha = self.get_alpha()
maximum = self.location.sprite.alpha
if out is None:
out = alpha == maximum
if out and alpha > 0 or not out and alpha < maximum:
self.fade_length = self.fade_remaining = length
self.start_time = self.time_filter.get_ticks()
self.fading_out = out
def draw(self, areas=None, substitute=None, flags=0):
sprite = self.location.sprite
if substitute is None:
frame = sprite.get_current_frame()
else:
frame = substitute
if self.fade_remaining is not None and self.fade_remaining >= 0:
self.update_alpha()
self.clear()
frame.set_alpha(255)
self.blit(frame, (0, 0))
frame.set_alpha(sprite.alpha)
if not self.location.is_hidden():
if frame.get_colorkey() is None:
ratio = self.get_alpha() / 255.0
pixels = PixelArray(frame.copy())
color = Color(0, 0, 0)
for x in range(len(pixels)):
for y in range(len(pixels[0])):
h, s, l, a = Color(*frame.unmap_rgb(pixels[x][y])).hsla
if a:
color.hsla = h, s, l, int(a * ratio)
pixels[x][y] = color
self.blit_to_display(pixels.make_surface(), areas, flags)
del pixels
else:
self.blit_to_display(self, areas, flags)
elif self.fade_remaining is None or self.get_alpha() >= sprite.alpha:
if self.fade_remaining is not None and self.fade_remaining >= 0:
self.update_alpha()
if not self.location.is_hidden():
self.blit_to_display(frame, areas, flags)
def blit_to_display(self, frame, areas=None, flags=0):
if not isinstance(areas, list):
areas = [areas]
for area in areas:
if area:
dest = area.left + self.location.left, \
area.top + self.location.top
else:
dest = self.location
self.location.sprite.display_surface.blit(frame, dest, area, flags)
def update_alpha(self):
remaining = self.fade_remaining = self.fade_length - \
(self.time_filter.get_ticks() - self.start_time)
ratio = self.fade_length and float(remaining) / self.fade_length
if not self.fading_out:
ratio = 1 - ratio
maximum = self.location.sprite.alpha
alpha = int(ratio * maximum)
if alpha > maximum:
alpha = maximum
elif alpha < 0:
alpha = 0
self.set_alpha(alpha)
def clear(self):
self.blit(self.background, (0, 0))
class Frameset:
def __init__(self, sprite, order=[], framerate=None, name=None):
self.sprite = sprite
self.name = name
self.reversed = False
self.order = []
self.rect = Rect(0, 0, 0, 0)
self.add_index(order)
self.set_framerate(framerate)
self.reset()
def add_index(self, order):
if isinstance(order, int):
order = [order]
self.order += order
self.measure_rect()
def set_framerate(self, framerate):
self.framerate = framerate
def reset(self):
self.current_index = 0
self.sprite.accounts[self.sprite.shift_frame].reset_interval()
def get_current_id(self):
return self.order[self.current_index]
def measure_rect(self):
max_width, max_height = 0, 0
frames = self.sprite.frames
for index in self.order:
frame = frames[index]
width, height = frame.get_size()
max_width = max(width, max_width)
max_height = max(height, max_height)
self.rect.size = max_width, max_height
def shift(self):
if len(self.order) > 1:
self.increment_index()
def increment_index(self, increment=None):
if increment is None:
increment = 1 if not self.reversed else -1
index = self.current_index + increment
while index < 0:
index += self.length()
while index >= self.length():
index -= self.length()
self.current_index = index
def length(self):
return len(self.order)
def reverse(self):
self.reversed = not self.reversed
def __str__(self):
if self.name is None:
return ""
else:
return self.name
class BlinkingSprite(Sprite):
def __init__(self, parent, blink_rate, framerate=None):
Sprite.__init__(self, parent, framerate)
self.register(self.blink, interval=blink_rate)
self.register(self.start_blinking, self.stop_blinking)
self.play(self.blink)
def reset(self):
self.unhide()
def blink(self):
self.toggle_hidden()
def start_blinking(self):
self.play(self.blink)
def stop_blinking(self):
self.halt(self.blink)
self.unhide()
class RainbowSprite(Sprite):
def __init__(self, parent, image=None, hue_shift=8, framerate=None):
Sprite.__init__(self, parent, framerate)
self.hue_shift = hue_shift
if image is not None:
self.set_frames(image)
def set_frames(self, image):
for hue in range(0, 360, self.hue_shift):
self.add_frame(get_hue_shifted_surface(image, hue))
class ShadowedSprite(Sprite):
def __init__(self, parent, offset=Vector(3, 3), opacity=1, color=(0, 0, 0), framerate=None):
Sprite.__init__(self, parent, framerate)
if not isinstance(offset, Vector):
offset = Vector(*offset)
if not isinstance(color, Color):
color = Color(*color)
self.shadow_color = color
self.shadow_offset = offset
self.shadow_opacity = opacity
self.shadow_color = color
self.shadow = Sprite(self)
def add_frame(self, frame, omit=False):
Sprite.add_frame(self, frame, omit)
fr = frame.get_rect()
shadow = Surface(fr.size, SRCALPHA)
pixels = PixelArray(frame)
shadow_pixels = PixelArray(shadow)
r, g, b = self.shadow_color.r, self.shadow_color.g, self.shadow_color.b
for y in range(len(pixels)):
for x in range(len(pixels[0])):
frame_pixel_color = frame.unmap_rgb(pixels[y][x])
if frame_pixel_color == frame.get_colorkey():
shadow_pixel_color = 0, 0, 0, 0
else:
shadow_pixel_color = r, g, b, frame_pixel_color.a * self.shadow_opacity
shadow_pixels[y][x] = shadow_pixel_color
self.shadow.add_frame(shadow)
def clear_frames(self):
self.shadow.clear_frames()
Sprite.clear_frames(self)
def update(self):
self.shadow.location.topleft = Vector(*self.location.topleft).get_moved(
*self.shadow_offset)
self.shadow.update()
Sprite.update(self)

View File

@ -1,39 +0,0 @@
from pygame.time import get_ticks
from .GameChild import GameChild
class TimeFilter(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.reset_ticks()
self.open()
def reset_ticks(self):
self.ticks = self.unfiltered_ticks = self.last_ticks = get_ticks()
def close(self):
self.closed = True
def open(self):
self.closed = False
def get_ticks(self):
return self.ticks
def get_unfiltered_ticks(self):
return self.unfiltered_ticks
def get_last_ticks(self):
return self.last_ticks
def get_last_frame_duration(self):
return self.last_frame_duration
def update(self):
ticks = get_ticks()
self.last_frame_duration = duration = ticks - self.last_ticks
if not self.closed:
self.ticks += duration
self.unfiltered_ticks += duration
self.last_ticks = ticks

View File

@ -1,148 +0,0 @@
from math import pi, degrees
class Vector(list):
def __init__(self, x=0, y=0):
list.__init__(self, (x, y))
def __repr__(self):
message = "<%f" % self[0]
for value in self[1:]:
message += ", %f" % value
return message + ">"
def __getattr__(self, name):
if name == "x":
return self[0]
elif name == "y":
return self[1]
else:
raise AttributeError
def __setattr__(self, name, value):
if name == "x":
self[0] = value
elif name == "y":
self[1] = value
else:
list.__setattr__(self, name, value)
def __add__(self, other):
return Vector(self.x + other[0], self.y + other[1])
__radd__ = __add__
def __iadd__(self, other):
self.x += other[0]
self.y += other[1]
return self
def __sub__(self, other):
return Vector(self.x - other[0], self.y - other[1])
def __rsub__(self, other):
return Vector(other[0] - self.x, other[1] - self.y)
def __isub__(self, other):
self.x -= other[0]
self.y -= other[1]
return self
def __mul__(self, other):
return Vector(self.x * other, self.y * other)
__rmul__ = __mul__
def __imul__(self, other):
self.x *= other
self.y *= other
return self
def __eq__(self, other):
for sv, ov in zip(self, other):
if sv != ov:
return False
return True
def __ne__(self, other):
for sv, ov in zip(self, other):
if sv == ov:
return False
return True
def __nonzero__(self):
for value in self:
if bool(value):
return True
return False
def apply_to_components(self, function):
self.x = function(self.x)
self.y = function(self.y)
def place(self, x=None, y=None):
if x is not None:
self.x = x
if y is not None:
self.y = y
def move(self, dx=0, dy=0):
if dx:
self.x += dx
if dy:
self.y += dy
def get_moved(self, dx=0, dy=0):
return Vector(self.x + dx, self.y + dy)
def place_at_origin(self):
self.place(0, 0)
def vol(self):
return self.x * self.y
def copy(self):
return Vector(self.x, self.y)
class EVector(Vector):
def __init__(self, x=0, y=0, dx=0, dy=0, magnitude=None, angle=0):
Vector.__init__(self, x, y)
self.angle = None
self.set_step(dx, dy, magnitude, angle)
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):
"""specify angle in radians, counter-clockwise, 0 is up"""
if magnitude is not None:
from .extension import get_delta
self.magnitude = magnitude
self.angle = angle
self.dx, self.dy = get_delta(angle, magnitude, False)
else:
self.dx = dx
self.dy = dy
if dx == 0 and dy == 0:
self.magnitude = 0
self.angle = 0
else:
from .extension import get_angle, get_distance
end = self.x + dx, self.y + dy
self.angle = get_angle(self, end, True)
self.magnitude = get_distance(self, end)
def __repr__(self):
return "<dx=%.2f, dy=%.2f, m=%.2f, ang=%.2f>" % \
(self.dx, self.dy, self.magnitude, self.angle)
def __nonzero__(self):
return bool(self.magnitude)
def __setattr__(self, name, value):
list.__setattr__(self, name, value)
if name == "magnitude" and self.angle is not None:
from .extension import get_delta
self.dx, self.dy = get_delta(self.angle, value, False)
def move(self):
self += self.dx, self.dy

View File

@ -1,113 +0,0 @@
from os import makedirs
from os.path import exists, join
from tempfile import TemporaryFile
from time import strftime
from pygame.image import tostring, frombuffer, save
from pygame.time import get_ticks
from .GameChild import GameChild
class VideoRecorder(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.set_requested()
if self.requested:
self.display_surface = self.get_display_surface()
self.delegate = self.get_delegate()
self.load_configuration()
self.reset()
self.subscribe(self.respond)
def set_requested(self):
self.requested = self.get_configuration("video-recordings")["enable"] \
or self.check_command_line("-enable-video")
def load_configuration(self):
config = self.get_configuration("video-recordings")
self.root = config["path"]
self.directory_name_format = config["directory-name-format"]
self.file_extension = config["file-extension"]
self.frame_format = config["frame-format"]
self.framerate = config["framerate"]
def reset(self):
self.recording = False
self.frame_length = None
self.frames = None
self.last_frame = 0
def respond(self, event):
compare = self.delegate.compare
if compare(event, "record-video"):
self.toggle_record()
elif compare(event, "reset-game"):
self.reset()
def is_audio_recording_enabled(self):
return self.get_configuration("video-recordings", "record-audio") or\
self.check_command_line("-enable-sound-recording")
def toggle_record(self):
recording = not self.recording
if recording:
self.frame_length = len(self.get_string())
temp_dir = self.get_configuration("video-recordings", "temp-directory")
if temp_dir == "":
temp_dir = None
self.frames = TemporaryFile(dir=temp_dir)
print("writing video frames to {}".format(self.frames.name))
if self.is_audio_recording_enabled():
import pyaudio
import wave
self.audio_frames = []
self.audio_recorder = pyaudio.PyAudio()
def audio_callback(in_data, frame_count, time_info, status):
self.audio_frames.append(in_data)
return (in_data, pyaudio.paContinue)
for ii in range(self.audio_recorder.get_device_count()):
print("device {}: {}".format(ii, self.audio_recorder.get_device_info_by_index(ii)))
self.audio_stream = self.audio_recorder.open(format=pyaudio.paInt32, channels=2, rate=44100,
frames_per_buffer=1024,
input=True, stream_callback=audio_callback)
print("writing audio data to {}".format(self.audio_stream))
else:
self.write_frames()
self.recording = recording
def get_string(self):
return tostring(self.display_surface, self.frame_format)
def write_frames(self):
root = join(self.root, strftime(self.directory_name_format))
if not exists(root):
makedirs(root)
if self.is_audio_recording_enabled():
import pyaudio
import wave
self.audio_stream.stop_stream()
self.audio_stream.close()
self.audio_recorder.terminate()
wf = wave.open(join(root, "audio.wav"), "wb")
wf.setnchannels(2)
wf.setsampwidth(self.audio_recorder.get_sample_size(pyaudio.paInt32))
wf.setframerate(44100)
wf.writeframes(b"".join(self.audio_frames))
wf.close()
print("saved audio in {}".format(root))
size = self.display_surface.get_size()
frames = self.frames
frames.seek(0)
for ii, frame in enumerate(
iter(lambda: frames.read(self.frame_length), b'')):
path = join(root, "{:0{digits}}.png".format(
ii, digits=self.get_configuration("video-recordings", "filename-digits")))
save(frombuffer(frame, size, self.frame_format), path)
print("saved video frames in {}".format(root))
def update(self):
ticks = get_ticks()
if self.recording and ticks - self.last_frame >= self.framerate:
self.frames.write(self.get_string())
self.last_frame = ticks

View File

@ -1,12 +0,0 @@
# __init__.py
#
# Import all classes from module files, making them accessible through `pgfw.CLASSNAME`
# Game imports most (all?) of PGFW, so those class become accessible as well (?)
from .Game import *
# Not sure why but pgfw.GameChild causes a module error without importing Animation directly
from .Animation import *
# Import all classes defined in Sprite
from .Sprite import *

View File

@ -0,0 +1,245 @@
from os import sep, getcwd
from os.path import join, exists, basename, dirname, expanduser
from sys import argv
from pprint import pformat
from ConfigParser import RawConfigParser
class Configuration(RawConfigParser):
default_project_file_rel_path = "config"
default_resources_paths = [".", "resources"]
list_member_sep = ','
def __init__(self, project_file_rel_path=None, resources_path=None,
type_declarations=None):
RawConfigParser.__init__(self)
self.project_file_rel_path = project_file_rel_path
self.resources_path = resources_path
self.set_type_declarations(type_declarations)
self.set_defaults()
self.read_project_config_file()
self.modify_defaults()
self.print_debug(self)
def set_type_declarations(self, type_declarations):
if type_declarations is None:
type_declarations = TypeDeclarations()
self.type_declarations = type_declarations
# def set(self, section, option, value):
# value = self.cast_value(section, option, value)
# RawConfigParser.set(self, section, option, value)
def set_defaults(self):
add_section = self.add_section
set_option = self.set
section = "setup"
add_section(section)
set_option(section, "package-root", basename(getcwd()))
set_option(section, "title", "")
set_option(section, "classifiers", "")
set_option(section, "resources-search-path", "./, resources/")
set_option(section, "installation-dir", "/usr/local/share/games/")
set_option(section, "changelog", "changelog")
set_option(section, "description-file", "")
set_option(section, "init-script", "")
set_option(section, "version", "")
set_option(section, "summary", "")
set_option(section, "license", "")
set_option(section, "platforms", "")
set_option(section, "contact-name", "")
set_option(section, "contact-email", "")
set_option(section, "url", "")
set_option(section, "requirements", "")
set_option(section, "main-object", "pgfw/Game.py")
set_option(section, "resources-path-identifier", "resources_path")
section = "display"
add_section(section)
set_option(section, "dimensions", "480, 320")
set_option(section, "frame-duration", "33")
set_option(section, "wait-duration", "2")
set_option(section, "caption", "")
set_option(section, "centered", "yes")
section = "screen-captures"
add_section(section)
set_option(section, "rel-path", "caps")
set_option(section, "file-name-format", "%Y-%m-%d_%H:%M:%S")
set_option(section, "file-extension", "png")
section = "keys"
add_section(section)
set_option(section, "up", "K_UP, K_w")
set_option(section, "right", "K_RIGHT, K_d")
set_option(section, "down", "K_DOWN, K_s")
set_option(section, "left", "K_LEFT, K_a")
set_option(section, "confirm", "K_RETURN")
set_option(section, "capture-screen", "K_F9")
section = "event"
add_section(section)
set_option(section, "user-event-id", "USEREVENT")
set_option(section, "command-event-name", "command")
def read(self, filenames):
files_read = RawConfigParser.read(self, filenames)
for section in self.sections():
for option, value in self.items(section):
self.set(section, option, value)
return files_read
def read_project_config_file(self):
path = self.locate_project_config_file()
if path:
self.read(path)
else:
self.print_debug("No configuration file found")
def locate_project_config_file(self):
rel_path = self.project_file_rel_path
if not rel_path:
rel_path = self.default_project_file_rel_path
if exists(rel_path) and not self.is_shared_mode():
return rel_path
if self.resources_path:
installed_path = join(self.resources_path, rel_path)
if exists(installed_path):
return installed_path
def is_shared_mode(self):
return "-s" in argv
def print_debug(self, statement):
if self.is_debug_mode():
print statement
def is_debug_mode(self):
return "-d" in argv
def modify_defaults(self):
self.set_installation_path()
self.set_resources_search_path()
self.set_screen_captures_path()
self.set_data_exclusion_list()
self.set_requirements()
def set_installation_path(self):
self.set("setup", "installation-path",
join(self.get("setup", "installation-dir", "path"),
self.get("setup", "package-root", "path")))
def set_resources_search_path(self):
section, option = "setup", "resources-search-path"
search_path = self.get(section, option, "list")
if self.resources_path:
search_path.append(self.resources_path)
else:
search_path.append(self.get("setup", "installation-path", "path"))
self.set(section, option, search_path)
def get(self, section, option, cast=None):
value = RawConfigParser.get(self, section, option)
if value is None:
value = self.get_substitute(section, option)
if cast:
value = self.cast_value(value, cast)
return value
def cast_value(self, value, cast):
list_sep = self.list_member_sep
if cast == "bool":
return True if value == "yes" else False
elif cast == "int":
return int(value)
elif cast == "float":
return float(value)
elif cast == "path":
return self.translate_path(value)
elif cast == "list":
if value == "":
return []
else:
return map(str.strip, value.split(list_sep))
elif cast == "int-list":
return map(int, value.split(list_sep))
return value
def translate_path(self, path):
new = ""
if path and path[0] == sep:
new += sep
return expanduser("{0}{1}".format(new, join(*path.split(sep))))
def get_substitute(self, section, option):
if section == "display":
if option == "caption":
return self.get("setup", "title")
def set_screen_captures_path(self):
section, option = "screen-captures", "path"
if not self.has_option(section, option):
self.set(section, option, join(self.build_home_path(),
self.get(section, "rel-path",
"path")))
def build_home_path(self):
return join("~", "." + self.get("setup", "package-root", "path"))
def set_data_exclusion_list(self):
section, option = "setup", "data-exclude"
exclude = []
if self.has_option(section, option):
exclude = self.get(section, option, "list")
exclude += [".git", ".gitignore", "README", "build/", "dist/",
"setup.py", "MANIFEST",
self.get("setup", "package-root", "path"),
self.get("setup", "changelog", "path")]
self.set(section, option, exclude)
def set_requirements(self):
section, option = "setup", "requirements"
requirements = []
if self.has_option(section, option):
requirements = self.get(section, option, "list")
if "pygame" not in requirements:
requirements.append("pygame")
self.set(section, option, requirements)
def get_section(self, section, cast=None):
assignments = {}
for option in self.options(section):
assignments[option] = self.get(section, option, cast)
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):
def __init__(self):
dict.__init__(self, {"bool": [], "int": [], "float": [], "path": [],
"list": [], "int-list": []})
add = self.add
add("int", "display", "frame-duration")
add("int", "display", "wait-duration")
add("bool", "display", "centered")
add("int-list", "display", "dimensions")
add("path", "screen-captures", "path")
add("list", "setup", "classifiers")
add("list", "setup", "resources-search-path")
add("path", "setup", "installation-dir")
add("path", "setup", "package-root")
add("list", "setup", "data-exclude")
add("path", "setup", "changelog")
add("list", "setup", "requirements")
add("path", "setup", "description-file")
add("path", "setup", "main-object")
add("list", "keys", "up")
add("list", "keys", "right")
add("list", "keys", "down")
add("list", "keys", "left")
def add(self, type, section, option):
self[type].append((section, option))

View File

@ -1,10 +0,0 @@
CONFIRM_QUIT_MESSAGE = '''
............................................................
..###..###..#....#.####.#.####..#...#....###..#...#.#.#####.
.#....#...#.##...#.#....#.#...#.##.##...#...#.#...#.#...#...
.#....#...#.#.#..#.####.#.#...#.#.#.#...#...#.#...#.#...#...
.#....#...#.#..#.#.#....#.####..#...#...#.#.#.#...#.#...#...
.#....#...#.#...##.#....#.#...#.#...#...#..#..#...#.#...#...
..###..###..#....#.#....#.#...#.#...#....##.#..###..#...#...
............................................................
'''

View File

@ -1,570 +0,0 @@
import itertools, random, os, glob
from math import sin, cos, atan2, radians, sqrt, pi
import pygame
from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw
from pygame.mixer import get_num_channels, Channel
from pygame.locals import *
from .Vector import Vector
def clamp(n, min_n, max_n):
if n < min_n:
return min_n
elif n > max_n:
return max_n
else:
return n
def get_step(start, end, speed):
x0, y0 = start
x1, y1 = end
angle = atan2(x1 - x0, y1 - y0)
return Vector(speed * sin(angle), speed * cos(angle))
def get_step_relative(start, end, step):
return get_step(start, end, get_distance(start, end) * step)
def get_segments(start, end, count):
rel_step = get_step_relative(start, end, 1 / float(count))
segs = [[Vector(start[0], start[1])]]
for ii in range(count):
seg_end = Vector(segs[-1][0].x + rel_step.x, segs[-1][0].y + rel_step.y)
segs[-1].append(seg_end)
if ii < count - 1:
segs.append([seg_end])
return segs
def get_points_on_line(start, end, count):
rel_step = get_step_relative(start, end, 1 / float(count - 1))
points = [Vector(start[0], start[1])]
for ii in range(count - 2):
points.append(Vector(points[-1][0] + rel_step[0], points[-1][1] + rel_step[1]))
points.append(Vector(end[0], end[1]))
return points
def get_angle(start, end, transpose=False):
"""counterclockwise, 0 is down"""
angle = atan2(end[0] - start[0], end[1] - start[1])
if transpose:
angle = -angle - pi
return angle
def get_endpoint(start, angle, magnitude, translate_angle=True):
"""clockwise, 0 is up"""
x0, y0 = start
dx, dy = get_delta(angle, magnitude, translate_angle)
return Vector(x0 + dx, y0 + dy)
def get_delta(angle, magnitude, translate_angle=True):
if translate_angle:
angle = radians(angle)
return Vector(sin(angle) * magnitude, -cos(angle) * magnitude)
def reflect_angle(angle, wall):
return wall - angle
def rotate_2d(point, center, angle, translate_angle=True):
if translate_angle:
angle = radians(angle)
x, y = point
cx, cy = center
return cos(angle) * (x - cx) - sin(angle) * (y - cy) + cx, \
sin(angle) * (x - cx) + cos(angle) * (y - cy) + cy
def get_points_on_circle(center, radius, count, offset=0):
angle_step = 360.0 / count
points = []
current_angle = 0
for _ in range(count):
points.append(get_point_on_circle(center, radius,
current_angle + offset))
current_angle += angle_step
return points
def get_point_on_circle(center, radius, angle, translate_angle=True):
if translate_angle:
angle = radians(angle)
return Vector(center[0] + sin(angle) * radius,
center[1] - cos(angle) * radius)
def get_range_steps(start, end, count, omit=[]):
'''
Iterator that yields evenly spaced steps from `start` to `end` as floats based on step `count`. Indicies in the
omit parameter will be skipped.
'''
# normalize array indicies (for example -1 becomes count - 1)
for ii in range(len(omit)):
omit[ii] = omit[ii] % count
for ii in range(count):
if ii in omit:
continue
else:
yield start + (end - start) * ii / float(count - 1)
def get_percent_way(iterable):
for ii in range(len(iterable)):
yield iterable[ii], float(ii) / (len(iterable) - 1)
def mirrored(iterable, full=False, tail=True):
for ii, item in enumerate(itertools.chain(iterable, reversed(iterable))):
if not full and ii == len(iterable):
continue
elif not tail and ii == len(iterable) * 2 - 1:
continue
else:
yield item
def get_distance(p0, p1):
"""
Return the distance between two points
"""
return sqrt((p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2)
def place_in_rect(rect, incoming, contain=True, *args):
while True:
incoming.center = random.randint(0, rect.w), random.randint(0, rect.h)
if not contain or rect.contains(incoming):
collides = False
for inner in args:
if inner.colliderect(incoming):
collides = True
break
if not collides:
break
def constrain_dimensions_2d(vector, container):
dw = vector[0] - container[0]
dh = vector[1] - container[1]
if dw > 0 or dh > 0:
if dw > dh:
size = Vector(container[0], int(round(container[0] / vector[0] * vector[1])))
else:
size = Vector(int(round(container[1] / vector[1] * vector[0])), container[1])
else:
size = Vector(vector[0], vector[1])
return size
# from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c
def get_intersection(p0, p1, p2, p3):
x0, y0 = p0
x1, y1 = p1
x2, y2 = p2
x3, y3 = p3
a0 = y1 - y0
b0 = x0 - x1
c0 = x1 * y0 - x0 * y1
r2 = a0 * x2 + b0 * y2 + c0
r3 = a0 * x3 + b0 * y3 + c0
if r2 != 0 and r3 != 0 and r2 * r3 > 0:
return None
a1 = y3 - y2
b1 = x2 - x3
c1 = x3 * y2 - x2 * y3
r0 = a1 * x0 + b1 * y0 + c1
r1 = a1 * x1 + b1 * y1 + c1
if r0 != 0 and r1 != 0 and r0 * r1 > 0:
return None
denominator = a0 * b1 - a1 * b0
if denominator == 0:
return (x0 + x1 + x2 + x3) / 4, (y0 + y1 + y2 + y3) / 4
if denominator < 0:
offset = -denominator / 2
else:
offset = denominator / 2
numerator = b0 * c1 - b1 * c0
x = ((-1, 1)[numerator < 0] * offset + numerator) / denominator
numerator = a1 * c0 - a0 * c1
y = ((-1, 1)[numerator < 0] * offset + numerator) / denominator
return x, y
def collide_line_with_rect(rect, p0, p1):
for line in ((rect.topleft, rect.topright),
(rect.topright, rect.bottomright),
(rect.bottomright, rect.bottomleft),
(rect.bottomleft, rect.topleft)):
if get_intersection(p0, p1, *line):
return True
def get_random_number_in_range(start, end):
return random.random() * (end - start) + start
def get_value_in_range(start, end, position, reverse=False):
if reverse:
position = 1 - position
return (end - start) * position + start
def fill_borders(surface, color=Color(0, 0, 0), thickness=1, rect=None, flags=0, include=(True, True, True, True)):
'''
Draw borders on a surface at rect position. A subset of the four borders can be drawn by passing the include
iterable with bools for each position (top, right, bottom, left)
'''
if rect is None:
rect = surface.get_rect()
# handle horizontal sides differently based on whether top and bottom are being drawn
horizontal_h = rect.h - thickness * 2
horizontal_y = rect.top + thickness
if not include[0]:
horizontal_y = rect.top
horizontal_h += thickness
if not include[2]:
horizontal_h += thickness
if include[0]:
surface.fill(color, (rect.left, rect.top, rect.w, thickness), flags)
if include[1]:
surface.fill(color, (rect.right - thickness, horizontal_y, thickness, horizontal_h), flags)
if include[2]:
surface.fill(color, (rect.left, rect.bottom - thickness, rect.w, thickness), flags)
if include[3]:
surface.fill(color, (rect.left, horizontal_y, thickness, horizontal_h), flags)
def get_boxed_surface(surface, background=None, border=None, border_width=1, padding=0):
if padding:
if isinstance(padding, int):
padding = [padding] * 2
padding = [x * 2 for x in padding]
rect = surface.get_rect()
padded_surface = Surface(rect.inflate(padding).size, SRCALPHA)
if background is not None:
padded_surface.fill(background)
rect.center = padded_surface.get_rect().center
padded_surface.blit(surface, rect)
surface = padded_surface
if border is not None:
if isinstance(border_width, int):
border_width = [border_width] * 2
border_width = [x * 2 for x in border_width]
rect = surface.get_rect()
bordered_surface = Surface(rect.inflate(border_width).size, SRCALPHA)
bordered_surface.fill(border)
rect.center = bordered_surface.get_rect().center
bordered_surface.fill((255, 255, 255, 255), rect)
bordered_surface.blit(surface, rect, None, BLEND_RGBA_MIN)
surface = bordered_surface
return surface
def render_box(font=None, text=None, antialias=True, color=(0, 0, 0), background=None, border=None,
border_width=1, padding=0, width=None, height=None):
if font is not None:
surface = font.render(text, antialias, color, background)
if width is not None or height is not None:
if width is None:
width = surface.get_width()
if height is None:
height = surface.get_height()
container = Surface((width, height), SRCALPHA)
if background is not None:
container.fill(background)
text_rect = surface.get_rect()
text_rect.center = container.get_rect().center
container.blit(surface, text_rect)
surface = container
else:
surface = pygame.Surface((width, height), SRCALPHA)
if background is not None:
surface.fill(background)
return get_boxed_surface(surface, background, border, border_width, padding)
def get_wrapped_text_surface(font, text, width, antialias=True, color=(0, 0, 0),
background=None, border=None, border_width=1,
padding=0, align="left"):
lines = []
height = 0
for chunk in text.split("\n"):
line_text = ""
ii = 0
finished = False
if chunk.startswith("\*") and chunk.endswith("\*"):
chunk = chunk.replace("\*", "*")
elif chunk.startswith("*") and chunk.endswith("*"):
chunk = chunk[1:-1]
font.set_italic(True)
words = chunk.split(" ")
while not finished:
line_width = font.size(line_text + " " + words[ii])[0]
if line_width > width or ii == len(words) - 1:
if ii == len(words) - 1 and line_width <= width:
if line_text != "":
line_text += " "
line_text += words[ii]
finished = True
line = font.render(line_text, antialias, color)
height += line.get_height()
lines.append(line)
line_text = ""
else:
if ii > 0:
line_text += " "
line_text += words[ii]
ii += 1
font.set_italic(False)
top = 0
surface = Surface((width, height), pygame.SRCALPHA)
# if background:
# surface.fill(background)
rect = surface.get_rect()
for line in lines:
line_rect = line.get_rect()
line_rect.top = top
if align == "center":
line_rect.centerx = rect.centerx
surface.blit(line, line_rect)
top += line_rect.h
return get_boxed_surface(surface, background, border, border_width, padding)
def replace_color(surface, color, replacement):
'''
Replace a color on a surface without creating a copy
'''
pixels = PixelArray(surface)
pixels.replace(color, replacement)
del pixels
def get_color_swapped_surface(surface, color, replacement):
'''
Get a copy of given Surface with given color replaced by given replacement
'''
swapped = surface.copy()
replace_color(swapped, color, replacement)
return swapped
def get_busy_channel_count():
'''
Return count of audio channels currently playing audio
'''
count = 0
for index in range(get_num_channels()):
count += Channel(index).get_busy()
return count
def get_hue_shifted_surface(base, offset):
surface = base.copy()
pixels = PixelArray(surface)
for x in range(surface.get_width()):
for y in range(surface.get_height()):
rgb = surface.unmap_rgb(pixels[x][y])
color = Color(*rgb)
h, s, l, a = color.hsla
if a and surface.get_colorkey() != rgb:
color.hsla = (int(h) + offset) % 360, int(s), int(l), int(a)
pixels[x][y] = color
del pixels
return surface
def get_inverted_color(color):
return Color(255 - color[0], 255 - color[1], 255 - color[2])
def get_inverted_surface(base):
surface = base.copy()
pixels = PixelArray(surface)
for x in range(surface.get_width()):
for y in range(surface.get_height()):
color = Color(*surface.unmap_rgb(pixels[x][y]))
if color.hsla[3]:
pixels[x][y] = get_inverted_color(color)
del pixels
return surface
def fill_tile(surface, tile, rect=None, flags=0, offset=Vector(0, 0)):
'''
Fill surface with tile surface. If rect is set, only fill in that section. If offset is set, start filling
with tile shifted by offset (-x, -y)
'''
w, h = tile.get_size()
surface.set_clip(rect)
for x in range(-offset.x, surface.get_width(), w):
for y in range(-offset.y, surface.get_height(), h):
surface.blit(tile, (x, y), None, flags)
surface.set_clip(None)
def load_frames(path, transparency=False, ppa=True, key=None, query=None):
if os.path.isfile(path):
paths = [path]
else:
if query:
paths = sorted(glob.glob(os.path.join(path, query)))
else:
paths = [os.path.join(path, name) for name in sorted(os.listdir(path))]
frames = []
for path in paths:
img = pygame.image.load(path)
if transparency:
if ppa:
frame = img.convert_alpha()
else:
frame = fill_colorkey(img, key)
else:
frame = img.convert()
frames.append(frame)
return frames
def fill_colorkey(img, key=None):
if key is None:
key = 255, 0, 255
img = img.convert_alpha()
frame = Surface(img.get_size())
frame.fill(key)
frame.set_colorkey(key)
frame.blit(img, (0, 0))
return frame
def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0, 0, 0),
colorkey=(255, 0, 255)):
foreground = font.render(text, antialias, color)
background = font.render(text, antialias, shadow_color)
alpha = SRCALPHA if antialias else 0
surface = Surface((foreground.get_width() + abs(offset[0]),
foreground.get_height() + abs(offset[1])), alpha)
if not antialias:
surface.set_colorkey(colorkey)
surface.fill(colorkey)
surface.blit(background, ((abs(offset[0]) + offset[0]) / 2,
(abs(offset[1]) + offset[1]) / 2))
surface.blit(foreground, ((abs(offset[0]) - offset[0]) / 2,
(abs(offset[1]) - offset[1]) / 2))
return surface
def get_blinds_rects(w, h, step=.05, count=4):
blinds = []
blind_h = int(round(h / float(count)))
for ii in range(1, count + 1):
blinds.append(Rect(0, blind_h * ii, w, 0))
inflate_h = int(round(blind_h * step))
if inflate_h < 1:
inflate_h = 1
rects = []
while blinds[0].h < blind_h:
rects.append([])
for blind in blinds:
bottom = blind.bottom
blind.inflate_ip(0, inflate_h)
blind.bottom = bottom
rects[-1].append(blind.copy())
return rects
def get_blinds_frames(surface, step=.05, count=4, fill=(0, 0, 0, 0)):
frames = []
rects = []
h = int(round(surface.get_height() / float(count)))
for ii in range(1, count + 1):
rects.append(Rect(0, h * ii, surface.get_width(), 0))
bar_h = int(round(h * step))
if bar_h < 1:
bar_h = 1
while rects[0].h < h:
frame = surface.copy()
for rect in rects:
bottom = rect.bottom
rect.inflate_ip(0, bar_h)
rect.bottom = bottom
frame.fill(fill, rect)
frames.append(frame)
return frames
def get_hsla_color(hue, saturation=100, lightness=50, alpha=100):
'''
Get a pygame Color object from HSLA value
'''
color = Color(0, 0, 0, 0)
color.hsla = hue % 360, clamp(saturation, 0, 100), clamp(lightness, 0, 100), clamp(alpha, 0, 100)
return color
def get_random_hsla_color(hue_range=(0, 359), saturation_range=(0, 100), lightness_range=(0, 100), alpha_range=(100, 100)):
'''
Get a random color with constrained hue, saturation, lightness and alpha
'''
return get_hsla_color(
random.randint(*hue_range), random.randint(*saturation_range), random.randint(*lightness_range),
random.randint(*alpha_range))
def get_hsva_color(hue, saturation=100, value=100, alpha=100):
'''
Get a pygame Color object from HSVA value
'''
color = Color(0, 0, 0, 0)
color.hsva = hue % 360, saturation, value, alpha
return color
def get_lightened_color(color, lightness):
'''
Return a copy of the provided color with specified lightness
'''
h, s, _, a = color.hsla
return get_hsla_color(h, s, clamp(lightness, 0, 100), a)
def get_glow_frames(radius, segments, colors=[(0, 0, 0), (255, 255, 255)], minsize=4, transparency=True):
frames = []
radius = int(round(radius))
sizes = [int(round(minsize + float(ii) / (segments - 1) * (radius - minsize))) for ii in range(segments)]
if transparency:
alpha_step = 255.0 / segments
alpha = alpha_step
else:
alpha = 255
for color_offset in range(len(colors)):
frame = Surface([radius * 2] * 2, SRCALPHA if transparency else None)
if transparency:
alpha = alpha_step
for segment_ii, segment_radius in enumerate(reversed(sizes)):
color = Color(*(colors[(color_offset + segment_ii) % len(colors)] + (int(round(alpha)),)))
gfxdraw.filled_circle(frame, radius, radius, int(round(segment_radius)), color)
if transparency:
alpha += alpha_step
frames.append(frame)
return frames
# http://www.pygame.org/wiki/BezierCurve
def compute_bezier_points(vertices, numPoints=60):
points = []
b0x = vertices[0][0]
b0y = vertices[0][1]
b1x = vertices[1][0]
b1y = vertices[1][1]
b2x = vertices[2][0]
b2y = vertices[2][1]
b3x = vertices[3][0]
b3y = vertices[3][1]
ax = -b0x + 3 * b1x + -3 * b2x + b3x
ay = -b0y + 3 * b1y + -3 * b2y + b3y
bx = 3 * b0x + -6 * b1x + 3 * b2x
by = 3 * b0y + -6 * b1y + 3 * b2y
cx = -3 * b0x + 3 * b1x
cy = -3 * b0y + 3 * b1y
dx = b0x
dy = b0y
numSteps = numPoints - 1
h = 1.0 / numSteps
pointX = dx
pointY = dy
firstFDX = ax * h ** 3 + bx * h ** 2 + cx * h
firstFDY = ay * h ** 3 + by * h ** 2 + cy * h
secondFDX = 6 * ax * h ** 3 + 2 * bx * h ** 2
secondFDY = 6 * ay * h ** 3 + 2 * by * h ** 2
thirdFDX = 6 * ax * h ** 3
thirdFDY = 6 * ay * h ** 3
points.append(Vector(pointX, pointY))
for i in range(numSteps):
pointX += firstFDX
pointY += firstFDY
firstFDX += secondFDX
firstFDY += secondFDY
secondFDX += thirdFDX
secondFDY += thirdFDY
points.append(Vector(pointX, pointY))
return points
def get_marching_ants_color(position, time=0, colors=((0, 0, 0), (255, 255, 255))):
return Color(*(colors[0], colors[1])[((position // 2) % 2 + (time % 2)) % 2])
def diagonal_to_rect(start, end):
sx, sy = start
ex, ey = end
return Rect(min(sx, ex), min(sy, ey), abs(sx - ex), abs(ex - ey))
def scale_2x_multiple(surface, times):
'''
Run the scale2x scale on a surface times number of times
'''
for _ in range(times):
surface = pygame.transform.scale2x(surface)
return surface

View File

@ -1,14 +0,0 @@
from pygame.gfxdraw import (aacircle, filled_circle, aatrigon, filled_trigon,
aapolygon, filled_polygon)
def aa_filled_circle(surface, cx, cy, radius, color):
aacircle(surface, cx, cy, radius, color)
filled_circle(surface, cx, cy, radius, color)
def aa_filled_trigon(surface, x1, y1, x2, y2, x3, y3, color):
aatrigon(surface, x1, y1, x2, y2, x3, y3, color)
filled_trigon(surface, x1, y1, x2, y2, x3, y3, color)
def aa_filled_polygon(surface, points, color):
aapolygon(surface, points, color)
filled_polygon(surface, points, color)

View File

@ -21,4 +21,4 @@ class SampleGame(Game):
if __name__ == '__main__':
# the play method begins the project's animation
SampleGame().run()
SampleGame().play()

View File

@ -1,14 +0,0 @@
from distutils.core import setup
setup(name="Pygame Framework",
version="0.1",
description="Classes to facilitate the creation of pygame projects",
author="Frank DeMarco",
author_email="frank.s.demarco@gmail.com",
url="http://usethematrixze.us",
packages=["pgfw"],
classifiers=["Development Status :: 2 - Pre-Alpha",
"Environment :: Plugins",
"Intended Audience :: Developers",
"License :: Public Domain",
"Programming Language :: Python :: 2.7"])