/// <summary> /// Preps for running the maze navigation simulations by decoding the given maze/navigator combinations genomes. These /// are presumably combinations that were determined to be successful in solving the respective maze. /// </summary> /// <param name="navigationCombos">The combinations of mazes and navigators.</param> public void Initialize( IEnumerable <Tuple <MccexperimentMazeGenome, MccexperimentNavigatorGenome> > navigationCombos) { foreach (var navigationCombo in navigationCombos) { MazeGenome mazeGenome; NeatGenome navigatorGenome; // Deserialize the maze XML into a maze genome using (var xmlReader = XmlReader.Create(new StringReader(navigationCombo.Item1.GenomeXml))) { mazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xmlReader, _mazeGenomeFactory); } // Deserialize the navigator XML into a NEAT genome using (var xmlReader = XmlReader.Create(new StringReader(navigationCombo.Item2.GenomeXml))) { navigatorGenome = NeatGenomeXmlIO.ReadSingleGenomeFromRoot(xmlReader, false, _neatGenomeFactory); } // Decode to maze and navigator phenomes and add to the evaluation units list EvaluationUnits.Add(new MazeNavigatorEvaluationUnit(_mazeDecoder.Decode(mazeGenome), _agentDecoder.Decode(navigatorGenome), navigationCombo.Item1.GenomeId, navigationCombo.Item2.GenomeId)); } }
public void MutatedGenomeDecodeTest() { int scaleMultiplier = 16; // Mock up maze genome (just use null genome factory) MazeGenome mazeGenome = new MazeGenome(new MazeGenomeFactory(), 1, 1); MazeDecoder mazeDecoder = new MazeDecoder(20, 20, scaleMultiplier); MazeStructure mazeGrid = mazeDecoder.Decode(mazeGenome); DisplayMaze(mazeGrid.MazeArray); mazeGrid.ConvertGridArrayToWalls(mazeGrid.MazeArray); uint birthGeneration = 1; do { // Generate an offspring (perform mutation) mazeGenome.CreateOffspring(++birthGeneration); if (birthGeneration%100 == 0) { mazeGrid = mazeDecoder.Decode(mazeGenome); PrintBitmapMaze(mazeGrid.Walls, 20*scaleMultiplier, 20*scaleMultiplier, (int) birthGeneration); } } while (birthGeneration < 1000000); // Create the maze decoder mazeDecoder = new MazeDecoder(20, 20, scaleMultiplier); mazeGrid = mazeDecoder.Decode(mazeGenome); DisplayMaze(mazeGrid.MazeArray); mazeGrid.ConvertGridArrayToWalls(mazeGrid.MazeArray); //PrintBitmapMaze(mazeGrid.Walls, 20 * scaleMultiplier, 20 * scaleMultiplier); }
/// <summary> /// Converts the maze genome into a maze structure and prints it to a bitmap file. /// </summary> /// <param name="mazeGenome">The maze genome to convert and print.</param> /// <param name="mazeImageName">The name of the maze output file.</param> private static void PrintMazeToFile(MazeGenome mazeGenome, string mazeImageName) { // Read in the maze decode parameters int mazeScaleFactor = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeScaleFactor]); string mazeBitmapOutputDirectory = _executionConfiguration[ExecutionParameter.BitmapOutputBaseDirectory]; // Instantiate the maze genome decoder MazeDecoder mazeDecoder = new MazeDecoder(mazeScaleFactor); // Decode the maze to get a maze structure MazeStructure mazeStructure = mazeDecoder.Decode(mazeGenome); // Create pen and initialize bitmap canvas Pen blackPen = new Pen(Color.Black, 0.0001f); Bitmap mazeBitmap = new Bitmap(mazeStructure.ScaledMazeWidth + 1, mazeStructure.ScaledMazeHeight + 1); using (Graphics graphics = Graphics.FromImage(mazeBitmap)) { // Fill with white Rectangle imageSize = new Rectangle(0, 0, mazeStructure.ScaledMazeWidth + 1, mazeStructure.ScaledMazeHeight + 1); graphics.FillRectangle(Brushes.White, imageSize); // Draw start and end points graphics.FillEllipse(Brushes.Green, mazeStructure.StartLocation.X, mazeStructure.StartLocation.Y, 5, 5); graphics.FillEllipse(Brushes.Red, mazeStructure.TargetLocation.X, mazeStructure.TargetLocation.Y, 5, 5); // Draw all of the walls foreach (MazeStructureWall wall in mazeStructure.Walls) { // Convert line start/end points to Point objects from drawing namespace Point startPoint = new Point(wall.StartMazeStructurePoint.X, wall.StartMazeStructurePoint.Y); Point endPoint = new Point(wall.EndMazeStructurePoint.X, wall.EndMazeStructurePoint.Y); // Draw wall graphics.DrawLine(blackPen, startPoint, endPoint); } } // Save the bitmap image mazeBitmap.Save(Path.Combine(mazeBitmapOutputDirectory, mazeImageName)); }
public void HardCodedGenomeDecodeTest() { // Mock up maze genome (just use null genome factory) MazeGenome mazeGenome = new MazeGenome((MazeGenomeFactory) null, 1, 1); // Add some genes mazeGenome.GeneList.Add(new MazeGene(1, 0.6, 0.3, false)); mazeGenome.GeneList.Add(new MazeGene(2, 0.7, 0.4, false)); mazeGenome.GeneList.Add(new MazeGene(3, 0.3, 0.8, true)); mazeGenome.GeneList.Add(new MazeGene(4, 0.9, 0.2, false)); mazeGenome.GeneList.Add(new MazeGene(5, 0.5, 0.3, false)); mazeGenome.GeneList.Add(new MazeGene(6, 0.2, 0.5, false)); mazeGenome.GeneList.Add(new MazeGene(7, 0.4, 0.1, true)); mazeGenome.GeneList.Add(new MazeGene(8, 0.7, 0.8, true)); mazeGenome.GeneList.Add(new MazeGene(9, 0.3, 0.2, false)); // Create the maze decoder MazeDecoder mazeDecoder = new MazeDecoder(20, 20); MazeStructure mazeGrid = mazeDecoder.Decode(mazeGenome); //DisplayMaze(mazeGrid.MazeArray); }
private static void Main(string[] args) { // TODO: Refactor all of this to be more configurable - just hard-coding everything now to get results // Base path for maze bitmap output string mazeImageBase = @"F:\User Data\Jonathan\Documents\school\Jonathan\Graduate\PhD\Minimal Criteria Search\Analysis\Coevolution MCS\Images\Final Batch Mazes"; // These are the experiments for which we want to get mazes in the final population List<string> experimentNames = new List<string> { "Coevolution MCS with Maze Initialization 9", "Coevolution MCS with Maze Initialization 10", "Coevolution MCS with Maze Initialization 11", "Coevolution MCS with Maze Initialization 12", "Coevolution MCS with Maze Initialization 13", "Coevolution MCS with Maze Initialization 14", "Coevolution MCS with Maze Initialization 15", "Coevolution MCS with Maze Initialization 16" }; // Setup maze decoder with hard-coded height/width and scale multiplier MazeDecoder mazeDecoder = new MazeDecoder(10, 10, 32); // Create default maze genome factory MazeGenomeFactory mazeGenomeFactory = new MazeGenomeFactory(); foreach (string experimentName in experimentNames) { // Get the current experiment configuration ExperimentDictionary curExperiment = ExperimentDataHandler.LookupExperimentConfiguration(experimentName); // Get the total number of runs of the experiment int numRuns = ExperimentDataHandler.GetNumRuns(curExperiment.ExperimentDictionaryID); for (int runIdx = 1; runIdx <= numRuns; runIdx++) { // Get the total number of batches in the run int numBatches = ExperimentDataHandler.GetNumBatchesForRun(curExperiment.ExperimentDictionaryID, runIdx); // Get the maze population extant in the last batch of the run IList<string> mazePopulationGenomes = ExperimentDataHandler.GetMazeGenomeXml(curExperiment.ExperimentDictionaryID, runIdx, numBatches); // Build output directory string imageOutputDirectory = Path.Combine(mazeImageBase, string.Format("ExperimentName_{0}", curExperiment.ExperimentName), string.Format("Run_{0}", runIdx)); if (Directory.Exists(imageOutputDirectory) == false) { Directory.CreateDirectory(imageOutputDirectory); } // Decode all genomes and render image of structure foreach (string mazeGenomeStr in mazePopulationGenomes) { MazeGenome curMazeGenome; // Unmarshall to maze genome object using (XmlReader xmlReader = XmlReader.Create(new StringReader(mazeGenomeStr))) { curMazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xmlReader, mazeGenomeFactory); } // Generate maze bitmap image ImageGenerationHandler.GenerateMazeStructureImage( Path.Combine(imageOutputDirectory, string.Format("ExperimentID_{0}_Run_{1}_MazeID_{2}.bmp", curExperiment.ExperimentDictionaryID, runIdx, curMazeGenome.Id)), mazeDecoder.Decode(curMazeGenome)); } } } }
/// <summary> /// Creates the evolution algorithm container using the given factories and genome lists. /// </summary> /// <param name="genomeFactory1">The agent genome factory.</param> /// <param name="genomeFactory2">The maze genome factory.</param> /// <param name="genomeList1">The agent genome list.</param> /// <param name="genomeList2">The maze genome list.</param> /// <returns>The instantiated coevolution algorithm container.</returns> public override ICoevolutionAlgorithmContainer<NeatGenome, MazeGenome> CreateCoevolutionAlgorithmContainer( IGenomeFactory<NeatGenome> genomeFactory1, IGenomeFactory<MazeGenome> genomeFactory2, List<NeatGenome> genomeList1, List<MazeGenome> genomeList2) { List<NeatGenome> seedAgentPopulation = new List<NeatGenome>(); // Compute the maze max complexity ((MazeGenomeFactory) genomeFactory2).MaxComplexity = MazeUtils.DetermineMaxPartitions(_mazeHeight, _mazeWidth, 200); // Create maze decoder to decode initialization mazes MazeDecoder mazeDecoder = new MazeDecoder(_mazeHeight, _mazeWidth, _mazeScaleMultiplier); // Loop through every maze and evolve the requisite number of viable genomes that solve it for (int idx = 0; idx < genomeList2.Count; idx++) { Console.WriteLine(@"Evolving viable agents for maze population index {0} and maze ID {1}", idx, genomeList2[idx].Id); // Evolve the number of agents required to meet the success MC for the current maze List<NeatGenome> viableMazeAgents = EvolveViableAgents(genomeFactory1, genomeList1.ToList(), mazeDecoder.Decode(genomeList2[idx])); // Add the viable agent genomes who solve the current maze (but avoid adding duplicates, as identified by the genome ID) // Note that it's fine to have multiple mazes solved by the same agent, so in this case, we'll leave the agent // in the pool of seed agent genomes foreach ( NeatGenome viableMazeAgent in viableMazeAgents.Where( viableMazeAgent => seedAgentPopulation.Select(sap => sap.Id).Contains(viableMazeAgent.Id) == false)) { seedAgentPopulation.Add(viableMazeAgent); } } // If we still lack the genomes to fill out agent specie count while still satisfying the maze MC, // iteratively pick a random maze and evolve agents on that maze until we reach the requisite number while (seedAgentPopulation.ToList().Count < _numAgentSuccessCriteria*AgentNumSpecies) { FastRandom rndMazePicker = new FastRandom(); // Pick a random maze on which to evolve agent(s) MazeGenome mazeGenome = genomeList2[rndMazePicker.Next(genomeList2.Count - 1)]; Console.WriteLine( @"Continuing viable agent evolution on maze {0}, with {1} of {2} required agents in place", mazeGenome.Id, seedAgentPopulation.Count, (_numAgentSuccessCriteria*AgentNumSpecies)); // Evolve the number of agents required to meet the success MC for the maze List<NeatGenome> viableMazeAgents = EvolveViableAgents(genomeFactory1, genomeList1.ToList(), mazeDecoder.Decode(mazeGenome)); // Iterate through each viable agent and remove them if they've already solved a maze or add them to the list // of viable agents if they have not foreach (NeatGenome viableMazeAgent in viableMazeAgents) { // If they agent has already solved maze and is in the list of viable agents, remove that agent // from the pool of seed genomes (this is done because here, we're interested in getting unique // agents and want to avoid an endless loop wherein the same viable agents are returned) if (seedAgentPopulation.Select(sap => sap.Id).Contains(viableMazeAgent.Id)) { genomeList1.Remove(viableMazeAgent); } // Otherwise, add that agent to the list of viable agents else { seedAgentPopulation.Add(viableMazeAgent); } } } // Set dummy fitness so that seed maze(s) will be marked as evaluated foreach (MazeGenome mazeGenome in genomeList2) { mazeGenome.EvaluationInfo.SetFitness(0); } // Reset primary NEAT genome parameters on agent genome factory ((NeatGenomeFactory) genomeFactory1).ResetNeatGenomeParameters(NeatGenomeParameters); // Create the NEAT (i.e. navigator) queueing evolution algorithm AbstractEvolutionAlgorithm<NeatGenome> neatEvolutionAlgorithm = new MultiQueueNeatEvolutionAlgorithm<NeatGenome>( new NeatEvolutionAlgorithmParameters { SpecieCount = AgentNumSpecies, MaxSpecieSize = AgentDefaultPopulationSize/AgentNumSpecies }, new ParallelKMeansClusteringStrategy<NeatGenome>(new ManhattanDistanceMetric(1.0, 0.0, 10.0), ParallelOptions), null, NavigatorBatchSize, RunPhase.Primary, _navigatorEvolutionDataLogger, _navigatorLogFieldEnableMap, _navigatorPopulationGenomesDataLogger, _populationLoggingBatchInterval); // Create the maze queueing evolution algorithm AbstractEvolutionAlgorithm<MazeGenome> mazeEvolutionAlgorithm = new MultiQueueNeatEvolutionAlgorithm<MazeGenome>( new NeatEvolutionAlgorithmParameters { SpecieCount = MazeNumSpecies, MaxSpecieSize = MazeDefaultPopulationSize/MazeNumSpecies }, new ParallelKMeansClusteringStrategy<MazeGenome>(new ManhattanDistanceMetric(1.0, 0.0, 10.0), ParallelOptions), null, MazeBatchSize, RunPhase.Primary, _mazeEvolutionDataLogger, _mazeLogFieldEnableMap, _mazePopulationGenomesDataLogger, _populationLoggingBatchInterval); // Create the maze phenome evaluator IPhenomeEvaluator<MazeStructure, BehaviorInfo> mazeEvaluator = new MazeEnvironmentMCSEvaluator( _maxTimesteps, _minSuccessDistance, BehaviorCharacterizationFactory, _numAgentSuccessCriteria, 0); // Create navigator phenome evaluator IPhenomeEvaluator<IBlackBox, BehaviorInfo> navigatorEvaluator = new MazeNavigatorMCSEvaluator( _maxTimesteps, _minSuccessDistance, BehaviorCharacterizationFactory, _numMazeSuccessCriteria); // Create maze genome decoder IGenomeDecoder<MazeGenome, MazeStructure> mazeGenomeDecoder = new MazeDecoder(_mazeHeight, _mazeWidth, _mazeScaleMultiplier); // Create navigator genome decoder IGenomeDecoder<NeatGenome, IBlackBox> navigatorGenomeDecoder = new NeatGenomeDecoder(ActivationScheme); // Create the maze genome evaluator IGenomeEvaluator<MazeGenome> mazeFitnessEvaluator = new ParallelGenomeBehaviorEvaluator<MazeGenome, MazeStructure>(mazeGenomeDecoder, mazeEvaluator, SelectionType.Queueing, SearchType.MinimalCriteriaSearch, ParallelOptions); // Create navigator genome evaluator IGenomeEvaluator<NeatGenome> navigatorFitnessEvaluator = new ParallelGenomeBehaviorEvaluator<NeatGenome, IBlackBox>(navigatorGenomeDecoder, navigatorEvaluator, SelectionType.Queueing, SearchType.MinimalCriteriaSearch, ParallelOptions); // Create the coevolution container ICoevolutionAlgorithmContainer<NeatGenome, MazeGenome> coevolutionAlgorithmContainer = new CoevolutionAlgorithmContainer<NeatGenome, MazeGenome>(neatEvolutionAlgorithm, mazeEvolutionAlgorithm); // Initialize the container and component algorithms coevolutionAlgorithmContainer.Initialize(navigatorFitnessEvaluator, genomeFactory1, seedAgentPopulation, AgentDefaultPopulationSize, mazeFitnessEvaluator, genomeFactory2, genomeList2, MazeDefaultPopulationSize, MaxGenerations, MaxEvaluations); return coevolutionAlgorithmContainer; }
/// <summary> /// Evolves the requisite number of agents to meet the MC for each maze in the initial population. This is performed /// using a non-MCC based algorithm (such as fitness or novelty search). /// </summary> /// <param name="agentPopulation">The agents (NEAT genomes) in the initial, randomly generated population.</param> /// <param name="mazePopulation">The mazes in the initial population (either randomly generated or read from a file).</param> /// <param name="agentGenomeFactory">The factory class for producing agent (NEAT) genomes.</param> /// <param name="numAgents">The number of seed agents to evolve.</param> /// <returns> /// The list of viable agents, each of whom is able to solve at least one of the initial mazes and, in totality, /// meet the MC for solving each of the mazes. /// </returns> protected List <NeatGenome> EvolveSeedAgents(List <NeatGenome> agentPopulation, List <MazeGenome> mazePopulation, IGenomeFactory <NeatGenome> agentGenomeFactory, int numAgents) { var seedAgentPopulation = new List <NeatGenome>(); // Create maze decoder to decode initialization mazes var mazeDecoder = new MazeDecoder(MazeScaleMultiplier); // Loop through every maze and evolve the requisite number of viable genomes that solve it for (var idx = 0; idx < mazePopulation.Count; idx++) { Console.WriteLine(@"Evolving viable agents for maze population index {0} and maze ID {1}", idx, mazePopulation[idx].Id); // Evolve the number of agents required to meet the success MC for the current maze var viableMazeAgents = _mazeNavigationInitializer.EvolveViableAgents(agentGenomeFactory, agentPopulation.ToList(), mazeDecoder.Decode(mazePopulation[idx]), _maxInitializationEvaluations, ActivationScheme, ParallelOptions); // Add the viable agent genomes who solve the current maze (but avoid adding duplicates, as identified by the genome ID) // Note that it's fine to have multiple mazes solved by the same agent, so in this case, we'll leave the agent // in the pool of seed agent genomes foreach ( var viableMazeAgent in viableMazeAgents.Where( viableMazeAgent => seedAgentPopulation.Select(sap => sap.Id).Contains(viableMazeAgent.Id) == false)) { seedAgentPopulation.Add(viableMazeAgent); } } // If we still lack the genomes to fill out seed agent gnome count while still satisfying the maze MC, // iteratively pick a random maze and evolve agents on that maze until we reach the requisite number while (seedAgentPopulation.ToList().Count < numAgents) { var rndMazePicker = RandomDefaults.CreateRandomSource(); // Pick a random maze on which to evolve agent(s) var mazeGenome = mazePopulation[rndMazePicker.Next(mazePopulation.Count - 1)]; Console.WriteLine( @"Continuing viable agent evolution on maze {0}, with {1} of {2} required agents in place", mazeGenome.Id, seedAgentPopulation.Count, numAgents); // Evolve the number of agents required to meet the success MC for the maze var viableMazeAgents = _mazeNavigationInitializer.EvolveViableAgents(agentGenomeFactory, agentPopulation.ToList(), mazeDecoder.Decode(mazeGenome), _maxInitializationEvaluations, ActivationScheme, ParallelOptions); // Iterate through each viable agent and remove them if they've already solved a maze or add them to the list // of viable agents if they have not foreach (var viableMazeAgent in viableMazeAgents) { // If they agent has already solved maze and is in the list of viable agents, remove that agent // from the pool of seed genomes (this is done because here, we're interested in getting unique // agents and want to avoid an endless loop wherein the same viable agents are returned) if (seedAgentPopulation.Select(sap => sap.Id).Contains(viableMazeAgent.Id)) { agentPopulation.Remove(viableMazeAgent); } // Otherwise, add that agent to the list of viable agents else { seedAgentPopulation.Add(viableMazeAgent); } } } return(seedAgentPopulation); }
/// <summary> /// Handles per-batch comparison of coevolution-generated mazes on a reference algorithm (e.g. novelty search). /// </summary> /// <param name="batchesWithGenomeData">List of the batches that contain maze genome data.</param> /// <param name="curCoevoExperimentId">Identifier of the coevolution experiment.</param> /// <param name="curCoevoExperimentName">Name of the coevolution experiment.</param> /// <param name="curRun">The current run.</param> /// <param name="numRuns">The total number of runs in the experiment.</param> /// <param name="mazeDecoder">Reference to the maze genome decoder.</param> /// <param name="noveltySearchExperimentConfig">The experiment configuration for the reference (novelty search) experiment.</param> private static void RunPerBatchComparativeAnalysis(IList<int> batchesWithGenomeData, int curCoevoExperimentId, string curCoevoExperimentName, int curRun, int numRuns, MazeDecoder mazeDecoder, ExperimentDictionary noveltySearchExperimentConfig) { // Declare list to hold maze IDs that have already been evaluated using the comparison algorithm List<int> evaluatedMazeGenomeIds = new List<int>(); // Get the total number of initialization evaluations for the current run int coEvoInitEvaluations = ExperimentDataHandler.GetInitializationEvaluationsForRun(curCoevoExperimentId, curRun); // Iterate through each batch and run comparative algorithm on each maze foreach (int curBatch in batchesWithGenomeData) { _executionLogger.Info(string.Format("Executing comparative analysis for batch [{0}] of run [{1}/{2}]", curBatch, curRun, numRuns)); // Get list of all extant maze genomes for this batch IList<string> mazeGenomeXml = ExperimentDataHandler.GetMazeGenomeXml(curCoevoExperimentId, curRun, curBatch); foreach (string genomeXml in mazeGenomeXml) { MazeStructure curMazeStructure = null; MazeGenome curMazeGenome = null; // Create a new (mostly dummy) maze genome factory MazeGenomeFactory curMazeGenomeFactory = new MazeGenomeFactory(); // Convert each genome string to an equivalent genome object using (XmlReader xr = XmlReader.Create(new StringReader(genomeXml))) { curMazeGenome = MazeGenomeXmlIO.ReadSingleGenomeFromRoot(xr, curMazeGenomeFactory); } // If the maze has already been processed, continue on to the next one // (also exclude the initialization maze as a special case) if (evaluatedMazeGenomeIds.Contains((int) curMazeGenome.BirthGeneration) || curMazeGenome.BirthGeneration == 0) { continue; } // Get the total number of evaluations required to solve this maze // (this amounts to the total number of evaluations from both queues at the time of maze birth) int coEvoTotalEvaluations = coEvoInitEvaluations + ExperimentDataHandler.GetTotalPrimaryEvaluationsAtBatch( curCoevoExperimentId, curRun, (int) curMazeGenome.BirthGeneration); // Decode the maze structure curMazeStructure = mazeDecoder.Decode(curMazeGenome); // Instantiate new novelty search experiment for the current experiment/batch/maze NoveltySearchRunner nsRunner = new NoveltySearchRunner(noveltySearchExperimentConfig, curCoevoExperimentName, curMazeStructure, curRun, numRuns, _executionLogger); _executionLogger.Info(string.Format( "Executing novelty search on maze [{0}] with birth batch [{1}]", curMazeGenome.Id, curMazeGenome.BirthGeneration)); // Run novelty search on that maze until the maze is solved or the max evals have been reached INeatEvolutionAlgorithm<NeatGenome> eaFinalState = nsRunner.RunNoveltySearch(); // Write comparison results to output file ExperimentDataHandler.WriteNoveltySearchComparisonResults(curCoevoExperimentId, noveltySearchExperimentConfig.ExperimentDictionaryID, curRun, curBatch, (int) curMazeGenome.Id, (int) curMazeGenome.BirthGeneration, (int) eaFinalState.Statistics._minComplexity, (int) eaFinalState.Statistics._maxComplexity, eaFinalState.Statistics._meanComplexity, coEvoTotalEvaluations, (int) eaFinalState.CurrentEvaluations, !((int) eaFinalState.CurrentEvaluations >= noveltySearchExperimentConfig.MaxEvaluations), false); // Add maze genome ID to the list of processed mazes to its not considered for evaluation again evaluatedMazeGenomeIds.Add((int) curMazeGenome.Id); } } }
/// <summary> /// Converts the maze genome into a maze structure and prints it to a bitmap file. /// </summary> /// <param name="mazeGenome">The maze genome to convert and print.</param> /// <param name="mazeImageName">The name of the maze output file.</param> private static void PrintMazeToFile(MazeGenome mazeGenome, string mazeImageName) { // Read in the maze decode parameters int mazeHeight = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeHeight]); int mazeWidth = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeWidth]); int mazeScaleFactor = Int32.Parse(_executionConfiguration[ExecutionParameter.MazeScaleFactor]); string mazeBitmapOutputDirectory = _executionConfiguration[ExecutionParameter.BitmapOutputBaseDirectory]; // Instantiate the maze genome decoder MazeDecoder mazeDecoder = new MazeDecoder(mazeHeight, mazeWidth, mazeScaleFactor); // Decode the maze to get a maze structure MazeStructure mazeStructure = mazeDecoder.Decode(mazeGenome); // Create pen and initialize bitmap canvas Pen blackPen = new Pen(Color.Black, 0.0001f); Bitmap mazeBitmap = new Bitmap(mazeStructure.ScaledMazeWidth + 1, mazeStructure.ScaledMazeHeight + 1); using (Graphics graphics = Graphics.FromImage(mazeBitmap)) { // Fill with white Rectangle imageSize = new Rectangle(0, 0, mazeStructure.ScaledMazeWidth + 1, mazeStructure.ScaledMazeHeight + 1); graphics.FillRectangle(Brushes.White, imageSize); // Draw start and end points graphics.FillEllipse(Brushes.Green, mazeStructure.StartLocation.X, mazeStructure.StartLocation.Y, 5, 5); graphics.FillEllipse(Brushes.Red, mazeStructure.TargetLocation.X, mazeStructure.TargetLocation.Y, 5, 5); // Draw all of the walls foreach (MazeStructureWall wall in mazeStructure.Walls) { // Convert line start/end points to Point objects from drawing namespace Point startPoint = new Point(wall.StartMazeStructurePoint.X, wall.StartMazeStructurePoint.Y); Point endPoint = new Point(wall.EndMazeStructurePoint.X, wall.EndMazeStructurePoint.Y); // Draw wall graphics.DrawLine(blackPen, startPoint, endPoint); } } // Save the bitmap image mazeBitmap.Save(Path.Combine(mazeBitmapOutputDirectory, mazeImageName)); }