# HG changeset patch # User Simon Cross # Date 1259185854 0 # Node ID 8897a436a8cb933c9c74248a448c858e2acb30aa # Parent 6f0385ebcb4f9afce0adbd45f3e49215f308f30e Factor out save game logic and (new, simplified) dialogs into their own module. Add preferences folder to concept to config. Save games under preferences folder. diff -r 6f0385ebcb4f -r 8897a436a8cb gamelib/config.py --- a/gamelib/config.py Wed Nov 25 21:41:08 2009 +0000 +++ b/gamelib/config.py Wed Nov 25 21:50:54 2009 +0000 @@ -2,6 +2,8 @@ from ConfigParser import RawConfigParser from optparse import OptionParser +import sys +import os class Config(object): """Container for various global configuration knobs and levers.""" @@ -11,8 +13,6 @@ 'level_name': {'type': 'string', 'default': 'two_weeks'}, } - config_filename = 'config.ini' - def configure(self, params=None): self._config = RawConfigParser(dict( [(k, v['default']) for k, v in self.valid_options.items() if 'default' in v] @@ -22,10 +22,19 @@ self._config.read(self.config_filename) self._process_params() + def ensure_dir_exists(self, folder): + """Ensure the given folder exists.""" + if os.path.exists(folder): + assert os.path.isdir(folder) + else: + os.makedirs(folder) + def _set_up_params(self, params): parser = OptionParser() parser.add_option("-c", "--config", metavar="FILE", dest="config_filename", help="read configuration from FILE") + parser.add_option("-p", "--prefs-folder", metavar="PREFS_FOLDER", dest="prefs_folder", + help="store preferences and save games in PREFS_FOLDER") parser.add_option("-l", "--level", metavar="LEVEL", dest="level_name", help="select level LEVEL") parser.add_option("--sound", action="store_const", const="on", dest="sound", @@ -33,7 +42,19 @@ parser.add_option("--no-sound", action="store_const", const="off", dest="sound", help="disable sound") (self._opts, _) = parser.parse_args(params or []) - self.config_filename = self._opts.config_filename or self.config_filename + self.prefs_folder = self._opts.prefs_folder or self._default_prefs_dir() + self.ensure_dir_exists(self.prefs_folder) + self.save_folder = os.path.join(self.prefs_folder, "savegames") + self.ensure_dir_exists(self.save_folder) + self.config_filename = self._opts.config_filename or os.path.join(self.prefs_folder, "config.ini") + + def _default_prefs_dir(self): + """Return a default preference folder name.""" + app = "foxassault" + if sys.platform.startswith("win") and "APPDATA" in os.environ: + return os.path.join(os.environ["APPDATA"], app) + else: + return os.path.join(os.path.expanduser("~"), ".%s" % app) def _process_params(self): for name in self.valid_options: diff -r 6f0385ebcb4f -r 8897a436a8cb gamelib/savegame.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gamelib/savegame.py Wed Nov 25 21:50:54 2009 +0000 @@ -0,0 +1,145 @@ +"""Utilities and widgets for saving and restoring games.""" + +import xmlrpclib +import os + +from pgu import gui + +import config +import version + + +class BaseSaveRestoreDialog(gui.Dialog): + """Save game dialog.""" + + def __init__(self, title_txt, button_txt, allow_new, cls="dialog"): + self.value = None + self.save_folder = config.config.save_folder + + self.save_games = {} + self._populate_save_games() + + if allow_new: + self.name_input = gui.Input() + else: + self.name_input = None + + td_style = { + 'padding_left': 4, + 'padding_right': 4, + 'padding_top': 2, + 'padding_bottom': 2, + } + + self.save_list = gui.List(width=350, height=250) + games = self.save_games.keys() + games.sort() + for name in games: + self.save_list.add(name, value=name) + self.save_list.set_vertical_scroll(0) + self.save_list.connect(gui.CHANGE, self._save_list_change) + + button_ok = gui.Button(button_txt) + button_ok.connect(gui.CLICK, self._click_ok) + + button_cancel = gui.Button("Cancel") + button_cancel.connect(gui.CLICK, self._click_cancel) + + body = gui.Table() + body.tr() + body.td(self.save_list, style=td_style, colspan=2) + body.td(gui.Label("Image"), style=td_style, colspan=2) + body.tr() + if self.name_input: + body.td(gui.Label("Save as:"), style=td_style, align=1) + body.td(self.name_input, style=td_style) + else: + body.td(gui.Spacer(0, 0), style=td_style, colspan=2) + body.td(button_ok, style=td_style, align=1) + body.td(button_cancel, style=td_style, align=1) + + title = gui.Label(title_txt, cls=cls + ".title.label") + gui.Dialog.__init__(self, title, body) + + def get_fullpath(self): + """Return the fullpath of the select save game file or None.""" + if self.value is None: + return None + return os.path.join(self.save_folder, self.value + ".xml") + + def _populate_save_games(self): + """Read list of save games.""" + for filename in os.listdir(self.save_folder): + fullpath = os.path.join(self.save_folder, filename) + root, ext = os.path.splitext(filename) + if not os.path.isfile(fullpath): + continue + if ext != ".xml": + continue + self.save_games[root] = None + + def _save_list_change(self): + if self.name_input: + self.name_input.value = self.save_list.value + + def _click_ok(self): + if self.name_input: + self.value = self.name_input.value + else: + self.value = self.save_list.value + if self.value: + self.send(gui.CHANGE) + self.close() + + def _click_cancel(self): + self.value = None + self.send(gui.CHANGE) + self.close() + + +class SaveDialog(BaseSaveRestoreDialog): + """Save game dialog.""" + + def __init__(self, gameboard): + BaseSaveRestoreDialog.__init__(self, "Save Game ...", "Save", allow_new=True) + self.connect(gui.CHANGE, self._save, gameboard) + + def _save(self, gameboard): + filename = self.get_fullpath() + if filename is None: + return + data = gameboard.save_game() + params = (version.SAVE_GAME_VERSION, data) + xml = xmlrpclib.dumps(params, "foxassault") + try: + open(filename, "wb").write(xml) + except Exception, e: + print "Failed to save game: %s" % (e,) + + +class RestoreDialog(BaseSaveRestoreDialog): + """Restore game dialog.""" + + def __init__(self, gameboard): + BaseSaveRestoreDialog.__init__(self, "Load Game ...", "Load", allow_new=False) + self.connect(gui.CHANGE, self._restore, gameboard) + + def _restore(self, gameboard): + filename = self.get_fullpath() + if filename is None: + return + try: + xml = open(filename, "rb").read() + params, methodname = xmlrpclib.loads(xml) + if methodname != "foxassault": + raise ValueError("File does not appear to be a " + "Fox Assault save game.") + save_version = params[0] + if save_version != version.SAVE_GAME_VERSION: + raise ValueError("Incompatible save game version.") + data = params[1] + except Exception, e: + "Failed to load game: %s" % (e,) + return + + gameboard.restore_game(data) diff -r 6f0385ebcb4f -r 8897a436a8cb gamelib/toolbar.py --- a/gamelib/toolbar.py Wed Nov 25 21:41:08 2009 +0000 +++ b/gamelib/toolbar.py Wed Nov 25 21:50:54 2009 +0000 @@ -1,5 +1,4 @@ import pygame -import xmlrpclib from pgu import gui import icons @@ -8,7 +7,7 @@ import equipment import cursors import engine -import version +import savegame class RinkhalsTool(gui.Tool): def __init__(self, group, label, value, func, **params): @@ -177,47 +176,11 @@ def save_game(self): """Save game 'dialog'.""" - dialog = gui.FileDialog("Save game ...", button_txt="Save") - - def save(): - if dialog.value is None: - return - data = self.gameboard.save_game() - params = (version.SAVE_GAME_VERSION, data) - xml = xmlrpclib.dumps(params, "foxassault") - try: - open(dialog.value, "wb").write(xml) - except Exception, e: - print "Failed to save game: %s" % (e,) - - dialog.connect(gui.CHANGE, save) - dialog.open() + savegame.SaveDialog(self.gameboard).open() def load_game(self): """Load game 'dialog'.""" - dialog = gui.FileDialog("Load game ...", button_txt="Load") - - def restore(): - if dialog.value is None: - return - try: - xml = open(dialog.value, "rb").read() - params, methodname = xmlrpclib.loads(xml) - if methodname != "foxassault": - raise ValueError("File does not appear to be a " - "Fox Assault save game.") - save_version = params[0] - if save_version != version.SAVE_GAME_VERSION: - raise ValueError("Incompatible save game version.") - data = params[1] - except Exception, e: - "Failed to load game: %s" % (e,) - return - - self.gameboard.restore_game(data) - - dialog.connect(gui.CHANGE, restore) - dialog.open() + savegame.RestoreDialog(self.gameboard).open() update_cash_counter = mkcountupdate('cash_counter') update_wood_counter = mkcountupdate('wood_counter')