Repository: AVEgame/AVE

Issue:
Create a built in games manifest
We should have a JSON manifest of all built in games constructed before each release. This manifest could then be read to determine the default games on the menu.

A similar strategy could be used for games hosted online. Each time a game is uploaded, it is added to the online manifest (probably in a database rather than a JSON file).

Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index cd234b2..3e2b7e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
 
 build
 dist
+gamelist.json
diff --git a/ave/__main__.py b/ave/__main__.py
index 5d8e0ad..971ab74 100644
--- a/ave/__main__.py
+++ b/ave/__main__.py
@@ -1,10 +1,33 @@
 """Functions to run AVE."""
 
+import os
+import json
 from ave import AVE, config
 
 
 def run():
     """Run AVE in terminal."""
+    from .screen import Screen
+    ave = AVE(screen=Screen())
+    ave.load_games_from_json(os.path.join(config.root_folder, "gamelist.json"))
+    ave.start()
+
+
+def make_json():
+    """Make a json containing metadata for every game."""
+    config.debug = True
     ave = AVE()
     ave.load_games(config.games_folder)
-    ave.start()
+    gamelist = [{
+        "title": game.title,
+        "author": game.author,
+        "desc": game.description,
+        "active": game.active,
+        "version": game.version,
+        "ave_version": game.ave_version,
+        "filename": game.filename,
+        "number": game.number
+    } for game in ave.games]
+
+    with open(os.path.join(config.root_folder, "gamelist.json"), "w") as f:
+        json.dump(gamelist, f)
diff --git a/ave/ave.py b/ave/ave.py
index 9bced08..8daf6b8 100644
--- a/ave/ave.py
+++ b/ave/ave.py
@@ -1,17 +1,19 @@
 """The AVE and GameLibrary classes that run AVE in a terminal."""
 
 import os
+import json
 from .exceptions import (AVEGameOver, AVEWinner, AVEToMenu, AVEQuit,
                          AVENoInternet)
-from .game import Character
+from .game import Game, Character
+from .game_loader import (load_game_from_file, load_library_json,
+                          load_game_from_library)
 from . import config
-from .screen import Screen
 
 
 class AVE:
     """The AVE class that runs the Character, Screen and Game."""
 
-    def __init__(self, start_screen=True):
+    def __init__(self, screen=None):
         """Create an AVE class.
 
         Parameters
@@ -19,9 +21,7 @@ class AVE:
         start_screen : bool
             Should the Screen be started?
         """
-        self.screen = None
-        if start_screen:
-            self.screen = Screen()
+        self.screen = screen
         self.character = Character()
         self.games = None
         self.items = None
@@ -71,6 +71,23 @@ class AVE:
             the_game = self.games[game_to_load]
             self.run_the_game(the_game)
 
+    def sort_games(self, games):
+        """Remove disabled games and sort the games by number."""
+        ordered_games = {}
+        other_games = []
+        for g in games:
+            if config.version_tuple < g.ave_version:
+                continue
+            if g.active or config.debug:
+                if g.number is None:
+                    other_games.append(g)
+                else:
+                    assert g.number not in ordered_games
+                    ordered_games[g.number] = g
+        self.games = GameLibrary([
+            ordered_games[i]
+            for i in sorted(ordered_games.keys())] + other_games)
+
     def load_games(self, folder):
         """Load the metadata of games from a folder.
 
@@ -79,23 +96,33 @@ class AVE:
         folder: str
             The folder
         """
-        from .game_loader import load_game_from_file
-        ordered_games = {}
-        other_games = []
+        games = []
         for game in os.listdir(folder):
             if game[-4:] == ".ave":
-                g = load_game_from_file(os.path.join(folder, game))
-                if config.version_tuple < g.ave_version:
-                    continue
-                if g.active or config.debug:
-                    if g.number is None:
-                        other_games.append(g)
-                    else:
-                        assert g.number not in ordered_games
-                        ordered_games[g.number] = g
-        self.games = GameLibrary([
-            ordered_games[i]
-            for i in sorted(ordered_games.keys())] + other_games)
+                games.append(load_game_from_file(
+                    os.path.join(folder, game), game))
+        self.sort_games(games)
+
+    def load_games_from_json(self, json_file):
+        """Load the metadata of games from a json.
+
+        Parameters
+        ----------
+        json_file: str
+            The location of the json file
+        """
+        with open(json_file) as f:
+            gamelist = json.load(f)
+        games = []
+        for game in gamelist:
+            games.append(Game(
+                file=os.path.join(config.games_folder, game["filename"]),
+                title=game["title"], number=game["number"],
+                description=game["desc"],
+                author=game["author"], active=game["active"],
+                version=game["version"], filename=game["filename"],
+                ave_version=game["ave_version"]))
+        self.sort_games(games)
 
     def get_download_menu(self):
         """Get the list of games from the online library.
@@ -105,7 +132,6 @@ class AVE:
         list
             A list of the title, author and local url for each game.
         """
-        from .game_loader import load_library_json
         try:
             the_json = load_library_json()
         except AVENoInternet:
@@ -120,8 +146,6 @@ class AVE:
 
     def show_download_menu(self):
         """Show a menu of games from the online library."""
-        from .game_loader import load_game_from_library
-
         try:
             self.screen.print_download()
             menu_items = self.get_download_menu()
diff --git a/ave/config.py b/ave/config.py
index 5539bd9..3cf36a8 100644
--- a/ave/config.py
+++ b/ave/config.py
@@ -4,14 +4,16 @@ import os
 
 debug = os.getenv("DEBUG")
 ave_folder = os.path.dirname(os.path.realpath(__file__))
+root_folder = os.path.join(ave_folder, "..")
+
 folder_prefix = ""
-if not os.path.isdir(os.path.join(ave_folder, "../games")):
+if not os.path.isdir(os.path.join(root_folder, "games")):
     folder_prefix = "_ave"
 
-screens_folder = os.path.join(ave_folder, "../" + folder_prefix + "screens")
-games_folder = os.path.join(ave_folder, "../" + folder_prefix + "games")
+screens_folder = os.path.join(root_folder, folder_prefix + "screens")
+games_folder = os.path.join(root_folder, folder_prefix + "games")
 
-with open(os.path.join(ave_folder, "../VERSION")) as f:
+with open(os.path.join(root_folder, "VERSION")) as f:
     version = f.read()
 
 version_tuple = tuple(int(i) for i in version.split("."))
diff --git a/ave/game.py b/ave/game.py
index deeb9cd..22b63c3 100644
--- a/ave/game.py
+++ b/ave/game.py
@@ -121,7 +121,7 @@ class Game:
     """The Game classes that stores all the data to run the game."""
 
     def __init__(self, file=None, url=None,
-                 title="untitled", number=None,
+                 filename=None, title="untitled", number=None,
                  description="", author="anonymous",
                  version=0, ave_version=(0, 0),
                  active=True):
@@ -130,9 +130,11 @@ class Game:
         Parameters
         ----------
         file : string
-            The filename of the .ave file of this game
+            The full path and filename filename of the .ave file of this game
         url : string
             The url of the .ave file of this game
+        filename : string
+            The filename of the .ave file of this game
         title : string
             The title of the game
         number : int
@@ -149,6 +151,7 @@ class Game:
             If False, this game will only be shown in debug mode
         """
         self.file = file
+        self.filename = filename
         self.url = url
         self.number = number
         self.title = title
@@ -156,7 +159,7 @@ class Game:
         self.author = author
         self.active = active
         self.version = version
-        self.ave_version = ave_version
+        self.ave_version = tuple(ave_version)
         self.rooms = None
 
         self.options = []
diff --git a/ave/game_loader.py b/ave/game_loader.py
index 28b8e72..b7a9c7f 100644
--- a/ave/game_loader.py
+++ b/ave/game_loader.py
@@ -220,7 +220,7 @@ def load_full_game(text):
     return rooms, items
 
 
-def load_game_from_file(file):
+def load_game_from_file(file, filename=None):
     """Load the metadata of a game from a file."""
     title = "untitled"
     number = None
@@ -251,9 +251,8 @@ def load_game_from_file(file):
                 if clean(line[2:-2]) == "off":
                     active = False
 
-    return Game(file=file, title=title, number=number,
-                description=description,
-                author=author, active=active,
+    return Game(file=file, filename=filename, title=title, number=number,
+                description=description, author=author, active=active,
                 version=version, ave_version=ave_version)
 
 
diff --git a/setup.py b/setup.py
index 2abf3de..004076f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,7 @@
 import os
 import sys
 import setuptools
+from ave.__main__ import make_json
 
 if sys.version_info < (3, 5):
     print("Python 3.5 or higher required, please upgrade.")
@@ -9,18 +10,20 @@ if sys.version_info < (3, 5):
 with open("VERSION") as f:
     VERSION = f.read()
 
+make_json()
+
 requirements = []
 if os.name == 'nt':
-    # TODO: test this!
     requirements.append("windows-curses")
 
-entry_points = {'console_scripts': ['ave = ave.__main__:run']}
+entry_points = {'console_scripts': ['ave = ave.__main__:run',
+                                    'ave-make-json = ave.__main__:make_json']}
 
 data_files = [
     ("_avegames", [os.path.join("games", i) for i in os.listdir("games")
                    if i.endswith(".ave")]),
     ("_avescreens", ["screens/credits", "screens/title", "screens/user"]),
-    ("", ["VERSION"])]
+    ("", ["VERSION", "gamelist.json"])]
 
 if __name__ == "__main__":
     setuptools.setup(


Test patch:
diff --git a/test/test_games.py b/test/test_games.py
index d00a9a5..4eb5cfe 100755
--- a/test/test_games.py
+++ b/test/test_games.py
@@ -23,7 +23,7 @@ def test_version_checking(filename):
     except exceptions.AVEVersionError:
         pass
 
-    ave = AVE(start_screen=False)
+    ave = AVE()
     ave.load_games("test/games")
     for game in ave.games:
         assert game.file != filename
@@ -76,7 +76,7 @@ def test_has_start(filename):
 
 @pytest.mark.parametrize('filename', games)
 def test_first_room(filename):
-    ave = AVE(start_screen=False)
+    ave = AVE()
     game = load_game_from_file(filename)
     game.load()
     game["start"].get_text(ave.character)
@@ -84,12 +84,12 @@ def test_first_room(filename):
 
 
 def test_game_library():
-    ave = AVE(start_screen=False)
+    ave = AVE()
     ave.get_download_menu()
 
 
 def test_load_game_from_library():
-    ave = AVE(start_screen=False)
+    ave = AVE()
     game = load_game_from_library(ave.get_download_menu()[0][2])
     game.load()
     assert game["start"].id != "fail"