Contents tagged with SimCity
-
Building a City – Part IV
Welcome back to the Building a City Series, a series of blog entries on the open source release of SimCity (called Micropolis) and building up your own world with it. It’s been almost two years since the original release and a lot has happened in the codebase.
This entry is going to focus on the change to the codebase itself and a lap around what’s in the current source code, walking around the new Python UI, some talk about SWIG and what we’re doing (and why we’re doing it) and laying the foundation for some upcoming posts that are in the works.
Old and Busted
The Micropolis source code, when it was released as open source in 2008, was really two entities. The first is the original C code, ported to X11 by Don Hopkins, for the Unix operating system. SimCity was released for various *nix systems (Sun, X11, Indigo, etc.) and that code is what is the basis of what’s currently in the micropolis-activity folder in the subversion repository. This code was originally ported from the DOS-based SimCity that came out of Will Wright’s magical mind. However back then there was little concern for separation of concerns so much of the UI code is intermixed with the game logic and it’s all one big tangled set of dependencies that’s pretty hard to unravel. So Don took it upon himself (over the course of a few years) to unwind the logic of the system and put it into a set of source files where there is no user interface to stumble over when porting to other platforms. Enter the MicropolisCore project which is the current path of destruction in the codebase.
New Hotness
MicropolisCore is a set of C++ files compiled together which are then processed by a tool called SWIG (Simplified Wrapper and Interface Generator). SWIG was created for the sole purpose of being able to take C/C++ code, wrap it in a series of shadow output files (in the language of your choice) and use that code on various platforms and call it from more scriptable languages (Python, Perl, PHP, Tcl, etc.). After all, trying to prototype user interfaces or write things that are dynamically changing is no fun in C++ and frankly, the user interface toolkits for C++ are ugly and cumbersome.
Why bother wrapping all the code in SWIG just so you can build the UI in another language you might ask? The primary goal for Micropolis was to get it to the point where a new UI could be built (the initial release was for the OLPC program using the Sugar user interface). SWIG (along with the GTK bindings PyGTK) offers a way to build out that UI with platform independent Python scripts. The Python scripts for the current working copy of MicropolisCore are just that, scripts. You can quickly edit them and change the way the UI behaves but still be hooked up to the backend game engine through the SWIG wrappers calling into the original C++ code.
Magic
That’s the magic here. SWIG takes the original C++ code, processes it into a series of Python scripts (along with a C++ wrapper class to bridge the gap between the two languages). The pyMicropolis directory in the repository contains Python only code for making calls to that original Sim engine and letting it do it’s thing while the UI handles responses from the user and forwards those requests on to the C++ code.
SWIG wraps the C++ code but also provides a means for talking back to the calling language (in this case, Python). There are some callback functions defined in the SWIG interface file which handle the C callback function (named callback, you’ll find it in the stubs.cpp file in the Micropolis project). This is hooked up in Python so anytime the game engine makes a call to the callback method, Python code will be invoked and that handles dispatching the callback type to the appropriate function in the scripting language which then updates whatever view or control is attached to it.
When I said platform independence it really is just that. You’ll need to make sure you have a few things installed first (Python, PyGTK, GTK). Once you get a copy of the current codebase and have built the system for your platform (Windows, Mac, or Linux) go into the MicropolisCore/src directory.
On Linux run this “./run-gtkfrontend”. This Python script launches the PyGTK UI and produces this (running under Gnome on Debian):
On Windows run “python run-gtkfrontend” from the same directory, which produces this (running under Windows 7):
Both are using the same C++ files (MicropolisCore, compiled for that platform) and both are using the exact same Python scripts. Platform Coolness.
Wrap or Rewrite
Some might argue that porting all the original C code into a C++ class (yes, it’s a single God class called Micropolis) is crazy. However what’s probably more insane is having to go through, line by line, in the original C code and rip out the hard coded UI references and pull them into something more manageable. That ugliness was already done for you by Mr. Hopkins. Okay, it’s an ugly single C++ class and there are still a lot of “C” constructs in the code.
However it’s all in C++ code now and SWIG wraps it up pretty nicely. There’s very little Python specific code that had to be written to manage talking to the C++ code (really just the callback code and a few typemaps). To continue development on one platform or another (or another language) is pretty brainless at this point.
Could you go back and rewrite all the original C code into some kind of “portable” C. Perhaps. You would still have to rip out the UI specific calls to the Tcl/Tk toolkit and all of it’s ugliness. Then what do you do? Replace the Tcl code with calls to something portable like SDL? So now you’re swapping one technology for another.
SWIG was done for a reason, which was primarily to provide a means to build a new UI with something a little more scriptable than C or C++. At the time, Mono really hadn’t got to the maturity level it is now but then there’s still the Mac factor so rewriting in .NET isn’t the best option. SWIG doesn’t close the door on .NET (as you’ll see later) but there’s also work underway for a web based version.
Taking it to the Web!
Head on over to MicropolisOnline and you’ll see the initial seeds of the Micropolis Eduverse, an online version of Micropolis that’s being developed with an educational focus. The web based version (all of the source code can be found in the subversion repository) runs from a Python based web server using TurboGears for the backend (along with the SWIG-ified Python code from MicropolisCore) and OpenLaszlo for the front end (which is very XAML-like). There’s a working English and Dutch demo that you can play with from any web browser (although leaving your city up and running for a few hours tends to crash, the system is still in active development).
A Working Game, Almost
Getting back to the desktop version, the current MicropolisCore code is vastly improved from the test scripts we created last year. When you launch the GTK frontend you get a splash screen that lets you a) choose from a pre-determined set of scenarios (each with a “win” situation, just like the original SimCity game) b) load a city from the “cities” folder or c) keep clicking the Generate City button to randomly create a city to play with.
Once inside the simulator you’ll see changes from both the original “test” code we did and even the original Tcl/X11 user interface. Here’s the game screen under Windows after I put down a few roads, power lines, and zones:
Things you can do with this build using the current PyGTK interface:
- Start a new game
- Load cities
- Load scenarios
- Generate new cities
- Lay infrastructure
- Roads
- Rail lines
- Power lines
- Building Tools
- Nuclear and Coal power plants
- Stadiums and Parks
- Airport
- Seaport
- Zone Tools
- Residential, Commercial, Industrial zones
- Police and Fire Departments
Most of the tools are accessed via a pie menu which gives you quick access to building tools, zones, and other elements. The pie menu is completely implemented in Python (using GTK for the UI) and talks back to the game engine to invoke the appropriate tool (again, through callbacks). You can find all the pie menu code in the pyMicropolis/piemenu directory.
The simulator runs and you can control a lot of aspects of it. These are accessible from a series of tabs across the top. Each tab either displays some information from the game engine or allows you to control some aspect of it.
Notice
This contains any notifications from the system (the first one being when you reach a population of 2,000) and replaces the old annoying pop-up screen you would see. Like the old popups? Then make a change in the micropolisnoticeview.py file and modify the updateMessage method (a simple popup could be used like gtk.MessageDialog).
Messages
The Messages tab contains a scrolling list of short notifications that the game has sent to the UI via the callback function.
Evaluation
This panel shows a brief summary of how you’re doing. It’s updated frequently from various places in the engine (most often updated at year end when all the calculations are done for taxes, people immigrate in and out of your city, etc.).
History
The History tab keeps a running total of all the various zones and stats about your city including crime rate, pollution, and cash flow. It shows both 10 years of history and 120 years (click on the button to see each graph) and is updated live as the sim runs. Here’s the 10 year view:
Budget
This panel shows you what your current budget is running at and allows you to tweak values in real-time with the engine using a slider control.
Map
The map is a really interesting tab as it lets you overlap various aspects of your city on top of the main window. It uses cairo (and the Python bindings via PyCairo) to blend colours over the tiles rather than the harsh 1 color / 1 pixel approach you see in SimCity classic (or even the SimCity for Windows game). For example here’s the Power Grid overlap on our map which shows power distribution for the city.
Very nice!
Disasters
What’s a SimCity game without disasters? This panel lets you unleash a tornado on your Sims or melt down your nuclear power plant. All of the buttons here work so go ahead and kill off a few million residents.
Control
This panel lets you tweak some aspects of the game like how fast it runs, what options are turned on or off (disasters, animation, etc.). It also contains some buttons that will take you back to the splash screen and allow you to change to a different city or load a scenario. Note: There is no “Are you sure” type option here yet so you might lose your current city if you’re not careful.
Like I said, it’s almost a working game (playable but not 100% there). I would say the current Python implementation does about 80% what the original game did. There are still a few gaps but those can be easily filled with a few UI enhancements. There are also a few “gaps” in the engine that have yet to be coded. For a list of TODOs check it out the files in the root of the repository here.
No, it’s not pretty. It’s an open source project built by geeks. Give it some time and if there are any graphic designers out there willing to spice up the UI let us know.
Abandoning the Past
Like I said at the beginning of this post, there are really two parts to the codebase. There’s micropolis-activity, the X11/Unix system that’s the original C code (with tight hooks into the Tcl/Tk libraries to do the UI) and there’s MicropolisCore which is the C++ code, SWIG wrappers and Python bindings that form the Python/GTK/Cairo desktop version.
You can pretty much consider the micropolis-activity code abandoned-in-place. I noticed recently on the (unstable) Debian mailing list a new patch to fix some problems with the X11 code. Someone will probably fold these fixes into the repository but for the most part nobody is actively working on it. Also the desktop portion of MicropolisCore won’t be going too much farther (at least by Don). He’s focusing on the web front end (which is all OpenLaszlo). We’ll still be updating the MicropolisCore C++ code and updating the Python wrappers as we add new functionality (or fix old ones) but there won’t be too much progress on the PyGTK frontend.
The Future is Bright
If you do check out the latest copy of trunk from source control, you might notice a few new changes. First off, the Visual Studio solution has been upgraded to 2008 (with a 2010 version coming out when it ships). There’s a new “CSharp Release” configuration in the solution. Changing to this and rebuilding the solution will kick off SWIG and produce C# wrappers for the C++ code (and copy all the generated files into the right locations). There’s also a new C# project called MicropolisClient which is a WinForms client I created to mimic the gtk-frontend (it actually creates an executable called cs-frontend in the same directory). It contains a folder called MicropolisEngine which holds all the generated files from SWIG. Once the client is built it launches the engine and (will eventually) provide a WinForms .NET front end to the simulator (it doesn’t do a lot right now as it’s in development and the callback mechanism back into C# isn’t written yet like it is with the Python code).
There’s a lot of work to do on the C# client that’s already done in the PyGTK version. The Python version also has the benefit of using the TileEngine project (and it’s Python wrappers) which is an independent tile system for rendering the Micropolis cityscape. I chose not to wrap this project in C# because it relies on the Cairo library and I really didn’t want to put any dependencies on 3rd party libraries for rendering graphics. The C# version is going to use GDI+ for it’s rendering or if that doesn’t pan out, DirectX (or possibly XNA). Basically you won’t have to install anything other than the Micropolis binaries on a Windows box to run it using this front end. Another option is to create a WPF frontend instead of WinForms as the graphical look and feel might be easier with XAML and there’s more flexibility with regards to image manipulation and WPF. Adventurists can feel free to use the current build as a basis to write a console version of Micropolis in C# (kidding!).
In any case, the current source code builds for Linux, Mac, and Windows and produces a nice, playable simulator you can play around with.
Where we go from here is up to you.
This is a series of posts exploring and extending the Micropolis code. You can view the full list of posts here.
-
Will the real Micropolis please stand up?
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!
-
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 V - Cloning and Improving the SimCity UI
- Building A City - Part VI - Wiring Up Ideas Together
- Building A City - Part VII - Refactoring Towards a Managed View
- Building A City - Part VIII - Talking To Other Languages
- Building A City - Part IX - Introducing Micropolis.XNA
- Building A City - Part X - Networking, Multiplayer, and the Web
-
Building A City - Part III
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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"Created Micropolis simulator engine:"</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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060"> 6:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"Loading city file:"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060"> 14:</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060"> 16:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 23:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 5:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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:
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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 5:</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, 'Courier New', 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, 'Courier New', 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">"File"</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"Generate City"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 13:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 5:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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:
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:
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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: #f4f4f4; border-bottom-style: none"><span style="color: #606060"> 4:</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, 'Courier New', 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, 'Courier New', 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">"Load City..."</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"Open City.."</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"\\cities"</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"All files"</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, 'Courier New', 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">"*"</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"Micropolis City Files"</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, 'Courier New', 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">"*.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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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:
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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 3:</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, 'Courier New', 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">"Dullsville"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 7:</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, 'Courier New', 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">"San Francisco"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 11:</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, 'Courier New', 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">"Hamburg"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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">"Bern"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 19:</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, 'Courier New', 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">"Tokyo"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 23:</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, 'Courier New', 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">"Detroit"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 27:</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, 'Courier New', 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">"Boston"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 31:</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, 'Courier New', 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">"Rio de Janeiro"</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, 'Courier New', 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">"activate"</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, 'Courier New', 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, 'Courier New', courier, monospace; border-right-style: none; border-left-style: none; background-color: white; border-bottom-style: none"><span style="color: #606060"> 35:</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, 'Courier New', 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, 'Courier New', 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, 'Courier New', 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.
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
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:
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:
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:
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:
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
Well. 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.