Archives

Archives / 2008 / January
  • Lack of Coverage Tools for .NET

    For the longest time, I've been using NCover for coverage. It was free, gave good results and with NCoverExplorer and CI integration it was the perfect tool. Easy to use, easy to setup, and worth the typical hassle of new tools (setup, learning, configuration, etc.)

    NCover has since gone commercial and the old versions won't run properly against 3.0/3.5 code. I'm ditching NCover support in TreeSurgeon because of this. TS nees to be friction free and something anyone can just grab and run and not have to deal with buying or obtaining licenses for products the generated code depends on. I looked at Clover.NET as an alternative (last time I checked it was free?) but it's $600 just for an "academic" version.

    So what's with coverage and .NET these days? Are these the only options? Have all the open source/free tools gone the way of the Dodo for coverage and .NET 3.0 projects? My quick Google checks this freezing morning (it was minus 38 as I drove in this morning, that's -36 in Fahrenheit kids) don't show anything.

    To be friction-free a code coverage tool needs to be:

    • Command line driven (have to be able to integrate into continuous integration process)
    • Xml report output (again, CI ready)
    • Usable with any unit testing framework
    • Support 3.0/3.5 .NET framework
    • Somewhat stable (i.e. non-alpha/beta versions)

    Nice to haves:

    • Free (or relatively cheap)
    • HTML or Text output
    • Ability to set thresholds and get failures back (for example, break the build if the coverage falls below 80%)

    Looking for some feedback on your experience here. Thanks!

  • Will the real Micropolis please stand up?

    image This morning I stumbled across a project on SourceForge named Micropolis. Of course this got my attention and after realizing there was nothing there (no code, no web pages, nothing) I fired a note off to Don to see what was up? I knew his distaste of SourceForge and there was talk of setting up a Google code repository. Don visited the SourceForge site and left a nice happy-happy-joy-joy note for the to-be-named-at-a-later-date owner:

    Would the person who started this Micropolis project on sourceforge please identify themselves and contact me? 

    I'm Don Hopkins, dhopkins@DonHopkins.com, who developed SimCity for Unix and Micropolis for the OLPC. 
    I did not start this sourceforge project myself, and I don't know who did.  
    This is not the official Micropolis project repository.

    I strongly want to avoid using sourceforge for hosting the Micropolis project, because I dislike sourceforge's baroque user interface and slow response time, and I absolutely do not want to use CVS.

    Sourceforge requires far too many clicks and waits to simply download a file, and it takes far too long for all the heavy weight ads and pointlessly complex php, html and javascript pages to load.  
    There is never any reason for a web site to say "please wait for a while until your download begins, and if eventually nothing ever happens, then click this link just in case." I usually end up downloading two copies because it takes so long for the delayed download to start that I click the emergency link, and eventually a while later the other "automatic" one starts as well.

    I can't understand why web sites like sourceforge use this flakey "please wait" user interface for downloading, instead of simply presenting a link that you can click to download instantly. Sourceforge's bizarre approach to making what should be a simple download be so annoying and "automatic" never saves any time. The computer should never ask the user to "please wait for a while until your download begins" for no good reason, and it's ridiculous to have a javascript count down timer in the loop just to slow everything down, with no way to cancel it if you click the emergency download link. That is one of the most annoying things about sourceforge! My impression is that showing lots of ads is more important to Sourceforge than making it easy for developers to host and download code. 

    I would much rather use google code to host the project in subversion, so I have set up the official Micropolis project there: 
    http://code.google.com/p/micropolis/

    -Don Hopkins

    I didn't have heart to tell Don that SF does support Subversion, the owner of the Micropolis project there just didn't set it up that way. However I agree with him on all other points. Too many clicks to get simple stuff. CodePlex suffers from this as well. You have to click and accept a license agreement for *every download* you make. Overkill IMHO.

    In any case, there it is. The new "official" Micropolis code repository up on Google code, where it should be. This latest version includes changes to the initial release in the C++/Python project to enable callbacks into the Python code. This will enable the UI to respond to the engine rather than the other way around. My next blog post covers using this in extensive detail which should be done sometime this weekend.

    Have fun!

  • My XBox 360 is blogging, and I didn't even know about it...

    skynet-terminator-727080

    Here's a twist on that old adage that the machines are taking over the world.

    In a freaky SkyNet like fashion, I *discovered* that my XBox 360 is blogging. You heard me right. My game console is blogging, and it's not me writing the blog.

    Here's my XBox 360's blog in all it's glory.

    I did not sign up or create this (at least I don't remember doing it). I simply turn my machine on from time to time and get my butt kicked by 15 year old pre-pubescent's who simply rock at Gears of War.

    Somehow, somewhere, for some reason my game console just started blogging one day. And it does it every day.

    Some of the oddly weirder moments in my Xbox 360's posts:

    image

    And:

    image

    And:

    image

    What worries me now is the Wii and PlayStation 3. With the XBox (well, he's been blogging for a good year or so now) what are *they* going to do?

    Bil needs to take a drink and lie down now...

  • Micropolis UI Sneak-a-Peek

    clip_image002

    As you can tell, I really suck at running a city. By the year 2023 nobody likes me, power blackouts are everywhere and pollution has consumed the city creating a race of carbon dioxide breathing mutants. The UI is Python using Glade.

    Yes, the numbers are real and being fed from the Micropolis game engine.

    No, source is not available yet as we're still working building it out (and making it pretty, oh so pretty).

  • ALT.NET, Round 2. Redmond, Washington. April 18-20. Be there.

    Title says all. We're pleased to announce the 2nd major ALT.NET Open Spaces conference. This time we're in Redmond at the DigiPen Institute of Technology. It's a 5 minute drive from the Microsoft Campus and we're looking to have some softies come down for the fun.

    We start up April 18th (Friday) with the similar Open Spaces approach that was quite successful in Austin, getting together a list of topics to discuss and hammer out over the weekend. The 19th is for sessions, informal talks and whatever code mashups may come out of it. Finally we wind down on Sunday with maybe a closing session or discussion.

    We've bumped up the number of first-come, first-served attendees from 120 to 150 this time. When registration opens up (sometime in early February) you'll have a chance at grabbing one of the 150 spots. Just get your name on the list. That's all. No mess. No fuss. And best of all, no cost!

    Yes, the event is free again (you'll have to provide your way to the conference and any hotel bills you might incur along the way, whether it be by plane, train, or automobile).

    The main website is up and running here. It's very minimalistic and not much to see there right now. Of course, that will change...

    So mark it in your calendars, get your traveling boots on, and make your way to Washington in April. It'll be a blast and you know it.

  • Building A City - The Series

    Building A City - The Series

    As I spend more time with the SimCity Micropolis source code I'm digging the Python interface, trying to get used to Glade and GTK all over again (I haven't used it for years) and trying to build something useful from the excellent core C++ engine that Don Hopkins has provided us.

    Out of this passion I'm starting to build several posts around exploring the code. So here's the plan for the series with links to the various entries I'll complete over the next while.

  • Building A City - Part III

    image

    Now you've got a runtime distribution running of the Micropolis demo. Doesn't do much does it? Sure you can spin around and navigate throughout the city. Perhaps you've changed the name of the city to load and checked out the other layouts. Otherwise, it's pretty bland and boring. Let's spend a little time building a new Python script to exercise two parts of the engine, terraforming and file access.

    Catching Up

    First let's catch up on what we need to get going here. To compile, please read Part I to get the required software installed and the C++ code compiled on Windows. Once you've done that, check out Part II where we get the additional files needed for runtime and launching the Python code. You can grab a binary release of the files if you don't want to build your own from here (note: You'll still need to install Python and the extensions listed in Part II to run the simulator).

    Codebase Reflections

    Just a side note about the codebase that I wanted to point out in order to try to clear up some confusion. The micropolis-activity-source package is the TCL/Tk version for x86 Linux (there's also a compiled version available here). This is the updated X11 version Don wrote and ported to use TCL/Tk for it's windowing system. This comes in C source form and can be compiled (with TCL/Tk) on a Linux system.

    The C source was then cleaned up and "ported" to non-managed C++ and reborn into the MicropolisCore package, the focus of this series of blog entries. The code can be compiled on Linux but there's also Visual Studio 2005 project files for building on Windows. This version is ported from the original C code into a C++ class called Micropolis. That class is then processed by SWIG (as part of the Visual Studio compile) to generate export libraries and wrappers so the routines can be called by Python.

    The "new" C++ code isn't complete but it is arguably better than the C code. The Micropolis project is fully devoid of any UI or Windowing libraries of any kind. At some point that means we can make the Micropolis project testable with unit tests (yeah, that'll come later). However it also means the new project is not just something you can plug in and run Micropolis as a full game on Windows. The TCL/Tk version is fully functional, this project isn't (yet). There are stubs for the calls and when you dig into the code you'll find that none of the routines that display screens or allow you to place tiles works yet. This is all coming so we'll grow into it.

    Why Python? In lieu of TCL/Tk it's a godsend. At least it's a real object oriented language (even if it is a scripting language) and using SWIG allows you to expose C or C++ methods and classes to Python, which is what we're working with here. So why not take the original C code and run SWIG against it? Because the code is tied to the GUI toolkit and trying to get all that running on Windows is an exercise left for the bored. Sure, the Micropolis-mega class isn't the way I would have done it but the code is out there and able for blokes like me to break apart and make it, let's say, more testable and extensible. Stay tuned on that front.

    More Tools

    Yes, even more tools and downloads are needed. As we're going to be working (mostly) in Python we need a half decent editor. Notepad++ is fine for editing files, but you want some kind of debugging capabilities and perhaps some syntax checking or intellisense. I have a low tolerance for patience when it comes to tools. If I can't get something to work or figure out a tool within a few minutes I move on. It might be cruel but software has to be intuitive and make sense, just like a codebase. You should be able to work your way around a good codebase or a new tool without scratching your head saying "Huh?".

    After combing the net I found a few reviews of Python IDEs. The pickings are not great but I settled on taking a look at Komodo, Wing IDE, and PyScripter.

    Komodo was a bit of a mess. There was a free editor-only version which I tried, but gave up instantly with it. I switched gears to the professional version which I thought would help but it's like a bad episode of Hell's Kitchen. It bears no resemblance to any IDE I've worked with. Creating a "project" led to some folder that linked into the file system and there was no way to organize files (that I could see anyways). In the end, it seemed like nothing but bloatware so I gave up on that tool quick.

    Wing IDE from Wingware is a pretty sophisticated set of tools and generally looks nice and performs well. If you're looking to do some serious Python work I highly recommend it. It has all the features you would want in an IDE and doesn't suffer from a confusing UI or bloated load times like Komodo was.

    PyScripter was small but powerful and overall very nice. Both a project (file) explorer and a class explorer which was handy. Uncluttered interface and even detected when a new version was available (complete with a quick download, shutdown and restart) however it lacked any intellisense and would only launch the app the first time. First time it worked like a charm, any subsequent launch would produce an error that it couldn't find the gdk library (even though it launched fine the first time). Restart the app and it can launch it without error.

    In the end I settled on the free version of Wing IDE (Wing IDE 101). First, it was free and that's a good thing. I don't do enough Python development to warrant the $129 price tag. There's a personal version for $30 but it doesn't give you much over the free editor version, so that's what I'm using. It has syntax highlighting and formatting (a must), can launch and debug a Python script, and even has intellisense. Unfortunately the class browser doesn't come with the free or even personal versions and I'm not that dedicated to the language to shell out for the professional version (not to mention the fact that I'm a cheap bastard) so it's the free version for us.

    Whatever tool you use, launch it and let's go about cleaning up the initial script.

    Starting Clean

    We want to clean up the Python code a little and focus on the changes we'll make for this exercise. We'll build a new Python file (exercise-1.py) for this. Mainly we're just making things a little easier to maintain and read. In the code we mimic what the micropoliswindow.py file does (just with a few less lines). Now we're ready for our modifications.

    Terraforming

    Terraforming is the process where the game engine creates a new blank landscape. It's called when you start a new blank city. We'll do this in the startup of our engine by calling the GenerateNewCity function. Here's the original Python code called to start the Micropolis engine:

       1: def createEngine(self):
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span>     engine = micropolis.Micropolis()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span>     self.engine = engine</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span>     print <span style="color: #006080">&quot;Created Micropolis simulator engine:&quot;</span>, engine</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span>     engine.ResourceDir = <span style="color: #006080">'res'</span></pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span>     engine.InitGame()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span>     # Load a city file.</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  11:</span>     cityFileName = <span style="color: #006080">'cities/deadwood.cty'</span></pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  12:</span>     print <span style="color: #006080">&quot;Loading city file:&quot;</span>, cityFileName</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  13:</span>     engine.loadFile(cityFileName)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  14:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  15:</span>     # Initialize the simulator engine.</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  16:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  17:</span>     engine.Resume()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  18:</span>     engine.setSpeed(2)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  19:</span>     engine.setSkips(100)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  20:</span>     engine.SetFunds(1000000000)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  21:</span>     engine.autoGo = 0</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  22:</span>     engine.CityTax = 9</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  23:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  24:</span>     tilewindow.TileDrawingArea.createEngine(self)</pre>
    

    It loads the deadwood.cty file from the cities folder using the loadFile method of the engin. Here's the changes we'll make to generate the blank landscape:

       1: def createEngine(self):
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span>     engine = micropolis.Micropolis()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span>     self.engine = engine</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span>     engine.ResourceDir = <span style="color: #008000">'res'</span></pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span>     engine.InitGame()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span>     engine.GenerateNewCity()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span>     </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span>     engine.<span style="color: #0000ff">Resume</span>()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  11:</span>     engine.setSpeed(1) </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  12:</span>     engine.SetFunds(1000000000)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  13:</span>     engine.autoGo = 0</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  14:</span>     engine.CityTax = 9</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  15:</span>     </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  16:</span>     tilewindow.TileDrawingArea.createEngine(self)</pre>
    

    Rather than calling engine.loadFile, we'll call engine.GenerateNewCity. This is found in generate.cpp in the Micropolis project and exposed to us via SWIG (from the _micropolis.pyd file generated by the Visual Studio project). Launch the app ("Python.exe -i exercise-1.py" or from your IDE) and you'll get something like this:

    image

    Here's the source file exercise-1.py so far. Now that we have a new, blank city to work with we can make it more interactive. First, let's create a popup menu and add it it our window. Start by adding a call to a method we'll create called createPopupMenu by modifying the constructor of the MicropolisDrawingArea class:

       1: self.engine = engine
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span> self.createPopupMenu()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span> tilewindow.TileDrawingArea.__init__(self, **args)</pre>
    
    Now we'll need to create the method. This is going to create a gtkMenu object, add the menu option to generate a new city, and setup our bindings. Add this method to the MicropolisDrawingArea class:
       1: def createPopupMenu(self):
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>     </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span>         # main popup menu</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span>         self.popup = gtk.Menu()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span>         # file/system menu</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span>         menu = gtk.MenuItem(<span style="color: #006080">&quot;File&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span>         childMenu = gtk.Menu()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span>         menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Generate City&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  11:</span>         menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.GenerateNewCity)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  12:</span>         childMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  13:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  14:</span>         menu.set_submenu(childMenu)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  15:</span>         self.popup.append(menu)</pre>
    
    The connect call will bind the activation of this menu item to a method called GenerateNewCity. Here's the method which calls the engines method of the same name:
       1: def GenerateNewCity(self, widget):
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>     self.engine.GenerateNewCity()</pre>
    

    Finally we need to invoke the popup menu. We'll do this from the right-click menu. The mouse handling is already dealt with in the tilewindow class (you can see this in tilewindow.py in the handleMousePress method) but we're going to intercept the call in our own class and pass it on if we don't handle it. Add this method to the MicropolisDrawingArea class:

       1: def handleButtonPress(
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>     self,</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span>     widget,</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span>     <span style="color: #0000ff">event</span>):</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span>     <span style="color: #0000ff">if</span> <span style="color: #0000ff">event</span>.button == 3: # right-click</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span>         self.popup.show_all()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span>         self.popup.popup(None, None, None, <span style="color: #0000ff">event</span>.button, <span style="color: #0000ff">event</span>.time)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span>     <span style="color: #0000ff">else</span>:</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span>         tilewindow.TileDrawingArea.handleButtonPress(self, widget, <span style="color: #0000ff">event</span>)</pre>
    

    Now when we run the app and right-click on the drawing surface, we can use our new popup menu:

    image

    And when clicked it runs through the various routines creating a new landscape each time. You can check out the C++ code in the generate.cpp file for details on how it works. You can grab the Python script exercise-2.py with this new functionality. Here are some variations the engine produces:

    image image image

    Now that we have an interface to let the user interact with the system, we can extend this. Two features of the system are loading existing cities (from the cities folder) and loading scenarios. Let's wire these up to the new interface.

    Loading Cities

    Cities are kept as binary files in the cities folder. The engine loads them up via the LoadCity method that takes in a filename for a parameter. So first we'll add some code to our menu to allow the user to select a Load City option:

       1: menuItem = gtk.MenuItem("Generate City")
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.GenerateNewCity)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span> childMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span> # Start New Load City menu option</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Load City...&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.LoadCity)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span> childMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span> # End New Load City menu option</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  11:</span> menu.set_submenu(childMenu)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  12:</span> self.popup.append(menu)</pre>
    

    Now let's write the LoadCity method. This is going to use the gtk.FileChooserDialog which let's us pick a file from a directory. The Python version doesn't use the standard Windows File Open look and feel so it might look weird when you run it, but it does the job.

    In the new LoadCity method we'll make a few modifications like only allowing to load local files; we'll set the working folder to the cities folder; and we'll add a filter to show *.cty files. Here's the new code snippet to add:

       1: def LoadCity(self, widget):
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>     dialog = gtk.FileChooserDialog(<span style="color: #006080">&quot;Open City..&quot;</span>,</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span>                                    None,</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span>                                    gtk.FILE_CHOOSER_ACTION_OPEN,</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span>                                    (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span>     dialog.set_local_only(True)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span>     dialog.set_select_multiple(False)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span>     cityFolder = os.getcwd() + <span style="color: #006080">&quot;\\cities&quot;</span></pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span>     dialog.set_current_folder(cityFolder)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span>     </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  11:</span>     filter = gtk.FileFilter()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  12:</span>     filter.set_name(<span style="color: #006080">&quot;All files&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  13:</span>     filter.add_pattern(<span style="color: #006080">&quot;*&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  14:</span>     dialog.add_filter(filter)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  15:</span>     </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  16:</span>     filter = gtk.FileFilter()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  17:</span>     filter.set_name(<span style="color: #006080">&quot;Micropolis City Files&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  18:</span>     filter.add_pattern(<span style="color: #006080">&quot;*.cty&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  19:</span>     dialog.add_filter(filter)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  20:</span>     </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  21:</span>     response = dialog.run()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  22:</span>     <span style="color: #0000ff">if</span> response == gtk.RESPONSE_OK:</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  23:</span>         filename = dialog.get_filename()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  24:</span>         self.engine.LoadCity(filename)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  25:</span>     dialog.destroy()</pre>
    

    Now run the app and select Open City from the popup menu and you'll see something like this:

    image

    Click on a double-click on a city file or select one and click "Open". The new city will load in your window and you can move around the new landscape.

    You can find the code up to this point in the exercise-3.py file.

    Loading Scenarios

    There are 8 "custom" scenarios pre-built for Micropolis. Think of a scenario as a situation composed of a map file, location, funds, and a timeline. While cities have their own timeline stored with them, scenarios are loaded and set to a certain point in time along with a set of fixed funds. Scenarios include the 1906 earthquake of San Francisco; Hamburg, Germany during the height of World War II in 1944; and and futuristic Boston in the year 2010.

    The scenarios are hard coded inside fileio.cpp and can't be changed without modifications to the C++ source. There is an engine method called LoadScenario that takes in a number (1-8) for the scenario number to load. Again, like the Load City option with scenarios we'll build 8 menu items and hook them up to a callback. In this case, we can use a single call back and pass in (from the creation of the menu item) the number of the scenario to load.

    Here's the new menu code for the scenarios:

       1: playMenu = gtk.MenuItem("Play Scenario")
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span> subMenu = gtk.Menu()</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   3:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   4:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Dullsville&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   5:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 1)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   6:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   7:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   8:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;San Francisco&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">   9:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 2)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  10:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  11:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  12:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Hamburg&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  13:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 3)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  14:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  15:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  16:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Bern&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  17:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 4)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  18:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  19:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  20:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Tokyo&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  21:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 5)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  22:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  23:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  24:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Detroit&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  25:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 6)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  26:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  27:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  28:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Boston&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  29:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 7)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  30:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  31:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  32:</span> menuItem = gtk.MenuItem(<span style="color: #006080">&quot;Rio de Janeiro&quot;</span>)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  33:</span> menuItem.connect(<span style="color: #006080">&quot;activate&quot;</span>, self.PlayScenario, 8)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  34:</span> subMenu.append(menuItem)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  35:</span>&#160; </pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">  36:</span> playMenu.set_submenu(subMenu)</pre>
    
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060">  37:</span> childMenu.append(playMenu)</pre>
    

    And here's the PlayScenario method. Note we'll grab the scenario number as a parameter passed in and call the engine.LoadScenario method using it:

       1: def PlayScenario(self, widget, scenario):
    <pre style="padding-right: 0px; padding-left: 0px; font-size: 8pt; padding-bottom: 0px; margin: 0em; overflow: visible; width: 100%; color: black; border-top-style: none; line-height: 12pt; padding-top: 0px; font-family: consolas, &#39;Courier New&#39;, courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060">   2:</span>     self.engine.LoadScenario(scenario)</pre>
    

    Now with the modifications we've done we can load up scenario files, load up cities, and generate a new blank terrain all driven from a popup menu.

    image

    Finally we'll just add the remaining menu items that represent the ones in the game. We'll stub these out for now to call a placeholder method. You can grab the final exercise-final.py file from here. Just drop it in your distribution folder and you're ready to go.

    Things you can do to extend these ideas:

    • Modify the fileio.cpp code to read in an XML file or something and retrieve the scenario information from there rather than having it hard coded in the source. This will allow you to add new scenarios without rebuilding the system.
    • Alternately build the loading of the scenarios in Python instead so it can be modified.

    That's it for file access and getting started in the guts of the engine. If you have any questions so far, feel free to email me with them. Next up we'll continue to extend the UI and talk to more parts of the Metropolis engine.

    This is a series of posts exploring and extending the Micropolis code. You can view the full list of posts here.

  • Building A City - Part II

    image MicropolisCore is package put together that is a fairly clean port of the original C code into a C++ mega class (called Micropolis). The distribution is a set of three projects, but this post focuses on the main one Micropolis. This contains the core simulation code. One other project we need is the TileEngine which (via it's default implementation using Cairo built on top of GTK+) provides a UI so we can see our city.

    The new MicropolisCore project is all C++ and Python code. There is no TCL required, however it still uses GTK for it's UI. You'll need to download PyGTK, a GTK+ wrapper in Python, from here and install it. It'll find Python and install it for you. Also grab PyGObject which are the Python bindings for GObject and PyCairo and install them (you can get them all from the same location).

    Assuming you followed the last post and compiled the system, you'll have the Micropolis and TileEngine projects built, along with the generated Python files and other goodies that came along for the ride. You can run this from the MicropolisCore python folder, but I prefer to put everything together into a distribution folder to keep things clean.

    I've created a binary distribution for you which you can download here. It contains all the assets along with Micropolis and TileEngine (CellEngine isn't needed for this demo) and is ready to go once you install the pre-requisite files listed here (see the end of this post for a list of all the files you need with links for downloading).

    To create your own distribution, just copy the "python" folder from the Micropolis project. Add in the _micropolis.pyd file from the Release directory. Copy in the _tileengine.pyd file from the TileEngine release directory. Finally copy the *.py files from the TileEngine python directory. This is everything you need to run the demo.

    Now for the final moment. Run the following command from where you have your distribution files:

    C:\Python25\Python.exe -i micropoliswindow.py

    If the stars align, you should see this:

    image

    You now have Micropolis running on Windows! Well, at least a demo running the Micropolis engine from Python. Go ahead and explore your city. Everything is there that you'll remember from the original SimCity, complete with traffic running around.

    There are a few keystrokes you can use while the demo is running. You can use the arrow keys to move the cursor box around. "O" will zoom in and allow you to see the demo city (the demo script loads up the "cities/haight.cty" file). Messing around with the zoom lets you get something like this displayed:

    image

    Other keystrokes:

    • "R" resets the zoom
    • "O" zooms out
    • "I" zooms in

    Go ahead and maximize the screen. Here's the full city in all it's Python/GTK glory:

    image

    Running from the command line is fine and dandy, but how about debugging the program? Glad you asked.

    First, you'll need to switch your configuration to ReleaseSymbols. This will output the export libraries for Python to find in the correct directory. If you want to change this, you'll need to edit a bunch of the *.py files in the python directory for each project. Just look and you'll see where it references the ReleaseSymbols folder right before it does the import.

    After you switch to the ReleaseSymbols configuration, you'll need to set python.exe as your debugger and have it launch the micropoliswindow.py file. Right click on the Micropolis project and select Debugger from the tree. Then setup the debugger options like this:

    image

    Now you're ready to debug. Pick a file and set a breakpoint then hit F5. You'll see the GTK Window popup with the demo city loaded, and you can fully debug the C++ code at this point.

    Small victories.

    Next up is building some additional functionality into the system, writing a new Python front-end, and starting to build out a full blow Micropolis client on Windows. You might also notice that there's a sample micropoliswebserver.py file. Now that the system is compiled and can be driven from Python, thanks to SWIG, why not? I haven't looked at the webserver yet, but Python itself has the facilities to launch one. And now that Python is hooked into the Micropolis engine you can easily serve up the core to the web. Pretty slick.

    Here's that list of additional files you'll need to install for running the system:

    This is a series of posts exploring and extending the Micropolis code. You can view the full list of posts here.

  • Building A City - Part I

    imageWell. My last post was a little, ummm... popular? Slashdot, Boing Boing, BluesNews, and pretty much the rest of the planet have all linked to it. Glad everyone is happy about the source release.

    Anyways, there's been a lot of buzz about the code and the main questions that come up are "how do I get it to compile?". Let's go through this.

    This is the first part of a series. In this post we'll go through the tools you need to install and the steps to get the Python and Windows code compiled. In part II, we'll hook up what we've built with a UI. Beyond that I'll dig into the various engines and ways to manipulate and change them.

    Tools of the Trade

    The Micropolis code isn't just C code. There is that (bascially the original code slightly retrofitted and updated for OLPC). There's also the python and TCL code. Python is an OO programming language that Don Hopkins chose to work with. Python itself is a pretty good language to work with but there's additional power using a tool called SWIG. SWIG let's you take your existing C/C++/etc. code and "pythonify" it. No, we don't mean to teach it how to lop off the Black Knight's arms and legs in battle. We mean expose your already existing property (in this case the SimCity code) to python. This will allow us to plug in scripting to Micropolis and make it do our bidding.

    Anywho, here's the rundown of the tools you'll need to get started.

    SWIG

    SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages. It's run by the projects to generate Python files from the C++ ones. Grab the latest version of SWIG for Windows from here and install to C:\SWIG.

    GTK

    GTK is a multi-platform graphical windowing library. Download and install the gtk++win32-devel package here. Use the default C:\GTK folder for installing. The GTK install includes Cairo, a 2D graphics library used by Micropolis so you don't have to install it separately.

    Pycairo

    While you get the Cairo libraries for free with the GTK install, you'll need the python bindings for Cairo. These are available through the pycairo project here. Install to C:\pycairo. This will enable you to build the TileEngine project.

    Python 2.5

    Python is an OO scripting language that has a lot of flexibility and power. Micropolis exposes its internals using SWIG to python so you can plug it in as an engine to drive the core functionality. Download Python 2.5 from here. You can install 2.5 just to get going although the site recommends 2.5.1. Install it to the default location (C:\Python25) and you're all set.

    Compiling

    The source code comes in two packages. The first one, micropolis-activity-source.tgz, is the TCL/Tk version for Linux with the (mostly) original C code. The second package, MicropolisCore.tgz, (the one we're going to build with Visual Studio) is a C++ version which has the following features:

    • A Micropolis project containing cleaned up C++ code from the original C code for the main game engine
    • The cellular automata engine pulled into a separate project of its own called CellEngine
    • A TileEngine project (using Cairo by default) which displays Micropolis tiles and CellEngine cells.

    Once you have all the pre-requisite tools listed above installed you're ready to go. Note that if you installed the tools in anywhere but the path mentioned, you'll have to modify the projects in Visual Studio so it can find them accordingly.

    First, right click on the Micropolis project and build it. You should get output similar to this:

    1>------ Build started: Project: Micropolis, Configuration: Release Win32 ------
    1>Executing tool...
    1>..\micropolis.h(1240): Warning(462): Unable to set dimensionless array variable
    1>..\micropolis.h(1242): Warning(462): Unable to set dimensionless array variable
    1>..\micropolis.h(2093): Warning(462): Unable to set dimensionless array variable
    1>..\micropolis.h(2095): Warning(462): Unable to set dimensionless array variable
    1>..\micropolis.h(2097): Warning(462): Unable to set dimensionless array variable
    1>..\micropolis.h(2099): Warning(462): Unable to set dimensionless array variable
    1>Compiling...
    1>micropolis_wrap.cpp
    1>Linking...
    1>   Creating library d:\Development\Projects\MicropolisCore\src\Micropolis\python\\Release\_micropolis.lib and object d:\Development\Projects\MicropolisCore\src\Micropolis\python\\Release\_micropolis.exp
    1>Generating code
    1>Finished generating code
    1>Embedding manifest...
    1>Build log was saved at "file://d:\Development\Projects\MicropolisCore\src\Micropolis\python\Release\BuildLog.htm"
    1>Micropolis - 0 error(s), 0 warning(s)
    ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

    Once this is built in your Micropolis project you'll see that the SWIG folder is now populated. This contains all the python wrappers that talk to the C++ Micropolis code (generated by the SWIG tool).

    Now build the CellEngine project. You should get output similar to this:

    1>------ Build started: Project: CellEngine, Configuration: Release Win32 ------
    1>Executing tool...
    1>Build log was saved at "file://d:\Development\Projects\MicropolisCore\src\CellEngine\python\Release\BuildLog.htm"
    1>CellEngine - 0 error(s), 0 warning(s)
    ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

    Finally build TileEngine project. You should get output similar to this:

    1>------ Build started: Project: TileEngine, Configuration: Release Win32 ------
    1>Executing tool...
    1>..\tileengine.h(125): Warning(454): Setting a pointer/reference variable may leak memory.
    1>Build log was saved at "file://d:\Development\Projects\MicropolisCore\src\TileEngine\python\Release\BuildLog.htm"
    1>TileEngine - 0 error(s), 0 warning(s)
    ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

    Now that everything is built you've got yourself 3 import libraries that can be used by python. At this point you could drive the system purely from python. The Linux port uses Tcl/Tk as it's windowing system and we'll get into using this to interact with the python output we created here in the next blog post.

    This is a series of posts exploring and extending the Micropolis code. You can view the full list of posts here.

  • SimCity Source Code Released to the Wild! Let the ports begin...

    This past holiday (I've been bugging him since November about it) my good friend Don Hopkins got a lot of work done on the finishing touches on releasing the original SimCity source code under the GNU General Public Library (GPL). The code won't have reference to any SimCity name as that has all be renamed to Micropolis. Micropolis was the original working title of the game and since EA requires that the GPL open source version not use the same name as SimCity (to protect their trademark) a little work had to be done to the code.

    There's been changes to the original system like a new splash screen, some UI feedback from QA, etc. The plane crash disaster has been removed as a result of 9/11. What is initially released under GPL is the Linux version based on TCL/Tk, adapted for the OLPC (but not yet natively ported to the Sugar user interface and Python), which will also run on any Linux/X11 platform. The OLPC has an officially sanctioned and QA'ed version of SimCity that is actually called SimCity. EA wanted to have the right to approve and QA anything that was shipped with the trademarked name SimCity. But the GPL version will have a different name than SimCity, so people will be allowed to modify and distribute that without having EA QA and approve it. Future versions of SimCity that are included with the OLPC and called SimCity will go through EA for approval, but versions based on the open source Micropolis source code can be distributed anywhere, including the OLPC, under the name Micropolis (or any other name than SimCity).

    The "MicropolisCore" project includes the latest Micropolis (SimCity) source code, cleaned up and recast into C++ classes, integrated into Python, using the wonderful SWIG interface generator tool. It also includes a Cairo based TileEngine, and a cellular automata machine CellEngine, which are independent but can be plugged together, so the tile engine can display cellular automata cells as well as SimCity tiles, or any other application's tiles.

    The key thing here is to peek inside the mind of the original Maxis programmers when they built it. Remember, this was back in the day when games had to fit inside of 640k so some "creative" programming techniques were employed. SimCity has been long a model used for urban planning and while it's just a game, there are a lot of business rules, ecosystem modeling, social dependencies, and other cool stuff going on in this codebase. It may not be pretty code but it's content sure is interesting to see.

    In any case, it's out there for you to grab and have fun with. It was originally written in C and of course is old (created before 1983 which is ancient in Internet time). Don spent a lot of time cleaning the code up (including ANSIfying it, reformatting it, optimizing, and bullet-proofing it) as best he could. Don ported the Mac version of SimCity to SunOS Unix running the NeWS window system about 15 years ago, writing the user interface in PostScript. A year or so later he ported it to various versions of Unix running X-Windows, using the TCL/Tk scripting language and gui toolkit. Several years later when Linux became viable, it was fairly straightforward to port that code to Linux, and then to port that to the OLPC.

    There's still a lot of craptastic code in there, but the heart of the software (the simulator) hasn't changed. I know there will be efforts underway to port it to a better platform, replace the age old graphics with new ones, rewrite the graphic routines with modern-day counterparts, etc. The modern challenge for game programming is to deconstruct games like SimCity into reusable components for making other games! The code hopefully serves as a good example of how to use SWIG to integrate C++ classes into Python and Cairo, in a portable cross platform way that works on Linux and Windows.

    Don also wrote some example Python code that uses the TileEngine module to make a scrolling zooming view of a live city with the Micropolis module, and a scrolling zooming view of a cellular automata with the CellEngine module. The TileEngine comes with a Python TileWindow base class that implements most of the plumbing, tile display and mouse tracking, so SimCity and the CellEngine can easily subclass and customize to suit their needs. You can pan with the mouse and arrow keys, and zoom in and out by typeing "i" or "o", or "r" to reset the zoom. The TileEngine supports zooming in and out, and has a lazy drawing feature that calls back into Python to render and cache the scaled tiles right before they're needed (so you can implement the tile caching strategy in Python, while the rendering is in efficient C++ code calling hardware accelerated Cairo -- and the Python code could easily render tiles with pretty scaled SVG graphics). The Micropolis engine can load a SimCity save file and run it, and use the TileEngine to draw it, but you can't actually interact with it or edit the map yet, since the user interface and other views haven't been implemented, just a scrolling zooming view of its tiles.

    Grab the source code from here and go have some fun!

    Enjoy!

    This is a series of posts exploring and extending the Micropolis code. You can view the full list of posts here.

  • To Release an Unbuildable TreeSurgeon?

    I mentioned we were getting a release of TreeSurgeon out that included 2005 and now 2008 support. The code is all checked in so if you're interested in getting it, go for it. The "official" release hasn't been put together yet for due to problems with the third party libraries and tools TS includes when creating a new project.

    The problem stems from the fact that TreeSurgeon uses old(er) and public releases of things like NCover in the build file it generates. These files are kept in a skeleton tree (where TreeSurgeon is installed to) and copied to the new tree you create when starting a new project. Trouble is Internet time has marched on and some of these tools no longer work for 2008.

    When we updated the code/project generation for 2005/2008 support, it was a pretty simple matter of cleaning up the templates and generating new ones. The trouble comes from the tools that don't work with the 2008 compilers or .NET 3.5 framework. This is compounded by the fact that NCover is now commercial. The last (free) version is 1.5.8 but won't work against 3.5 assemblies and the new version (2.0) requires a trial key.

    So it's a bit of a pickle when it comes to what version of a tool do we include with the latest version of TreeSurgeon that works for the new compilers? I didn't want to put together a released version of TS with a build that breaks (without developer intervention). There doesn't seem to be any other free coverage tool that I can find that will work here so I'm feeling a little stuck and looking for options.

    Anyways, that's the cause for the delay in the official release. As I said, you can still grab the change set which will generate a 2005 or 2008 project however you'll need to manually upgrade the version of NCover and perhaps some other tools in the generated solution in order to get it to compile and pass all the tests. Need to take a look at the latest version of CI Factory to see what they're doing and maybe see if anyone else has some ideas. Feel free to chime in with your thoughts either in the comments here on the blog or the discussion forums here.

  • Appending nodes in XML files with xmlpeek and xmlpoke using NAnt

    First post of the year and hopefully this is something useful. I think it is.

    I'm currently doing a major automation-overhaul to our projects, trying to streamline everything. Part of this involves doing automated deployments of the projects to a location (say a file or web server) where a QA person can come along later and with the click of a button they can just launch an installer for any build of an application. This is very much structured like the JetBrains Nightly Build page you see here, but 100% automated for all projects using NAnt.

    A lofty goal? Perhaps.

    Anywho, the time to update the builds and page has come and I went hunting to see how I could do it (without having to write a silly little console app or something). To start with (and I'm not done here, but this works and is a model anyone can use) we have a basic XML file:

    <?xml version="1.0" encoding="utf-8"?>

    <builds>

      <build>

        <date>01/03/2008 15:03:41</date>

        <build>0</build>

      </build>

    </builds>

    This contains build information for the project and will be transformed using XSLT into something pretty (and useful once I add more attributes).

    The challenge is that we want to append an XML node to this file during the deployment process, which is kicked off by CruiseControl.NET. Sounds easy huh. There are a few ways to do this. First, I could write that console app or something and have it update some file. Or maybe it would even write to a database. Or... no, that's getting too complicated. The next thought was to use the ability to write C# code in NAnt scripts, but then that started to get ugly real fast and more maintenance than I wanted.

    Then I turned to xmlpoke. This little NAnt task let's you replace a node (or nodes) in an XML file. Trouble is that's what it's designed to do. Replace a node or property. Not append one. After about 15 minutes of Googling (my patience is pretty thin for finding an answer on the 3rd page of Google) I realized xmlpoke wasn't going to be good enough for this. Someone had come up with xmlpoke2 which did exactly what I wanted (appended data to an XML file), but to date it hasn't made it into the core or even NAntContrib.

    After looking at the XML file I realized I might be able to use xmlpeek (read some XML from a file) and combine it with xmlpoke (modifying it on the way out) and write it back to the file. Maybe not the most elegant solution, but I think it's pretty nifty and it gets the job done.

    First we have our XML file (above) so I created a target in NAnt to handle the update to the XML file:

    <target name="publish-report" description="add the version deployed to an existing xml file">

    </target>

    Step 1 - Use xmlpeek to read in the entire XML node tree containing the current builds:

    <!-- read in all the builds for rewriting -->

    <property name="xmlnodes" value=""/>

    <xmlpeek xpath="//builds" file="c:\autobuild.xml" property="xmlnodes"></xmlpeek>

    Step 2 - Modify it by appending a new node with the new build info and saving it into a new property:

    <!-- modify the node by adding a new one to it -->

    <property name="newnode" value="&lt;build&gt;&lt;date&gt;${datetime::now()}&lt;/date&gt;&lt;build&gt;${CCNetLabel}&lt;/build&gt;&lt;/build&gt;" />

    <property name="xmlnodes" value="${xmlnodes}${newnode}" />

    Step 3 - Write it back out to the original XML file replacing the entire XML tree using xmlpoke:

    <!-- rewrite it back out to the xml file using xmlpeek -->

    <xmlpoke file="c:\autobuild.xml" xpath="//builds" value="${xmlnodes}" />

    The result. Here's the updated XML file after running NAnt with our target task (and faking out the CCNetLabel that would usually get set by CruiseControl via a command line definition):

    tools\nant\nant.exe publish-report -D:CCNetLabel=1

    NAnt 0.85 (Build 0.85.2478.0; release; 14/10/2006)

    Copyright (C) 2001-2006 Gerry Shaw

    http://nant.sourceforge.net

     

    Buildfile: file:///C:/development/common/Library/Common/Common.build

    Target framework: Microsoft .NET Framework 2.0

    Target(s) specified: publish-report

     

    publish-report:

     

    [xmlpeek] Peeking at 'c:\autobuild.xml' with XPath expression '//builds'.

    [xmlpeek] Found '1' nodes with the XPath expression '//builds'.

    [xmlpoke] Found '1' nodes matching XPath expression '//builds'.

     

    BUILD SUCCEEDED

     

    Total time: 0.2 seconds.

    <?xml version="1.0" encoding="utf-8"?>

    <builds>

      <build>

        <date>01/03/2008 15:03:41</date>

        <build>0</build>

      </build>

      <build>

        <date>01/03/2008 15:30:07</date>

        <build>1</build>

      </build>

    </builds>

    Now I have a continuously growing XML file with all my build numbers in them. Of course there's more info to add here like where to get the file and such but the concept works and I think it's a half decent compromise (to having to write my own task or more script). The cool thing is that you can even use it against a file like this:

    <?xml version="1.0" encoding="utf-8"?>

    <builds>

    </builds>

    This lets you start from scratch for new projects and start with build 1 (which will come from CruiseControl.NET). If the file didn't exist at all, you could even use the echo task or something like it to create the file, then update it with the task info above. Is it bullet proof? Hardly. It should work though and gives me the automation I want.

    Well, I'm done for the day. That was a worthwhile hour to build this. Now I just have to go off and add in all the extra goop and hook it up to our builds.

    Enjoy!