Skip to content
This repository has been archived by the owner on Nov 30, 2020. It is now read-only.

Commit

Permalink
Add sound options and semi-separate front-end from engine
Browse files Browse the repository at this point in the history
  • Loading branch information
McSinyx committed Feb 19, 2018
1 parent 8852a9f commit bc47fb3
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 67 deletions.
17 changes: 9 additions & 8 deletions brutalmaze/characters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from sys import modules

import pygame
from pygame.mixer import Sound
from pygame.time import get_ticks

from .constants import *
Expand All @@ -50,7 +49,7 @@ class Hero:
spin_speed (float): speed of spinning (in frames per slash)
spin_queue (float): frames left to finish spinning
wound (float): amount of wound
sfx_heart (Sound): heart beat sound effect
sfx_heart (pygame.mixer.Sound): heart beat sound effect
"""
def __init__(self, surface, fps):
self.surface = surface
Expand All @@ -64,7 +63,7 @@ def __init__(self, surface, fps):
self.spin_speed = fps / HERO_HP
self.spin_queue = self.wound = 0.0

self.sfx_heart = Sound(SFX_HEART)
self.sfx_heart = SFX_HEART

def update(self, fps):
"""Update the hero."""
Expand Down Expand Up @@ -92,6 +91,9 @@ def update(self, fps):
x, y = pygame.mouse.get_pos()
self.angle = atan2(y - self.y, x - self.x)
self.spin_queue = 0.0

def draw(self):
"""Draw the hero."""
trigon = regpoly(3, self.R, self.angle, self.x, self.y)
fill_aapolygon(self.surface, trigon, self.color[int(self.wound)])

Expand All @@ -117,7 +119,7 @@ class Enemy:
spin_speed (float): speed of spinning (in frames per slash)
spin_queue (float): frames left to finish spinning
wound (float): amount of wound
sfx_slash (Sound): sound effect indicating close-range attack damage
sfx_slash (pygame.mixer.Sound): sound effect of slashed hero
"""
def __init__(self, maze, x, y, color):
self.maze = maze
Expand All @@ -132,7 +134,7 @@ def __init__(self, maze, x, y, color):
self.spin_speed = self.maze.fps / ENEMY_HP
self.spin_queue = self.wound = 0.0

self.sfx_slash = Sound(SFX_SLASH_HERO)
self.sfx_slash = SFX_SLASH_HERO

def get_pos(self):
"""Return coordinate of the center of the enemy."""
Expand Down Expand Up @@ -238,6 +240,7 @@ def get_angle(self, reversed=False):

def draw(self):
"""Draw the enemy."""
if get_ticks() < self.maze.next_move and not self.awake: return
radious = self.maze.distance/SQRT2 - self.awake*2
square = regpoly(4, radious, self.angle, *self.get_pos())
color = TANGO[self.color][int(self.wound)] if self.awake else FG_COLOR
Expand All @@ -257,7 +260,6 @@ def update(self):
self.spin_queue -= sign(self.spin_queue)
else:
self.angle, self.spin_queue = pi / 4, 0.0
if self.awake or get_ticks() >= self.maze.next_move: self.draw()

def hit(self, wound):
"""Handle the enemy when it's attacked."""
Expand Down Expand Up @@ -290,8 +292,7 @@ def wake(self):

def draw(self):
"""Draw the Chameleon."""
if (not self.awake or self.spin_queue
or get_ticks() < max(self.visible, self.maze.next_move)):
if not self.awake or get_ticks() < self.visible or self.spin_queue:
Enemy.draw(self)

def hit(self, wound):
Expand Down
30 changes: 17 additions & 13 deletions brutalmaze/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,25 @@

__doc__ = 'brutalmaze module for shared constants'

from pkg_resources import resource_filename
from pygame import image
from pkg_resources import resource_filename as pkg_file
import pygame
from pygame.mixer import Sound

SETTINGS = resource_filename('brutalmaze', 'settings.ini')
ICON = image.load(resource_filename('brutalmaze', 'icon.png'))
MUSIC = resource_filename('brutalmaze', 'soundfx/music.ogg')
SFX_SPAWN = resource_filename('brutalmaze', 'soundfx/spawn.ogg')
SFX_SLASH_ENEMY = resource_filename('brutalmaze', 'soundfx/slash-enemy.ogg')
SFX_SLASH_HERO = resource_filename('brutalmaze', 'soundfx/slash-hero.ogg')
SFX_SHOT_ENEMY = resource_filename('brutalmaze', 'soundfx/shot-enemy.ogg')
SFX_SHOT_HERO = resource_filename('brutalmaze', 'soundfx/shot-hero.ogg')
SFX_MISSED = resource_filename('brutalmaze', 'soundfx/missed.ogg')
SFX_HEART = resource_filename('brutalmaze', 'soundfx/heart.ogg')
SFX_LOSE = resource_filename('brutalmaze', 'soundfx/lose.ogg')
SETTINGS = pkg_file('brutalmaze', 'settings.ini')
ICON = pygame.image.load(pkg_file('brutalmaze', 'icon.png'))
MUSIC = pkg_file('brutalmaze', 'soundfx/music.ogg')

mixer = pygame.mixer.get_init()
if mixer is None: pygame.mixer.init(frequency=44100)
SFX_SPAWN = Sound(pkg_file('brutalmaze', 'soundfx/spawn.ogg'))
SFX_SLASH_ENEMY = Sound(pkg_file('brutalmaze', 'soundfx/slash-enemy.ogg'))
SFX_SLASH_HERO = Sound(pkg_file('brutalmaze', 'soundfx/slash-hero.ogg'))
SFX_SHOT_ENEMY = Sound(pkg_file('brutalmaze', 'soundfx/shot-enemy.ogg'))
SFX_SHOT_HERO = Sound(pkg_file('brutalmaze', 'soundfx/shot-hero.ogg'))
SFX_MISSED = Sound(pkg_file('brutalmaze', 'soundfx/missed.ogg'))
SFX_HEART = Sound(pkg_file('brutalmaze', 'soundfx/heart.ogg'))
SFX_LOSE = Sound(pkg_file('brutalmaze', 'soundfx/lose.ogg'))
if mixer is None: pygame.mixer.quit()

SQRT2 = 2 ** 0.5
INIT_SCORE = 5**0.5/2 + 0.5 # golden mean
Expand Down
54 changes: 39 additions & 15 deletions brutalmaze/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from pygame.time import Clock, get_ticks
from appdirs import AppDirs

from .constants import *
from .constants import SETTINGS, ICON, MUSIC, HERO_SPEED
from .maze import Maze
from .misc import sign

Expand All @@ -44,7 +44,8 @@ class ConfigReader:
"""Object reading and processing INI configuration file for
Brutal Maze.
"""
CONTROL_ALIASES = (('New game', 'new'), ('Pause', 'pause'),
CONTROL_ALIASES = (('New game', 'new'), ('Toggle pause', 'pause'),
('Toggle mute', 'mute'),
('Move left', 'left'), ('Move right', 'right'),
('Move up', 'up'), ('Move down', 'down'),
('Long-range attack', 'shot'),
Expand All @@ -57,12 +58,14 @@ def __init__(self, filenames):
self.config.read(SETTINGS) # default configuration
self.config.read(filenames)

def parse_graphics(self):
"""Parse graphics configurations."""
def parse_output(self):
"""Parse graphics and sound configurations."""
self.size = (self.config.getint('Graphics', 'Screen width'),
self.config.getint('Graphics', 'Screen height'))
self.opengl = self.config.getboolean('Graphics', 'OpenGL')
self.max_fps = self.config.getint('Graphics', 'Maximum FPS')
self.muted = self.config.getboolean('Sound', 'Muted')
self.musicvol = self.config.getfloat('Sound', 'Music volume')

def parse_control(self):
"""Parse control configurations."""
Expand All @@ -84,27 +87,31 @@ def parse_control(self):

def read_args(self, arguments):
"""Read and parse a ArgumentParser.Namespace."""
if arguments.size is not None: self.size = arguments.size
if arguments.opengl is not None: self.opengl = arguments.opengl
if arguments.max_fps is not None: self.max_fps = arguments.max_fps
for option in 'size', 'opengl', 'max_fps', 'muted', 'musicvol':
value = getattr(arguments, option)
if value is not None: setattr(self, option, value)


class Game:
"""Object handling main loop and IO."""
def __init__(self, size, scrtype, max_fps, key, mouse):
def __init__(self, size, scrtype, max_fps, muted, musicvol, key, mouse):
pygame.mixer.pre_init(frequency=44100)
pygame.init()
pygame.mixer.music.load(MUSIC)
pygame.mixer.music.play(-1)
if muted:
pygame.mixer.quit()
else:
pygame.mixer.music.load(MUSIC)
pygame.mixer.music.set_volume(musicvol)
pygame.mixer.music.play(-1)
pygame.display.set_icon(ICON)
pygame.fastevent.init()
self.clock = Clock()
# self.fps is a float to make sure floordiv won't be used in Python 2
self.max_fps, self.fps = max_fps, float(max_fps)
self.musicvol = musicvol
self.key, self.mouse = key, mouse
self.maze = Maze(max_fps, size, scrtype)
self.hero = self.maze.hero
self.paused = False
self.clock, self.paused = Clock(), False

def __enter__(self): return self

Expand Down Expand Up @@ -145,6 +152,14 @@ def loop(self):
self.maze.__init__(self.fps)
elif event.key == self.key['pause'] and not self.hero.dead:
self.paused ^= True
elif event.key == self.key['mute']:
if pygame.mixer.get_init() is None:
pygame.mixer.init(frequency=44100)
pygame.mixer.music.load(MUSIC)
pygame.mixer.music.set_volume(self.musicvol)
pygame.mixer.music.play(-1)
else:
pygame.mixer.quit()

if not self.hero.dead:
keys = pygame.key.get_pressed()
Expand Down Expand Up @@ -181,7 +196,7 @@ def main():
parents.append(dirs.user_config_dir)
filenames = [join(parent, 'settings.ini') for parent in parents]
config = ConfigReader(filenames)
config.parse_graphics()
config.parse_output()

# Parse command-line arguments
parser = ArgumentParser(formatter_class=RawTextHelpFormatter)
Expand All @@ -206,6 +221,14 @@ def main():
parser.add_argument(
'-f', '--max-fps', type=int, metavar='FPS',
help='the desired maximum FPS (fallback: {})'.format(config.max_fps))
parser.add_argument(
'--mute', '-m', action='store_true', default=None,
help='mute all sounds (fallback: {})'.format(config.muted))
parser.add_argument('--unmute', action='store_false', dest='muted',
help='unmute sound')
parser.add_argument(
'--music-volume', type=float, metavar='VOL', dest='musicvol',
help='between 0.0 and 1.0 (fallback: {})'.format(config.musicvol))
args = parser.parse_args()
if args.defaultcfg is not None:
with open(SETTINGS) as settings: args.defaultcfg.write(settings.read())
Expand All @@ -215,10 +238,11 @@ def main():
# Manipulate config
if args.config: config.config.read(args.config)
config.read_args(args)
config.parse_graphics()
config.parse_output()
config.parse_control()

# Main loop
scrtype = (config.opengl and DOUBLEBUF|OPENGL) | RESIZABLE
with Game(config.size, scrtype, config.max_fps, config.key, config.mouse) as game:
with Game(config.size, scrtype, config.max_fps, config.muted,
config.musicvol, config.key, config.mouse) as game:
while game.loop(): pass
42 changes: 22 additions & 20 deletions brutalmaze/maze.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

import pygame
from pygame import RESIZABLE
from pygame.mixer import Sound
from pygame.time import get_ticks

from .characters import Hero, new_enemy
Expand Down Expand Up @@ -76,9 +75,8 @@ class Maze:
next_move (int): the tick that the hero gets mobilized
next_slashfx (int): the tick to play next slash effect of the hero
slashd (float): minimum distance for slashes to be effective
sfx_slash (Sound): sound effect indicating an enemy get slashed
sfx_shot (Sound): sound effect indicating an enemy get shot
sfx_lose (Sound): sound effect to be played when you lose
sfx_slash (pygame.mixer.Sound): sound effect of slashed enemy
sfx_lose (pygame.mixer.Sound): sound effect to be played when you lose
"""
def __init__(self, fps, size=None, scrtype=None):
self.fps = fps
Expand Down Expand Up @@ -109,10 +107,9 @@ def __init__(self, fps, size=None, scrtype=None):
self.next_move = self.next_slashfx = 0
self.slashd = self.hero.R + self.distance/SQRT2

self.sfx_spawn = Sound(SFX_SPAWN)
self.sfx_slash = Sound(SFX_SLASH_ENEMY)
self.sfx_shot = Sound(SFX_SHOT_ENEMY)
self.sfx_lose = Sound(SFX_LOSE)
self.sfx_spawn = SFX_SPAWN
self.sfx_slash = SFX_SLASH_ENEMY
self.sfx_lose = SFX_LOSE

def add_enemy(self):
"""Add enough enemies."""
Expand Down Expand Up @@ -140,13 +137,21 @@ def get_pos(self, x, y):
def draw(self):
"""Draw the maze."""
self.surface.fill(BG_COLOR)
if get_ticks() < self.next_move: return
for i in self.rangex:
for j in self.rangey:
if self.map[i][j] != WALL: continue
x, y = self.get_pos(i, j)
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
fill_aapolygon(self.surface, square, FG_COLOR)
if get_ticks() >= self.next_move:
for i in self.rangex:
for j in self.rangey:
if self.map[i][j] != WALL: continue
x, y = self.get_pos(i, j)
square = regpoly(4, self.distance / SQRT2, pi / 4, x, y)
fill_aapolygon(self.surface, square, FG_COLOR)

for enemy in self.enemies: enemy.draw()
self.hero.draw()
bullet_radius = self.distance / 4
for bullet in self.bullets: bullet.draw(bullet_radius)
pygame.display.flip()
pygame.display.set_caption('Brutal Maze - Score: {}'.format(
int(self.score - INIT_SCORE)))

def rotate(self):
"""Rotate the maze if needed."""
Expand Down Expand Up @@ -266,7 +271,7 @@ def track_bullets(self):
self.score += enemy.wound
enemy.die()
self.enemies.pop(j)
play(self.sfx_shot, wound, bullet.angle)
play(bullet.sfx_hit, wound, bullet.angle)
fallen.append(i)
break
elif bullet.get_distance(self.x, self.y) < self.distance:
Expand Down Expand Up @@ -310,15 +315,12 @@ def update(self, fps):
for enemy in self.enemies: enemy.wake()
for bullet in self.bullets: bullet.place(dx, dy)

self.draw()
for enemy in self.enemies: enemy.update()
if not self.hero.dead:
self.hero.update(fps)
self.slash()
self.track_bullets()
pygame.display.flip()
pygame.display.set_caption('Brutal Maze - Score: {}'.format(
int(self.score - INIT_SCORE)))
self.draw()

def resize(self, size):
"""Resize the maze."""
Expand Down
1 change: 1 addition & 0 deletions brutalmaze/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def choices(d):

def play(sound, volume=1.0, angle=None):
"""Play a pygame.mixer.Sound at the given volume."""
if pygame.mixer.get_init() is None: return
if pygame.mixer.find_channel() is None:
pygame.mixer.set_num_channels(pygame.mixer.get_num_channels() + 1)

Expand Down
8 changes: 7 additions & 1 deletion brutalmaze/settings.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ OpenGL: no
# FPS should not be greater than refresh rate.
Maximum FPS: 60

[Sound]
Muted: no
# Volume must be between 0.0 and 1.0
Music volume: 1.0

[Control]
# Input values should be either from Mouse1 to Mouse3 or a keyboard key
# and they are case-insensitively read.
# Aliases for special keys are listed here (without the K_ part):
# http://www.pygame.org/docs/ref/key.html
# Key combinations are not supported.
New game: F2
Pause: p
Toggle pause: p
Toggle mute: m
Move left: Left
Move right: Right
Move up: Up
Expand Down
Loading

0 comments on commit bc47fb3

Please sign in to comment.