Adam Rumpf: Math Person

Combat Turtles

GitHub Link

This is a Python module for programming turtle robots to compete against each other in arena combat. See the online documentation here for a complete guide.

Combat Turtles is meant as a learning tool for intermediate-level Python students. It defines a combat game in which programmable turtle robots move around a battlefield firing missiles to destroy each other. A parent class, TurtleParent, defines a variety of basic attributes and methods for these turtle robots, and an ai/ folder contains a variety of subclasses of this parent class which define different turtle AIs. The main driver script combatturtles.py loads selected AI subclasses to compete against each other in combat.

The player can create their own turtle AIs by extending the TurtleParent class and overwriting a few key methods. The game is run using discrete step events (at a rate of approximately 30 steps/second), with each turtle defining its actions on a per-step basis. Custom AI submodules (in the form of standalone .py files) can be added to the ai/ directory to import the player's AI into the game. Several example and template subclasses are included in this directory to get the player started. Python students might enjoy competing against each other to see whom can come up with the best AI, while Python instructors might consider running a class tournament to encourage students to learn more about object-oriented programming.

Screenshots

Project Background

When I was but a mere larval game developer, I stumbled upon a game which was meant as an educational tool to get students interested in programming and AI design. It was an arena combat game in which two robots could move around and shoot projectiles at each other, and the player could write their own AI script to determine each robot's behavior. Then I swiftly forgot about it before actually bothering to write any AI scripts. (I also don't remember what the game is and have been unable to find it since. I want to say that it was developed in 3D Game Studio? Boy, I'm a great raconteur.) But apparently some aspect of the project has haunted my brain for all these years, and I've wanted to make something like it since I started teaching computer science.

I've had the opportunity to teach a few Python classes, and part of the introductory curriculum involves using the built-in turtle module to program virtual turtle robots to move around the screen and draw shapes. Turtle graphics have long been a popular educational tool to get students interested in programming (which is why they're included in the Python Standard Library). They're cool to work with, they can create pretty pictures, and they provide a lot of visual feedback to help students to tie their program to a physical result in a way that pure text programs do not. They even have a connection to my favorite thing, fractals, via the Thue-Morse sequence.

But the built-in turtle objects in Python can't shoot missiles at each other, so I fixed that.

Technical Difficulties

My original intention was to extend the built-in turtle class to include a variety of gameplay-related methods for the player to use in their custom AI modules. In order to properly define the AI's behavior I opted for a discretized time scale so that the player could define, at any given moment, what their turtle should do in the next step. It turns out that the built-in module has a lot of overhead, which led to a lot of slowdown while attempting to execute many step events per second, and so I ended up having to program my own turtle module from scratch.

Lacking access to the graphical capabilities of the built-in turtle class, I used Tkinter to create the game window and all of the in-game graphics. In order to make the module as portable as possible I wanted to stick to using only modules from the Python Standard Library, which resulted in a few more challenges to work around. As far as I can tell there is no built-in command for rotating sprites in Tkinter, so it was not an option to include an image folder for all of the different turtle shapes. As a result I ended up defining all of the different turtle shapes as polygons, vertex-by-vertex, with the coordinates being translated and rotated between each step in order for the image to rotate with the turtle's heading.

As a relative neophyte of Python, one of the most difficult parts of implementing this module was figuring out how to load the user-defined AI submodules in the ai/ folder. Not knowing the number of submodules ahead of time, and not knowing their names, meant that I could not just hard-code them into an import statement, and as far as I know there is no straightforward way to get Python to import every class from every submodule in a folder. After much consulting with my experts, the solution I ended up going with was to have the ai submodule's initialization script search through the folder for any and all AI files and then manually load their names into the module's __all__ variable, and then evaluating a string that included the text version of the module's name.

Handling angles and headings was also challenging for two reasons: The inverted vertical axis and the periodic nature of angles. Anyone familiar with computer graphics or game design will be familiar with an unusual quirk of how the coordinate system is defined. Normally mathematicians like to picture the positive \(y\)-direction as being up and the negative \(y\)-direction as being down, but in computer graphics it is the opposite. This is for largely historical reasons, since screens scan from top to bottom, but it is easy to forget to program everything upside-down relative to how you probably imagine it.

This being an arena combat game seen from a top-down perspective, the inverted vertical axis was not really much of a concern except when it came to dealing with angles. While computer graphics usually invert the direction of the positive \(y\)-axis, they keep the usual orientations of the trigonometric functions the same, meaning (for example) that increasing the argument of the sine function from zero decreases the sine rather than increasing it, so it's pretty easy to accidentally reverse the effects of clockwise and counterclockwise rotation. In order to preserve the mathematical definitions of the trigonometric functions, and to preserve the idea of angle increase corresponding to counterclockwise rotation, I ended up having to invert the sign of the \(y\)-coordinate within any calculations involving angles (although some people might see that decision as a sine sign sin).

In addition, when angles are used as navigational headings (as they are in turtle graphics), they have an inherently periodic nature. Adding one full rotation to any heading produces an equivalent heading, which requires some extra care when defining mathematical operations. Our standard idea of the "distance" between two numbers is simply their difference (i.e. absolute value of one minus the other), and numbers with a greater difference are intuitively "further apart" from each other, but that idea does not carry over to headings. For example, the standard numerical "difference" between \(1^\circ\) and \(359^\circ\) is just \(|1^\circ - 359^\circ| = 358^\circ\), which might intuitively lead us to conclude that the two headings are very different. However if we look at a compass we can see that they are really only \(2^\circ\) apart, since we can add \(2^\circ\) to \(359^\circ\) to get \(361^\circ\), which is equivalent to \(1^\circ\).

In order to deal with these sorts of calculations I wrote a small utility module that defines an Angle class, with overloaded magic methods to make them act more intuitively like headings for the purposes of numerical calculations. Among other things this class normalizes its heading between calculations to always produce a result within \(\pm 180^\circ\), and it defines comparison operations based on whether the smallest angle between two headings is clockwise or counterclockwise. The module was written for use in this project, but you can find a standalone version here, which you might find useful for other projects involving headings.