Wednesday, May 20, 2015

Programming a programming computer game – .Net run time type creator

A while ago me and a friend had an idea for a computer game. You would control a collection of bacteria all which needed to feed and would die of old age given enough time. They could also reproduce in order of keep their population going and fight enemy bacteria population controlled by an other player. The aim of the game was from your bacteria to out compete the other players bacteria on the map. So far so unoriginal, our new idea was that rather than controlling the creatures through say using a mouse and keyboard to give them orders you would instead controls them by writing the code for how they behaved. It would be a real time competitive programming game.

The game interface would be a map with a text panel on the right where the user would enter code that the creatures would execute to make their decisions. There were commands for where to move, what to eat, when to breed, etc. This was also a really nice space from which to play with algorithms like neural nets, evolutionary algorithms, clustering, A*, etc. We played around with it a bit and had a fair amount of fun, but we eventually realized that even for us who had built it, the game was too complicated for anyone to actually play. At least not in real time. So we abandoned it as a fun experiment.

But I recently saw this post on stack overflow that reminded me of that game. So I thought I would share some of the code for how to do in application code compilation in .Net. Hopefully it will be of use to some people and maybe even if I get enough interest I may try and clean up the rest of the code and release it as an open source project. Because despite being painfully complicated, when it did work it was fun, at least for uber nerds like us.


RunTimeTypeCreator

Here is the one and only method in the lib method:

 public static T CreateType<T>(string source,   
                          IEnumerable<string> assemblies,   
                          out List compilationErrors)   
                             where T : class  
It will attempt to create an instance of the type T from the source passed in. The source will be compiled with references to all the assemblies in the assemblies parameter. So for example you could do this with it.

 namespace RunTimeTypeCreator.Tests   
 {   
    public interface ITestType   
    {   
      bool Success { get; }   
    }   
    public class RunTimeTypeCreatorTests   
    {   
       public static bool TestVariable = false;   
       public void Example()   
       {   
          const string source = @"   
 using RunTimeTypeCreator.Tests;   
 public class TestTypeClass : RunTimeTypeCreatorTests.ITestType   
 {   
    public bool Success { get { return RunTimeTypeCreatorTests.TestVariable; } }   
 }";   
          List<string> compilationErrors;       
          var type = RunTimeTypeCreator.CreateType<ITestType>(source,   
                     new[] { "RunTimeTypeCreator.Tests.dll" }, //the name of this assembly  
                     out compilationErrors);   
          TestVariable = false;   
          //will print true   
          Console.WriteLine(type.Success)   
          //will print false   
          TestVariable = true;   
          Console.WriteLine(type.Success)   
       }   
    }   
 }   

Which is kind of cool I think. Here's a quick run through of how it works, this just shows the code minus bits of validation and error reporting, so if you want the full thing I would recommend getting it from github

 var csc = new CSharpCodeProvider();   
   
 var parameters = new CompilerParameters   
     {   
      //we don't want a physical executable in this case   
      GenerateExecutable = false,   
      //this is what allows it to access variables in our domain   
      GenerateInMemory = true   
     };   
   
 //add all the assmeblies we care about   
 foreach (var assembly in assemblies)   
     parameters.ReferencedAssemblies.Add(assembly);   
   
 //compile away, will load the class into memory   
 var result = csc.CompileAssemblyFromSource(parameters, source);   
   
 //we compiled succesfully so now just use reflection to get the type we want   
     var types = result.CompiledAssembly.GetTypes()   
              .Where(x => typeof(T).IsAssignableFrom(x)).ToList();    
   
 //create the type and return   
 return (T)Activator.CreateInstance(types.First());  

I also had some other code around validating what the user was doing, Making sure they weren't trying to access the file system, open ports or creating memory leaks/recursive loops. I'll try and clean this up and post it at a future date.

Full code is available here on github. 

1 comment:

  1. A while ago me and a friend had an idea for a computer game. You would control a collection of bacteria all which needed to feed and would die of old age given enough time bus driving simulator

    ReplyDelete