Tuesday, April 26, 2011

Python C Extension example

Writing a Python extension in C is intimidating. After a week learning the Python/C API, here's a very simple example for a Python C extension, with comments and compilation instructions.
Also on collabedit with highlighting: http://collabedit.com/mj742
//cTest.c

#include

//an example method:
static PyObject* py_double(PyObject* self, PyObject* args){

//self is always here, but only used if it's an
//instance method.
int a;
PyArg_ParseTuple(args, "i:py_double", &a);
//"i:py_double": i for extract one int.
// Py_Arg_ParseTuple(args, "isfo:py_double", &a, &b, &c, &d);
// would extract an (i)nt, (s)tring, (f)loat, Py_(O)bject
// to a, b, c, d respectively.
// After the colon is a string used for exception printouts
// so if this causes an error we'll know it came from here.
return PyInt_FromLong(a * 2);
}

//table of methods to include in this module:
//it's an array of type struct PyMethodDef.
static PyMethodDef test_functions[] = {
/*{python name, c pointer to func, arg format, doc string},*/
{"doubler", py_double, METH_VARARGS, "doubles an int"},
{NULL, NULL, 0, NULL}//sentinal value
};

//This functions is called by Python when you import the module:
//this macro handles void + extern for python module init functions:
PyMODINIT_FUNC
initcTest(void){
Py_InitModule("cTest", test_functions);
//params: module name, method list
}

/*
The name (here cTest) must be consitent in:
1. the filename cTest.pyd (Windows) or cTest.so (Linux)
2. the init method initcTest(void)
3. the first parameter to Py_InitModule("cTest", test_funtions).

note that the name does not have to start with 'c'.

Compiling:
Windows & MSVC:
I use the Visual Studio Command Prompt. These things can also be
configured in the IDE.
1.configure your environment so that the Include path
includes c:\python26\Include (change this for your python location.
Where ever Python.h is). Using the Visual Studio Command Prompt:
> set include=d:\python26\include;%include%
2.compile this to a .dll. Using the Visual Studio Command Prompt:
>cl cTest.c /LD
3.rename it to cTest.pyd
>move cTest.dll cTest.pyd
4.try it in Python!
>python
Python x.y blah blah blah
>>>import cTest
>>>cTest.doubler(3)
6
>>>
Linux & gcc:
1.build an object file:
$gcc -c cTest.c -I/usr/include/python2.6
(change this for your Python include location, where ever Python.h
is.) Note: sometimes Python is installed without the header files.
in Ubuntu you need to get the Python-Dev repository.
2.create a static library:
$ar rcs libcTest.a cTest.o
3.make a shared library:
$gcc -shared -W1,-soname,libcTest.so.1 cTest.o
4.try it in Python!
$python
Python x.y blah blah blah
>>>import cTest
>>>cTest.doubler(3)
6
>>>
*/

note: PSF recommends compiling these using disutils instead. I'm sharing this because of my interest in how the low-level interaction works.

Tuesday, April 12, 2011

pyweek: nanite_fight

I participated in pyweek for the first time last week.  It's a twice-yearly competition to make a game in Python in one week.  I didn't start until Wednesday (looks pretty par for the course), but it was my first time spending 12-18 hours every day on a programming project.  I rather liked it. 

It was also the first time that I aimed to design a relatively small, contained game.  Without resources to develop much content, it makes sense to aim for really emergent gameplay:  a small set of options that can be combined in many different ways to create a large number of outcomes (e.g. the game Go).  

Design
nanite_fight is a programming game.  You get conditions and actions, a limited coding space, and function calls to jump around the code.  You can change the code while it runs.  If your nanite shoots another nanite it makes it into a copy of itself that independently runs through the same code.  A second player can do the same thing.

That's pretty much it, but it does allow for a huge number of permutations.  Unfortunately I didn't have time to make a great interface for two players playing at the same time (but you can load saved scripts from two players).  Also, the skill-curve seems diminishing: a nanite that just turns and shoots is way ahead of a nanite that just shoots.  You can make a nanite that will consistently defeat it, but it will take substantially more time.  Defeating that nanite will a lot of work.  There are too few opportunities to learn a new trick and gain an advantage.

Programming
I set a goal to write this outside of my usual object-oriented paradigm.  I didn't define a single class, and a nanite is essentially a struct of ints in the form of an array.  

All the nanite programming is handled with first-order functions.  The code grids are 2D arrays of pointers to functions, and the nanites store the row and column they're on, do it, then move to the next. 

I made extensive use of hash tables in handling both input (which is all drag-and-drop) and visual and audio output.  The code grid is an array of functions, so the view has a table with function keys and icon values.  Each time a nanite takes an action, another table looks it up in a table with function keys and sound effect values. 

Wednesday, January 5, 2011

2D RTTI in collision detection

RTTI is run time type information, e.g. "if isinstance(object, Ship):".  The action depends on what (sub)type an object is.  It's usually better to make each class handle it's own special cases by overriding an inherited method.

Here's an exception: the action depends on what each of two (or more, I suppose, but I haven't dealt with that) objects

are. This comes up doing collision detection and resolving the collisions. I'm writing a game, and I have different objects floating around in space- parts, bullets, ships, planets, etc. They can hit each other, and what happens depends on what the parts are. If a ship hits a part, it collects it. If a ship hits a bullet, the ship takes damage and the bullet is destroyed.

One option is to keep separate lists of each type of objects, and do:
for bullet in universe.bullets:
   for ship in universe.ships:
      //handle case

for part in universe.parts:
   for ship in universe.ships:
     //handle case
//etc. - O(n^2) cases

  • This means having to maintain many more lists.  If a new type of object is added, Universe needs to gain a new list. 
  • It will make collision optimizations (like quadtrees or sorting) much more complicated. 
  • It does avoid any RTTI, but Universe still needs to be aware of all the possibilities, so it isn't any more abstract. 

Another option is to force everything into instance methods:
class Ship:
    def collide(self, other):
       if isinstance(other, bullet):
             //handle case
       else if isinstance(other, part):
            //handle case
class Bullet:
     def collide(self, other):
          if isinstance(other, ship)
//etc.

  • This is nasty.  
  • It means that everytime you add a new type of object, every other object has to gain a new case!  This maintenance is the antitheses of encapsulation.  
  • You get the choice of doubling all the code (Bullet can handle Ships and Ship can handle Bullets) or creating a precedence list and ensuring that (Bullet, Ship) and (Ship, Bullet) both result in Ship.collide(Bullet)--never Bullet.collide(Ship), which will take more RTTI. 
  • It's also the way you end up doing things if you start by trying to follow the usual OOP solution to RTTI. 

I believe that the best solution is to write one big, ugly function. The function should work like a table, with types on the top and side, and how to handle each type of collision in the cells:

def collide(a, b):
    if isinstance(a, Ship):
        if is instance(b, Part):
             //handle case
        else if isinstance(b, Bullet):
            //handle case
    else if isinstance(a, Part):
          if isinstance(b, Ship):
               //handle case
//etc.

  • This is ugly, but all the ugliness is encapsulated in one function, so everything else can stay in blissful ignorance.
  • When adding a new object type, you have to dig back into this function and add the necessary branches.
  • This function makes extensive use of RTTI calls, but nothing outside it should have to. 
  • If you're careful and keep to the table analogy, the function will have an intuitive ordering and it will be clear what needs to added when.
  • An optimization can remove the redundant triangle of the table:

if isinstance(b, Ship):
      a, b = b, a
if isinstance(a, Ship):
        if is instance(b, Part):
             //handle case
        else if isinstance(b, Bullet):
            //handle case
//now nothing below will have to handle cases where b is a Ship.
if isinstance(b, Part):

      a, b = b, a
if isinstance(a, Part):
          if isinstance(b, Bullet):
               //handle case
//etc.