changeset 486:8897a436a8cb

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.
author Simon Cross <hodgestar@gmail.com>
date Wed, 25 Nov 2009 21:50:54 +0000
parents 6f0385ebcb4f
children a5dc09881aa1
files gamelib/config.py gamelib/savegame.py gamelib/toolbar.py
diffstat 3 files changed, 172 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- 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:
--- /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)
--- 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')