Sunday, September 12, 2010

Roguelike

During last 3 years I spent some of my time trying to learn how to write a roguelike. Roguelikes are cRPG games with no graphical mode, where environment is represented with ASCII characters (for example @ is player character). I discovered my first roguelike - ADOM when I was around 13 and I have always wanted to code one. I decided to write a post about developing a roguelike, because it's a way to summarize some things (and if I won't write it down it will rot on my hard drive for infinity).

I started by reading tutorials about writing games. In general it's very hard to find a good tutorial about game writing. Game development, like any other technical activity is about layers of knowledge - you begin with some simple functions that do only one thing, but you organize them into bigger structures that can provide complicated results. Most tutorials contain only the basic steps, but tell you nothing about the upper layers. The best tutorial about game writing I found is Philip Hanna's CSC207. It's a university course, and contains many good examples, as well as the game engine written in Java. The engine is written in a very generic way. It allows you to start your own project, but it's never optimized for it - so sooner or later it forces you to write your own engine - but at that time you already know what you want and how to achieve it. Very good teaching trick Mr Hanna!

I started my game using Philip Hanna's engine. At the beginning I really liked writing in Java. It was much faster to write than in C or C++. But after a while I became fed up with it - I had to create getter/setter methods for every variable, and the code was hard to change or read. Later I learned some tricks in the Eclipse IDE which helped a lot - automated refactoring and getter/setter creators. But I still wasn't happy with it. Then I found Python.

Python is probably the best language for hobbyists out there - it's fun to use, reasonably fast (at least I think so), and with quite good support. At the beginning Python webpage scared me off because there is some new version of Python 3.0 coming out, and no library works with it. But then I realised that nobody uses it yet, and everything works with Python 2.6 (it's a risky idea to make language backward incompatible).

I decided to start with Pygame - a library which is basically a binding to SDL library written in C. I read some basic tutorials, and started to port my earlier Java code to Python - mostly that meant holding DEL button for a long, long time. Python code is much shorter and easier to read than Java code (however some things are longer - like always including "self" as the first argument to a class method).

Below is a main class of my game - its temporary nickname is Towers :) :

  1. #File: game.py
  2. #Author: MW
  3.  
  4. import pygame, sys,os
  5. from pygame.locals import *
  6. from map import Map
  7. from input import Input
  8.  
  9. # Uncomment the lines below to enable profiling
  10. #import profile
  11. #import pstats
  12.  
  13. if not pygame.font: print 'Warning, fonts disabled'
  14. if not pygame.mixer: print 'Warning, sound disabled'
  15.  
  16. class Game:
  17.    
  18.     def __init__(self,width=800,height=600):
  19.         self._running = True
  20.         self.screen = None
  21.         self.screen_width = width
  22.         self.screen_height = height
  23.        
  24.     def game_init(self):    
  25.         pygame.init()
  26.         self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
  27.         pygame.key.set_repeat(200,50)
  28.         pygame.display.set_caption('Game')
  29.         self.input = Input()
  30.         self.map = Map(self,self.input,100,100)
  31.         self.clock = pygame.time.Clock()
  32.    
  33.     def game_input(self):
  34.         self.input.set_events(pygame.event.get())
  35.         if self.input.get_event(QUIT):
  36.                 sys.exit()
  37.  
  38.     def game_update(self):
  39.         self.map.update()
  40.  
  41.     def game_render(self):
  42.         self.map.render()
  43.         pygame.display.update()  
  44.      
  45.     def main_loop(self):
  46.         self.game_init()
  47.         while True:
  48.             self.time_passed = self.clock.tick(60)
  49.             self.game_input()
  50.             self.game_update()
  51.             self.game_render()
  52.      
  53.          
  54. if __name__ == "__main__":
  55.     towers = Game()
  56.     towers.main_loop()
  57.     # Comment 1 line above and uncomment the lines below to enable profiling
  58.     #profile.run('import game; towers.main_loop()', 'profile.tmp')    
  59.     #p = pstats.Stats('profile.tmp')
  60.     #p.sort_stats('cumulative').print_stats(10)
  61.  

Python allows for both object-oriented and procedure-oriented programming, but my code is rather oo-way. The object of the Game class is created in the if statement at the end of the file. After that the main_loop method is called. At first it calls game_init method and initializes Pygame objects: display and clock. It also creates input object which is a wrapping over Pygame's event module, and map object which contains the rest of the game. Then it loops infinitely through three methods: game_input, game_update and game_render. Such design separates game logic (game_update) from gathering player input and displaying the game on the screen.

So much writing and code still does nothing, but that's the beauty of the OOP - you don't need to know what's inside the object (the implementation) to write a proper code.