Example #1
0
 def from_url(qparams):
     params = SeedGenParams()
     params.seed = qparams.get("seed")
     if not params.seed:
         log.error("No seed in %r! returning None" % qparams)
         return None
     params.variations = enums_from_strlist(Variation, qparams.getall("var"))
     params.logic_paths = enums_from_strlist(LogicPath, qparams.getall("path"))
     if not params.logic_paths:
         log.error("No logic paths in %r! returning None" % qparams)
         return None
     params.key_mode = KeyMode(qparams.get("key_mode", "Clues"))
     params.path_diff = PathDifficulty(qparams.get("path_diff", "Normal"))
     params.exp_pool = int(qparams.get("exp_pool", 10000))
     params.balanced = qparams.get("gen_mode") != "Classic"
     params.players = int(qparams.get("players", 1))
     params.tracking = qparams.get("tracking") != "Disabled"
     params.warmth = WarmthFragmentOptions.from_url(qparams)
     params.sync = MultiplayerOptions.from_url(qparams)
     raw_fass = qparams.get("fass")
     if raw_fass:
         params.placements = []
         for fass in raw_fass.split("|"):
             loc, _, item = fass.partition(":")
             stuff = [Stuff(code=item[:2], id=item[2:], player="")]
             params.placements.append(Placement(location=loc, zone="", stuff=stuff))
     return params.put()
Example #2
0
 def from_json(json):
     opts = MultiplayerOptions()
     opts.enabled = json.get("players", 1) > 1
     opts.teams = json.get("teams", {})
     if opts.enabled:
         opts.mode = MultiplayerGameType(json.get("coopGameMode", "None"))
         opts.cloned = json.get("coopGenMode") != "disjoint"
         if opts.cloned:
             opts.teams = {1: range(1, json.get("players", 1) + 1)}
             opts.dedup = bool(json.get("dedupShared", False))
         opts.hints = bool(opts.cloned and json.get("syncHints"))
         opts.shared = enums_from_strlist(ShareType,
                                          json.get("syncShared", []))
     return opts
Example #3
0
 def from_json(json):
     params = SeedGenParams()
     params.seed = str(json.get("seed"))
     if not params.seed:
         log.error("No seed in %r! returning None" % json)
         return None
     params.variations = enums_from_strlist(Variation,
                                            json.get("variations", []))
     params.logic_paths = enums_from_strlist(LogicPath,
                                             json.get("paths", []))
     if not params.logic_paths:
         log.error("No logic paths in %r! returning None" % json)
         return None
     params.key_mode = KeyMode(json.get("keyMode", "Clues"))
     params.path_diff = PathDifficulty(json.get("pathDiff", "Normal"))
     params.exp_pool = json.get("expPool", 10000)
     params.balanced = json.get("fillAlg") != "Classic"
     params.players = json.get("players", 1)
     params.tracking = json.get("tracking")
     params.frag_count = json.get("fragCount", 30)
     params.frag_req = json.get("fragReq", 20)
     params.relic_count = json.get("relicCount", 8)
     params.cell_freq = json.get("cellFreq", 256)
     params.sync = MultiplayerOptions.from_json(json)
     params.sense = json.get("senseData")
     params.item_pool = json.get("itemPool", {})
     params.bingo_lines = json.get("bingoLines", 3)
     params.pool_preset = json.get("selectedPool", "Standard")
     params.placements = [
         Placement(
             location=fass["loc"],
             zone="",
             stuff=[Stuff(code=fass["code"], id=fass["id"], player="")])
         for fass in json.get("fass", [])
     ]
     return params.put()
Example #4
0
    def from_url(qparams):
        opts = MultiplayerOptions()
        opts.enabled = int(qparams.get("players", 1)) > 1
        if opts.enabled:
            opts.mode = MultiplayerGameType(qparams.get("sync_mode", "None"))
            opts.cloned = qparams.get("sync_gen") != "disjoint"
            opts.hints = bool(opts.cloned and qparams.get("sync_hints"))
            opts.shared = enums_from_strlist(ShareType, qparams.getall("sync_shared"))

            teamsRaw = qparams.get("teams")
            if teamsRaw and opts.mode == MultiplayerGameType.SHARED and opts.cloned:
                cnt = 1
                teams = {}
                for teamRaw in teamsRaw.split("|"):
                    teams[cnt] = [int(p) for p in teamRaw.split(",")]
                    cnt += 1
                opts.teams = teams
        return opts
Example #5
0
 def from_plando(plando, tracking=True):
     params = SeedGenParams(seed=plando.name,
                            players=plando.players,
                            tracking=tracking,
                            is_plando=True,
                            plando_flags=plando.flags,
                            placements=plando.placements,
                            spoilers=[plando.description])
     params.sync = MultiplayerOptions()
     if plando.mode():
         params.sync.mode = plando.mode()
     params.sync.shared = plando.shared()
     for flag in plando.flags:
         if flag.capitalize() in presets:
             params.logic_paths = presets[flag.capitalize()]
             break
     params.set_vars(enums_from_strlist(Variation, plando.flags))
     params.put()
     return params
Example #6
0
 def get_shared(self):
     return enums_from_strlist(ShareType, self.str_shared)
Example #7
0
 def from_url(qparams):
     params = SeedGenParams()
     params.seed = qparams.get("seed")
     if not params.seed:
         log.error("No seed in %r! returning None" % qparams)
         return None
     params.variations = enums_from_strlist(Variation,
                                            qparams.getall("var"))
     params.logic_paths = enums_from_strlist(LogicPath,
                                             qparams.getall("path"))
     if not params.logic_paths:
         log.error("No logic paths in %r! returning None" % qparams)
         return None
     params.key_mode = KeyMode(qparams.get("key_mode", "Clues"))
     params.path_diff = PathDifficulty(qparams.get("path_diff", "Normal"))
     params.exp_pool = int(qparams.get("exp_pool", 10000))
     params.balanced = qparams.get("gen_mode") != "Classic"
     params.players = int(qparams.get("players", 1))
     params.tracking = qparams.get("tracking") != "Disabled"
     params.frag_count = int(qparams.get("frags", 30))
     params.frag_req = int(qparams.get("frags_req", 20))
     params.relic_count = int(qparams.get("relics", 8))
     params.cell_freq = int(qparams.get("cell_freq", 256))
     params.sync = MultiplayerOptions.from_url(qparams)
     params.sense = qparams.get("sense")
     params.pool_preset = qparams.get("pool_preset", "Standard").title()
     params.item_pool = {}
     raw_pool = qparams.get("item_pool")
     if raw_pool:
         for itemcnt in raw_pool.split("|"):
             item, _, count = itemcnt.partition(":")
             params.item_pool[item] = int(count)
     else:
         if Variation.EXTRA_BONUS_PICKUPS in params.variations or params.pool_preset == "Extra Bonus":
             params.pool_preset = "Extra Bonus"
             params.item_pool = {
                 "TP|Grove": [1],
                 "TP|Swamp": [1],
                 "TP|Grotto": [1],
                 "TP|Valley": [1],
                 "TP|Sorrow": [1],
                 "TP|Ginso": [1],
                 "TP|Horu": [1],
                 "TP|Forlorn": [1],
                 "HC|1": [12],
                 "EC|1": [14],
                 "AC|1": [33],
                 "RP|RB/0": [3],
                 "RP|RB/1": [3],
                 "RB|6": [5],
                 "RB|9": [1],
                 "RB|10": [1],
                 "RB|11": [1],
                 "RB|12": [3],
                 "RB|37": [3],
                 "RB|13": [3],
                 "RB|15": [3],
                 "RB|31": [1],
                 "RB|32": [1],
                 "RB|33": [3],
                 "BS|*": [4],
                 "WP|*": [4, 8],
             }
         elif params.pool_preset == "Bonus Lite":
             params.item_pool = {
                 "TP|Grove": [1],
                 "TP|Swamp": [1],
                 "TP|Grotto": [1],
                 "TP|Valley": [1],
                 "TP|Sorrow": [1],
                 "TP|Ginso": [1],
                 "TP|Horu": [1],
                 "TP|Forlorn": [1],
                 "HC|1": [12],
                 "EC|1": [14],
                 "AC|1": [33],
                 "RB|0": [3],
                 "RB|1": [3],
                 "RB|6": [5],
                 "RB|9": [1],
                 "RB|10": [1],
                 "RB|11": [1],
                 "RB|12": [3],
                 "RB|37": [3],
                 "RB|13": [3],
                 "RB|15": [3],
                 "RB|31": [1],
                 "RB|32": [1],
                 "RB|33": [3],
                 "RB|36": [1],
                 "WP|*": [4, 8],
             }
         elif params.pool_preset == "Competitive":
             params.item_pool = {
                 "TP|Grove": [1],
                 "TP|Swamp": [1],
                 "TP|Grotto": [1],
                 "TP|Valley": [1],
                 "TP|Sorrow": [1],
                 "TP|Forlorn": [1],
                 "HC|1": [12],
                 "EC|1": [14],
                 "AC|1": [33],
                 "RB|0": [3],
                 "RB|1": [3],
                 "RB|6": [3],
                 "RB|9": [1],
                 "RB|10": [1],
                 "RB|11": [1],
                 "RB|12": [1],
                 "RB|13": [3],
                 "RB|15": [3],
             }
         elif params.pool_preset == "Hard":
             params.item_pool = {
                 "TP|Grove": [1],
                 "TP|Swamp": [1],
                 "TP|Grotto": [1],
                 "TP|Valley": [1],
                 "TP|Sorrow": [1],
                 "EC|1": [3]
             }
         else:
             params.pool_preset = "Standard"
             params.item_pool = {
                 "TP|Grove": [1],
                 "TP|Swamp": [1],
                 "TP|Grotto": [1],
                 "TP|Valley": [1],
                 "TP|Sorrow": [1],
                 "TP|Ginso": [1],
                 "TP|Horu": [1],
                 "TP|Forlorn": [1],
                 "HC|1": [12],
                 "EC|1": [14],
                 "AC|1": [33],
                 "RB|0": [3],
                 "RB|1": [3],
                 "RB|6": [3],
                 "RB|9": [1],
                 "RB|10": [1],
                 "RB|11": [1],
                 "RB|12": [1],
                 "RB|13": [3],
                 "RB|15": [3],
             }
     raw_fass = qparams.get("fass")
     if raw_fass:
         params.placements = []
         for fass in raw_fass.split("|"):
             loc, _, item = fass.partition(":")
             stuff = [Stuff(code=item[:2], id=item[2:], player="")]
             params.placements.append(
                 Placement(location=loc, zone="", stuff=stuff))
     return params.put()
Example #8
0
 def get_paths(self):
     return enums_from_strlist(LogicPath, self.str_paths)
Example #9
0
 def get_vars(self):
     return enums_from_strlist(Variation, self.str_vars)
    def from_cli(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("--output-dir",
                            help="directory to put the seeds in",
                            type=str,
                            default=".")
        parser.add_argument(
            "--preset",
            help="Choose a preset group of paths for the generator to use")
        parser.add_argument(
            "--custom-logic",
            help=
            "Customize paths that the generator will use, comma-separated: %s"
            % ", ".join(vals(LogicPath)))
        parser.add_argument("--seed",
                            help="Seed value (default 'test')",
                            type=str,
                            default="test")
        parser.add_argument(
            "--keymode",
            help=
            """Changes how the dungeon keys (The Water Vein, Gumon Seal, and Sunstone) are handled:
        Default: The dungeon keys are placed without any special consideration.
        Clues: For each 3 trees visited, the zone of a random dungeon key will be revealed
        Shards: The dungeon keys will be awarded after 3/5 shards are found
        LimitKeys: The Water Vein, Gumon Seal, and Sunstone will only appear at skill trees or event sources
        Free: The dungeon keys are given to the player upon picking up the first Energy Cell.
        """,
            type=str)
        # variations
        parser.add_argument("--hard",
                            help="Enable hard mode",
                            action="store_true")
        parser.add_argument("--ohko",
                            help="Enable one-hit-ko mode",
                            action="store_true")
        parser.add_argument("--zeroxp",
                            help="Enable 0xp mode",
                            action="store_true")
        parser.add_argument(
            "--starved",
            help=
            "Reduces the rate at which skills will appear when not required to advance",
            action="store_true")
        parser.add_argument(
            "--tp-starved",
            help=
            "Reduces the rate at which teleporters will appear early game when not required to advance",
            action="store_true")
        parser.add_argument(
            "--wall-starved",
            help=
            "Reduces the rate at which WallJump and Climb will appear early game when not required to advance",
            action="store_true")
        parser.add_argument(
            "--non-progressive-mapstones",
            help=
            "Map Stones will retain their behaviour from before v1.2, having their own unique drops",
            action="store_true")
        parser.add_argument(
            "--force-trees",
            help=
            "Prevent Ori from entering the final escape room until all skill trees have been visited",
            action="store_true")
        parser.add_argument(
            "--force-mapstones",
            help=
            "Prevent Ori from entering the final escape room until all mapstone altars have been activated",
            action="store_true")
        parser.add_argument("--entrance",
                            help="Randomize entrances",
                            action="store_true")
        parser.add_argument("--closed-dungeons",
                            help="deactivate open mode within dungeons",
                            action="store_true")
        parser.add_argument("--open-world",
                            help="Activate open mode on the world map",
                            action="store_true")
        parser.add_argument(
            "--bonus-pickups",
            help=
            "Adds some extra bonus pickups not balanced for competitive play",
            action="store_true")
        parser.add_argument(
            "--easy",
            help=
            "Add an extra copy of double jump, bash, stomp, glide, charge jump, dash, grenade, water, and wind",
            action="store_true")
        parser.add_argument(
            "--strict-mapstones",
            help=
            "Require a mapstone to be placed when a map monument becomes accessible",
            action="store_true")
        parser.add_argument(
            "--world-tour",
            help=
            "Prevent Ori from entering the final escape until collecting one relic from each of the zones in the world. Recommended default: 8",
            type=int)
        parser.add_argument(
            "--warmth-frags",
            help=
            "Prevent Ori from entering the final escape until collecting some number of warmth fragments. Recommended default: 40",
            type=int)

        # misc
        parser.add_argument(
            "--verbose-paths",
            help="print every logic path in the flagline for debug purposes",
            action="store_true")
        parser.add_argument("--exp-pool",
                            help="Size of the experience pool (default 10000)",
                            type=int,
                            default=10000)
        parser.add_argument(
            "--extra-frags",
            help=
            """Sets the number of extra warmth fragments. Total frag number is still the value passed to --warmth-frags;
        --warmth-frags 40 --extra-frags 10 will place 40 total frags, 30 of which will be required to finish""",
            type=int,
            default=10)
        parser.add_argument(
            "--prefer-path-difficulty",
            help=
            "Increase the chances of putting items in more convenient (easy) or less convenient (hard) locations",
            choices=["easy", "hard"])
        parser.add_argument(
            "--balanced",
            help=
            "Reduce the value of newly discovered locations for progression placements",
            action="store_true")
        parser.add_argument(
            "--force-cells",
            help=
            "Force health and energy cells to appear every N pickups, if they don't randomly",
            type=int,
            default=256)
        # anal TODO: IMPL
        parser.add_argument(
            "--analysis",
            help="Report stats on the skill order for all seeds generated",
            action="store_true")
        parser.add_argument(
            "--loc-analysis",
            help="Report stats on where skills are placed over multiple seeds",
            action="store_true")
        parser.add_argument("--count",
                            help="Number of seeds to generate (default 1)",
                            type=int,
                            default=1)
        # sync
        parser.add_argument("--players",
                            help="Player count for paired randomizer",
                            type=int,
                            default=1)
        parser.add_argument(
            "--tracking",
            help="Place a sync ID in a seed for tracking purposes",
            action="store_true")
        parser.add_argument(
            "--sync-id",
            help="Team identifier number for paired randomizer",
            type=int)
        parser.add_argument(
            "--shared-items",
            help=
            "What will be shared by sync, comma-separated: skills,worldevents,misc,teleporters,upgrades",
            default="skills,worldevents")
        parser.add_argument(
            "--share-mode",
            help=
            "How the server will handle shared pickups, one of: shared,swap,split,none",
            default="shared")
        parser.add_argument(
            "--cloned",
            help="Make a split cloned seed instead of seperate seeds",
            action="store_true")
        parser.add_argument(
            "--teams",
            help=
            "Cloned seeds only: define teams. Format: 1|2,3,4|5,6. Each player must appear once",
            type=str)
        parser.add_argument(
            "--hints",
            help=
            "Cloned seeds only: display a hint with the item category on a shared location instead of 'Warmth Returned'",
            action="store_true")
        parser.add_argument(
            "--do-reachability-analysis",
            help=
            "Analyze how many locations are opened by various progression items in various inventory states",
            action="store_true")
        args = parser.parse_args()
        """
        path_diff = property(get_pathdiff, set_pathdiff)
        exp_pool = ndb.IntegerProperty(default=10000)
        balanced = ndb.BooleanProperty(default=True)
        tracking = ndb.BooleanProperty(default=True)
        players = ndb.IntegerProperty(default=1)
        sync = ndb.LocalStructuredProperty(MultiplayerOptions)
        frag_count = ndb.IntegerProperty(default=40)
        frag_extra = ndb.IntegerProperty(default=10)
        cell_freq = ndb.IntegerProperty(default=256)
        """
        self.seed = args.seed
        if args.preset:
            self.logic_paths = presets[args.preset.capitalize()]
        elif args.custom_logic:
            self.logic_paths = enums_from_strlist(LogicPath,
                                                  args.custom_logic.split(","))
        else:
            self.logic_paths = presets["Standard"]
        self.key_mode = KeyMode.mk(args.keymode) or KeyMode.NONE
        # variations (help)
        varMap = {
            "zeroxp": "0XP",
            "non_progressive_mapstones": "NonProgressMapStones",
            "ohko": "OHKO",
            "force_trees": "ForceTrees",
            "starved": "Starved",
            "force_mapstones": "ForceMapStones",
            "entrance": "Entrance",
            "open_world": "OpenWorld",
            "easy": "DoubleSkills",
            "strict_mapstones": "StrictMapstones",
            "warmth_frags": "WarmthFrags",
            "world_tour": "WorldTour",
            "closed_dungeons": "ClosedDungeons",
            "tp_starved": "TPStarved",
            "wall_starved": "WallStarved"
        }
        self.variations = []
        for argName, flagStr in varMap.iteritems():
            if getattr(args, argName, False):
                v = Variation.mk(flagStr)
                if v:
                    self.variations.append(v)
                else:
                    log.warning("Failed to make a Variation from %s" % flagStr)
        if Variation.WORLD_TOUR in self.variations:
            self.relic_count = args.world_tour
        if Variation.WARMTH_FRAGMENTS in self.variations:
            self.frag_count = args.warmth_frags
            self.frag_extra = args.extra_frags
        #misc
        self.exp_pool = args.exp_pool
        if args.prefer_path_difficulty:
            if args.prefer_path_difficulty == "easy":
                self.path_diff = PathDifficulty.EASY
            else:
                self.path_diff = PathDifficulty.HARD
        else:
            self.path_diff = PathDifficulty.NORMAL
        self.balanced = args.balanced or False
        self.cell_freq = args.force_cells
        self.players = args.players
        self.tracking = args.tracking or False
        self.sync = CLIMultiOptions()
        if Variation.EXTRA_BONUS_PICKUPS in self.variations:
            self.pool_preset = "Extra Bonus"
            self.item_pool = {
                "TP|Grove": [1],
                "TP|Swamp": [1],
                "TP|Grotto": [1],
                "TP|Valley": [1],
                "TP|Sorrow": [1],
                "TP|Ginso": [1],
                "TP|Horu": [1],
                "TP|Forlorn": [1],
                "HC|1": [12],
                "EC|1": [14],
                "AC|1": [33],
                "RB|0": [3],
                "RB|1": [3],
                "RB|6": [5],
                "RB|9": [1],
                "RB|10": [1],
                "RB|11": [1],
                "RB|12": [5],
                "RB|13": [3],
                "RB|15": [3],
                "RB|31": [1],
                "RB|32": [1],
                "RB|33": [3],
                "BS|*": [4],
                "WP|*": [4, 8],
            }
        else:
            self.pool_preset = "Standard"
            self.item_pool = {
                "TP|Grove": [1],
                "TP|Swamp": [1],
                "TP|Grotto": [1],
                "TP|Valley": [1],
                "TP|Sorrow": [1],
                "TP|Ginso": [1],
                "TP|Horu": [1],
                "TP|Forlorn": [1],
                "HC|1": [12],
                "EC|1": [14],
                "AC|1": [33],
                "RB|0": [3],
                "RB|1": [3],
                "RB|6": [3],
                "RB|9": [1],
                "RB|10": [1],
                "RB|11": [1],
                "RB|12": [1],
                "RB|13": [3],
                "RB|15": [3],
            }

        if self.players > 1 or self.tracking:
            self.sync_id = args.sync_id or int(time.time() * 1000 % 1073741824)
        if self.players > 1:
            self.sync.enabled = True
            self.sync.mode = MultiplayerGameType.mk(
                args.share_mode) or MultiplayerGameType.SIMUSOLO
            self.sync.shared = enums_from_strlist(ShareType,
                                                  args.shared_items.split(","))
            self.sync.cloned = args.cloned or False
            if self.sync.cloned:
                self.sync.hints = args.hints or False
                if args.teams:
                    cnt = 1
                    self.sync.teams = {}
                    for team in args.teams.split("|"):
                        self.sync.teams[cnt] = [
                            int(p) for p in team.split(",")
                        ]
                        cnt += 1

        # todo: respect these LMAO
        self.do_analysis = args.analysis
        self.do_loc_analysis = args.loc_analysis
        self.repeat_count = args.count

        base_seed = self.seed[:]
        if self.do_analysis:
            key_items = [
                "WallJump", "ChargeFlame", "DoubleJump", "Bash", "Stomp",
                "Glide", "Climb", "ChargeJump", "Dash", "Grenade", "GinsoKey",
                "ForlornKey", "HoruKey", "Water", "Wind", "TPForlorn",
                "TPGrotto", "TPSorrow", "TPGrove", "TPSwamp", "TPValley",
                "TPGinso", "TPHoru"
            ]
            info_by_group = defaultdict(defaultgroup)

        if self.do_loc_analysis:
            self.locationAnalysis = {}
            self.itemsToAnalyze = {
                "WallJump": 0,
                "ChargeFlame": 0,
                "DoubleJump": 0,
                "Bash": 0,
                "Stomp": 0,
                "Glide": 0,
                "Climb": 0,
                "ChargeJump": 0,
                "Dash": 0,
                "Grenade": 0,
                "GinsoKey": 0,
                "ForlornKey": 0,
                "HoruKey": 0,
                "Water": 0,
                "Wind": 0,
                "WaterVeinShard": 0,
                "GumonSealShard": 0,
                "SunstoneShard": 0,
                "TPForlorn": 0,
                "TPGrotto": 0,
                "TPSorrow": 0,
                "TPGrove": 0,
                "TPSwamp": 0,
                "TPValley": 0,
                "TPGinso": 0,
                "TPHoru": 0,
                "Relic": 0
            }
            for i in range(1, 10):
                self.locationAnalysis["MapStone " +
                                      str(i)] = self.itemsToAnalyze.copy()
                self.locationAnalysis["MapStone " +
                                      str(i)]["Zone"] = "MapStone"

        for count in range(0, self.repeat_count):

            if self.repeat_count > 1:
                self.seed = "%s_%s" % (base_seed, count)

            if self.do_loc_analysis:
                print(self.seed)

            sg = SeedGenerator()

            if args.do_reachability_analysis:
                sg.do_reachability_analysis(self)
                return

            raw = sg.setSeedAndPlaceItems(self, preplaced={})
            seeds = []
            spoilers = []
            if not raw:
                log.error("Couldn't build seed!")
                if self.do_loc_analysis:
                    continue
                return
            player = 0
            for player_raw in raw:
                player += 1
                seed, spoiler = tuple(player_raw)
                if self.tracking:
                    seed = "Sync%s.%s," % (self.sync_id, player) + seed
                seedfile = "randomizer_%s.dat" % player
                spoilerfile = "spoiler_%s.txt" % player
                if self.players == 1:
                    seedfile = "randomizer" + str(count) + ".dat"
                    spoilerfile = "spoiler" + str(count) + ".txt"

                if not self.do_analysis and not self.do_loc_analysis:
                    with open(args.output_dir + "/" + seedfile, 'w') as f:
                        f.write(seed)
                    with open(args.output_dir + "/" + spoilerfile, 'w') as f:
                        f.write(spoiler)
                if self.do_analysis:
                    i = 0
                    for spoiler_line in spoiler.split("\n"):
                        fl = first_line_pattern.match(spoiler_line)
                        if fl:
                            raw_group, raw_locs = fl.group(1, 2)
                            i = int(raw_group)
                            info_by_group[i]["seeds"] += 1
                            info_by_group[i]["locs"] += raw_locs.count(",") + 1
                            continue
                        fp = forced_pickup_pattern.match(spoiler_line)
                        if fp:
                            info_by_group[i]["force"] += 1
                            for raw in fp.group(1).split(","):
                                info_by_group[i]["forced"][raw.strip(
                                    " '")] += 1
                            continue
                        nl = normal_line_pattern.match(spoiler_line)
                        if nl:
                            item = nl.group(1)
                            if item in key_items:
                                info_by_group[i]["items"][item] += 1

        if self.do_analysis:
            #            output = open("analysis.csv", 'w')
            #            output.write("Location,Zone,WallJump,ChargeFlame,DoubleJump,Bash,Stomp,Glide,Climb,ChargeJump,Dash,Grenade,GinsoKey,ForlornKey,HoruKey,Water,Wind,WaterVeinShard,GumonSealShard,SunstoneShard,TPGrove,TPGrotto,TPSwamp,TPValley,TPSorrow,TPGinso,TPForlorn,TPHoru,Relic\n")
            for i, group in info_by_group.items():
                seeds = float(group["seeds"])
                print "%d (%d): " % (i, int(seeds))
                print "\tkey items: [",
                for item, count in group["items"].items():
                    print '%s: %02.2f%%,' % (item, 100 * float(count) / seeds),
                print "]\n\tforced: [",
                for item, count in group["forced"].items():
                    print '%s: %02.2f%%,' % (item, 100 * float(count) /
                                             float(group["force"])),
                print "]\n\taverage locs", float(group['locs']) / seeds
            with open("anal.pickle", 'w') as out_file:
                pickle.dump(info_by_group, out_file)
            with open("analysis.csv", 'w') as out_file:
                out_file.write(
                    "Group,Seeds,Forced,Locs,WallJump,ChargeFlame,DoubleJump,Bash,Stomp,Glide,Climb,ChargeJump,Dash,Grenade,GinsoKey,ForlornKey,HoruKey,Water,Wind,TPGrove,TPGrotto,TPSwamp,TPValley,TPSorrow,TPGinso,TPForlorn,TPHoru,WallJump,ChargeFlame,DoubleJump,Bash,Stomp,Glide,Climb,ChargeJump,Dash,Grenade,GinsoKey,ForlornKey,HoruKey,Water,Wind,TPGrove,TPGrotto,TPSwamp,TPValley,TPSorrow,TPGinso,TPForlorn,TPHoru,KS,EC,HC,MS,AC\n"
                )
                for i, group in info_by_group.items():
                    seeds = float(group["seeds"])
                    line = [
                        i,
                        int(seeds),
                        int(group["force"]),
                        "%02.2f" % (float(group["locs"]) / seeds)
                    ]
                    for key_item in [
                            "WallJump", "ChargeFlame", "DoubleJump", "Bash",
                            "Stomp", "Glide", "Climb", "ChargeJump", "Dash",
                            "Grenade", "GinsoKey", "ForlornKey", "HoruKey",
                            "Water", "Wind", "TPGrove", "TPGrotto", "TPSwamp",
                            "TPValley", "TPSorrow", "TPGinso", "TPForlorn",
                            "TPHoru"
                    ]:
                        line.append(
                            "%02.2f%%" %
                            (100 * float(group["items"][key_item]) / seeds))
                    for forced_prog in [
                            "WallJump", "ChargeFlame", "DoubleJump", "Bash",
                            "Stomp", "Glide", "Climb", "ChargeJump", "Dash",
                            "Grenade", "GinsoKey", "ForlornKey", "HoruKey",
                            "Water", "Wind", "TPGrove", "TPGrotto", "TPSwamp",
                            "TPValley", "TPSorrow", "TPGinso", "TPForlorn",
                            "TPHoru", "KS", "EC", "HC", "MS", "AC"
                    ]:
                        line.append(
                            "%02.2f%%" %
                            (100 * float(group["forced"][forced_prog]) /
                             float(group["force"])
                             if group["forced"][forced_prog] else 0))
                    out_file.write(",".join([str(x) for x in line]) + '\n')
        if self.do_loc_analysis:
            output = open("analysis.csv", 'w')
            output.write(
                "Group,WallJump,ChargeFlame,DoubleJump,Bash,Stomp,Glide,Climb,ChargeJump,Dash,Grenade,Water,Wind,WaterVeinShard,GumonSealShard,SunstoneShard,TPGrove,TPGrotto,TPSwamp,TPValley,TPSorrow,TPGinso,TPForlorn,TPHoru,\n"
            )
            for key in self.locationAnalysis.keys():
                line = key + ","
                line += str(self.locationAnalysis[key]["Zone"]) + ","
                line += str(self.locationAnalysis[key]["WallJump"]) + ","
                line += str(self.locationAnalysis[key]["ChargeFlame"]) + ","
                line += str(self.locationAnalysis[key]["DoubleJump"]) + ","
                line += str(self.locationAnalysis[key]["Bash"]) + ","
                line += str(self.locationAnalysis[key]["Stomp"]) + ","
                line += str(self.locationAnalysis[key]["Glide"]) + ","
                line += str(self.locationAnalysis[key]["Climb"]) + ","
                line += str(self.locationAnalysis[key]["ChargeJump"]) + ","
                line += str(self.locationAnalysis[key]["Dash"]) + ","
                line += str(self.locationAnalysis[key]["Grenade"]) + ","
                line += str(self.locationAnalysis[key]["GinsoKey"]) + ","
                line += str(self.locationAnalysis[key]["ForlornKey"]) + ","
                line += str(self.locationAnalysis[key]["HoruKey"]) + ","
                line += str(self.locationAnalysis[key]["Water"]) + ","
                line += str(self.locationAnalysis[key]["Wind"]) + ","
                line += str(self.locationAnalysis[key]["WaterVeinShard"]) + ","
                line += str(self.locationAnalysis[key]["GumonSealShard"]) + ","
                line += str(self.locationAnalysis[key]["SunstoneShard"]) + ","
                line += str(self.locationAnalysis[key]["TPGrove"]) + ","
                line += str(self.locationAnalysis[key]["TPGrotto"]) + ","
                line += str(self.locationAnalysis[key]["TPSwamp"]) + ","
                line += str(self.locationAnalysis[key]["TPValley"]) + ","
                line += str(self.locationAnalysis[key]["TPSorrow"]) + ","
                line += str(self.locationAnalysis[key]["TPGinso"]) + ","
                line += str(self.locationAnalysis[key]["TPForlorn"]) + ","
                line += str(self.locationAnalysis[key]["TPHoru"]) + ","
                line += str(self.locationAnalysis[key]["Relic"])

                output.write(line + "\n")