diff options
author | Nguyễn Gia Phong <vn.mcsinyx@gmail.com> | 2018-02-13 20:51:41 +0700 |
---|---|---|
committer | Nguyễn Gia Phong <vn.mcsinyx@gmail.com> | 2018-02-13 20:51:41 +0700 |
commit | 6a4f7e47aed7510e8e45bddf1308fe4eeb87534d (patch) | |
tree | be6cf79ce70194b02b0ad875f1688e493c86ef04 | |
parent | 465be4493db11a458ae05c1be67bac109c753fb0 (diff) | |
download | brutalmaze-6a4f7e47aed7510e8e45bddf1308fe4eeb87534d.tar.gz |
Add argument parser
-rw-r--r-- | brutalmaze/__init__.py | 4 | ||||
-rw-r--r-- | brutalmaze/constants.py | 11 | ||||
-rw-r--r-- | brutalmaze/main.py | 199 | ||||
-rwxr-xr-x | setup.py | 4 |
4 files changed, 140 insertions, 78 deletions
diff --git a/brutalmaze/__init__.py b/brutalmaze/__init__.py index 75b07c7..46e1d6d 100644 --- a/brutalmaze/__init__.py +++ b/brutalmaze/__init__.py @@ -1,5 +1,3 @@ -"""Brutal Maze is a hash and slash game with fast-paced action and a -minimalist art style. -""" +"""Brutal Maze is a minimalist hack and slash game with fast-paced action""" from .main import main diff --git a/brutalmaze/constants.py b/brutalmaze/constants.py index c30488a..d7f3630 100644 --- a/brutalmaze/constants.py +++ b/brutalmaze/constants.py @@ -19,21 +19,10 @@ __doc__ = 'brutalmaze module for shared constants' -from os.path import join - -from appdirs import user_config_dir, site_config_dir from pkg_resources import resource_filename from pygame import image from pygame.mixer import Sound -USER_CONFIG = join(user_config_dir('brutalmaze'), 'settings.ini') -SITE_CONFIG = join(site_config_dir('brutalmaze'), 'settings.ini') -DEFAULT_BINDINGS = {'New game': 'F2', 'Pause': 'p', - 'Move left': 'Left', 'Move right': 'Right', - 'Move up': 'Up', 'Move down': 'Down', - 'Long-range attack': 'Mouse1', - 'Close-range attack': 'Mouse3'} - ICON = image.load(resource_filename('brutalmaze', 'icon.png')) MUSIC = resource_filename('brutalmaze', 'soundfx/music.ogg') SFX_SPAWN = resource_filename('brutalmaze', 'soundfx/spawn.ogg') diff --git a/brutalmaze/main.py b/brutalmaze/main.py index e1d5279..1bcf3d1 100644 --- a/brutalmaze/main.py +++ b/brutalmaze/main.py @@ -17,76 +17,149 @@ # # Copyright (C) 2017 Nguyễn Gia Phong +__version__ = '0.5.1' + import re +from argparse import ArgumentParser, RawTextHelpFormatter from collections import deque try: # Python 3 from configparser import ConfigParser, NoOptionError, NoSectionError except ImportError: # Python 2 from ConfigParser import ConfigParser, NoOptionError, NoSectionError +from os.path import join, pathsep + import pygame from pygame import DOUBLEBUF, KEYDOWN, OPENGL, QUIT, RESIZABLE, VIDEORESIZE +from appdirs import AppDirs -from .constants import USER_CONFIG, SITE_CONFIG, DEFAULT_BINDINGS, ICON, MUSIC +from .constants import * from .maze import Maze -def getconf(config, section, option, valtype=str, fallback=None): - """Return an option value for a given section from a ConfigParser - object. - - If the key is not found, return the fallback value. +class ConfigReader: + """Object reading and processing INI configuration file for + Brutal Maze. """ - if fallback is None: fallback = valtype() - try: - if valtype == str: - return config.get(section, option) - elif valtype == bool: - return config.getboolean(section, option) - elif valtype == float: - return config.getfloat(section, option) - elif valtype == int: - return config.getint(section, option) - except NoSectionError, NoOptionError: - return fallback + DEFAULT_BINDINGS = (('New game', 'new', 'F2'), + ('Pause', 'pause', 'p'), + ('Move left', 'left', 'Left'), + ('Move right', 'right', 'Right'), + ('Move up', 'up', 'Up'), + ('Move down', 'down', 'Down'), + ('Long-range attack', 'shot', 'Mouse1'), + ('Close-range attack', 'slash', 'Mouse3')) + WEIRD_MOUSE_ERR = '{}: Mouse is not a suitable control' + INVALID_CONTROL_ERR = '{}: {} is not recognized as a valid control key' + + def __init__(self, filenames): + self.config = ConfigParser() + self.config.read(filenames) + + def _getconf(self, section, option, val_t): + if val_t == str: + return self.config.get(section, option) + elif val_t == bool: + return self.config.getboolean(section, option) + elif val_t == float: + return self.config.getfloat(section, option) + elif val_t == int: + return self.config.getint(section, option) + + def getconf(self, section, option, val_t=str, fallback=None): + """Return the value of the option in the given section. + + If the value is not found, return fallback. + """ + try: + return self._getconf(section, option, val_t) + except NoSectionError, NoOptionError: + return val_t() if fallback is None else fallback + + def setconf(self, name, section, option, val_t=str, fallback=None): + """Set the named attribute to the value of the option in + the given section. + + If the value is not found and attribute does not exist, + use fallback. + """ + try: + setattr(self, name, self._getconf(section, option, val_t)) + except NoSectionError, NoOptionError: + try: + getattr(self, name) + except AttributeError: + setattr(self, name, val_t() if fallback is None else fallback) + + def parse_graphics(self): + """Parse graphics configurations.""" + self.setconf('width', 'Graphics', 'Screen width', int, 640) + self.setconf('height', 'Graphics', 'Screen height', int, 480) + self.setconf('opengl', 'Graphics', 'OpenGL', bool) + self.setconf('max_fps', 'Graphics', 'Maximum FPS', float, 60.0) + + def parse_control(self): + """Parse control configurations.""" + self.key, self.mouse = {}, {} + for cmd, alias, bind in self.DEFAULT_BINDINGS: + i = self.getconf('Control', cmd, fallback=bind).lower() + if re.match('mouse[1-3]$', i): + if alias not in ('shot', 'slash'): + raise ValueError(self.WEIRD_MOUSE_ERR.format(cmd)) + self.mouse[alias] = int(i[-1]) - 1 + continue + if len(i) == 1: + self.key[alias] = ord(i) + continue + try: + self.key[alias] = getattr(pygame, 'K_{}'.format(i.upper())) + except AttributeError: + raise ValueError(self.INVALID_CONTROL_ERR.format(cmd, i)) + + def read_args(self, arguments): + """Read and parse a ArgumentParser.Namespace.""" + if arguments.size is not None: self.width, self.height = 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 def main(): """Start game and main loop.""" # Read configuration file - config = ConfigParser() - conf = config.read(USER_CONFIG) - if not conf: conf = config.read(SITE_CONFIG) - conf = conf[0] if conf else '' - - # Read graphics configurations - width = getconf(config, 'Graphics', 'Screen width', int, 640) - height = getconf(config, 'Graphics', 'Screen height', int, 480) - scrtype = RESIZABLE - if getconf(config, 'Graphics', 'OpenGL', bool): - scrtype |= OPENGL | DOUBLEBUF - fps = max_fps = getconf(config, 'Graphics', 'Maximum FPS', float, 60.0) - - # Read control configurations - key, mouse = {}, {} - for cmd, bind in DEFAULT_BINDINGS.items(): - i = getconf(config, 'Control', cmd, fallback=bind).lower() - if re.match('mouse[1-3]$', i): - if cmd not in ('Long-range attack', 'Close-range attack'): - print('File "{}", section Control'.format(conf)) - print('\tOne does not simply {} using a mouse'.format(cmd)) - quit() - mouse[cmd] = int(i[-1]) - 1 - continue - if len(i) == 1: - key[cmd] = ord(i) - continue - try: - key[cmd] = getattr(pygame, 'K_{}'.format(i.upper())) - except AttributeError: - print('File "{}", section Control, option {}'.format(conf, cmd)) - print('\t"{}" is not recognized as a valid input'.format(i)) - quit() + dirs = AppDirs(appname='brutalmaze', appauthor=False, multipath=True) + parents = dirs.site_config_dir.split(pathsep) + parents.append(dirs.user_config_dir) + filenames = [join(parent, 'settings.ini') for parent in parents] + config = ConfigReader(filenames) + config.parse_graphics() + + # Parse command line arguments + parser = ArgumentParser(formatter_class=RawTextHelpFormatter) + parser.add_argument('-v', '--version', action='version', + version='Brutal Maze {}'.format(__version__)) + parser.add_argument( + '-c', '--config', metavar='PATH', + help='location of the configuration file (fallback: {})'.format( + pathsep.join(filenames))) + parser.add_argument( + '-s', '--size', type=int, nargs=2, metavar=('X', 'Y'), + help='the desired screen size (fallback: {}x{})'.format(config.width, + config.height)) + parser.add_argument( + '--opengl', action='store_true', default=None, + help='enable OpenGL (fallback: {})'.format(config.opengl)) + parser.add_argument('--no-opengl', action='store_false', dest='opengl', + help='disable OpenGL') + parser.add_argument( + '-f', '--max-fps', type=float, metavar='FPS', + help='the desired maximum FPS (fallback: {})'.format(config.max_fps)) + args = parser.parse_args() + + # Manipulate config + if args.config: config.config.read(args.config) + config.read_args(args) + config.parse_graphics() + config.parse_control() # Initialization pygame.mixer.pre_init(frequency=44100) @@ -95,10 +168,12 @@ def main(): pygame.mixer.music.play(-1) pygame.display.set_icon(ICON) pygame.fastevent.init() - maze = Maze((width, height), scrtype, fps) - clock, flash_time, going = pygame.time.Clock(), deque(), True + clock, flash_time, fps = pygame.time.Clock(), deque(), config.max_fps + scrtype = (config.opengl and DOUBLEBUF|OPENGL) | RESIZABLE + maze = Maze((config.width, config.height), scrtype, fps) # Main loop + going = True while going: events = pygame.fastevent.get() for event in events: @@ -107,24 +182,24 @@ def main(): elif event.type == VIDEORESIZE: maze.resize((event.w, event.h), scrtype) elif event.type == KEYDOWN: - if event.key == key['New game']: + if event.key == config.key['new']: maze.__init__((maze.w, maze.h), scrtype, fps) - elif event.key == key['Pause'] and not maze.hero.dead: + elif event.key == config.key['pause'] and not maze.hero.dead: maze.paused ^= True if not maze.hero.dead: keys = pygame.key.get_pressed() - maze.move(keys[key['Move left']] - keys[key['Move right']], - keys[key['Move up']] - keys[key['Move down']], fps) + maze.move(keys[config.key['left']] - keys[config.key['right']], + keys[config.key['up']] - keys[config.key['down']], fps) buttons = pygame.mouse.get_pressed() try: - maze.hero.firing = keys[key['Long-range attack']] + maze.hero.firing = keys[config.key['shot']] except KeyError: - maze.hero.firing = buttons[mouse['Long-range attack']] + maze.hero.firing = buttons[config.mouse['shot']] try: - maze.hero.slashing = keys[key['Close-range attack']] + maze.hero.slashing = keys[config.key['slash']] except KeyError: - maze.hero.slashing = buttons[mouse['Close-range attack']] + maze.hero.slashing = buttons[config.mouse['slash']] # Compare current FPS with the average of the last 5 seconds if len(flash_time) > 5: @@ -132,7 +207,7 @@ def main(): flash_time.popleft() if new_fps < fps: fps -= 1 - elif fps < max_fps and not maze.paused: + elif fps < config.max_fps and not maze.paused: fps += 5 maze.update(fps) flash_time.append(pygame.time.get_ticks()) diff --git a/setup.py b/setup.py index 8a2421a..db966d1 100755 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ with open('README.rst') as f: setup( name='brutalmaze', - version='0.5.0', - description='A hash and slash game with fast-paced action and a minimalist art style', + version='0.5.1', + description='A minimalist hack and slash game with fast-paced action', long_description=long_description, url='https://github.com/McSinyx/brutalmaze', author='Nguyễn Gia Phong', |