Example #1
0
        /*
         * This algorithm begins by assuming the player has access to all items, which implies all
         * locations are reachable, so R is initialized to the entire game and I contains all items. A random
         * item is selected from I and is removed from the player’s assumed item list, meaning that some
         * locations may become unreachable, and removed from R. A random, empty location which is
         * still reachable will then be selected and the previously removed item will be placed there. The
         * way this algorithm initially assumes all items are available and slowly removes them reducing
         * reachable areas, it can be thought of as a reverse fill.
         */
        public WorldGraph AssumedFill(WorldGraph world, List <Item> itempool)
        {
            List <Item> owneditems = itempool; //In contrast to other two algos, I is initialized to all items and itempool is empty

            itempool = new List <Item>();
            WorldGraph      reachable          = searcher.GetReachableLocationsAssumed(world, owneditems); //Initially R should equal all locations in the game
            List <Location> reachablelocations = reachable.GetAllEmptyLocations();

            helper.Shuffle(owneditems);
            while (reachablelocations.Count > 0 && owneditems.Count > 0)
            {
                Item item = helper.Pop(owneditems);                                            //Pop random item from I, R will shrink
                helper.Shuffle(owneditems);
                reachable          = searcher.GetReachableLocationsAssumed(world, owneditems); //Recalculate R now that less items are owned
                reachablelocations = reachable.GetAllEmptyLocations();                         //Get empty locations which are reachable
                helper.Shuffle(reachablelocations);
                if (reachablelocations.Count() == 0)                                           //If this happens, means there are no reachable locations left and must return, usually indicates uncompletable permutation
                {
                    break;
                }
                Location location = helper.Pop(reachablelocations); //Remove location from list
                world.Place(location, item);                        //Place random item in random location
                itempool.Add(item);                                 //Add item to item pool
            }
            return(world);                                          //World has been filled with items, return
        }
Example #2
0
        //Utilizes the recusive DFS for exit score function to return a score for this exit if its requirement is met, otherwise returns -1
        private double ExitScore(WorldGraph world, List <Item> owneditems, Region current, List <Region> traversed, Exit exit)
        {
            if (parser.RequirementsMet(exit.Requirements, owneditems))
            {
                double        score   = 0;
                List <Region> visited = new List <Region>();
                visited.Add(current);                                                           //Do this so path does not go through current region
                Region exitto = world.Regions.First(x => x.Name == exit.ToRegionName);          //Get the region this edge leads to
                maxtraversed = 0;
                score       += RecursiveDFSForExitScore(world, exitto, visited, owneditems, 1); //Add score from a recursive search which scores item locations, scored lower more regions traversed
                int multiplier = maxtraversed == 1 ? 2 : 1;                                     //Give a multiplier if this edge leads to a single, dead-end region

                //Give divider based on how recently the region was visited
                int divider   = 1;
                int lastindex = traversed.FindLastIndex(x => x.Name == exit.ToRegionName);
                if (lastindex > -1)
                {
                    int howrecent = traversed.Count - 2 - lastindex;
                    divider = 16 - howrecent; //Most recent region divided by 8, 2nd most recent divided by 7, etc to minimum of 1
                    divider = divider < 1 ? 1 : divider;
                }
                return(score * multiplier / divider);
            }
            else //Can not traverse exit
            {
                return(-1); //Return -1 to indicate it cannot be crossed
            }
        }
Example #3
0
        /*
         * Sphere Search is done iteratively in “Spheres” and is used to attempt to trace a path
         * from the beginning to the end of the game. The first sphere s is simply all locations which are
         * reachable from the beginning of the game. As it searches these locations, it adds key items
         * found to a temporary set; we do not want those items to affect reachability until the next sphere
         * iteration so we do not yet add them to I. After all reachable locations have been found, sphere s
         * is added to the list of spheres S, and all items in the temporary set are added to I. It then
         * iterates again with a new sphere s.
         */
        public SphereSearchInfo SphereSearch(WorldGraph world)
        {
            SphereSearchInfo output = new SphereSearchInfo();

            output.Spheres = new List <WorldGraph>();
            List <Item> owneditems = new List <Item>();
            //Initial sphere s0 includes items reachable from the start of the game
            WorldGraph s0 = GetReachableLocations(world, owneditems);

            owneditems = s0.CollectMajorItems(); //Collect all major items reachable from the start of the game
            output.Spheres.Add(s0);              //Add initial sphere to sphere list
            //sx indicates every sphere after the first. Any major items found in s0 means sx should be bigger.
            WorldGraph sx   = GetReachableLocations(world, owneditems);
            int        temp = owneditems.Where(x => x.Importance >= 2).Count(); //Temp is the count of previously owned major items

            owneditems = sx.CollectAllItems();                                  //Used to find new count of major items
            //If counts are equal then no new major items found, stop searching
            while (owneditems.Where(x => x.Importance >= 2).Count() > temp)     //If new count is not greater than old count, that means all currently reachable locations have been found
            {
                output.Spheres.Add(sx);                                         //If new locations found, add to sphere list
                //Take the same steps taken before the loop: Get new reachable locations, collect new major items, and check to see if new count is larger than old count
                sx         = GetReachableLocations(world, owneditems);
                temp       = owneditems.Where(x => x.Importance >= 2).Count(); //Only want to consider count of major items
                owneditems = sx.CollectAllItems();
            }
            //At this point, either a dead end has been found or all locations have been discovered
            //If the goal item is in the list of owned items, means the end has been found and thus the game is completable
            output.Completable = owneditems.Count(x => x.Name == world.GoalItemName) > 0;
            return(output);
        }
Example #4
0
 //Constructor from a previous world graph
 //Copies the start region and goal but new items and regions
 //Used when constructing partial graph of world
 public WorldGraph(WorldGraph world)
 {
     StartRegionName = world.StartRegionName;
     GoalItemName    = world.GoalItemName;
     Regions         = new HashSet <Region>();
     Items           = new List <Item>();
 }
Example #5
0
        public WorldGraph Generated;              //Result of world generation

        //Constructor which only specifies a number of items which are then generated
        public WorldGenerator(int regions, int items)
        {
            rng           = new Random();
            helper        = new Helpers();
            Regions       = regions;
            MajorItemList = GenItemList(items);
            Generated     = new WorldGraph();
        }
Example #6
0
 //Constructor which specifies an item list
 public WorldGenerator(int regions, List <Item> itemlist)
 {
     rng           = new Random();
     helper        = new Helpers();
     Regions       = regions;
     MajorItemList = itemlist;
     Generated     = new WorldGraph();
 }
Example #7
0
        //Calculates the bias for a given permutation of items in the world graph
        public BiasOutput CalcDistributionBias(WorldGraph world)
        {
            //Get total counts for majors and items so a percent can be calculated
            int totalmajorcount    = world.Items.Where(x => x.Importance >= 2).Count();
            int totallocationcount = world.GetLocationCount();
            //Find spheres in randomized world
            Search            searcher = new Search();
            List <WorldGraph> spheres  = searcher.SphereSearch(world).Spheres;

            //Initialize variables to use in the loop
            double[] spherebias           = new double[spheres.Count];
            int      rollingmajorcount    = 0;
            int      rollinglocationcount = 0;

            for (int i = 0; i < spheres.Count; i++)
            {
                WorldGraph sphere = spheres[i];
                //Check the number of major items in the sphere, not counting those in previous spheres
                int majoritemcount = sphere.CollectMajorItems().Count - rollingmajorcount;
                rollingmajorcount += majoritemcount;
                //Check the number of locations in the sphere, not counting those in previous spheres
                int locationcount = sphere.GetLocationCount() - rollinglocationcount;
                rollinglocationcount += locationcount;
                //Find the percentage of major items and locations in this sphere
                double majorpercent    = majoritemcount / (double)totalmajorcount;
                double locationpercent = locationcount / (double)totallocationcount;
                //Now find the difference between the two percentages
                double difference = majorpercent - locationpercent;
                spherebias[i] = difference;
            }
            //Now that we have a list of biases find the sum of their absolute values to determine absolute bias
            //Also use the positivity of bias before and after the median to determine bias direction
            double overallsum = 0;
            double beforesum  = 0;                          //Sums bias before median so a bias direction can be computed
            double aftersum   = 0;                          //Sums bias after median so a bias direction can be computed
            bool   even       = spherebias.Length % 2 == 0; //Want to check if even so can determine when after the median is
            int    median     = spherebias.Length / 2;      //Use median to determine bias direction

            for (int i = 0; i < spherebias.Length; i++)
            {
                overallsum += Math.Abs(spherebias[i]);
                if (i < median) //Before median, add to that sum
                {
                    beforesum += spherebias[i];
                }
                else if ((i >= median && even) || (i > median && !even)) //After median, add to that sum. If it's even then >= makes sense so every index is checked, if odd then skip middle
                {
                    aftersum = spherebias[i];
                }
            }
            //Package output and return
            BiasOutput output = new BiasOutput();

            output.biasvalue = overallsum / spherebias.Length; //Get average of absolute value to determine overall bias
            output.direction = beforesum < aftersum;           //If bias is more positive before the median, the direction is toward the beginning, otherwise toward end
            return(output);
        }
Example #8
0
        //Utilizes BFS search algorithm to find all locations in the world which are reachable with the current item set
        //In this algorithm, we want to check for items which have been removed from I but are still contained within R, so an initial search is done to collect items,
        //then repeated iteratively until no new items are found, at which point the final reachability graph is returned.
        public WorldGraph GetReachableLocationsAssumed(WorldGraph world, List <Item> owneditems)
        {
            WorldGraph  copy     = world.Copy();                 //Used so items may be removed from world at will
            List <Item> newitems = ItemSearch(copy, owneditems); //Find items within R
            List <Item> combined = owneditems.ToList();          //Copy list

            while (newitems.Count > 0)
            {
                combined.AddRange(newitems);                //Add items to currently used items
                newitems = ItemSearch(copy, combined);      //Find items within R
            }
            return(GetReachableLocations(world, combined)); //Use that combined list to find final search result
        }
Example #9
0
        //Calculate the average complexity from generating many worlds with a specific regioncount and itemcount
        static List <TestComplexityOutput> AverageComplexity(int regioncount, int itemcount)
        {
            //First do x trials to determine an average complexity
            int gentrials = 5;
            List <TestComplexityOutput> outputs = new List <TestComplexityOutput>();

            for (int i = 0; i < gentrials; i++)
            {
                WorldGenerator generator = new WorldGenerator(regioncount, itemcount);
                WorldGraph     generated = generator.Generate();
                outputs.Add(generator.GetComplexity());
            }
            return(outputs); //Determine average complexity, we want the goal complexity to be within some% of this
        }
Example #10
0
        private List <List <Region> > paths = new List <List <Region> >(); //Declared outside of function scope so multiple instances of following two functions can access

        //Use DFS to find all possible paths from the root to the specified region
        //Not including paths that go back on themselves
        public List <List <Region> > PathsToRegion(WorldGraph world, Region dest)
        {
            Region root = world.Regions.First(x => x.Name == world.StartRegionName);

            List <Region> visited = new List <Region>();

            paths = new List <List <Region> >();
            if (root == dest) //If root and dest equal, return empty list
            {
                return(paths);
            }

            RecursiveDFSForPathList(world, root, dest, visited); //Recursively run DFS, when dest found add the path to paths var

            return(paths);                                       //Return list of paths
        }
Example #11
0
        //Calculate info about human-like playthrough and then return the score
        public InterestingnessOutput CalcDistributionInterestingness(WorldGraph world)
        {
            PlaythroughInfo info = new PlaythroughInfo();

            try
            {
                info = searcher.PlaythroughSearch(world.Copy());
            }
            catch
            {
                throw new Exception(); //Something went wrong, have calling code retry
            }
            BiasOutput biasinfo = CalcDistributionBias(world);

            return(ScorePlaythrough(world, info, biasinfo));
        }
Example #12
0
        //Initially, went to collect all items which are reachable with the current item set and not already contained within the item set
        public List <Item> ItemSearch(WorldGraph world, List <Item> owneditems)
        {
            List <Item> newitems = new List <Item>();

            Region           root    = world.Regions.First(x => x.Name == world.StartRegionName);
            Queue <Region>   Q       = new Queue <Region>();
            HashSet <Region> visited = new HashSet <Region>();

            Q.Enqueue(root);
            visited.Add(root);

            //Implementation of BFS
            while (Q.Count > 0)
            {
                Region r = Q.Dequeue();

                foreach (Exit e in r.Exits)
                {
                    //Normally in BFS, all exits would be added
                    //But in this case, we only want to add exits which are reachable
                    if (parser.RequirementsMet(e.Requirements, owneditems))
                    {
                        Region exitto = world.Regions.First(x => x.Name == e.ToRegionName); //Get the region this edge leads to
                        if (!visited.Contains(exitto))                                      //Don't revisit already visited nodes on this path
                        {
                            Q.Enqueue(exitto);
                            visited.Add(exitto);
                        }
                    }
                }
                //Subsearch to check each edge to a location in the current region
                //If requirement is met, add it to reachable locations
                foreach (Location l in r.Locations)
                {
                    if (parser.RequirementsMet(l.Requirements, owneditems))
                    {
                        if (l.Item.Importance == 2) //If location contains a major item
                        {
                            newitems.Add(l.Item);
                            l.Item = new Item(); //Remove item so it isn't added again in future iterations
                        }
                    }
                }
            }
            return(newitems);
        }
Example #13
0
 //Recursively check exits with copy of visited list
 //It's done this way so that after the destination or a dead end is met, the code flow "backs up"
 public void RecursiveDFSForPathList(WorldGraph world, Region r, Region dest, List <Region> visited)
 {
     visited.Add(r); //Add to visited list
     if (r == dest)
     {
         paths.Add(visited); //If this is the dest, then visited currently equals a possible path
         return;
     }
     foreach (Exit e in r.Exits)
     {
         Region exitto = world.Regions.First(x => x.Name == e.ToRegionName); //Get the region this edge leads to
         if (!visited.Contains(exitto))                                      //Don't revisit already visited nodes on this path
         {
             List <Region> copy = new List <Region>(visited);                //If don't do this List is passed by reference, algo doesn't work
             RecursiveDFSForPathList(world, exitto, dest, copy);
         }
     }
 }
Example #14
0
        //Utilizes BFS search algorithm to find all locations in the world which are reachable with the current item set
        //Important note is that throughout this function the owned items are static, they are not collected throughout (as they are in sphere search)
        //It is used for forward search, where there is no need to check for items currently within R, as well as other places such as sphere search
        public WorldGraph GetReachableLocations(WorldGraph world, List <Item> owneditems)
        {
            WorldGraph       reachable = new WorldGraph(world);
            Region           root      = world.Regions.First(x => x.Name == world.StartRegionName);
            Queue <Region>   Q         = new Queue <Region>();
            HashSet <Region> visited   = new HashSet <Region>();

            Q.Enqueue(root);
            visited.Add(root);

            //Implementation of BFS
            while (Q.Count > 0)
            {
                Region r     = Q.Dequeue();
                Region toadd = new Region(r);

                foreach (Exit e in r.Exits)
                {
                    //Normally in BFS, all exits would be added
                    //But in this case, we only want to add exits which are reachable
                    if (parser.RequirementsMet(e.Requirements, owneditems))
                    {
                        toadd.Exits.Add(e);
                        Region exitto = world.Regions.First(x => x.Name == e.ToRegionName); //Get the region this edge leads to
                        if (!visited.Contains(exitto))                                      //Don't revisit already visited nodes on this path
                        {
                            Q.Enqueue(exitto);
                            visited.Add(exitto);
                        }
                    }
                }
                //Subsearch to check each edge to a location in the current region
                //If requirement is met, add it to reachable locations
                foreach (Location l in r.Locations)
                {
                    if (parser.RequirementsMet(l.Requirements, owneditems))
                    {
                        toadd.Locations.Add(l);
                    }
                }
                reachable.Regions.Add(toadd); //Add every reachable exit and location discovered in this iteration
            }
            return(reachable);                //Return graph of reachable locations
        }
Example #15
0
        //Copy all properties of graph and return
        //Used so that input graph is not copied by reference and overwritten
        public WorldGraph Copy()
        {
            WorldGraph copy = new WorldGraph();

            copy.StartRegionName = StartRegionName;
            copy.GoalItemName    = GoalItemName;
            copy.Regions         = new HashSet <Region>();
            foreach (Region r in Regions)
            {
                Region rcopy = new Region();
                rcopy.Name  = r.Name;
                rcopy.Exits = new HashSet <Exit>();
                foreach (Exit e in r.Exits)
                {
                    Exit ecopy = new Exit();
                    ecopy.ToRegionName = e.ToRegionName;
                    ecopy.Requirements = e.Requirements;
                    rcopy.Exits.Add(e);
                }
                rcopy.Locations = new HashSet <Location>();
                foreach (Location l in r.Locations)
                {
                    Location lcopy = new Location();
                    lcopy.Name         = l.Name;
                    lcopy.Requirements = l.Requirements;
                    Item icopy = new Item();
                    icopy.Importance = l.Item.Importance;
                    icopy.Name       = l.Item.Name;
                    lcopy.Item       = icopy;
                    rcopy.Locations.Add(lcopy);
                }
                copy.Regions.Add(rcopy);
            }
            copy.Items = new List <Item>();
            foreach (Item i in Items)
            {
                Item icopy = new Item();
                icopy.Importance = i.Importance;
                icopy.Name       = i.Name;
                copy.Items.Add(icopy);
            }
            return(copy);
        }
Example #16
0
        //G: Graph of world locations (called world in code)
        // A node in G initially has a null value, this value can be filled with a key item
        // An edge in G may require certain items to traverse
        //R: Graph of reachable locations (called reachable in code)
        //I: Set of items owned, determines R, called owneditems in code
        //I*: Set of items not owned, inverse of I, called itempool in code
        //Goal: A specific item in G which signifies the end of the game
        //Start: A specific region in G which the player starts in


        /*
         * This algorithm simply places a random key item in a random location until either of these
         * sets are empty (Usually items). After placing all a check is done to see if the game is beatable. If
         * not it runs the algorithm again. In complex world this could potentially take hundreds of attempts.
         */
        public WorldGraph RandomFill(WorldGraph world, List <Item> itempool)
        {
            //Initialize owneditems to empty and locations to all that are empty
            List <Item>     owneditems = new List <Item>();
            List <Location> locations  = world.GetAllEmptyLocations();

            helper.Shuffle(locations);
            helper.Shuffle(itempool);
            while (locations.Count > 0 && itempool.Count > 0)
            {
                Location location = helper.Pop(locations); //Select random location
                helper.Shuffle(locations);
                Item item = helper.Pop(itempool);          //Take random item from item pool
                helper.Shuffle(itempool);
                world.Place(location, item);               //Place random item in random location
                owneditems.Add(item);                      //Add to owned items
            }
            return(world);                                 //World has been filled with items, return
        }
Example #17
0
        //Generates a world with a specific count of regions and items
        //First generates many worlds to determine an average complexity then returns a world generated with a certain tolerance of that complexity
        //This takes a while to run, mainly because each complexity calculation takes ~2 seconds due to running the external python script
        static string GenerateWorld(int regioncount, int itemcount)
        {
            double goalcomplexity = AverageComplexity(regioncount, itemcount).Average(x => x.top50);
            //Now generate worlds until one is generated within a certain tolerance of the average
            double tolerance = .10; //10%

            while (true)
            {
                //Generate a world, check its complexity
                WorldGenerator generator  = new WorldGenerator(regioncount, itemcount);
                WorldGraph     generated  = generator.Generate();
                int            test       = generated.GetLocationCount();
                double         complexity = generator.GetComplexity().top50;
                if (goalcomplexity * (1 - tolerance) < complexity && complexity < goalcomplexity * (1 + tolerance))
                {
                    //Once complexity within x% of average has been generated, return json of the world so it can be saved
                    return(generated.ToJson());
                }
            }
        }
Example #18
0
        /*
         * This algorithm initializes set R to be the reachable locations from the start of the game. It
         * then chooses an item from the item pool I* and places it in a random location in set R, meaning
         * it is also added to I. This location is then removed from consideration and all locations that
         * become reachable are added to R. Repeat until R or I* is empty. To be clear, items related to
         * progression are placed first, and then everything else is filled in with helpful or junk items. In
         * fact, usually the helpful and junk items are placed using random fill since it’s faster and
         * placement doesn’t matter.
         */
        public WorldGraph ForwardFill(WorldGraph world, List <Item> itempool)
        {
            List <Item>     owneditems = new List <Item>();                                 //Initialize owneditems to empty
            WorldGraph      reachable  = searcher.GetReachableLocations(world, owneditems); //Initially R should only equal locations reachable from the start of the game
            List <Location> locations  = reachable.GetAllEmptyLocations();

            helper.Shuffle(locations);
            helper.Shuffle(itempool);
            while (locations.Count > 0 && itempool.Count > 0)
            {
                Location location = helper.Pop(locations); //Get random location and item
                Item     item     = helper.Pop(itempool);
                helper.Shuffle(itempool);
                world.Place(location, item);                                   //Place random item in random reachable location
                owneditems.Add(item);                                          //Add new item to owned items, R will expand
                reachable = searcher.GetReachableLocations(world, owneditems); //Recalculate R now that more items are owned
                locations = reachable.GetAllEmptyLocations();
                helper.Shuffle(locations);
            }
            return(world); //World has been filled with items, return
        }
Example #19
0
        //Scores an exit by recurisvely searching for items, adding score for every available location, with less weight if farther away
        public double RecursiveDFSForExitScore(WorldGraph world, Region r, List <Region> visited, List <Item> owneditems, int traversed)
        {
            maxtraversed = Math.Max(traversed, maxtraversed);
            visited.Add(r);                                             //Add to visited list
            double score      = 0;
            double multiplier = Math.Max(1 / 8, 1 / (double)traversed); //Max multiplier is 1, Minimum multiplier is 1/8

            //First look at all locations in region to add to score
            foreach (Location l in r.Locations)
            {
                if (parser.RequirementsMet(l.Requirements, owneditems)) //Only want to consider available locations
                {
                    if (l.Item.Importance == 3)                         //Path contains goal item, add large amount to score, add 100 / traversed so that shorter paths to goal preferred
                    {
                        score += 10000000000 + (100 / traversed);
                    }
                    else if (l.Item.Importance > -1) //Else just add 1 if item isn't already collected (remember, although player knows there is a location here, they don't know what item it is unless it's the goal)
                    {
                        score += 1 * multiplier;
                    }
                }
            }
            //Now recursively look through each exit
            foreach (Exit e in r.Exits)
            {
                if (parser.RequirementsMet(e.Requirements, owneditems))                 //Only consider exit if it can be traversed
                {
                    Region exitto = world.Regions.First(x => x.Name == e.ToRegionName); //Get the region this edge leads to
                    if (!visited.Contains(exitto))                                      //Don't revisit already visited nodes on this path
                    {
                        //Recursively call this function, adding 1 to traversed, score will be added to our score and returned
                        //We purposely pass visited by reference rather than by value, ensuring that locations are only visited once
                        score += RecursiveDFSForExitScore(world, exitto, visited, owneditems, traversed + 1);
                    }
                }
            }
            return(score);
        }
Example #20
0
        //Experiment space
        static void Main(string[] args)
        {
            Fill       filler   = new Fill();
            Search     searcher = new Search();
            Statistics stats    = new Statistics();

            //string testjsontext = File.ReadAllText("../../../WorldGraphs/World3.json");
            //WorldGraph testworld = JsonConvert.DeserializeObject<WorldGraph>(testjsontext);

            //double[] testaverages = new double[5];
            //for (int regioncount = 10; regioncount <= 50; regioncount += 5)
            //{
            //    for (int itemcount = 5; itemcount <= Math.Min(regioncount, 30); itemcount += 5)
            //    {
            //        List<TestComplexityOutput> complexity = AverageComplexity(regioncount, itemcount);
            //        Console.WriteLine("Regions: " + regioncount + ", Items: " + itemcount);
            //        Console.WriteLine("Sum: " + complexity.Average(x => x.sum));
            //        Console.WriteLine("Avg: " + complexity.Average(x => x.average));
            //        Console.WriteLine("Max: " + complexity.Average(x => x.max));
            //        Console.WriteLine("SOS: " + complexity.Average(x => x.sumofsquares));
            //        Console.WriteLine("Avg50: " + complexity.Average(x => x.top50));
            //        Console.WriteLine("Avg75: " + complexity.Average(x => x.top75));
            //        Console.Write(Environment.NewLine);
            //    }
            //}

            //string generatedjson = GenerateWorld(50, 30);

            //string jsontest = File.ReadAllText("../../../WorldGraphs/World5.json");
            //WorldGraph testworld = JsonConvert.DeserializeObject<WorldGraph>(jsontest);
            //int testlocationcount = testworld.GetLocationCount();

            //Search testsearcher = new Search();
            //testsearcher.PathsToRegion(world, world.Regions.First(x => x.Name == "Waterfall"));

            //Parser testparse = new Parser();
            //string result = testparse.Simplify("(Sword and Bow and Bow) or Has(Key,2)"); //Should be simplified to something like (Sword and Bow) or Has(Key,2)
            //string result2 = testparse.Simplify("Sword or Sword and Bow"); //Should be simplified to Sword
            ////majoritempool.RemoveAt(8);
            ////majoritempool.RemoveAt(0);
            //bool result = testparse.RequirementsMet("(Sword and Bow) or Has(Key,2)", majoritempool);

            //string testjsontext = File.ReadAllText("../../../WorldGraphs/TestWorldOriginal.json");
            //WorldGraph testworld = JsonConvert.DeserializeObject<WorldGraph>(testjsontext);
            //SphereSearchInfo testoutput = searcher.SphereSearch(testworld);
            //Print_Spheres(testoutput);

            string[] algos = { "Random", "Forward", "Assumed" };
            foreach (string worldname in testworlds)
            {
                DateTime   expstart = DateTime.Now;
                string     jsontext = File.ReadAllText("../../../WorldGraphs/" + worldname + ".json");
                WorldGraph world    = JsonConvert.DeserializeObject <WorldGraph>(jsontext);
                int        l        = world.GetLocationCount();
                //Loop to perform fill
                for (int i = 0; i < 3; i++) //0 = Random, 1 = Forward, 2 = assumed
                {
                    if (dotests[i])
                    {
                        //List<InterestingnessOutput> intstats = new List<InterestingnessOutput>();
                        //double totaltime = 0;
                        int savecounter = 0;
                        int countofexp  = db.Results.Count(x => x.Algorithm == algos[i] && x.World == worldname);
                        //int countofexp = 0;
                        while (countofexp < trials) //Go until there are trial number of records in db
                        {
                            InterestingnessOutput intstat = new InterestingnessOutput();
                            double difference             = -1;
                            while (true)                                    //If something goes wrong in playthrough search, may need to retry
                            {
                                WorldGraph  input           = world.Copy(); //Copy so that world is not passed by reference and overwritten
                                List <Item> majoritempool   = input.Items.Where(x => x.Importance == 2).ToList();
                                List <Item> minoritempool   = input.Items.Where(x => x.Importance < 2).ToList();
                                WorldGraph  randomizedgraph = new WorldGraph();
                                DateTime    start           = DateTime.Now; //Start timing right before algorithm
                                //Decide which algo to use based on i
                                switch (i)
                                {
                                case 0:
                                    randomizedgraph = filler.RandomFill(input, majoritempool);
                                    break;

                                case 1:
                                    randomizedgraph = filler.ForwardFill(input, majoritempool);
                                    break;

                                case 2:
                                    randomizedgraph = filler.AssumedFill(input, majoritempool);
                                    break;
                                }
                                randomizedgraph = filler.RandomFill(randomizedgraph, minoritempool); //Use random for minor items always since they don't matter
                                //Calculate metrics
                                DateTime end = DateTime.Now;
                                difference = (end - start).TotalMilliseconds;
                                //totaltime += difference;
                                //string randomizedjson = JsonConvert.SerializeObject(randomizedgraph);
                                //SphereSearchInfo output = searcher.SphereSearch(randomizedgraph);
                                //Print_Spheres(output);
                                try
                                {
                                    intstat = stats.CalcDistributionInterestingness(randomizedgraph);
                                    break; //Was successful, continue
                                }
                                catch { } //Something went wrong, retry fill from scratch
                            }
                            //intstats.Add(intstat);
                            //Store result in database
                            Result result = new Result();
                            result.Algorithm       = algos[i];
                            result.World           = worldname;
                            result.Completable     = intstat.completable;
                            result.ExecutionTime   = difference;
                            result.Bias            = intstat.bias.biasvalue;
                            result.BiasDirection   = intstat.bias.direction;
                            result.Interestingness = intstat.interestingness;
                            result.Fun             = intstat.fun;
                            result.Challenge       = intstat.challenge;
                            result.Satisfyingness  = intstat.satisfyingness;
                            result.Boredom         = intstat.boredom;
                            db.Entry(result).State = EntityState.Added;
                            savecounter++;
                            if (savecounter >= 1000) //Save every 1000 results processed
                            {
                                db.SaveChanges();
                                savecounter = 0;
                            }
                            countofexp++;
                        }
                        //double avgint = intstats.Where(x => x.completable).Average(x => x.interestingness);
                        //Console.WriteLine("Average interestingness for " + algos[i] + " Fill in world " + worldname + ": " + avgint);
                        //double avgbias = intstats.Where(x => x.completable).Average(x => x.bias.biasvalue);
                        //Console.WriteLine("Average bias for " + algos[i] + " Fill in world " + worldname + ": " + avgbias);
                        //double avgfun = intstats.Where(x => x.completable).Average(x => x.fun);
                        //Console.WriteLine("Average fun for " + algos[i] + " Fill in world " + worldname + ": " + avgfun);
                        //double avgchal = intstats.Where(x => x.completable).Average(x => x.challenge);
                        //Console.WriteLine("Average challenge for " + algos[i] + " Fill in world " + worldname + ": " + avgchal);
                        //double avgsat = intstats.Where(x => x.completable).Average(x => x.satisfyingness);
                        //Console.WriteLine("Average satisfyingness for " + algos[i] + " Fill in world " + worldname + ": " + avgsat);
                        //double avgbore = intstats.Where(x => x.completable).Average(x => x.boredom);
                        //Console.WriteLine("Average boredom for " + algos[i] + " Fill in world " + worldname + ": " + avgbore);
                        //double avgtime = totaltime / trials;
                        //Console.WriteLine("Average time to generate for " + algos[i] + " Fill in world " + worldname + ": " + avgtime + "ms");

                        db.SaveChanges(); //Save changes when combo of algo and world is done
                    }
                    //Console.Write(Environment.NewLine);
                }
                //Console.Write(Environment.NewLine);
                //Console.Write(Environment.NewLine);
                DateTime expend        = DateTime.Now;
                double   expdifference = (expend - expstart).TotalMinutes;
                Console.WriteLine("Time to perform " + trials + " iterations for world " + worldname + ": " + expdifference + " minutes");
            }
            Console.ReadLine();
        }
Example #21
0
        //Experiment space
        static void Main(string[] args)
        {
            Fill       filler   = new Fill();
            Search     searcher = new Search();
            Statistics stats    = new Statistics();

            ////Uncomment to test different complexity measures and generate many worlds with different parameters.
            //double[] testaverages = new double[5];
            //for (int regioncount = 10; regioncount <= 50; regioncount += 5)
            //{
            //    for (int itemcount = 5; itemcount <= Math.Min(regioncount, 30); itemcount += 5)
            //    {
            //        List<TestComplexityOutput> complexity = AverageComplexity(regioncount, itemcount);
            //        Console.WriteLine("Regions: " + regioncount + ", Items: " + itemcount);
            //        Console.WriteLine("Sum: " + complexity.Average(x => x.sum));
            //        Console.WriteLine("Avg: " + complexity.Average(x => x.average));
            //        Console.WriteLine("Max: " + complexity.Average(x => x.max));
            //        Console.WriteLine("SOS: " + complexity.Average(x => x.sumofsquares));
            //        Console.WriteLine("Avg50: " + complexity.Average(x => x.top50));
            //        Console.WriteLine("Avg75: " + complexity.Average(x => x.top75));
            //        Console.Write(Environment.NewLine);
            //    }
            //}

            //Loop through each algorithm set to be used and each world in the list, performing specified algorithm on specified world and recording information about the result.
            string[] algos = { "Random", "Forward", "Assumed" };
            foreach (string worldname in testworlds)
            {
                DateTime   expstart = DateTime.Now;
                string     jsontext = File.ReadAllText("../../../WorldGraphs/" + worldname + ".json");
                WorldGraph world    = JsonConvert.DeserializeObject <WorldGraph>(jsontext);
                //Loop to perform fill algorithms
                for (int i = 0; i < 3; i++) //0 = Random, 1 = Forward, 2 = Assumed
                {
                    if (dotests[i])
                    {
                        int savecounter = 0;
                        int countofexp  = db.Results.Count(x => x.Algorithm == algos[i] && x.World == worldname);
                        while (countofexp < trials) //Go until there are trial number of records in db
                        {
                            InterestingnessOutput intstat = new InterestingnessOutput();
                            double difference             = -1;
                            while (true)                                    //If something goes wrong in playthrough search, may need to retry
                            {
                                WorldGraph  input           = world.Copy(); //Copy so that world is not passed by reference and overwritten
                                List <Item> majoritempool   = input.Items.Where(x => x.Importance == 2).ToList();
                                List <Item> minoritempool   = input.Items.Where(x => x.Importance < 2).ToList();
                                WorldGraph  randomizedgraph = new WorldGraph();
                                DateTime    start           = DateTime.Now; //Start timing right before algorithm
                                //Decide which algo to use based on i
                                switch (i)
                                {
                                case 0:
                                    randomizedgraph = filler.RandomFill(input, majoritempool);
                                    break;

                                case 1:
                                    randomizedgraph = filler.ForwardFill(input, majoritempool);
                                    break;

                                case 2:
                                    randomizedgraph = filler.AssumedFill(input, majoritempool);
                                    break;
                                }
                                randomizedgraph = filler.RandomFill(randomizedgraph, minoritempool); //Use random for minor items always since they don't matter
                                //Calculate metrics
                                DateTime end = DateTime.Now;
                                difference = (end - start).TotalMilliseconds;
                                try
                                {
                                    intstat = stats.CalcDistributionInterestingness(randomizedgraph);
                                    break; //Was successful, continue
                                }
                                catch { } //Something went wrong, retry fill from scratch
                                ////Uncomment to print the spheres of the result.
                                //SphereSearchInfo output = searcher.SphereSearch(randomizedgraph);
                                //Print_Spheres(output);
                            }
                            //Store result in database
                            Result result = new Result();
                            result.Algorithm       = algos[i];
                            result.World           = worldname;
                            result.Completable     = intstat.completable;
                            result.ExecutionTime   = difference;
                            result.Bias            = intstat.bias.biasvalue;
                            result.BiasDirection   = intstat.bias.direction;
                            result.Interestingness = intstat.interestingness;
                            result.Fun             = intstat.fun;
                            result.Challenge       = intstat.challenge;
                            result.Satisfyingness  = intstat.satisfyingness;
                            result.Boredom         = intstat.boredom;
                            db.Entry(result).State = EntityState.Added;
                            savecounter++;
                            if (savecounter >= 1000) //Save every 1000 results processed
                            {
                                db.SaveChanges();
                                savecounter = 0;
                            }
                            countofexp++;
                        }
                        db.SaveChanges(); //Save changes when combo of algo and world is done
                    }
                }
                DateTime expend        = DateTime.Now;
                double   expdifference = (expend - expstart).TotalMinutes;
                Console.WriteLine("Time to perform " + trials + " iterations for world " + worldname + ": " + expdifference + " minutes"); //Print how long this world took to do
            }
            Console.ReadLine();
        }
Example #22
0
        //Generate a random worldgraph using the specified number of regions and item list
        public WorldGraph Generate()
        {
            HashSet <Region> regions = new HashSet <Region>(); //Set of all regions in the world

            for (int i = 0; i < Regions; i++)
            {
                Region r = new Region("Region-" + i.ToString()); //Each region is named Region_x. So Region-1, Region-2, etc.
                regions.Add(r);
            }
            //Not must loop through each region to add exits
            //Separate loop from the previous so that all regions are available to add as exits
            foreach (Region r in regions)
            {
                //The first region has some specific conditions:
                // 1. First exit is guaranteed to have no requirement and goes to hub region
                // 2. There is a guaranteed second exit, that will have a single item requirement
                // 3. There is a 50% chance to have a third exit, which has a 50% chance between a single item and no item
                if (r.Name == "Region-0")
                {
                    //Add exit to hub region with no requirement
                    List <string> currentexits = new List <string>();
                    Region        hub          = regions.First(x => x.Name == "Region-1");
                    AddExitsNoRequirement(regions, r, hub);
                    currentexits.Add("Region-1");
                    //Add exit to 2nd region with single requirement
                    Region second = GetRandomAvailableRegion(regions, r, currentexits);
                    AddExitsOneRequirement(regions, r, second);
                    currentexits.Add(second.Name);
                    //50% chance to add a third region
                    int random = rng.Next(1, 3); //Either 1 or 2
                    if (random == 2)
                    {
                        Region third = GetRandomAvailableRegion(regions, r, currentexits);
                        random = rng.Next(1, 3);
                        //50% chance to have 1 requirement, 50% chance to have none
                        if (random == 2)
                        {
                            AddExitsNoRequirement(regions, r, third);
                        }
                        else
                        {
                            AddExitsOneRequirement(regions, r, third);
                        }
                    }
                }
                //The second region is the hub region and also has some specific conditions:
                // 1. Will connect to 5 regions besides the start region
                // 2. Half of its exits will have no item requirement, the other half will have one
                else if (r.Name == "Region-1")
                {
                    List <string> currentexits = new List <string>();
                    currentexits.Add("Region-0");
                    for (int i = 0; i < 5; i++) //Run for 5 iterations
                    {
                        Region to = GetRandomAvailableRegion(regions, r, currentexits);
                        if (i < 2) //First 3 exits (including start region) have no requirement
                        {
                            AddExitsNoRequirement(regions, r, to);
                        }
                        else //Next 3 iterations will have 1 requirement
                        {
                            AddExitsOneRequirement(regions, r, to);
                        }
                        currentexits.Add(to.Name);
                    }
                }
                //Every other region will have a number of exits in [1, 4], however max of 2 chosen at generation, 2 more can be added by a later region
                else
                {
                    int ExitNum = rng.Next(1, 3); //Generate random number in [1, 2]
                    //In case r already has exits, create a list which contains all its current exits
                    List <string> currentexits = new List <string>();
                    foreach (Exit e in r.Exits)
                    {
                        currentexits.Add(e.ToRegionName);
                    }
                    while (r.Exits.Count < ExitNum) //Possible that location already has specified number of exits, no big deal if so
                    {
                        Region to = GetRandomAvailableRegion(regions, r, currentexits);
                        if (!string.IsNullOrEmpty(to.Name))
                        {
                            AddExits(regions, r, to);  //Add exit from r to the random region
                            currentexits.Add(to.Name); //Also add dest region to list so it does not get added twice
                        }
                        else //Don't want to do this if r has 0 exits, but that logic is handled in GetRandomAvailableRegion
                        {
                            break;
                        }
                    }
                }
            }
            //Must make sure all locations are reachable
            Generated = new WorldGraph("Region-0", "Goal", regions.ToHashSet(), MajorItemList);
            List <Region> unreachable = Generated.GetUnreachableRegions();

            while (unreachable.Count > 0) //At least one reachable location
            {
                //Create a connection from a random reachable location to a random unreachable location
                List <Region> regionscopy = regions.ToList();
                helper.Shuffle(regionscopy);
                Region from = regionscopy.First(x => !unreachable.Contains(x)); //Not in unreachable, so it is reachable
                helper.Shuffle(unreachable);
                Region to = unreachable.First();                                //Unreachable
                AddExits(regions, from, to);                                    //Add connection between two regions to join subgraphs
                Generated   = new WorldGraph("Region-0", "Goal", regions.ToHashSet(), MajorItemList);
                unreachable = Generated.GetUnreachableRegions();                //Recompute reachability
            }
            //Now before adding items, we will get the last region and place the goal there- No other items will be placed there
            Generated = new WorldGraph("Region-0", "Goal", regions.ToHashSet(), MajorItemList);
            Region   goalregion   = Generated.Regions.Last();
            Item     goalitem     = new Item("Goal", 3);                          //Create goal item
            Location goallocation = new Location("Final Boss", "None", goalitem); //Create location for goal item with no requirement since entrance to region will have full requirement
            //We want all exits to the goal region to require every item so that they will all be required to complete the game
            string fullrequirement = "";

            foreach (Item i in MajorItemList)
            {
                fullrequirement += i.Name + " and ";
            }
            fullrequirement = fullrequirement.Substring(0, fullrequirement.Length - 5); //Remove final " and "
            regions.First(x => x == goalregion).Locations.Add(goallocation);
            foreach (Exit e in regions.First(x => x == goalregion).Exits)
            {
                e.Requirements = fullrequirement;
            }
            //Must also write to requirements leading into final region
            foreach (Region r in regions)
            {
                foreach (Exit e in r.Exits)
                {
                    if (e.ToRegionName == goalregion.Name)
                    {
                        e.Requirements = fullrequirement;
                    }
                }
            }
            //Finally, generate item locations and place the location in the region
            foreach (Region r in regions)
            {
                //The starting region has some specific conditions:
                // 1. Three locations with no requirement
                // 2. 50% chance of a 4th location with one requirement
                if (r.Name == "Region-0")
                {
                    int random = rng.Next(3, 5);
                    for (int i = 0; i < random; i++)
                    {
                        if (i < random - 1) //Guaranteed 3 locations with no requirement
                        {
                            Location l = new Location("Region-0_Location-" + i.ToString(), "None", new Item());
                            regions.First(x => x == r).Locations.Add(l); //Add generated location to region
                        }
                        else //Possible 4th location, have 1 requirement
                        {
                            Location l = new Location("Region-0_Location-" + i.ToString(), GenerateOneRandomRequirement(), new Item());
                            regions.First(x => x == r).Locations.Add(l); //Add generated location to region
                        }
                    }
                }
                //The second region is the hub region and also has some specific conditions:
                // 1. Two locations with no requirement
                // 2. One location with one requirement
                // 3. One location with two requirements
                else if (r.Name == "Region-1")
                {
                    for (int i = 0; i < 4; i++)
                    {
                        if (i < 2)
                        {
                            Location l = new Location("Region-1_Location-" + i.ToString(), "None", new Item());
                            regions.First(x => x == r).Locations.Add(l); //Add generated location to region
                        }
                        else if (i == 2)
                        {
                            Location l = new Location("Region-1_Location-" + i.ToString(), GenerateOneRandomRequirement(), new Item());
                            regions.First(x => x == r).Locations.Add(l); //Add generated location to region
                        }
                        else if (i == 3)
                        {
                            Location l = new Location("Region-1_Location-" + i.ToString(), GenerateTwoRandomRequirements(), new Item());
                            regions.First(x => x == r).Locations.Add(l); //Add generated location to region
                        }
                    }
                }
                //Every other region will generate 2 to 4 locations, unless region contains goal, in which case we want that to be the only location in that region
                else if (r != goalregion)
                {
                    //Generate 2 to 4 locations per region
                    int random = rng.Next(2, 5);
                    for (int i = 0; i < random; i++)
                    {
                        //Generate a location with:
                        // Name: Region-x_Location-y, ex Region-5_Location-2
                        // Requirement: Randomly Generated
                        // Item: null item
                        Location l = new Location(r.Name + "_Location-" + i.ToString(), GenerateRandomRequirement(false), new Item());
                        regions.First(x => x == r).Locations.Add(l); //Add generated location to region
                    }
                }
            }
            //Now that we have a total number of regions and a count of major items, must generate junk items to fill out the item list
            List <Item> ItemList = MajorItemList; //Copy major item list and add goal item

            ItemList.Add(goalitem);
            Generated = new WorldGraph("Region-0", "Goal", regions.ToHashSet(), ItemList.OrderByDescending(x => x.Importance).ThenBy(x => x.Name).ToList()); //Remake generated now that items have been added
            int locationcount = Generated.GetLocationCount();                                                                                                //Get location count and find difference so we know how many junk items to generate
            int difference    = locationcount - MajorItemList.Count();

            for (int i = 0; i < difference; i++)
            {
                //For a junk item, importance will be either 0 or 1, so generate one of those numbers randomly
                int  importance = rng.Next(0, 2);
                Item newitem    = new Item("JunkItem" + importance.ToString(), importance); //Name will either be JunkItem0 or JunkItem1
                ItemList.Add(newitem);
            }
            Generated = new WorldGraph("Region-0", "Goal", regions.ToHashSet(), ItemList.OrderByDescending(x => x.Importance).ThenBy(x => x.Name).ToList()); //Remake generated now that items have been added
            return(Generated);
        }
Example #23
0
        /*
         * The goal of this function is to traverse the game world like a player of the game rather than like an algorithm.
         * It's assumed that the player has decent knowledge of the game, meaning they know where locations are and
         * wether or not those locations and regions are accessible.
         * Therefore a heuristic is used to score each posisble exit a player could take based on number of item locations
         * and how close they are to the current location.
         * Whichever exit has the maximum score (meaning maximum potential to gain new items) is taken.
         * We keep several counts which can be used to gauge interestingness of a seed:
         * 1. Number of locations collected for each region traversed
         * 2. Number of regions traversed between finding major or helpful items
         * 3. Number of regions traversed between finding major items
         */
        public PlaythroughInfo PlaythroughSearch(WorldGraph world)
        {
            List <int> BetweenMajorOrHelpfulList      = new List <int>(); //This did not end up being utilized.
            List <int> BetweenMajorList               = new List <int>();
            List <int> LocationsPerTraversal          = new List <int>();
            List <int> LocationsUnlockedPerMajorFound = new List <int>();

            Region        current    = world.Regions.First(x => x.Name == world.StartRegionName);
            Region        previous   = new Region();
            List <Item>   owneditems = new List <Item>();
            List <Region> traversed  = new List <Region>();

            int  BetweenMajorOrHelpfulCount = 0;
            int  BetweenMajorCount          = 0;
            bool gomode = false;
            int  prevlocationsunlocked = GetReachableLocations(world, owneditems).GetLocationCount(); //Initial count of unlocked locations
            int  initial = prevlocationsunlocked;                                                     //Saved to add to output later

            while (owneditems.Count(x => x.Importance == 3) < 1)                                      //Loop until goal item found, or dead end reached (see break statement below)
            {
                //If count gets this high usually indicates search is stuck in a loop... not great but just retry with a new permutation...
                //Seems to happen roughly one in every 5000 permutations of most complex world (World5) so fairly rare occurrence
                if (traversed.Count > world.Regions.Count() * 20)
                {
                    throw new Exception();
                }
                traversed.Add(current);
                int checkcount = 0;
                int prevcount  = -1;
                while (prevcount < owneditems.Count()) //While loop to re-check locations if major item is found in this region
                {
                    prevcount = owneditems.Count();
                    //First, check each location in the current region, if accessible and not already searched check it for major items
                    foreach (Location l in current.Locations)
                    {
                        if (l.Item.Importance > -1 && parser.RequirementsMet(l.Requirements, owneditems))
                        {
                            checkcount++;          //Add to check count
                            Item i = l.Item;
                            l.Item = new Item();   //Remove item, location importance set to -1
                            if (i.Importance == 1) //Helpful item, did not end up being utilized.
                            {
                                //Update helpful list only and reset counts
                                BetweenMajorOrHelpfulList.Add(BetweenMajorOrHelpfulCount);
                                BetweenMajorOrHelpfulCount = 0;
                            }
                            else if (i.Importance == 2) //Major item
                            {
                                owneditems.Add(i);      //Collect item
                                //Update both lists and reset counts
                                BetweenMajorOrHelpfulList.Add(BetweenMajorOrHelpfulCount);
                                BetweenMajorList.Add(BetweenMajorCount);
                                BetweenMajorOrHelpfulCount = 0;
                                BetweenMajorCount          = 0;
                                //Find number of locations unlocked, add to list, update count of locations unlocked
                                int locationsunlocked = GetReachableLocations(world, owneditems).GetLocationCount();
                                int newlocations      = locationsunlocked - prevlocationsunlocked;
                                LocationsUnlockedPerMajorFound.Add(newlocations);
                                prevlocationsunlocked = locationsunlocked;
                            }
                            else if (i.Importance == 3) //Goal item, break loop here
                            {
                                owneditems.Add(i);      //Collect goal item, indicates successful completion
                            }
                        }
                    }
                }
                if (!gomode) //Update this traversal with the number of locations checked within it, unless player is in go mode
                {
                    LocationsPerTraversal.Add(checkcount);
                }
                List <double> exitscores = new List <double>();
                if (current.Exits.Count == 1) //Only one exit, take it unless need to break
                {
                    double score = ExitScore(world, owneditems, current, traversed, current.Exits.First());
                    if (score > 0)                                                                        //If score is -1 and this is the only exit, then a dead end has been reached; if score is 0, then there are no items left to find; otherwise simply take exit since it is the only one
                    {
                        current = world.Regions.First(x => x.Name == current.Exits.First().ToRegionName); //Move to region
                        if (score >= 10000000000)                                                         //Score this high indicates player is in "go mode" where they are now rushing the end of the game
                        {
                            gomode = true;
                        }
                        //Update count of regions between finding items
                        BetweenMajorOrHelpfulCount++;
                        BetweenMajorCount++;
                    }
                    else
                    {
                        break; //Break loop in failure
                    }
                }
                else
                {
                    List <double> scores = new List <double>();
                    //Calculate score for each exit
                    foreach (Exit e in current.Exits)
                    {
                        scores.Add(ExitScore(world, owneditems, current, traversed, e));
                    }
                    if (scores.Count(x => x > 0) > 0)                                                                  //If none of the scores are greater than 0, all exits are either untraversable or have no available items, indicating dead end has been reached
                    {
                        int maxindex = scores.IndexOf(scores.Max());                                                   //Get index of the maximum score
                        previous = current;
                        current  = world.Regions.First(x => x.Name == current.Exits.ElementAt(maxindex).ToRegionName); //Move to region with maximum score
                        if (scores.Max() >= 10000000000)                                                               //Score this high indicates player is in "go mode" where they are now rushing the end of the game
                        {
                            gomode = true;
                        }
                        //Update count of regions between finding items
                        BetweenMajorOrHelpfulCount++;
                        BetweenMajorCount++;
                    }
                    else
                    {
                        break; //Break loop in failure
                    }
                }
            }
            //Package all lists into list of lists and return
            PlaythroughInfo output = new PlaythroughInfo();

            output.BetweenMajorOrHelpfulList      = BetweenMajorOrHelpfulList;
            output.BetweenMajorList               = BetweenMajorList;
            output.LocationsPerTraversal          = LocationsPerTraversal;
            output.LocationsUnlockedPerMajorFound = LocationsUnlockedPerMajorFound;
            output.Traversed             = traversed;
            output.Completable           = owneditems.Count(x => x.Importance == 3) > 0; //Has goal item, so game is completable
            output.InitialReachableCount = initial;
            return(output);
        }
Example #24
0
        /*
         * Score info about human-like playthrough
         * Several considerations:
         * 1. Number of locations collected for each region traversed
         * 2. Number of regions traversed between finding major or helpful items
         * 3. Number of regions traversed between finding major items
         */
        public InterestingnessOutput ScorePlaythrough(WorldGraph world, PlaythroughInfo input, BiasOutput biasinfo)
        {
            //First, calculate fun metric, which desires a consistently high rate of checking item locations
            Queue <int>   RollingAvg = new Queue <int>();
            List <double> avgs       = new List <double>();
            List <bool>   highavg    = new List <bool>();

            foreach (int num in input.LocationsPerTraversal)
            {
                if (RollingAvg.Count == 5) //Rolling average of last 5 values
                {
                    RollingAvg.Dequeue();
                }
                RollingAvg.Enqueue(num);
                double avg = RollingAvg.Average();
                highavg.Add(avg >= 1); //If average is above 1, considered high enough to be fun, so add true to list, else add false
                avgs.Add(avg);
            }
            double fun = (double)highavg.Count(x => x) / highavg.Count(); //Our "Fun" score is the percentage of high values in the list
            //Next calculate challenge metric, which desires rate at which items are found to be within some optimal range so that it is not too often or too rare
            double LocationToItemRatio = (double)world.GetLocationCount() / world.Items.Where(x => x.Importance == 2).Count();
            int    low  = (int)Math.Floor(LocationToItemRatio * .5);
            int    high = (int)Math.Ceiling(LocationToItemRatio * 1.5);

            RollingAvg = new Queue <int>();
            avgs       = new List <double>();
            List <bool> avginrange = new List <bool>();

            foreach (int num in input.BetweenMajorList)
            {
                if (RollingAvg.Count == 3) //Tighter rolling average of last 3 values
                {
                    RollingAvg.Dequeue();
                }
                RollingAvg.Enqueue(num);
                double avg = RollingAvg.Average();
                avginrange.Add(low <= avg && avg <= high); //If value is within range rather than too high or too low, add true to list to indicate it is within a good range
                avgs.Add(avg);
            }
            double challenge = (double)avginrange.Count(x => x) / avginrange.Count(); //Our "Challenge" score is the percentage of values in the list within desirable range
            //Next calculate satisfyingness metric based on how many locations are unlocked when an item is found
            double      LocationToItemRatioWithoutInitial = (double)(world.GetLocationCount() - input.InitialReachableCount) / world.Items.Where(x => x.Importance == 2).Count();
            int         satthreshold = (int)Math.Floor(LocationToItemRatioWithoutInitial); //Set threshold as number of not-immediately-accessible locations divided by number of major items
            List <bool> SatisfyingReachesThreshold = new List <bool>();

            foreach (int num in input.LocationsUnlockedPerMajorFound)
            {
                SatisfyingReachesThreshold.Add(num >= satthreshold);
            }
            double satisfyingness = (double)SatisfyingReachesThreshold.Count(x => x) / SatisfyingReachesThreshold.Count(); //Our "Satisfyingness" score is the percentage of values above the desired threshold
            //Finally calculate boredom by observing regions which were visited more often than is expected
            //First get a count of how many times each region was visited
            List <int> visitcounts = new List <int>();

            foreach (Region r in world.Regions)
            {
                visitcounts.Add(input.Traversed.Count(x => x.Name == r.Name));
            }
            //Calculate threshold with max number of times region should be visited being the number of traversals divided by number of regions
            double      TraversedToRegionRatio = (double)input.Traversed.Count() / world.Regions.Count();
            int         borethreshold          = (int)Math.Ceiling(TraversedToRegionRatio);
            List <bool> VisitsAboveThreshold   = new List <bool>();

            foreach (int num in visitcounts)
            {
                VisitsAboveThreshold.Add(num > borethreshold);                                          //Again as before, add list of bool when value is above threshold
            }
            double boredom = (double)VisitsAboveThreshold.Count(x => x) / VisitsAboveThreshold.Count(); //Our "Boredom" score is the percentage of values above the desired threshold
            //Add calculated stats to output. If a result is NaN (possible when not completable) save as -1
            InterestingnessOutput output = new InterestingnessOutput();

            output.bias           = biasinfo;
            output.fun            = double.IsNaN(fun) ? -1 : fun;
            output.challenge      = double.IsNaN(challenge) ? -1 : challenge;
            output.satisfyingness = double.IsNaN(satisfyingness) ? -1 : satisfyingness;
            output.boredom        = double.IsNaN(boredom) ? -1 : boredom;
            //Use stats to calculate final interestingness score
            //Each score is a double in the range [0, 1]
            //Multiply each score (or its 1 - score if low score is desirable) by its percentage share of the total
            double biasscore      = (1 - output.bias.biasvalue) * .2;
            double funscore       = output.fun * .2;
            double challengescore = output.challenge * .2;
            double satscore       = output.satisfyingness * .2;
            double borescore      = (1 - output.boredom) * .2;
            double intscore       = biasscore + funscore + challengescore + satscore + borescore;

            //If any components are NaN, consider interestingness as NaN as well
            if (double.IsNaN(fun) || double.IsNaN(challenge) || double.IsNaN(satisfyingness) || double.IsNaN(boredom))
            {
                output.interestingness = -1;
            }
            else
            {
                output.interestingness = intscore;
            }
            output.completable = input.Completable;
            return(output);
        }
Example #25
0
        //Calculate complexity of the base graph (Not considering items, only rules for location reachability)
        public TestComplexityOutput CalcWorldComplexity(WorldGraph world)
        {
            List <string> totalrules = new List <string>();

            /*
             * For each location, calculate a total rule
             * Total rule meaning it includes every possible path to get there plus the rule for the location itself
             * ex, 2 paths to location: Region A -> Region B -> Region C, Region A -> Region C
             * Location has Rule X
             * Then the total rule will equal:
             * ((A->B and B->C) or (A->C)) and Rule X
             */
            foreach (Region r in world.Regions)
            {
                //Must calculate every possible path (that doesn't go back on itself) from root to the region r
                List <List <Region> > paths = searcher.PathsToRegion(world, r);
                string regionstring         = "";
                if (paths.Count > 0) //If it equals 0, current region is root, do not need region string
                {
                    //Go through each path and calculate the rule for that path to construct an absolute rule for the region
                    for (int i = 0; i < paths.Count; i++)
                    {
                        List <Region> path       = paths[i];
                        string        pathstring = "";
                        for (int j = 0; j < path.Count - 1; j++) //Last region is dest, don't need to check thus paths.Count - 1
                        {
                            if (j > 0)
                            {
                                pathstring += " and "; // Every requirement on this path must be met, so use "and"
                            }
                            pathstring += "(" + path[j].Exits.First(x => x.ToRegionName == path[j + 1].Name).Requirements + ")";
                        }
                        if (i > 0)
                        {
                            regionstring += " or "; //The pathstrings are different options, so use "or"
                        }
                        regionstring += "(" + pathstring + ")";
                    }
                }
                //Now calculate the total rule for each location
                foreach (Location l in r.Locations)
                {
                    string totalrule = "";
                    if (string.IsNullOrEmpty(regionstring)) //For when region is root
                    {
                        totalrule = l.Requirements;
                    }
                    else
                    {
                        totalrule = "(" + regionstring + ") and " + l.Requirements; //Must meet at least one path requirement to reach the region and the location requirement
                    }
                    totalrule = parser.Simplify(totalrule.Replace("None", "true")); //Simplifies the boolean expression
                    totalrules.Add(totalrule);
                }
            }
            //We now have a list for the total rule of every location in the game
            //Calculate score for each rule and add them all to list
            List <double> scores = new List <double>();

            foreach (string rule in totalrules)
            {
                scores.Add(parser.CalcRuleScore(rule));
            }
            //Use list of scores to calculate final score and return
            return(ComplexityScoreCalculation(scores));
        }