Skip to content

JoshuaClouse/MarioLevelGenerator

Repository files navigation

P6 WRITE UP

What you changed from the template and why, especially related to your selection strategies, fitness functions, crossover and mutation operators, etc.

Something about each of your two favorite levels: Why do you like them? How many generations did it take and how many seconds to generate these levels?
Remember that a good writeup can make up for a lot of problems in the actual submission.

Selection Strategy
The two selection strategies we implemented are random selection and elitist selection. Random selection simply finds two random genomes from the population and generates children. Our elitist approach runs a sort over the 
list of genomes in population. We then take the highest fitness genome and have that breed with all the other genomes which gives us len(population)-1. Then to get the last child genome we take the 2nd best fitness genome and 
have that breed with the 3rd highest rated genome. Doing elitist gives us more control over how we want our children to look like, but it also means that it is highly reliant on the numbers we use in our metrics and in our
coefficients. We realized that our elitist strategy performed worse than our random strategy because our metrics for the "most fit" grid did not align with our definition of fun and beatable and our mutate function was more compatible with our random strategy.

Fitness Function:
The fitness function calls calculate_fitness() which essentially just sums up the fitness of all the individuals. Individual fitness is calculated by taking the metrics and multiplying them by the coefficients defined
by us. When doing random selection in determining which parents should breed fitness is never used, however when doing our elitist choice, we evaluate these fitnesses to choose the highest rated parent and then we have that
parent breed with all the other parents.

Crossover:
Our generate children returns a child genome with parent A for even numbered columns and parent B for odd numbered columns. 

Mutation Operators:
The bulk of mutation is to slowly clean up the grid over each generation iteration. We do so by, locating a block, coin, or enemy and giving a high probability of it surviving the mutation. This runs into a problem where we will inevitably end up with an empty map. To counter this issue we construct pipes that are at the height of Mario's jump height, have a small probability of generating an enemy anywhere on the map (except for the ground and first column of the map), and constructing platforms that grow horizontally. This makes sure that some elements of the map persists over generations and build a more vertical playthrough for players.


Individual_DE mutate():
This function first gets a random number to check to see if we are even going to attempt to do a mutation. If we are then we pick which one of the individuals in the genome we want to look at by again picking a random number, in
this case the indice of one of the individuals within the passed in genome. We then store the values of randomly chosen individual in variables de, de_type, x, and y. The de_type is the type of block that is located at position x, y, identified by a string value. Then there is a fourth option available in the brick block and the question block which determines whether there should be a power up or the block should be breakable or not(de[3]). A third random number is created and given to the variable choice. Based on this choice variable we can decide which conditional to enter. Each conditional will offset the x and y by a certain amount, or change the boolean value within de[3]. A new individual is then created with the new mutated values of x, y, and de[3s] and then returned.


Individual_DE generate_children()
The functionality of generate_children is to perform a crossover between two genomes. Individual_DE finds a random spot in genome A and B to split and then combine. The new genome A contains the first half of the split genome A and the latter split of genome B. The new genome B contains the latter split of genome A and the first split of genome B. Returned is an Individual DE generated by the new genomes A and B that have been mutated. 

OUR FAVORITE LEVEL
Our level is the result of 12 generations using random selection. On average the generation time took 21s. We wanted a map that required vertical scaling, precise hops, and scattered coins for added distraction.



About

Genetic algorithm mario level creator

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published