Global Game Jam 2010: Programming
Posted on Jan. 16, 2018Join me as we look at what went right and what went (hilariously) wrong on the technical side
---
Like I mentioned in my experience on GGJ2010:
> I've always wanted to be a game programmer. I will directly attribute my skills (and blind spots) on coding to my early attempts at making a game engine.
>
> Too bad I've never made an entire game. Its always demos and proof of concepts for my engine. That changed when I joined the Global Game Jam on 2010.
This is the first time I made a non-demo game in python and pygame. Frankly, I am quite embarrassed by my code. But everyone have to start somewhere.
# Tl;dr
1. Python is great for prototyping and quick coding
1. First class functions are amazing
1. Good documentation helps
1. You can learn to code with practical programming
1. Having a game programming background made it easier to make a new game from scratch. practice makes perfect
1. Hardcoding is great in hackathons, but there's a limit where it becomes less complex to write a parser
1. My bad programming is hilarious now that I'm more mature.
# Python and Pygame
I've been making a game engine in C and C++ for a while. I felt it wasn't ready for the game jam, so I took it as an opportunity to force myself to learn Python 2.6 and pygame. It was the best thing that I have done.
I've learned more about Python in those two days than a month of studying. This had a profound effect in the way I teach: I want my students to code by forcing them to code. You'd think this is common sense but you'll be surprise at the number of CS and IT class which doesn't do this.
# Speed reading the documentation
I'm a fast reader and I'm no stranger to reading technical books. Python and Pygame have good documentation. I was able to survive the game jam despite having surface knowledge.
# Programming designs used
## Time-based game loop
The naive way of running a game is an infinite loop and measuring the time passed between iteration. This time delta will be used in all movements such as
> player.pos.x += player.velocity.pos.x * (time_delta / 1000)
Unfortunately, this naive design have a problem: if the CPU speed increases, the game unintentionally becomes faster. You can find this design in old video games.
But for the purpose of a game jam, this is a quick way of implementing game loops.
## The state machine
> StateMachine = (
> ("cutscene", Game.BeginningCutscene, "title screen - 1st run"),
> ...
> )
A state machine handles the game state, making it easier to modularize each part of the game.
I've implemented the state machine as tuples. Each tuple contains the state name, state update function, and the next state to run.
In a complete game, I would have the update function return the next state to run. This will give the running state complete control on which exit state to goto.
## Game states as strings
In game engine programming, you use enum or id numbers for identification of game states. This is to minimize overhead when comparing game states. int should be used instead of char, std:string, or pointers.
For this game jam, I used strings. This made it easy to remember what the game state is. In a normal game design, we would have used a database system (file or code based) to handle naming the state and minimize hardcoding. But for a 2 day coding challenge, its better to spend the time working on getting something working first.
# function as a first class data type
I love that function is a data type in Python.
In C, if you want to pass a function as a value, you need to create an address pointer to a pointer to a function. Don't forget to dereference the pointer when calling it. Example.
With Python, you just pass the function.
Coming from C, this felt like taking off a large weight off my cognitive load.
# animation module
Because of my experience in game engine programming, it didn't take a long time to \design\ my animation module. But it still took a while to \make\ it.
I'll put this as a positive because I was able to take prior knowledge and implement it in a different programming language. Not only was I able to prove to myself that I understand how animation systems work, but it helped me in learning Python.
Nowadays, if someone wanted to move to a new programming language, I would recommend that they rewrite one of their systems in the new language. It helps speed up learning.
## Cutscene data is hardcoded
All the in-game text and time values are hardcoded into the code. If during development, I used open() and split(), it would have been possible to assign the development of the cutscene to someone else.
Even though this is a code smell, in the context of the game jam, hardcoding data sped up the development. This is a case of knowing when to hardcode and when to make a parser.
---
# Bad Programming
## Spaces and Tabs? Why not both?
TabsSpacesBoth
The most embarrassing thing I've done is mix spaces and tabs. Well, it worked on python 2.6, so I didn't noticed the problem until I needed to show off the game and it didn't work on newer python versions.
The initial commit of the code at github is the original, unaltered monstrosity. I don't recommend looking at it because this way leads to madness (madness that still needs to be recorded for prosperity).
## pep8 broken
I haven't heard of pep8 at this point of my life. It was only during the first PyCon Philippines that I've heard of this.
As such, my code is hilariously broken.
so hilariously broken
## incorrect use of import
> sys.path.append("./modules")
Look, I wasn't a realtm Python programmer back then. I didn't know how to import from other directories. So I added the modules directory to the list of default import directories.
## Code is not readable enough
Python is famous for being easy to read. I came from a C/C++ background, so my Python code during this time looks more C++ than Python.
Nowadays, I've fully embraced the readability of Python. Here are the things that made me cringe in my old code:
### no documentation
Even though its been years since I last read my old code, I can still somewhat understand what old me wanted to do. And that's because I've written that code. I think all programmers have an innate ability on how they wrote their code because of all the time they spent thinking deeply.
But one rule of readability is to write your code for the programmer and not for the computer. This is why some programming languages are hard to read: they are written for speed and working close to the hardware.
Another rule is to keep this quote in mind:
> Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability.
> -- John Woods
Nowadays, I try to write code to be as simple and self-documenting as possible. And if I can't, I'll add docstrings.
### no unpacking
Unpacking is a great way to write self-documenting code.
> for State in StateMachine:
> if State[0] == CurrentState:
> Result = State\1\
can be rewritten as
> for state, func, exit in StateMachine:
> if state == CurrentState:
> result = func(tick, EventList)
## Character data is hardcoded in python and not as a text file
Remember earlier that I put hardcoded data as a positive because of time constraints? It turns out to be a bad idea for the character data. This have a more complex data structure than the in-game text data. A text parser should have been used.
I've written the character data as code because I was avoiding making a text parser. I have made the incorrect assumption that it would be as hard to write as in C.
Nowadays, I'm no longer afraid of text parsing. I would have rewritten the character data using open() and split().
> def Data():
> idle_animation_files = ["m_01.png", "m_02.png"]
> idle_animation_speed = [150, 370]
> # ...
>
> return CharacterData("muscleman/",
> idle_animation_files, idle_animation_speed,
> ...)
can be rewritten as:
muscleman.txt
>m_01.png, m_02.png
>150, 370
and
>def load(filename, animation_dir):
> data = []
> with open("muscleman.txt") as f:
> for line in f:
> row = line.split(",")
> data.append([value.strip() for value in row])
> return CharacterData(animation_dir, *data)
>
> load("muscleman.txt", "muscleman")
I would also add code inside CharacterData to cast the animation speed values as int.
The original code have a leading slash for the directory because I concatenate the paths. It would be cleaner to use os.path.join instead.
---
Overall, I had a lot of fun with the first Global Game Jam. I am glad to have taken part as it influenced the kind of programmer I am today.
And while simple, the game design is fun and is still playable.