Wednesday, January 07, 2009

Pythonia

"I aten't dead"
- Granny Weatherwax


Warning: blog post contains factual inaccuracies. See comments section for discussion.

So I keep meaning to write up my experiences in India, and I keep getting distracted. For example I've developed an interest in clockwork (don't ask).

Even more braincell-consuming is my ongoing effort to produce some sort of computer game worthy of the title. This is going very slowly, mostly because I'm taking a very anal approach to it. In short, I want to make it Pythonic.

An explanation. My favourite programming language, Python, puts a lot of effort into inculcating its users with good programming style. This is something very different from simply being able to program in the language, in the same way that not every writer of English prose can compete with Shakespeare. Good Python code, like any other form of poetry, should be elegant, non-kludgy, readable, evocative... Pythonic.

In particular, Pythonicism discourages "procedural" programming. This is code that consists primarily of single instructions linked by commands to go to a given line. For example, if you wanted to print out the numbers from one to twenty, a procedural approach would look like:

line 1: x = 0
line 2: add 1 to x
line 3: print x
line 4: if x < 20 then go to line 2
line 5: quit

The problem with this approach is that, unless you read and comprehend the entire program, it's a pain to figure out what the blazes it's doing. Let's say you start reading at line 3. What does x equal? From line 4 you can figure out that it's a number, but there's no clear indication of what x starts out as or how it changes over time. This is not a problem for this program as it's so short, but a 1000-line example would soon make your brain explode.

By contrast, a Pythonic approach would look like:

line 1: for x in range(20):
line 2:     print x

If you start reading at line 2, you'll see that the text is indented, which means it's a consequence of some preceding statement. "Oh," you'll say, "I need to look to see what this command is a subclause of. I'll look at the line above." And you look, and lo and behold the line above contains all the info you need to understand x: its starting point, its limit, and its mode of change. Not only is this code shorter, it's vastly more readable. That 1000-line program starts to look manageable.

The Python idiom actually has a very simple root: the principle of minimum power. Always use the most restrictive command that can achieve a given goal. It takes a bit of thought to realise that a "for" statement is less powerful than a "go to" statement, but it's true. A "for" statement imposes certain constraints: the list of values must be predefined, and in any iteration you can only move to the next value in the list.

By contrast, with "go to" statements, you can achieve any pattern of recursion, however convoluted. When you read code, what you're mostly doing is narrowing down the possible explanations for what the program actually does and how it does it. "Go to" statements do not narrow things down at all, so code with them in is harder to read than code that uses more restrictive statements. This is why "go to" statements are evil: any program containing them tends to become an unmaintainable mass of spaghetti.

So Pythonicism makes for nice programs. But it also makes for headaches on the developer's part as he/she desperately wrestles with how to make a program not only functional but elegant. This is not easy. It's like trying to write instructions for an educationally subnormal employee that at the same time read like the most beautiful sonnet.

The example I'm hitting is game design. The game I'm working on is extremely simple in concept: it's a turn-based game, and the actual game logic is not complicated in the least. But I'm having real trouble because I can't see how to structure the code Pythonically. Procedurally, things look like this:

1) Start the program up
2) Draw the menu screen
3) Once the "start new game" option is selected, initialise all the game variables
4) Draw the game-board screen
5) Let the user play a round (updating the board as they go)
6) Let the computer play a round (updating the board as they go)
7) Go to 5

This is perfectly viable... and completely procedural. A more Pythonic approach is still forming in my brain, but certain subtleties have become apparent. Firstly, the user actually has two roles: "dungeon-master" and "player". As dungeon-master, the user gets to choose the game settings (difficulty etc) and save or load games. As player, the user is limited to playing with the pieces they're given - no metagaming.

Secondly, the core logic of the game should be agnostic as to whether a given player is human or AI - no cheating. There will therefore be two equivalent components that provide player decisions, one of which happens to have a lot of AI code in it and one of which happens to have a user interface. The user-interface component will have to be tolerant of occasional switches from player mode to dungeon-master mode. This raises a number of design questions, for example what happens if two players try to use dungeon-master mode simultaneously?

So what's the pay-off from this incredibly abstract approach to designing the game? Well, there are two main advantages. Firstly, once I've got the structure sorted out to my satisfaction, inserting the actual game logic will be a piece of cake: I won't have to worry about unexpected interactions between the various bits of code, because each will have a well-planned interface to the wider world. Secondly, extending the game will also be very easy - for example I could make it a network game with minimal effort.

It's still painful though. That's the joy and despair of poetic Pythonic programming.

5 comments:

Dunc said...

Sorry, but your definition of procedural programming is rubbish, and the "pythonic" alternative you compare it to is (a) equally procedural, and (b) perfectly standard even in primitive versions of BASIC. The main alternative to procedural programming is functional programming, and it's a very different beast.

As for your game design, what you're apparently trying to do is re-invent object oriented programming.

Lifewish said...

You're right about the definition of procedural programming, I used completely the wrong term there. I've stuck a message at the top of the post to warn readers that there's an error. Thanks for pointing it out.

Browsing wikipedia, the closest concept I can find is structured programming, although I'm not sure I'm using that term correctly either. Is there a better word for code that displays a lack of high-level planning?

I stand behind the rest of my post on the grounds that I was assuming a non-technical audience. The example code is indeed lame, but it gave some sense of what it means for code to have "good" style in a way that non-programmers could probably grasp. In fact I've just noticed that the wikipedia spaghetti code page uses the same examples.

Regarding the game design: yes I do know what OOP is. What's bugging me is finding the best object structure to represent the various game components, and figuring out what the permitted interactions should be. I have sod-all experience of designing graphical apps, so this is not easy for me. I'm using pygame, and I really don't like the way it encourages you to structure your program round the needs of the UI.

Dunc said...

Is there a better word for code that displays a lack of high-level planning?

How about "crap"? ;)

Regarding the game design [...]

Can't help you there much mate - I've got no experience of games programming, Python, or pygame. Personally, I'd probably be tempted go for some kind of model-view-controller architecture, but I have no idea how that would fit with your chosen framework. The core of the model would be a Board object, which would have methods to validate and evaluate moves, and would fire events to update the UI. Whether I'd break down the Board into a collection of sub-objects would probably depend on the complexity of the rules.

All that's comparatively easy. The hard bit is writing the "AI" player.

Lifewish said...

It's not all crap code though - that's a far wider topic than I'd have the nerve to examine. It's a particular type of crap, where the code monkey has clearly thought, "OK, what do I get this program to do first? I know, X! And then Y after that. And maybe Z, but only if the moon is blue and there's an R in the month."

The result is code that's brimming over with special cases and exceptions. It's really common in the pensions industry. Partly because all of our "developers" are actuaries rather than programmers, and partly because you can spend ages coming up with some beautiful abstraction only for some idiot in the Financial Services Authority to pass a regulation that breaks your model.

The core of the model would be a Board object, which would have methods to validate and evaluate moves, and would fire events to update the UI.

That's pretty much what I'm feeling my way towards. The problem is that updating the UI is a hell of a job. If you look at, say, the codebase for Endgame:Singularity, you'll see that it's almost entirely oriented round trying to get the UI to do what it's told, with the actual game logic being something of an afterthought. Needless to say, this offends my sense of elegance.

Dunc said...

you can spend ages coming up with some beautiful abstraction only for some idiot in the Financial Services Authority to pass a regulation that breaks your model.

I feel your pain, brother. Truly.