Why learn to make games?
This year, at PyCon, I'll be joining Richard Jones to teach a tutorial on PyGame. I'm no expert at PyGame (he is), so I'll be covering game design and theory, since it's something I wish people put more thought into, rather than impulsively shoving bits of sucessful franchises into the empty husk of a big title, like Dr. Frankenstein trying to sort out what he's going to do with all these leftovers.
Ahem.
Why would someone want to learn to write a game? We've all heard about the hell that is working in the gaming industry, and I've met very few veterens of Python that have any interest in joining it. We all know that being the next Minecraft is a one-in-a-million shot, and that even Minecraft came to the table with decks stacked (Notch is a pretty smart dude, and had been doing this for a while). If you want a game, why shouldn't you just buy one?
It makes you a better coder
Games are finicky things. Unlike systems where all we're doing is moving around bits of text and the occassional image, games are practically nothing but images. Even the text is treated as an image. You have to be more conservative with your resources, watching how every function affects the load on your system. Sure, that ponderous animation you wrote up works fine when you just have one little dude on the screen, but what about when you have fifty? Or add particle effects, or explosions? You know you want explosions, so maybe you should tidy that up now.
It also forces you to organize earlier. You can't just dive in with a game. You need to have a plan, because at the very least, you need some graphics. If you need graphics, chances are you're going to mock something up, which will at least give you a vision of where you're going. I know many developers who never bother to do a mock-up when doing a web app, figuring they'll work that out 'later.' Writing for a game opens your eyes to how much easier having a vision makes development.
You will finally learn to make graphics
Every developer should know how to make their own graphics. Yes, there are designers in the world, but few of them will willingly give up their Saturday to make you an orc, gratis. Now is the time that you'll learn that making okay game art is not as hard as you've been lead to believe. It just takes time.
It won't be fabuluous, but there was a time when everyone was taught how to draw. It wasn't seen as teaching art: it was a life skill. Okay, a life skill for the monied, but still. Making graphics should be one of your life skills. If your game does turn into the next Minecraft, you can have a visual artist overhaul the thing for you once your funding rolls in.
It's crazy awesome fun
Oh my goodness. It is such crazy awesome fun.
I didn't think it would be this much fun, honestly. I thought, meh, making websites is easier, I can do most of the prettifying with CSS and webfonts. Games are work.
Games do take a lot of work. I spent a lot more effort getting my little guy to move around than I did creating fake bank for my kids. It was addicting, though. Once I got him to move, well, I had to make some monsters, and move them around, too! And chests, oh, I needed chests! And the chests need things in them, what will I put in them...? It's layers of fun, because everything you make could have something even awesomer inside it!
I could have spent my mornings playing a game that was already done, but I found myself having more fun hacking at one that will never sell a million units, and that,hell, if I get 100 downloads, I'd be dancing. It made my kids laugh when I replaced the walls with silly tornados, or let them make me a new tileset. My daugher now understands that when I change the code, I change how my little man acts, or how many monsters there are, or what is in the chests. She thinks that's awesome.
And you know what? It totally is.
Pygame: Getting started
As I've mentioned before, I'll be helping Richard Jones with a tutorial on PyGame at PyCon this year. When I agreed to help him, I admit, I knew nothing about PyGame. I was coming on as someone who would talk about tropes and designs and pitfalls (and the crusher of a few dreams). I thought it would be wise to pick it up, though, so last month, I started playing around with it.
Getting started
System: Linux Mint 11
The first thing I tried to do was to get PyGame installed into a virtualenv. This went... poorly. gcc kept throwing the dreaded "Status 1" error that makes me want to put my fist through something. I spent the better part of an hour Googling and trying to tweak the env, until I finally said Screw it, and tried a system-wide install.
Worked first time.
Fine. Be that way. Whatever.
System: Lion
Curious, I tried the virtualenv trick on Lion, and came up with errors there, as well. This time, though, doing a global install from source failed just as badly. I ended up having to use one of their pre-packaged distros. I hate doing that. I always come away feeling dirty.
Round zero - Decisions, decisions
I'm not starting with zero to be cute. I'm starting with zero because there's something very important that needs to be done, once you've established that your machine can, in fact, work with PyGame.
You have to figure out what your first project is.
Now, I have some dream games that I'd love to make. I'd love to make a sim, or a quirky RPG, or a sandbox game, but I don't even know if I'm up for making Pacman at this point.
Rather than spending weeks writing out requirements for a game before I know how to make one, I decided to just copy something that's already been documented to hell and back. I decided to make a roguelike.
Roguelikes, one of the worst named genres ever, are nearly as old as the personal computer (if you discount the ones that weren't mass-produced). The basic premis is that dungeons are automatically generated and populated, and you descend levels until you get to some big baddie a the bottom. I wouldn't have to think about requirements when you start coding, as they're already written out for me.
Why not just make my dream game? Because there are subtle issues that crop up, and how you decide to solve these issues can impact design decisions down the road. You're already going to screw things up. Why screw the whole pooch?
Now that I knew what I was doing, I knew I would need some graphics. Sure, I could just make circles and squares and make do, but if I knew I was going to move to a graphic game, why not start out right? After struggling with Gimp, I was able to produce three things I knew I would need: a player, and a board.
I also had a list of things that I knew I wanted working for my first attempt:
- I wanted a nice background
- I wanted darkness where the player had not been
- I wanted the player to be able to move
- I wanted a sprite to represent the player
The list looks skimpy, but trust me, while learning you want a short, easy list. Impementation can be more subtle than expected.
Round one - Diving in
And here is where I made my first bad decision: I didn't start out with an object oriented design. My only excuse was that I was excited, and I was just trying to get the screen to pop up and look pretty.
# INTIALISATION
import pygame, math, sys
from pygame.locals import *
screen = pygame.display.set_mode((1024, 768))
player = pygame.image.load('dude.png')
clock = pygame.time.Clock()
k_up = k_down = k_left = k_right = 0
direction = 0
position = (300, 300)
BLACK = (0,0,0)
WHITE = (255, 255, 255)
def main():
while 1:
clock.tick(15)
hor = 0
vert = 0
for event in pygame.event.get():
if not hasattr(event, 'key'): continue
if event.key == K_ESCAPE: sys.exit(0)
if event.key == K_LEFT: hor = -25
if event.key == K_RIGHT: hor = 25
if event.key == K_UP: vert = -25
if event.key == K_DOWN: vert = 25
x, y = position
x = x + hor
y = y + vert
global position
position = (x, y)
screen.fill(BLACK)
screen.blit(player, position)
pygame.display.flip()
if __name__ == "__main__":
main()
The above certainly does that, as well as lets my little guy move around. There's a little bug in the above code that followed me around for a while, until I fixed it much, much later. No, I won't be telling you what it is... yet.
Round two - Doing it right
Happily, I remembered how to actually code properly, and tossed the above screwing-around code for some proper classes. Had I been thinking farther ahead I would have split things even more. As I've said before, making games is crazy fun, so it can be easy to get carried away.
# INTIALISATION
import pygame, math, sys
from pygame.locals import *
BLACK = (0,0,0)
WHITE = (255, 255, 255)
TILES_ACROSS = 20 - 1
TILES_DOWN = 15 - 1
class Map(object):
def __init__(self):
self.map = []
for i in range(TILES_ACROSS+1):
row = []
for j in range(TILES_DOWN+1):
row.append(0)
self.map.append(row)
def clear_block(self, position):
x, y = position
column = x/50
row = y/50
print "Column %s, Row %s" % (str(column), str(row))
self.map[column][row] = 1
def print_ascii_map(self):
for row in self.map:
print row
class Game(object):
def __init__(self):
self.screen = pygame.display.set_mode((1024, 768))
self.player = pygame.image.load('dude.png')
self.bg = pygame.image.load('rainbowbg.png')
self.clock = pygame.time.Clock()
self.direction = 0
self.position = (300, 300)
self.map = Map()
self.map.clear_block(self.position)
self.run()
def draw_darkness(self):
print self.map.map.__len__()
for row in range(TILES_ACROSS+1):
for col in range(TILES_DOWN+1):
if self.map.map[row][col] == 0:
pygame.draw.rect(self.screen, BLACK, (row*50, col*50, 50, 50))
def move(self, hor, vert):
x, y = self.position
x = x + hor
y = y + vert
if x > TILES_ACROSS * 50 or x < 0 or y > TILES_DOWN * 50 or y < 0:
return
self.position = (x, y)
self.map.clear_block(self.position)
self.screen.blit(self.bg, (0, 0))
self.draw_darkness()
self.screen.blit(self.player, self.position)
pygame.display.flip()
def run(self):
while 1:
self.clock.tick(30)
hor = 0
vert = 0
for event in pygame.event.get():
if not hasattr(event, 'key'): continue
if event.key == K_ESCAPE: sys.exit(0)
if event.key == K_LEFT: hor = -25
if event.key == K_RIGHT: hor = 25
if event.key == K_UP: vert = -25
if event.key == K_DOWN: vert = 25
self.move(hor, vert)
self.map.print_ascii_map()
def main():
while 1:
game = Game()
if __name__ == "__main__":
main()
Now, everything is snug in its own class. By accident, I made a good design decision above, making a master class called Game that would keep track of all of the pesky details within the game itself.
Explanations
At this iteration, I had a map, a player, a layer of darkness to obscure where the player hadn't been yet, and absolutely no documentation. Bad Katie. As I said, the fun can get in the way of being a good person, sometimes. Here are some explainations of what I have above:
Map
I needed something that would keep track of what blocks had been cleared. Though there was nothing to hide yet, I knew that I would eventually want to implement fog of war. I also tossed in a sanity checking function that prints out the ascii map on request. It's easy to screw up movement, even when it's just up, down, left, and right.
Game
The game itself keeps track of all the parts of the game with an init that's only going to grow. I also put it in charge of drawing everything, a move that I may regret later. For now, the player is a nebulous quality within the game itself. This is something that would have to change later.
Blits and flips
I admit, these confused me at first. A blit is just an image that is being drawn onto the screen on top of whatever else is already there. So, I draw the floor, then I draw the player. If I reversed this, the player would be under the floor, and the game would be a bit confusing.
Once an image is drawn, however, it isn't shown. PyGame draws everything, then reveals what it's drawn by 'flipping' the screen. It makes the graphics much more smooth, apparently. There's also an option to reveal the changes to just a part of the screen, but I wasn't rad enough at this point to play with that.
Event keys
Once the game is started, it starts listening for events. Any event that happens, such as a mouse click or a key stroke, goes into pygame.event. It's up to us to determine which of these events we want to actually use. I only care about movement and quitting, so I just watch for left, right, up, down, and escape. That's right. No WASD. I had enough to debug at this point. I didn't need to add optional movement choices.
Want to play with what I did?
Next
I get serious about splitting things up, and we find Treasure!
PyGame: Treasure!
Want to see where I am now? Here's the code!
Setting some defaults
Now that I was hip deep in moving my little dude around, I realized that I was going to need some defaults. In games, nothing is a given. You might need to make the board bigger, smaller, change the size of graphics, or even swap out colors.
BLACK = (0,0,0) WHITE = (255, 255, 255) COLUMNS = 16 ROWS = 21 TREASURES = 10 TILE_SIZE = 48
Had I been less eager, I would have started off with defaults written out. Seeing as how this was only the second session, however, I think I can be forgiven.
Since a rogue-like is a fairly basic game, I didn't need too many defaults: a few colors, columns and rows, and the size of the tiles for movement.
Colors in PyGame
Colors in PyGame are set through triads of red, blue, and green. Colors are the first thing I recommend you go ahead and add to defaults, as they can be annoying to puzzle out later.
I don't have any specific tool that I use to calculate RGB. I tend to use what's at hand. When I'm using my Mint box, I use Gimp. When I'm on my Mac, I pull up any one of a million, billion websites that do color conversions. What I'm looking for is RGB values:

I can take those, and make some default colors that will make reading the rest of my code so much easier.
BLACK = (0, 0, 0) WHITE = (255, 255, 255)
This way, if I decide later on that I want my whites to be off-white for usability reasons, I just have to change one value.
Why go binary?
I made a major change at this point, and it wasn't adding treasure: I moved to base 2.
Why do this? I'd never really thought about base 2 numbers, aside from the fact that they would crop up in computing. It was just one of those things that you learn to expect. I can't recall ever having it explained to me, and it wasn't quite interesting enough for me to look into.
I stumbled into the reasoning by accident, while reading, of all things, a crazy novel about religious extremists and MMO gold farmers.
Having everything in base two makes everything halve more easily, which is something you do all the time when making a game.
Let's say I have a tile width of 50px (which is what I started with). If I want something to be half on that tile, I can move it 25px. But what if I want it to be on a quarter of that tile? Now we're at 12.5... and there is no such thing as half a pixel.
As soon as I was out of the bath (yes, I read in the bath), I switched my tiles to be 48x48. Now, we were divisible all the way down, something that would become very important later.
Treasure!
Now that I had a little guy moving around a dungeon, I decided he needed something to find in the dungeon. Treasure! Finally, I had my Treasure class do something besides sit there and look sad.
ALL_TREASURES = {
"hat": "Quite cunning",
"sqord": "Knock-off sword. Probably from Ikea.",
"book": "What the hell are you going to do with this?"
}
LONG_STRING = "X" * 50
class Treasure(object):
''' Not implemented yet.
'''
def __init__(self):
k = ALL_TREASURES.keys()
r = random.randint(0, ALL_TREASURES.keys().__len__()-1)
self.title = ALL_TREASURES.keys()[r]
self.description = ALL_TREASURES[self.title]
The Treasure class, in its pupa stage, only has two things: a title, and a description. Why not go hog wild? Why not give it attributes and effects and all the awesomesauce I can?
Why? Because there's more things to consider before I go crazy. Being a little bit clever with the titles and descriptions was really all the complications I want at the moment. After all I have to get them on the board.
I decided to go with adding another layer to the Map. It worked for making the darkness, so it should work for treasures. Adding random treasures turned out to be quite easy, as was clearning them. Huzzah! This is going to be super easy to write!
Viewing area and fog
I realized, after placing treasures, that my rogue needed to be able to see a bit better. I paused on the treasure feature to give my guy a field of view.
I modified my move function to clear the blocks around the rogue, but only slightly. I still wanted some shadows, so my cleared layer got some subtle tweaks. Now, if the rogue hadn't been in a block, or by it twice, that block would be shadowy.
The 'walked by it twice' bit was an accident, but one that I ended up keeping. I imagined my little guy squinting into the darkness, looking really, really hard for treasures, worrying about the day I decide to add monsters.
One thing that bit me here: if I moved my rogue to the bottom of the screen, the top would light up. I was completely confused until I read up on some subtlties with python's arrays. They accept negative numbers as the index.
>>> a = [1,2,3,4,5] >>> a[-1] = 0 >>> a [1, 2, 3, 4, 0]
Back to treasures!
Now that my guy could actually see treasures, I could get back to making them awesome! Placing and picking up treasure was a cinch, but I was pretty sure that the player would want to know what they had picked up. Time to add alerts and an inventory.
I already had some dead area, due to the size I'd made my tiles and board. Whenever my guy ran across a treasure, it would be added to an inventory, and its title and description would be displayed in an alert area at the bottom of the screen.
I discovered very quickly that text likes to persist in PyGame. My hacky solution was to write over the existing text with a bunch of dark letters before writing something new.
# In the Game class... def draw_alert(self, alert, color=WHITE): ''' Draws the alert box at the bottom ''' self.alert = self.font.render(LONG_STRING, True, BLACK, BLACK) self.screen.blit(self.alert, (0, 790)) try: pygame.display.flip() except: pass self.alert = self.font.render(alert, True, color, BLACK)
This is a horrible, horrible solution, and it fills me with shame. Works, though.
Is that bug still there?
Oh yeah. Found it yet?
Next time
We add monsters!
Pygame: Monsters and Walls!
Where am I in my code base? Here!
Walls
One feature I know I'm going to need in the future is rooms. A dungeon isn't very interesting if it's just one big featureless room. No, no, you need a lot of little featureless rooms to make your game sing!
I considered briefly coding for rooms, but decided to start with something even more granular: a wall. It ended up being the right decision. Several design decisions popped up almost immediately.
- What do I do if my guy runs into a wall? Do I assume that was a mistake, or make a turn pass?
- What if a monster runs into a wall? Is everyone running around, smacking their faces into walls?
- What about light radius? Should that pass through walls?
That last one seems like a no brainer, right? Of course it shouldn't! You'd be surprised how many modern games allow for the user to sense what's beyond a wall. Next time your game has a minimap, check out what happens when you belly up to a wall. Many will show a ghostly outline of what's just beyond your field of view.
Either all of these developers are lazy, or there's some advantage to making players mildly psychic.
I decided I would solve this in the time honored way that many developers have solved such conundrums before me: ignore it for now. Chances are, if I code the solution for this now, I'll just have to redo it when I add in rooms. Might as well save the effort.
As for the other issues, I decided that running into walls is a perfectly valid behavior for people and monsters to have. I do it all the time. Why should they be any different? When I make smarter monsters, I may have them see walls and not run into them. Maybe.
I'm not sure I want monsters that are more perceptive than me.
Monsters!
Now that we have something for my little guy to pick up, we now need something to smack him back down.
My first monster isn't really all that terrifying. He's not that bright. He can't do any damage. He can't chase after the hero. He's really quite dull.
Dull is good, though. Dull is a good place to start coding. I decided the only thing I wanted my monster to do was move. That should be easy enough, right? I made another layer in my Map class, and expended its init for adding monsters to random tiles.
Once they were technically on the board, all I had to do was render them. This, I added to the Game class, since that was doing all the heavy work of rendering. For once, things went as I wanted. The monsters were added, each its own distinct object, and if I took darkness away, I could see that they populated the full bounds of the board. Huzzah!
Then I decided to move them.
I had the monsters move every time the hero moved in a random direction. I'd already learned about testing the boundaries of the board, so they didn't try to move outside of the board. If they tried to move onto the same tile, however... they overwrote each other. Only one monster would emerge from the tile, the other doomed to the garbage collector. They also had the annoying habit of jumping over my walls, which is not something derpy monsters should be doing.
Dammit.
Checking tiles
One incredibly important function I hadn't realized I needed yet was something that would check to see if a tile was open. That got written in a hurry and added to the Map class.
def is_block_empty(self, row, col):
if not self.treasure[row][col] \
and not self.monsters[row][col] \
and not self.walls[row][col]:
return True
else:
return False
It's not the most elegant bit of code, and it's highlighting where my method of 'just add another layer' may have issues down the road. For now, though, it works. I'm now starting to think about version 2.0, and how I might better deal with layers.
Why not just stop and fix it now? Because I'm not sure I know how I want to fix it. That's the beauty of prototypes. At the end of the day, you trash the project. The product of the prototype isn't something you use: it's all the lessons you learn.
Inventory
My guy has treasure, so now we need a way for the player to see what they've collected. After all, monsters imply fighting at some point, so the sqord he's been toting around will be for more than show.
Rather than just have a list of items stored somewhere, I decide to make the Inventory a class of its own. Sure, it's just a list of things right now, but inventory management is one of those things that can get stunningly complex. Do we have a weight limit to the bag? A volume limit? Do items take up varying amounts of volume, like in Diablo, or is it a thing-per-slot system, like Torchlight? Are we going to completely screw with the player by making it a weight AND volume system? Or is this bag simply a Bag of Holding, that can hold an infinity of objects?
With all these unanswered design decisions (I don't know how much I dislike the player, yet), it's better just to create a simple class that I can build off of, right now.
class Inventory(object):
''' The inventory for the player.
'''
def __init__(self):
''' Sets up the initial blank inventory.
'''
self.inventory = {}
def get_items(self):
return self.inventory.keys()
def add_to_inventory(self, item):
''' Adds an item to the inventory
'''
try:
self.inventory[item] += 1
except:
self.inventory[item] = 1
This set-up may not be perfect, but it will allow my little guy to carry more that one type of an item. I know I'll have to print out my inventory, so I might as well have a function that will return all the unique items in the bag.
Now that I have this nifty function for giving me all the items in the inventory, I suppose I should display them, huh? Displaying the inventory can be done in any number of ways: key stroke, icon, constant HUD, text listing, etc. I would like to say that I thought long and hard about how I would display my inventory, but that would be a dirty, filthy lie.
I had a big black space to the right side of my screen, and I decided to stick it there.
To the Game class, I added the following function:
def draw_inventory(self):
self.screen.blit(self.inventory_screen, (1008, 0))
items = self.inventory.get_items()
for i in range(items.__len__()):
line = self.small_font.render(LONG_STRING, \
True, BLACK, BLACK)
self.screen.blit(line, (1008, ((i+1)*15)))
pygame.display.flip()
for item in items:
line = self.small_font.render(item, \
True, WHITE, BLACK)
self.screen.blit(line, (1008, \
(items.index(item)+1)*15))
pygame.display.flip()
It's simple, and likely going to change as my UI gets more complex. For now, though, having it on the side means I don't have to hit any keys or icons while I'm trying to debug treasure functionality.
The bug!
By this commit, I had found the movement bug! Basically, every keystroke is, in fact, two events: KEY_DOWN and KEY_UP. Adding a check for KEY_DOWN made it so that I wasn't moving twice for every keystroke:
for event in pygame.event.get():
if not hasattr(event, 'key'): continue
if event.type == KEYDOWN:
if event.key == K_ESCAPE: sys.exit(0)
if event.key == K_LEFT: hor = -TILE_SIZE
if event.key == K_RIGHT: hor = TILE_SIZE
if event.key == K_UP: vert = -TILE_SIZE
if event.key == K_DOWN: vert = TILE_SIZE
self.map.move_monsters()
self.move(hor, vert)
Feedback!
I've been getting super feedback in posts about making the game work better, or about some bugs that people are spotting. Awesome!
Since I code a bunch, then write the blog posts, some of these fixes aren't going to show up for a while. I'm not ignoring you! For instance, Joshua found the another issue I had been having with movement. Marius contributed a better way to add choices to the game.
I was also alerted to an upcoming seven day roguelike challenge by purplearcanist. Though I don't have time to get involved, contests like this can be fun to jump into, or at least watch.
Getting kind of long, isn't it?
Next time, we start actually organizing the code and media, so it's easier to manage and grow.
PyGame: Fog of War and Organization
Where am I? Here!
Organization
Up to this point, I had been diving into the code, head-first, without any consideration as to organizing it. Bad Katie. I took some time to break out my code into more manageable chunks, and finally put all of my images in their own directories. My file structure ended up looking like this:
- roguey
__init__.py
- classes
constants.py
game.py
gamemap.py
items.py
monsters.py
player.py
+ images
+ comps
main.py
README.txt
Looking into the contents of the files, it's not quite the Java-esque style of one class per file, but it's close. While I abhor Java, and only tolerate it until I can use Python and C to make Android applications, this is one pattern that can be okay to copy. I don't plan on keeping it to one class per file, though. The player is one thing that I expect to grow to include all sorts of classes, from Inventory to SpellList to CharacterStats.
Paths
One thing tripped me up briefly, so I'll go ahead and lay out it out here, even though it fills me with shame.
I have an image directory. I'd like to point to it. In fact, I have to point to it throughout the game, since I'm not doing an ASCII version of Rogue.
Python's os library has a great function that returns the current working directory. At first, I thought that it would return the current working directory of the file. It doesn't, because that would be stupid. It returns the cwd of the user. So, in order to make the full path of my images a constant, I had to add this code to constants.py:
import os ... IMG_DIR = os.getcwd() + "/roguey/images/"
Since the game has to be run from the the same directory as main.py, this works. For now.
Also, now that I had all my class files in another directory, it became important to import them where needed. In the end, my imports ended up looking a bit like this:
Darn you, Python (Maps)
If you plan on having a map class in your game, I highly recommend training yourself from the start to call it anything but 'Map'. Initially, I called my map 'Map', and this caused all sorts of strangeness when I separated things out. Python already has a Map class, and was a bit uppity that I had my own.
I renamed the file to 'gamemap.py', so Python and I are friends again.
Fog of War
Fog of war is a nice way of making the player have memory, but be a bit less psychic. Essentially, the player can remember where they've been, but that doesn't give them the magical ability to know what's going on in those places. The simplest version of this is to not show monsters until they're in the player's field of vision.
There are some cool variations of this that I may play with in the future. For example, a listen check might reveal that a certain type of monster is in the next room. A spell might reveal creatures that are far away. Or, if I want to be really dastardly, the type of monster might not be revealed until they're in direct combat. Perhaps torches in the distance might show the player what's in its immediate radius, with blobby outlines further out.
It's a cool mechanic to play with.
For now, I'm keeping it simple. I added another layer, this time containing the view radius of the player. If a mob isn't in that radius, it isn't rendered. Everywhere else he's been on the board is now covered in a grey overlay.
# In gamemap.Map
def set_current_position(self, position):
row = row/TILE_SIZE
col = col/TILE_SIZE
self.current[row][col] = 1
for i in range(RADIUS):
if row-i > 0:
self.current[row-i-1][col] = 1
if row+i < ROWS-1:
self.current[row+i+1][col] = 1
if col-i > 0:
self.current[row][col-i-1] = 1
if col+i < COLUMNS-1:
self.current[row][col+i+1] = 1
for i in range(RADIUS-1):
if row-i > 0 and col-i > 0: self.current[row-i-1][col-i-1] = 1
if row-i > 0 and col-i < COLUMNS-1: self.current[row-i-1][col+i+1] = 1
if row+1 < ROWS-1 and col-i > 0: self.current[row+i+1][col-i-1] = 1
if row+1 < ROWS-1 and col+i < COLUMNS-1: self.current[row+i+1][col+i+1] = 1
# In game.Game
def draw_darkness(self):
''' Draws the darkness and shadows on the board. 0 is dark, 1 is in shadows,
'''
for row in range(ROWS):
for col in range(COLUMNS):
if self.map.cleared[row][col] == 0:
if not self.map.current[row][col]:
pygame.draw.rect(self.screen, BLACK, (row*TILE_SIZE, col*TILE_SIZE, TILE_SIZE, TILE_SIZE))
if self.map.cleared[row][col] == 1:
if not self.map.current[row][col]:
shadow = pygame.Surface((TILE_SIZE, TILE_SIZE))
shadow.set_alpha(200)
shadow.fill(BLACK)
self.screen.blit(shadow, (row*TILE_SIZE, col*TILE_SIZE))
Next!
I explore "Active Gaming," and we talk animation!
PyCon 2012 - Thoughts on the Poster Session
Everyone is posting quick round-ups about PyCon today, but I thought I'd focus on one thing that really got nailed this year: the poster session.
At other conferences, the poster session was often the home to those whose proposals were not quite cool enough to get a talk, but not so awful that they should get outright rejected. I always visited them, but they seemed... sad. There were some great things there, but so few people bothered to go check it out that it looked like a ghost town. Even serving snacks there only ensured people would dash in and out to get cookies and coffee.
This year, we were able to submit something as a poster. This was awesome. The people who were in there with posters? They wanted to be there. They had a message that was uniquely suited to the poster session. I know that for my poster, I really wanted to be able to engage with people more. It was like a two hour questions session.
Also, there were no talks during this time. Everything was shut down, making posters the only game in town. I didn't take pictures (I was constantly talking to people), but the room was filled. Not only was it filled, but it was filled with people who were actively engaging the presenters.
I was delighted to find out that we had supplies for our boards. I had brought thumb tacks and tape, but I'm a paranoid freak about things like this. Even for my crazy number of items, I had more than enough to pin my presentation up.
Recording the posters was genius. I now have another video I can show to people, when I get asked about my talks.
I do wish I could have set up the night before, but I understand that people needed time to break down and set up. I was slightly pouty about missing Guido, but then again, I got to see the awesome robot dance without fighting fifty other people.
Some thoughts I'd like to impart for future poster presenters:
- Printed posters are spendy (up to $250). I printed out mine as slides and arranged them nicely, and got a lot of compliments. Cost: pennies, and it fit in my backpack.
- You will be standing and talking for two hours. Have water at hand, wear comfortable shoes, and have someone you can message to get you coffee or tea. There is little down time.
- Bring some Ibuprofin. Seriously. That standing will kill you by the end. I would have paid $5000 for a backrub.
- Having a partner to present your poster with means you get to go to the bathroom.
- Bring business cards. The Moo containers tack nicely to the board.
- If you pierce your finger while tacking your Moo container to the board, scotch tape can double as a bandaid.
- Do not underestimate the power of QR codes. I put a few on mine, so that people could take my further reading links with them. Almost everyone grabbed them.
I hope that future conferences look to PyCon and their treatment of poster sessions, and copies them.
PyGame: Animation and Active Gaming
Where am I? Here!
Animation
Now that I had my movement working correctly, I had another issue. The bug had the 'feature' of giving the rogue the illusion of movement through a little pause halfway through the movement. Now that the movement was gone, I found myself annoyed by his jumping from square to square.
The first demo I ever used for PyGame involved animating a car and driving it around a box. Animation should be child's play, right? I decided to take my rogue, and have him move smoothly from square to square.
To move a something in PyGame, you have to step the blit through being drawn, flipping the screen, moving the bit a tiny bit, flipping again, and doing that until you get to the end point, if there even is an end point. If you've decided your blit is living in a frictionless world, then it could, technically, keep going forever.
I just want my guy to move to the next block, so I added this bit of code to game.py:
# In Game:
def animate_move(self, hor, vert, blit):
if vert:
if vert > 0:
for i in range(TILE_SIZE/MOVEMENT_SIZE):
self.draw_screen_layers()
self.screen.blit(self.__getattribute__(blit),\
[self.old_row, self.old_col+i*MOVEMENT_SIZE])
pygame.display.update()
else:
for i in range(TILE_SIZE/MOVEMENT_SIZE):
self.draw_screen_layers()
self.screen.blit(self.__getattribute__(blit),\
[self.old_row, self.old_col-i*MOVEMENT_SIZE])
pygame.display.update()
if hor:
if hor > 0:
for i in range(TILE_SIZE/MOVEMENT_SIZE):
self.draw_screen_layers()
self.screen.blit(self.__getattribute__(blit),\
[self.old_row+i*MOVEMENT_SIZE, self.old_col])
pygame.display.update()
else:
for i in range(TILE_SIZE/MOVEMENT_SIZE):
self.draw_screen_layers()
self.screen.blit(self.__getattribute__(blit),\
[self.old_row-i*MOVEMENT_SIZE, self.old_col])
pygame.display.update()
What is MOVEMENT_SIZE? It's the number of pixels I want my rogue to move for every frame. I found out quickly that moving him just one or two pixels caused the animation to be extremely slow, but not any more smooth. I ended up with a movement size of sixteen (set in constants.py) before I was happy.
Well, kind of happy.
Complications from Animation
While I was delighted that my guy was animated now, I noticed that the interface now seemed... off. My monsters were still leaping to and fro, so that was jarring. The shadows were leaping, too, with only the area where the rogue was going to be getting highlighted. Movement still seemed very slow, even with reducing it to only a few frames. One small decision now made my interface feel very, very strange.
I thought about making the monsters animated, but realized that still left the shadows leaping about. I could rewrite the shadow logic, but I knew that I wanted to rewrite it when I introduced rooms. Did I want to put up with jarring animations that slowed down my testing?
I decided that animation had become a time sink at this point. Combat still didn't exist, my player was nothing more than a bag of rainbows and sqords, I had no rooms, and I hadn't even gotten to adding multiple levels to my dungeon. I didn't even have a McGuffin yet.
I took it out, but left the functionality in there for another day.
Active Gaming
What the heck is active gaming? Am I making a Kinect version of my roguelike? Will this be on the Wii? Will the player have to sweat?
No. Active gaming allows a developer to think more deeply about gaming mechanics.
In high school, most people are taught 'active reading.' The name might differ across systems, but it comes down thinking about what you're reading while you're still reading it. You might underline text, or take notes, or reread every section before moving on. Whatever trick is used, it's meant to force the reader to engage with the material, rather than sitting back and letting it wash over him/her.
Active gaming is similar. Rather than just playing mindlessly, you take note of mechanics or plot devices or timing. You force yourself to become aware of how the game is manipulating you into playing the game a certain way. How are you brought to the next plot point? Did the developer motivate you with quest objectives, or did a big monster chase you there? How does combat work? If it's turn based, what starts a combat round? How does combat carry from round to round? Do the creatures stick it out the bitter end, or do they flee?
Let's Plays
One of the cool things about active gaming when compared to active reading is that there's more than one way to get input. This is great news for people like me who have hand issues, and can't do long gaming sessions. This is also huge for people who want to look at a game that's long out of print, or on a system they don't own.
One of my favorite ways to do an active gaming session is to wander over to YouTube and look for a 'Let's Play' for that game. Oh, you think there won't be one? You think your game is too rare for there to possibly be an LP? I found an LP of some Simpson's Driving game I'd never heard of. There are LP's of Flash games. You can find LP's for mods that add My Little Ponies to Skyrim, or for specific maps in Minecraft. People who make LP's are crazily dedicated.
Watching a Let's Play allows you to sit back and just take notes without getting obsessed about learning the keyboard commands, or what the left shoulder button does. You can pause and go back when you want to note how a certain mechanic works.
These people are also usually pretty good gamers, meaning they're not going to spend an hour locked in the bathroom because the texture of the handle is the same as the door and you just didn't notice it. Yes, I have done that. I'm not proud. Even if they're not great gamers, their death only sets you back seconds, since they usually cut out geting back to where they were before.
So, how do you do active gaming?
First, decide what you're going to be investigating. I like to go after game elements one at a time, so I can really focus on just those things. You might just focus on how movement is handled, or combat. You might spend a session tracing quests, and how you're being lead from objective to objective. I recently started working on combat in my roguelike, so I did a session just focusing on how you kill things in classic Rogue.
Note taking can be oenerous when you're trying to game, so I opt for video and audio capture. FRAPS does a great job at this, but Jing is quite good as well. I still haven't fully investigated any *nix options (sorry!).
The idea is to talk while playing, so you can later review your findings with pen in hand. I loaded up a Flash roguelike (don't judge), started Jing, and started playing.
After I was done, I had a rogue outline for what combat was like for a basic roguelike:
- Moving against the target, for dumb targets, initiates a round of combat.
- Each round allows the dumb target to hit back.
- Dumb targets do not move while in combat.
- A round of movement is lost for the player while in combat.
Just those simple things made me sit back and look at how I was doing rounds of combat, and ended up working as a list of starter requirements for the next round of coding.
Next week!
We get down to brass tacks and start fighting!
The Littlest Intern
This summer marks the end of my son's elementary school career. In this neck of the woods, sixth grade is the beginning of middle school, and the start of his world getting a little bigger.
Normally, his summers are a series of days in which he wrangles as much video game time as he can out of his grandmother, fights with his sister, and avoids going outside into the sweaty bumhole that is DC from June to September. We've done camps, with mixed results, and one bout of summer school, with tragic results.
What can I do with him for three months that keeps his brain from turning to mush?
The idea hit me when I was playing with Code Year. How cool would this be for him to dive into? There's no set-up, and the examples are incredibly clear. Unfortunately, school and homework leave him pretty drained. A tired kid is not a kid that's going to learn much.
So what about the summer? He has a laptop. I could send him to grandma's with it on his back and he could do it there. No, I realized, that wasn't likely to happen. He'd just turn on YouTube and watch NHL recaps and Let's Plays. It's not his grandmother's job to pester him to do a lesson, and she might not be able to help him if he got stuck. I'm sure he'd go with the intention of doing a lesson, but summer days have a way of getting away from you.
Then it hit me. He could be my intern.
I work from home, so I wouldn't have to annoy my office mates. And there are some little tasks around my home office that he could help with. He's shown interest in my work, but we really haven't had the time to sit down and go over what I do besides "Make computers go." Joining me in the office, one day a week, should give him the chance to learn what I do, help me with some of the more tedious things that must be done, and gain a skill or two.
And you know what? I'll even pay him and cover his lunch. I'm now 9000% better than most internships out there.
| Task | Skill |
|---|---|
| Scan in notes and sketches that have piled up. | Everyone should know how to use office equipment. There is nothing sadder than five people with advanced degrees panicking around a printer. |
| Perform office maintenence. | It took me a long time to learn that desks are not cleaned by the magic cleaning fairy. There is also no fairy to restock the printer paper. |
| Test websites | I am shocked at how many people who make websites can't test websites. Seriously? You didn't notice the header was gone? |
| Work through Code Year and Hello World | Part of internships is learning, and these are two great resources for doing just that. |
| Update his own site | It's been up forever, and he's been interested in adding to it. Now, he'll have dedicated time. |
My mother suggested filing. I didn't have the heart to tell her that I haven't had to file anything in six years.
PyGame: FIGHT FIGHT FIGHT
Stats
Now that I had decided to add combat to the game, I realized I had to give the player and the monsters something to lose. Namely, hit points. Deciding how hard they would hit would be nice, too.
Now that the player was more than a bag of stuff, he needed his own class filled out a bit more:
class Player(object):
"""The player class. Contains level, HP, stats, and deals with combat."""
def __init__(self):
self.level = 1
self.attack = 5
self.defense = 5
self.current_hp = 10
self.strength = 1
self.name = "Dudeguy McAwesomesauce"
@property
def max_hp(self):
return 10 + (self.level-1)*5
@property
def get_defense(self):
return 1
def receive_damage(self, damage):
self.current_hp -= damage
def attempt_block(self, attack):
pass
def get_attack(self):
return self.strength
The imporant bits here for combat came from my old stand-by, D&D 3.5. There might be simpler systems, but this one covers pretty much anything I might want my guy to do eventually. At the moment, I'm worrying only about good old hand to hand combat, so I have an attack value (affects likeliness of landing a hit), strength (how hard a landed hit is), and defense (likeliness of absorbing a hit).
The monster got a bit of a boost as well:
class Monster(object):
def __init__(self):
pass
def get_attack(self):
return self.strength
def receive_damage(self, damage):
self.current_hp -= damage
class Derpy(Monster):
def __init__(self):
self.title = "Derpy Slime"
self.level = 1
self.attack = 10
self.defense = 1
self.current_hp = 5
self.max_hp = 5
self.strength = 1
The monster is a better at hitting, but has a lower defense, and fewer hitpoints. Have to keep things interesting, after all!
Combat!
Oh, man. Combat.
If anything in your game has you breaking down things into the most basic of flow charts, it's combat.
At first, I tried to avoid dragging out the old flowchart. I'm not in CS 100 anymore! I can totally do this in my head! Besides, I've played lots of games. I know how combat works.
Oh, the hubris of the pre-alpha developer.
A few false starts later, it occurred to me that I was going to have to start writing some stuff down. My first go looked like this:
Player moves into monster square → Player hits monster → Monster hits back → Life goes on
Simple. I decided that, even if it looked simple, I would have a module to do the heavy lifting of combat.
from player import Player
from monsters import Derpy
from random import randint
class Combat(object):
def __init__(self, player, monster):
self.player = player
self.monster = monster
def fight(self):
'''For now, we'll always start with the player.'''
# Player, try to hit the monster!
hit_attempt = randint(0, self.player.attack)
if hit_attempt == 0:
pass
if hit_attempt > 0 and hit_attempt <= self.monster.defense:
pass
if hit_attempt > self.monster.defense:
damage = self.player.get_attack()
self.monster.receive_damage(damage)
# Monster, try to hit back.
if self.monster.current_hp > 0:
hit_attempt = randint(0, self.monster.attack)
if hit_attempt == 0:
pass
if hit_attempt > 0 and hit_attempt <= self.player.defense:
pass
if hit_attempt > self.player.defense:
damage = self.monster.get_attack()
self.player.receive_damage(damage)
I decided, since these were stupid monsters, that they wouldn't chase down the Player, nor would they stay locked in combat. They'd get their hit and go on about their business. This will likely change as I make more complex monsters, so this module is going to completely morph over time.
Looking over the code, I noticed that I had started to make myself some setters and getters, but never ended up using them. When I expand combat, this will probably be the first thing I rectify.
Also, checking for empty squares had to get more complicated. Again. UGH.
#In Map:
def is_block_empty(self, row, col):
if not self.treasure[row][col] and \
not self.monsters[row][col] and \
not self.walls[row][col] and \
not (self.player[0]/TILE_SIZE, self.player[1]/TILE_SIZE) == (row, col):
return True
else:
return False
Death
One thing I forgot to add at first was death. That's right, I had things running around the board with negative health. D'oh.
One thing about death is that you have to catch it right away. Checking after combat (my first attempt) lead to dead creatures getting one last lick in. Poetic as that was, I decided I wanted them to die right off. So, before trying to hit someone, I had to make sure the creature was still alive. Once we'd returned back to the game, I could deal with all the bodies.
# In Game.move:
if self.has_monster(row, col):
Combat(self.player_stats, self.map.monsters[row/TILE_SIZE][col/TILE_SIZE]).fight()
if self.map.monsters[row/TILE_SIZE][col/TILE_SIZE].current_hp <= 0:
pass #put death throes here
if self.player_stats.current_hp <= 0:
self.end_game()
self.move(0,0)
return
Now, why am I calling 'move' again, with no actual movement? Because I decided that, while the rogue is fighting something, life should go on. Creatures should keep moving around, possibly complicating his life when they get smart enough to chase after him.
Next time...
We think about seperating our our visual layers.
Fab for All
So, you know how you think you're going to get home from the conference and hop on your computer and do all sorts of awesome things like you said you would? And you know how that's complete and utter BS? I came home and collapsed for about a week. Or two.
At PyCon2012, I gave a talk about setting up your first site in the cloud. It was well received, and there were many requests for me to put up my fab files, in spite them being utter crap. Well, I've gone and done that.
The fab files are very rough. I tried to tidy them up a bit, but in the end, ended up hitting commit and hoping for the best. My hope for this repo is:
- Others will help me improve them
- I'll get versions for other services
- I'll get some that work for other frameworks
- I'll get versions for other setups, like one that uses Gunicon or Varnish
- I might even get some fancy stuff, like ssh keys and ssl in there!
I'd really like, at the end of the day, to have a set of scripts that are not only functional, but easy to learn from, and easy to use. And I'd really welcome community support on this!
(Sorry about the cross-posts on Planet Django and Planet Python! I usually try to pick one!)
PyGame: View seperation and throwing away code
Where am I? Here!
View seperation
I did a bad, bad thing.
We're taught very early on about model-view-controller. It's hammered into us constantly. Make your view seperate! It will make your life so much easier!
It's so true, too. MVC (or MVT) is wonderful, and it really does make things so much simpler. It's just easy to deviate from, when you're excited and caught up in making something awesome and new.
Well, no more. All my view stuff is going into it's own class! Game is now dedicated to all my game mechanics, and GameScreen is going to be for all my visual bits.
Splitting Game into two parts was a tiny bit tricky, but nothing that some wine, chocolate, and deep breaths couldn't take care of. Happily, most of my 'draw' functions were already seperate, so it became a game of move a function, update all calls to it, and run the game. If it breaks, fix it. If it doesn't break, move on to the next function.
One thing I had to consider now that everything wasn't in one bucket is what GameScreen had to know. I decided that all I should have to tell it is what I wanted to draw, where. All I'm passing back and forth is a map or some coordinates (where) and sometimes some text (what). This is as it should be.
GameScreen does not know what a monster is. It doesn't know anything about the player, or the inventory, or any other object in the game. Its only concern is what it needs to draw where, and in what order. If you have to import anything besides some constants and PyGame, you're starting to see MVC bleed.
As a counter, Game shouldn't know anything about how these things are being drawn. Maybe it's ASCII, maybe it's 3D. It should only be concerned about the mechanics of the game, like who hits first, and when we get to pick up some awesome stuff. If you see that, you have MVC bleed in that direction as well.
import pygame
from constants import *
class GameScreen(object):
def __init__(self):
''' Does the initial drawing of the game screen.
'''
self.screen = pygame.display.set_mode((1280, 832))
self.font = pygame.font.SysFont(None, 48)
self.small_font = pygame.font.SysFont(None, 20)
self.bg = pygame.image.load(IMG_DIR + 'rainbowbg.png')
self.player_blit = pygame.image.load(IMG_DIR + 'dude.png')
self.screen.blit(self.bg, (0,0))
self.inventory_screen = self.small_font.render("Inventory", True, WHITE, BLACK)
self.draw_alert("Welcome to Katie's Roguelike!")
self.stats_screen = self.small_font.render("ARGH", True, WHITE, BLACK)
pygame.display.flip()
def draw_player(self, coord):
''' Draws the player at a specific coordinate
'''
self.screen.blit(self.player_blit, coord)
def draw_stats(self, player_stats, color=WHITE):
''' Renders the stats for the player
'''
self.screen.blit(self.stats_screen, (1008, 0))
self.stats_screen = self.small_font.render(player_stats.name, True, color, BLACK)
self.screen.blit(self.stats_screen, (1008, 0))
self.stats_screen = self.small_font.render("Level: " + str(player_stats.level), True, color, BLACK)
self.screen.blit(self.stats_screen, (1008, 15))
self.stats_screen = self.small_font.render("HP: %s/%s" % (str(player_stats.current_hp), str(player_stats.max_hp)), True, color, BLACK)
self.screen.blit(self.stats_screen, (1008, 30))
def draw_alert(self, alert, color=WHITE):
''' Draws the alert box at the bottom
'''
self.alert = self.font.render(LONG_STRING, True, BLACK, BLACK)
self.screen.blit(self.alert, (0, 790))
try:
pygame.display.flip()
except:
pass
self.alert = self.font.render(alert, True, color, BLACK)
self.screen.blit(self.alert, (0, 790))
pygame.display.flip()
def draw_inventory(self, inventory):
''' Renders the inventory for the user
'''
self.screen.blit(self.inventory_screen, (1008, 100))
items = inventory.get_items()
for i in range(items.__len__()):
line = self.small_font.render(LONG_STRING, True, BLACK, BLACK)
self.screen.blit(line, (1008, ((i+1)*15)+100))
pygame.display.flip()
for item in items:
line = self.small_font.render(item, True, WHITE, BLACK)
self.screen.blit(line, (1008, (items.index(item)+1)*15+100))
def draw_treasure(self, treasure_map):
''' Draws the treasure chests yet to be opened.
'''
for row in range(ROWS):
for col in range(COLUMNS):
if treasure_map[row][col] != 0:
treasure = pygame.image.load(IMG_DIR + 'chest.png')
self.screen.blit(treasure, (row*TILE_SIZE, col*TILE_SIZE))
def draw_monsters(self, map):
''' Draws monsters that appear in the area that the rogue can see
'''
for row in range(ROWS):
for col in range(COLUMNS):
if map.monsters[row][col] != 0 and map.current[row][col] != 0:
monster = pygame.image.load(IMG_DIR + 'dumb_monster.png')
self.screen.blit(monster, (row*TILE_SIZE, col*TILE_SIZE))
def draw_walls(self, walls):
''' Draws walls on the game map
'''
for row in range(ROWS):
for col in range(COLUMNS):
if walls[row][col] != 0:
wall = pygame.image.load(IMG_DIR + 'wall.png')
self.screen.blit(wall, (row*TILE_SIZE, col*TILE_SIZE))
def draw_darkness(self, map):
''' Draws the darkness and shadows on the board. 0 is dark, 1 is in shadows,
'''
for row in range(ROWS):
for col in range(COLUMNS):
if map.cleared[row][col] == 0:
if not map.current[row][col]:
pygame.draw.rect(self.screen, BLACK, (row*TILE_SIZE, col*TILE_SIZE, TILE_SIZE, TILE_SIZE))
if map.cleared[row][col] == 1:
if not map.current[row][col]:
shadow = pygame.Surface((TILE_SIZE, TILE_SIZE))
shadow.set_alpha(200)
shadow.fill(BLACK)
self.screen.blit(shadow, (row*TILE_SIZE, col*TILE_SIZE))
def draw_background(self):
''' Draws my glorious background.
'''
self.screen.blit(self.bg, (0,0))
def draw_screen_layers(self, map, player_stats):
''' Draws the layers of the game screen
'''
self.draw_background()
self.draw_treasure(map.treasure)
self.draw_walls(map.walls)
self.draw_monsters(map)
self.draw_darkness(map)
self.draw_stats(player_stats=player_stats)
self.draw_player(coord=map.player)
pygame.display.flip()
Throwing away code
Sometimes, I look at my code, and think, what horrible thing have I wrought? I know others do, too. It's tempting, to go in and try to get everything working perfectly. Sure, the combat works, but it could be more efficient! It could be more elegant! I could rid myself of the dread that Guido is going to jump out of a closet and ax-murder me!
It's tempting, but I have to remind myself of something: I'm going to throw all this code away.
Keeping bad code is a habit that I've seen even the best developer succumb to. I spent forty hours on this! If I throw it away, I'll be throwing away all the time I spent on it! Look, just a few more hours, and it'll be better, okay? Maybe you just don't understand its innate genius.
No. It is bad code. Throw it away.
I've seen people treat code as if it's their child. Having two children, I say you should relish the things in your life that you can screw up and toss out without worry.
When I have a working roguelike, the most important thing I'll have won't be a game: it'll be the lessons I learned. At that point, I can spend a day looking over the monster I've brought into this world, and figure out where I could possibly be more efficient. Where am I duplicating effort? What did I never use? Where did I commit my cardinal sins because I was coding after my second glass of wine (again)?
Once I've bled the code dry of all its usefulness, into the wastebin it goes. With what I've learned under my belt, I can recreate the game, but this time without the stumbling about and 'Oh, Hell's of before. It won't take me nearly as long, and I can cut out the ugly bits from the start.
The only reason I decided to pull apart the screen code was because it was starting to make it harder to code, and I really wanted to talk about MVC. The time it took me to do that, however, was time I didn't spend making combat cooler, or adding more treasure, or adding rooms. In this case, I decided it was worth it, but for most things, I just choose to forge ahead.
Next time!
We're back to adding cool features! I think we need some rooms!
PyGame: Rooms!
Where am I? Here!
Algoritms
Before we dive in, I should point out that this world we're in is not short on room building algorithms. Google it, and you'll be awash in them. There's ones that do the basic rooms + hallways setup. Others generate walls in such a way that rooms naturally occur. Some add different kinds of rooms, like meeting halls and living quarters, and do it in a semi-reasonable fashion. Why don't I use one of those?
I might, eventually, use one of those (the one on Roguebasin looks especially promising). But before I do, I really want to understand the parts of the algorithm. I want to try making my own rooms and hallways before I let someone else do it for me. Will mine be awesome? Of course not! It'll be crap! It will have the minimum number of features I would want, and I bet its error checking won't be anywhere near as nice as one that's been tweaked for years, by dozens of hands, and tested by hundreds of users.
I believe in trying to do things on your own, at least once, though. I once built the world's most crap CMS ever from scratch. It not only gave me a deep appreciation for other frameworks, but made it easier for me to understand why they might have built theirs the way they did.
Making a room
I decided to start simple. All I wanted, for my first round, was to make a darn room. This is made much harder if you can't actually see the whole map, so I decided to turn off everything but the floor and the walls.
def draw_screen_layers(self, map, player_stats):
''' Draws the layers of the game screen
'''
self.draw_background()
#self.draw_treasure(map.treasure)
self.draw_walls(map.walls)
#self.draw_monsters(map)
#self.draw_darkness(map)
#self.draw_stats(player_stats=player_stats)
#self.draw_player(coord=map.player)
pygame.display.flip()
Then, I created a class for my room:
class Room(object):
def __init__(self, height=5, width=5, start=(0,0)):
self.title = "Generic room"
self.start = start
self.width = width
self.height = height
self.end = (self.start[0]+self.width, self.start[1]+self.width)
Why? Why not just generate room like structures with walls? Because I know me. I'm not going to be able to resist tossing some flavor text in there. It also seems really useful to have a list of rooms. Maybe, at some point, I want to make sure I don't have too many monsters being generated in one room, or that no one room has all the treasure. Or, maybe I want to make a room that has ALL the treasure. We'll see.
As for adding the room, Gamemap does that, since a room is really just a bunch of walls:
def create_room(self, room):
# make top and bottom walls
for i in range(0, room.width):
self.walls[room.start[0]+i][room.start[1]] = 1
self.walls[room.start[0]+i][room.start[1]+room.height-1] = 1
# make side walls
for i in range(0, room.height):
self.walls[room.start[0]][room.start[1]+i] = 1
self.walls[room.start[0]+room.width-1][room.start[1]+i] = 1
# fill in the floor
for x in range (1, room.width-1):
for y in range (1, room.height-1):
self.rooms[room.start[0]+x][room.start[1]+y] = 1
I will not lie. Getting the walls to draw correctly took me an embarrassingly long time. I kept forgetting to subtract one, or I'd add my incremental variable to the wrong index.
At this point, though, I can at least draw a room. But I don't want just one room, do I? I want a number of random rooms! I don't want them overlapping each other, though, so I need to check all the squares that a room might contain first. Thus, check_room is born.
def check_room(self, coord, height, length):
''' Are all the spaces in a room free?
'''
for i in range(0, height):
for j in range(0, length):
if coord[1] + i > COLUMNS-1:
return False
if coord[0] + j > ROWS-1:
return False
if self.rooms[coord[0]+j][coord[1]+i]:
return False
room = Room(start=coord, height=height, width=length)
return room
I don't care if the rooms share walls, as that's often how rooms work. I don't want them to share floorspace. You might have noticed, I've added a new layer to my map for rooms. This is JUST for the open floor in a room, and is filled in when the room is created.
Now, I can get a bunch of random rooms (always generating one at 0,0, because I am boring).
def get_rooms(self):
# Set initial room
room = self.check_room(coord=(0,0), height=5, length=5)
self.roomlist.append(room)
rooms = 1
keep_going = 50
while rooms <= MAX_ROOMS and keep_going:
height = randint(4,7)
length = randint(4,7)
x = randint(0, COLUMNS-1)
y = randint(0, ROWS)
room = self.check_room(coord=(x,y), height=height, length=length)
if room:
rooms += 1
self.roomlist.append(room)
else:
keep_going -=1
What the heck is keep_going? It's possible for the board to be filled with rooms in such a way that no more rooms can be placed. Infinite loops are bad and embarrassing, so at some point, we just tell the game to give up and continue with loading the level.
Adding stuff back
Another way having floors is useful: it makes it easier to tell where I can place something. I don't want my creatures outside of the room. That's just wasted XP, and is going to taunt the player. Happily, making things appear on floors is really easy. Just check to make sure your random square is actually a floor.
# in __init__()
for i in range(TREASURES):
while 1:
col = random.randint(0, COLUMNS-1)
row = random.randint(0, ROWS-1)
if not self.treasure[row][col] and self.rooms[row][col]:
self.treasure[row][col] = Treasure()
break
for i in range(MONSTERS):
while 1:
col = random.randint(0, COLUMNS-1)
row = random.randint(0, COLUMNS-1)
if not self.treasure[row][col] and self.rooms[row][col]:
self.monsters[row][col] = Derpy()
break
I could do something fancy, like get all the squares that are floors and pick from them, but doing it this way isn't eating up significant processing power. Now that they're back, we can start drawing them again, as well. The only thing I'm holding off on is the darkness, since it's very likely I'll be reimplementing that.
Next time!
Doors and hallways!
Pygame: Hallways
Where am I? Here!
Hallways!
In the last post, I made a reasonable number of rooms that would not overlap with each other. Rooms are nice and all, but what we really want are ways to get from room to room.
The plan
I ran through a ton of graph paper while brainstorming on how I wanted to do this. I didn't want an orphaned areas. I wanted some bends in the hallways and some reasonable variation. I wanted this plan to scale to larger spaces. I also wanted the simplest possible solution.
I finally settled on the simplest pattern I could find: give each room a door. Connect the doors, starting at the first generated room, and ending with the last room connecting to the first, using just one bend. Knock down all walls on the way. Simple, right? Super simple! I should barely be cursing at all!
Doors
The first thing I would need to make is doors. Right off the bat, I saw that this wasn't going to be as easy as knocking out a random wall. What use is a door that's in a corner, or at the edge of the map? What if the door is right next to another wall? Should I just smash the wall and connect the two rooms?
After lots and lots of sketching, I came up with the following rules:
- The door cannot be on an edge
- The door cannot be on a corner
- If the door is next to a wall, and that wall is next to a floor, knock out that wall
- If the door is next to a wall, but that wall is not next to a floor, that block cannot be a wall
The above lead to the following code:
def check_door(self, coord, check, next):
''' Can the current block be turned into a door?
'''
# Is it at the bounds?
if check[0] < 0 or check[1] < 0:
return False
# Is it next to a wall?
try:
if self.walls[check[0]][check[1]]:
# Is that wall next to another wall?
if self.walls[next[0]][next[1]]:
return False
else:
try:
self.walls[check[0]][check[1]] = 0
except:
pass # Sometimes, we're one away from the border. That's okay.
except:
return False
return True
With this, if the door is okay, I return True, with any extra blocks that needed destroying taken care of. If there are any issues, I return False, which will force the function that's trying to make the door to try again with a new random square.
def make_random_door(self, room):
while True:
wall = choice(DIRECTIONS)
if wall in ['north', 'south']:
block = randint(1, room.width-2)
else:
block = randint(1, room.height-2)
if wall == 'north':
coord = (room.start[0]+block,room.start[1])
check = (coord[0], coord[1]-1)
next = (coord[0], coord[1]-2)
if wall == 'south':
coord = (room.start[0]+block, room.start[1]+room.height-1)
check = (coord[0], coord[1]+1)
next = (coord[0], coord[1]+1)
if wall == 'east':
coord = (room.start[0],room.start[1]+block)
check = (coord[0]-1, coord[1])
next = (coord[0]-2, coord[1])
if wall == 'west':
coord = (room.start[0]+room.width-1, room.start[1]+block)
check = (coord[0]+1, coord[1])
next = (coord[0]+2, coord[1])
door = self.check_door(coord, check, next)
if door:
self.walls[coord[0]][coord[1]] = 0
self.floor[coord[0]][coord[1]] = 2
room.door = (coord[0],coord[1])
return
This way, every room has a proper door that we can use to make a hallway.
Hallways
I considered quite a few options when I thought of hallways. I played with the idea of having them bend a random number of times, or have random dead ends. I even considered making the passages twist and turn, in a more natural way. In the end, though, I went with what was simplest: start at a door, tunnel over until you're at the next door's column, tunnel up or down.
At this point, I had added a list of rooms to my map, and added the door coordinates to the rooms, so I was able to go go down the list, connecting one to the next as I went. I could have tried tunneling to the closest one, but I found I liked the branching more, connecting random rooms to each other.
def connect_rooms(self):
for room in self.roomlist:
i = self.roomlist.index(room)
# If we're at the last room, connect the last to the first.
try:
next = self.roomlist[i+1]
except:
next = self.roomlist[0]
#Determine where we're starting, and where we're ending.
if room.door[0] < next.door[0]:
start = room.door[0]
end = next.door[0]
else:
start = next.door[0]
end = room.door[0]
# Tunnel over
for x in range(start, end):
self.walls[x][room.door[1]] = 0
self.floor[x][room.door[1]] = 1
# Determine where we're starting, and where we're ending, again
if room.door[1] < next.door[1]:
start = room.door[1]
end = next.door[1]
else:
start = next.door[1]
end = room.door[1]
# Tunnel up/down
for y in range(start, end):
self.walls[next.door[0]][y] = 0
self.floor[next.door[0]][y] = 1
Filling in
I had considered building walls around my hallways, but in a moment of fatigue, filled in the rest of the blank spaces on the board, and decided that I liked that better. Goodbye, forever, glorious rainbow background!
def fill_map(self):
for i in range(ROWS):
for j in range(COLUMNS):
if not self.floor[i][j]:
self.walls[i][j] = 1
The Final Result
Next
We make our items worth getting!
PyGame: Returning to Items
Where am I? Here!
Buffing up items
At the moment, Items are rather bland: they're just a title and a description. We want them to actually mean something, though, so it's time to give them a bit more shape.
class Treasure(object):
''' Things that can be picked up.
'''
def __init__(self, title="Nada", description="", type="trash", armor=0, buff=0, attack=0):
self.title = title
self.description = description
self.type = type
self.armor = armor
self.buff = buff
self.attack = attack
Okay, not the most exciting upgrade ever, since we're only making armor and swords, but we're going with baby steps here. Though I've added three attributes, we'll only really be using two: armor and attack. Now that the item can have a bit more oomph, let's look at the Player again.
Getting Player ready
Right now, Dudeguy is pretty basic. I need to get him ready to deal with not only carrying things, but having those things matter.
class Player(object):
"""The player class. Contains level, HP, stats, and deals with combat."""
def __init__(self):
self.level = 1
self.stats = {
'strength': 1,
'attack': 5,
'defense': 5
}
self.current_hp = 10
self.name = "Dudeguy McAwesomesauce"
self.equipped = {}
for treasure in EQUIPMENT_TYPES:
self.equipped[treasure] = None
I decided to make stats a dictionary, since I might want to give some classes a stat that others didn't, and this seems like the easier way of doing this. Also, it's easier to iterate for my interface, once I get to that.
I've also added a dictionary for equipped items. He starts off with the standard equipment slots:
EQUIPMENT_TYPES = ('hat', 'shirt', 'pants', 'shoes', 'back', 'neck', 'hands', 'weapon')
but no equipment, of course. Naked adventuring, FTW!
Managing items
Now that I was buffing up items, I needed a better way to manage them. Looks like my game will need an admin tool. Enter admin.py:
import sys, pickle
import sys, pickle
sys.path.append("roguey/classes")
from items import Treasure
from constants import *
class Admin(object):
def __init__(self):
f = open("roguey/resources/items.pk")
try:
self.treasures = pickle.load(f)
except:
print "No treasures!"
self.treasures = []
f.close()
self.main()
def new_treasure(self):
for treasure in TREASURE_TYPES:
print "%s. %s" % \
(TREASURE_TYPES.index(treasure)+1, treasure)
choice = raw_input("Pick a type [1-9]: ")
type = TREASURE_TYPES[int(choice)-1]
title = raw_input("Give it a title: ")
desc = raw_input("Give it a description: ")
attack = 0
armor = 0
if type == 'weapon':
attack = raw_input("How much damage will it add? [1-999]: ")
attack = int(attack)
else:
armor = raw_input("How much armor will it add? [1-999]: ")
armor = int(armor)
tr = Treasure(title=title, \
description=desc, \
type=type, \
armor=armor, \
attack=attack)
self.treasures.append(tr)
def list_treasures(self):
for treasure in self.treasures:
print treasure.title
def save(self):
f = open("roguey/resources/items.pk", "w")
pickle.dump(self.treasures, f)
f.close()
def main(self):
while 1:
print "1. Make a new treasure"
print "2. List current treasures"
print "0. Quit"
c = raw_input("Make a choice [1-2, 0]: ")
if c[0] == "1": self.new_treasure()
if c[0] == "2": self.list_treasures()
if c[0] == "0":
self.save()
return
if __name__ == "__main__":
a = Admin()
It's simple, but it made managing my items so much easier. Now, rather than maintaining a text file (or *gasp* a spreadsheet) of items, I could deal with them through this script. My items could be saved as a Pickle and checked into the repo.
Wait, a Pickle?
Pickles!
Now that we have all this data, we need to save it somehow. In this instance, I've chosen to use Pickles.
Pickles get a bad rap, mostly due to the fact that they're not very secure. It's almost a knee-jerk reaction at this point. Go ahead, test it: walk into any Python discussion, and suggest that they use Pickles. Watch them foam at the mouth.
Pickles are inherantly insecure. You should never accept a Pickle from a source you can't trust (this is still true if talking about lower-case pickles). In this case, we know who made the Pickle, so we're good. If you decide to fork my code and turn my roguelike into something where people would be swapping these piles of items... well, you'll have a bit more work cut out for you.
Giving the player items
Now that the items are worth having, and we have a selection of them, let's have the player pick them up.
For now, I only have one of each type of item, so when the player comes to an item and doesn't have anything in that slot, it's automatically equipped. If something is already there, into the backpack it goes, because as we know, all players are cleptomaniacs.
def add_to_inventory(self, item, player):
''' Adds an item to the inventory
'''
if item.type == "trash":
return
if player.equipped[item.type]:
try:
self.inventory[item] += 1
except:
self.inventory[item] = 1
else:
player.equip_item(item)
Now that the player can equip items, we need to update our status screen area to reflect that:
def draw_equipment(self, equipment=START_EQUIPMENT):
''' Renders the equipment. Expect it to be exchanged for something
awesomer
'''
self.screen.blit(self.equipment_screen, (1008, 200))
for i in range(equipment.keys().__len__()):
line = self.small_font.render(LONG_STRING, True, BLACK, BLACK)
self.screen.blit(line, (1008, ((i+1)*15)+200))
pygame.display.flip()
i = 1
for slot in EQUIPMENT_TYPES:
try:
line_text = slot + ": " + equipment[slot].title
except:
line_text = slot + ": "
line = self.small_font.render(line_text, True, WHITE, BLACK)
self.screen.blit(line, (1008, i*15+200))
i += 1
pygame.display.flip()
Side note: I got a ten-minute lecture from my son about how 'awesomer' isn't a word. What are they teaching kids these days?!
Next time!
Combat needs to be updated, now that we have all these great items!
PyGame: Returning to Combat
Where am I? Here!
Revisiting admin.py
So, remember how I joked that Pythonistas froth at the mouth over Pickles? Yeah, my admin module (which was just a bit of trash code to be used while making the game) was easily the most contraversial bit of code I churned out. This is why working on open source is a good thing, though! It inspired someone to completely rewrite it! Thanks, Ian!
The new and improved admin.py:
import sys
import xml.etree.ElementTree as etree
from xml.dom.minidom import parseString
sys.path.append("roguey/classes")
from items import Treasure
from constants import *
def prettify(element):
# Helper function to make XML look more prettier
txt = etree.tostring(element)
return parseString(txt).toprettyxml()
class Admin(object):
def __init__(self):
# Load the existing treasures
f = open("roguey/resources/items.xml")
self.treasures = etree.fromstring(f.read())
f.close()
# trim the annoying whitespace...
self.treasures.text = ""
for element in self.treasures.iter():
element.text = element.text.strip()
element.tail = ""
# Load the list of treasure type templates
f = open("roguey/resources/item_templates.xml")
self.treasure_templates = etree.fromstring(f.read())
f.close()
# Enter main loop
self.running = True
self.main()
def new_treasure(self):
item_attributes = {} # This will hold optional stats only.
template_options = [
template.find("item_type").text for template in self.treasure_templates
]
# Gather the mandatory attributes
selection = self.prompt_for_selection(
prompt="Choose an item type",
options=template_options
)
item_type = template_options[selection]
template = self.treasure_templates[selection]
title = raw_input("Give it a title: ")
description = raw_input("Give it a description: ")
# Check if this template requires any additional attributes
for attr in template:
if attr.tag == "item_type":
continue
prompt = attr.attrib["prompt"]
value_type = attr.attrib.get("type", "string") # type defaults to "string" if not specified
item_attributes[attr.tag] = (raw_input("%s (%s): " % (prompt, value_type)), value_type)
# finally we can add this new item to the list
new_item = etree.SubElement(self.treasures, "item")
etree.SubElement(new_item, "item_type").text = item_type
etree.SubElement(new_item, "title").text = title
etree.SubElement(new_item, "description").text = description
for attrib, value in item_attributes.iteritems():
optional_stat = etree.SubElement(new_item, attrib)
optional_stat.text, optional_stat.attrib["type"] = value
def list_treasures(self):
for treasure in self.treasures:
print treasure.find('title').text.strip()
def save_and_quit(self):
f = open("roguey/resources/items.xml", "w")
f.write(prettify(self.treasures))
f.close()
self.running = False
def delete_treasure(self):
pass
def main(self):
menu_options_with_actions = [
("Make a new treasure", self.new_treasure),
("List current treasures", self.list_treasures),
("Delete a treasure", self.delete_treasure),
("Quit", self.save_and_quit),
]
menu_options = [x[0] for x in menu_options_with_actions]
menu_prompt = "Make a choice"
while self.running:
selection = self.prompt_for_selection(menu_prompt, menu_options)
# Call the appropriate action based on the user selection
menu_options_with_actions[selection][1]()
def prompt_for_selection(self, prompt, options):
"""Given a list of options and a prompt,
get the users selection and return the index of the selected option
"""
# Print out the numbered options
for i, option in enumerate(options):
print "%3s. %s" % (i+1, option)
# Get the users selection
selection = raw_input("%s: " % prompt)
return int(selection)-1
if __name__ == "__main__":
a = Admin()
It's been updated to use an XML file, which is semi-human-readable, and the code for generating the menu has been made cleaner. Woot!
Planning ahead
At one point, I had this lovely roadmap of what I was going to do, when, for my roguelike. That list is about as relevant today as a midieval monk's advice on what smartphone I should get. I quickly found that a round-robin approach was working much better for me. I was able to crank out enough for a blog post each week, and I wasn't getting caught up on smaller details that relied on other modules being done.
A list isn't a bad thing to have, though. You just have to respect that the universe hates a tidy plan.
As the game has grown more complex, however, I've found I've had to stop and think about what bit I need to develop first more often. Modules are starting to affect each other more, and the changes I'm making are more subtle. For example, when do I want to add animation back in? After I've added all my monsters? Before I put darkness back? Hm, when do I want to add darkness back? How about levels?
It's tempting fate, but I put together a rough roadmap for the rest of game:
- New Monster - Aggro
- Class, leveling up, and more stats
- Item update: buffs and potions
- Inventory - equip, de-equip
- Add levels to dungeon
- Content explosion, scrolling maps
- Art update
- Put darkness back in
- Animation
You may now take bets on how well I'll adhere to this.
Combat
Combat wasn't broken in the sense that it didn't work: it was broken in the sense that it worked in spite of the way it was written. In order to get it ready for new monsters, classes, stats, and levelling up, it needed some serious work. I needed to use proper settings and getters, because now my items had meaning. First, I had to update Player.
First, I decided that having stats as their own property was getting an little unweildy. It was much better to put them into a dictionary, for later spitting out onto the screen.
def __init__(self):
self.level = 1
self.stats = {
'strength': 1,
'attack': 1,
'defense': 5
}
self.current_hp = 10
self.name = "Dudeguy McAwesomesauce"
self.equipped = {}
for treasure in EQUIPMENT_TYPES:
self.equipped[treasure] = None
Now, throughout my code, I'd referred to the 'strength' and 'defense' attributes. Rather than hunt them all down, I decided to name my new properties the same thing.
@property
def defense(self):
return self.stats['defense'] + self.get_armor()
@property
def strength(self):
return self.stats['strength']
def get_armor(self):
armor = 0
for slot in self.equipped.keys():
if self.equipped[slot]:
try:
armor += self.equipped[slot].armor
except AttributeError:
# The Treasure in this slot doesn't have an 'armor' attribute
pass
except TypeError:
# The Treasure in this slot has an armor stat, however it
# appears to be a non numeric type. Make sure this Treasures armor stat
# has the attribute type="int" in its XML definition.
# This sort of error should probably get properly logged somewhere
print (
'Please make sure the armor stat for the "%s" weapon '
'has the type="int" attribute in its XML definition' % self.equipped[slot].title
)
return armor
The properties can now be called like any other attribute:
>>> from player import Player >>> p = Player() >>> p.strength 1 >>> p.defense 5
Now that the Player is working well, I need to update my Monster class to work the same:
class Monster(object):
def __init__(self):
self.level = 0
self.stats = {
'attack': 0,
'defense': 0,
'strength': 0,
}
self.max_hp = 1
self.current_hq = self.max_hp
@property
def attack(self):
return self.stats['attack']
def receive_damage(self, damage):
self.current_hp -= damage
@property
def defense(self):
return self.stats['defense']
@property
def strength(self):
return self.stats['strength']
class Derpy(Monster):
def __init__(self):
self.title = "Derpy Slime"
self.level = 1
self.stats ={
'attack': 5,
'defense': 1,
'strength': 1,
}
self.current_hp = 3
self.max_hp = 3
Hm. I'm starting to see overlap here (well, to be honest, I saw overlap a while back). I'll make a note to keep an eye on how alike these two things stay, and if it would be worth it to make a new base class when the time comes to refactor.
Now, let's update combat!
*looks at combat module*
And nothing there needs to be changed.
class Combat(object):
def __init__(self, player, monster):
self.player = player
self.monster = monster
self.fight()
def fight(self):
'''For now, we always start with the player.
'''
# Player, try to hit the monster!
hit_attempt = randint(0, self.player.attack)
if hit_attempt > self.monster.defense:
damage = self.player.strength
self.monster.receive_damage(damage)
else:
pass
# Monster, try to hit back.
if self.monster.current_hp > 0:
hit_attempt = randint(0, self.monster.attack)
if hit_attempt > self.player.defense:
damage = self.monster.strength
self.player.receive_damage(damage)
else:
pass
The only thing I re-jiggered was having combat call fight itself, since there's really no other reason for me to call Combat, and removed some unnecessary checks. Everything is set up, now, for our next addition...
Next time!
We add smart mobs!
Getting more women to your meet-up
I get this question a lot. Like, once a week.
I've puzzled over it, because whenever I start to answer it, I sound a bit rambly. There's a reason for that, though: women take up half of this planet. A population that big isn't easy to target (in spite what advertisers would have you believe).
See, there's so many reasons why women might not be coming to your meet-up or hacker space. Maybe it's a bit guy heavy (I know, catch-22, right?). Maybe they're already booked. Maybe they have obligations at home. Maybe they just don't know about your event. Maybe they think they won't get anything from it. Whatever the reason, one thing is clear:
You can't use just one strategy if you want more women to show up at your tech event.
So, what are some strategies you can use?
Invite them
No, don't just put 'Ladies welcome' on your flyer. Invite specific women to your event. A word of warning, though: there's a right way to do this, and a wrong way to do this. The wrong way:
Hey! We're trying to encourage more women to show up at our tech event, and I was wondering if you'd like to come?
Sounds pretty tame, right? It's polite, it's sleaze-free... and it makes it sound like the only reason you're inviting this person is because she's a woman. This does not make me want to come to your event, which is what you're aiming for, right? What if you drop the 'encourage women attendance' bit?
Hey! I run a meet-up on Thursdays! You should come!
That's fine, but there's lots of meet-ups this person could be going to. Also, if this worked well, you wouldn't be in the situation you're in right now, would you? What do you do? Well, here's an example that worked on me:
Hey! I read your roguelike posts, and loved them! Do you think you could do a talk about them at my meet-up once you're done?
I couldn't clear my calendar fast enough. I was flattered (and I felt the invite was genuine), and I felt like I had something I could add to this group. Even if he'd added that they were trying to attract more women (which came up later), I was still happy to attend.
But what if you don't know any women in your area who are active bloggers, or who actively commit to open source projects that you can use as conversation starters?
Do an alternate night now and then
Childcare isn't an issue for all women, but man, once it's on the table, it's a big issue. It's different for every parent, but often, it's easier to get alternative child care on one night rather than an other. For me, weekday events are almost impossible for me to get to, but weekend ones are a snap. Other women I know can get away during the week more easily than they can on the weekend.
So... should you get day care of your own at your meet-up? No, probably not. Legal and money issues aside, there's a simpler solution: move the date once a month.
I bet your meet-up is on the same day of the week every week, and at the same time. If the women in your area happen to be in the group that can't attend on that night, moving it once a month would enable them to attend. As a side benefit, anyone who can't attend due to work hours or other obligations can suddenly show up.
Advertise somewhere new
As everyone should be aware by now, the tech industry is having a hard time recruiting and holding on to women. This means that the women you would traditionally consider inviting to your meet-up are already pretty rare. What do you do?
At a recent PyLadies gathering, we had women from the following industries:
- Education
- Journalism
- Design
- Non-profit advocacy
Counting heads, I realized that almost half of the women present did not call themselves a 'developer,' and yet, they were interested in learning more about Python. So... why are you still advertising only on Planet Python?
Reach out to non-traditional venues that can take advantage of whatever your meet-up is about. Teachers benefit from pretty much anything that's free and can be used in the classroom, or to make their lives easier outside of it. Journalists are being asked to know more about the platforms their careers depend upon. If someone is working in an office that's cash-strapped, it benefits everyone if even the non-developers know about how to code, even if it's only to work with templates, and be able to read the code the devs checked in.
So, come up with a pitch about how your meet-up could benefit someone that isn't a developer, and see if you can push it somewhere new. Does someone in the group have kids? See if they can talk to the teachers at the kid's school.
Have a clear harassment policy, and enforce it
Yeah, this one has been an issue in the past for me.
Back in college, I went to the *nix meet-up at our school. There was a guy there that was, well, less than pleasant to anyone female that walked in the door. He would talk over us, deride anything we had to say, and if we had a question, he'd made snide comments. He was sometimes a jerk to the guys in the group, but he was batting 1.000 for the women.
The three women in the meet-up brought it up to the head of the group.
"Oh, just ignore him."
There was no harassment policy in place, and even if there had been, I wonder if anyone would have been willing to do anything.
The thing is, we had a choice as to what we could do with our Thursday nights. We didn't have to go to this group. There were other tech meet-ups in the area. We left.
Have a policy. Enforce it. It'll ensure the women that do show up actually stay.
More ideas?
This is far from an exhaustive list. They're simply the handful ideas I had when examining why I didn't go to more meet-ups (or issues that other women had shared with me). If you have more ideas, please, leave them in the comments.
DjangoCon - DC Tips
Mad props to Jackie Kazil, who helped me write this post!
So, coming to DjangoCon in September? Let's have a chat about DC.
The location
DjangoCon DC is actually in Northern Virginia, not in the heart of the Capitol. Specifically, it's being held in Crystal City at the Hyatt Regency, which is close to the Crystal City Metro Station (the yellow and blue metro lines) and one stop away from Reagan National Airport (DCA).
I worked in the area for many years, and while it doesn't have as much of the DC grit and history, it makes up for in being a great nexus between convenience and cost. As someone who's been to several conferences in DC, I can tell you this is a good thing. It is a safe, clean, affordable, and transportation-accessible part of the DC metro area that is littered with Django developers.
If, for some reason, you aren't going to be staying at the conference's hotel, you're in luck. DC has a LOT of hotels. In general, the closer they are to a metro stop, the more expensive they are, but it's still possible to find no-frills hotels very close to a metro stop. If you want to remain close to the main action, I would get a hotel at L'Enfant (which is close to the Mall), Crystal City, or National Airport. Franconia/Springfield may look convenient, but it's actually not close to anything.
Getting to DC
Flying
View DC airports in a larger map
There are three airports in the D.C. region. Travel sites like Hipmunk and Kayak try to make all our local airports equivalent, as if it's just a short jaunt from each to DC. This is a dirty, nasty lie.
The airport visible from the hotel is Reagan National. It is only one metro stop from the hotel (or just under a mile walk if you are crazy). The other airports, Dulles and BWI, are inconvenient but usually cheaper if you are on a budget and willing to spend an extra hour to two hours using a combination of shuttle services, Amtrak, buses, metro. If you choose to stomach one of the other options, then add about $15 to your trip. If you take a taxi, you could end up spending more than the cost of your savings.
If you're coming in on an international flight, it may be worth looking for a flight that lands at some other international airport like Atlanta or New York, then catch a flight into Reagan. Trust me, you will not regret this.
Train
The train station is located at Union Station. It can be nice way to travel if you are on the Eastern Seaboard. The commute from Union Station to the conference isn't bad, since Union Station is also a metro stop.
Chinatown buses and other bus services
For those on a budget or looking for adventure and located on the Eastern Seaboard you might check look into the Chinatown buses. Various companies in Chinatown shuttle to Baltimore, Philideplia, New York, and Boston. D.C. to New York is between three and five hours (depending on your driver aherance to the speed limit) and costs around $20 each way. They will drop you off in Chinatown near the metro, which is the same metro line that you would need to get to the conference. Sometimes you can find a service that offers wifi and plugs and sometimes there are live animals and sketchy late night stops. Adventure!
Other buses services will drop you off in other parts, but they are usually close to a metro stop. Bolt Bus is probably one of the nicest.
Driving
Don't. It's not worth it unless you are car pooling. You won't need your car while visiting. If you do though, you can park you car at the Hotel (expect to pay a pretty hefty amount per night, though). If you want to bring your car, bring a GPS and some antacids. I've been told we're pretty bad drivers around here.
Transportation
DC's metro is stupidly simple and is probably going to all the places you want to go. DC metro area cabs are priced pretty well as a back up. I wouldn't bother to rent a car.
However, if you want to rent a bike, I have good news! There's a Revolution Cycles in Crystal City that rents bikes by the week. I spoke with the owners, and they have about 100 bikes available. Given how many people are going to be in town, they recommended reserving one ahead of time.
I'm no bike enthusiast (I still think my green Huffy was pretty rad), but I have been informed by bike geeks that these are good bikes.
There is also BikeShare, which is like Zipcars for bikes, through DC and in the conference hotel neighbor. Bring your own helmet, though. Don't be one of those guys. But there are only 10 spots at the hotel, so don't count on easily finding a bike at the conference. This is a better option used to tool around D.C. for a couple hours.
At the moment, I don't have much information regarding bike parking at the hotel. Hotels in the region are starting to have bike parking sections available, but if you're renting a bike for the week, keep in mind that you might be keeping it in your room (I hope I'm wrong).
More soon!
I'm going to post something about our tourist scene, if anyone is interested in seeing the sites while you're here.
DjangoCon 2012 - Life in DC
As long as you're in town, you might as well see some of the sights, right?
Smithsonian
When people come into town, I always point them at the Smithsonian. It's an awesome set of museums that are completely free. You can spend a week going through them and still not see all of the exhibits. They're also only closed two days a year, and neither of those days are during the conference.
- Some important notes:
- You have to go through security to get in. They're going to poke around in your bag with a stick. Some places have metal detectors.
- The art museums are super-uppity about what you can bring in. If you think about it, this makes sense. Their exhibits aren't behind glass. They have a bag check so you can stash your stuff if needed.
- The museums open at 10am. One of the buildings, the castle, opens early, but there's not much to do there besides drink a coffee and sit in the garden.
- Pictures are allowed! Bring your cameras and camcorders!
- Almost all of the museums ring the Mall. The Mall is a big strip of grass in front of the Washington Monument. It does not have any stores.
- The zoo isn't even remotely close Mall. There's a metro stop called "Woodley Park-Zoo", but this is the stop is at the bottom of the world's biggest hill. Get off at Cleveland Park instead.
If you're a gamer, make sure to check out The Art of Video Games at the American Art Museum. If gaming isn't your thing, there's still plenty to see.
Other museums
I adore the International Spy Museum and the Museum of Crime and Punishment. They film America's Most Wanted in the latter! You can get deals for both museums off of Groupon on a regular basis, so keep an eye out for those.
Nearby, there's also a Madame Tussauds if you want to go look at creepy wax people. Also, the National Academy of Sciences has a museum that's super cheap and has some awesomely detailed exhibits. It's not a huge museum, but it's packed with information. They're closed on Tuesdays, so plan accordingly.
And there's so many more! We have no shortage of museums.
Monuments!
If you want to see monuments, buy a step-on-step-off tour. Seriously. The monuments are NOT close to each other, and some of them are surrounded by highway. The buses come by every thirty minutes, which is about how long a monument is likely to entertain most people.
I wanna run!
I confess: I'm not a runner. I've toyed with Couch-to-5k a few times, but the summer always beats me. I did, however, find this trail right by the hotel. amk also recommended Bike Washington for some multi-purpose trails.
I need to go shopping
You forgot to pack socks. We're hit by a freak snow storm. They lost your luggage. Your kids will not let you back in the house without souvenirs. The vendors ran out of your t-shirt size. It's cool. We've got you covered. We're right by the Crystal City Shops. They're easy to miss because the shops are hidden inside the buildings at Crystal City, behind the restaurants.
If your needs aren't covered by those stores, you can ride one metro stop over to Pentagon City Mall and pick up almost everything you need, from clothes to Apple connectors. They even have a Costco, if you need a palate of danishes or something.
Next: FOOD
Yes, you want to eat and possibly hit up a bar while you're here. I'm still compiling that list! Locals, shout out your favorites so I can add them.
DjangoCon 2012 - Eating in DC
A caveat: When I'm eating in DC, it's usually because there's a Capitals game going on. I have very deep knowledge of the food options near the Verizon center and that's about it. Happily, DC people are heavy users of Yelp and such, so finding the good places isn't too difficult.
I highly recommend making reservations if you can. DC is unpredictable. I've walked into a restaurant on Friday night and found it dead. I've gone back a few weeks later on a Tuesday, and it's jammed. You don't know when conferences will be in town, so just save yourself some heartache and use Open Table.
That said, here's my list of places to eat and drink in DC!
Near the hotel
The hotel is near quite a bit of food. Some of the restaurants are chains (though higher-quality chains) and some are family owned.
- My favorites:
- Kabob Place - As many commenters from my last post said, OH MY GOD. This place is awesome. Fantastic kabobs, and now they have a sit-down restaurant along side the original. You get a ton of food, but prepare to wait.
- Ted's Montana Grill- A chain, but I've yet to be disappointed there. Get the buffalo, no matter what you do.
- Cafe Pizzaiolo- Awesome pizza, and a great selection of beer for such a tiny place. Also, some good gelato.
- Legal Seafood- It's fish, it's reliably good.
- Cold Stone- Ice cream is a valid dinner choice.
- Jaleo- Tapas. A bit pricey, but they have some options that aren't as bad.
- Harar Mesob- Awesome Ethopian. Do this one family style.
- Cafe Italia- Excellent Italian, with gluten-free options.
- Neramitra - It's okay Thai. I love the pad see ew. They have outdoor seating. The portions are plentiful, so this may be a way to stretch your food budget without hitting up McDonalds.
Let's go to the city!
The city is a short metro ride away. I highly recommend making the trek at least once.
Chinatown
Um, this is awkward. See, we have a Chinatown... and we put a stadium in the middle of it. As a result, our Chinatown isn't what most people expect. We have a Ruby Tuesdays with Chinese calligraphy all over it. It's a bit weird.
It's still a great place to go grab something to eat. There's a ton of restaurants down there, and they range from the extremely pricey to the fairly cheap. If I'm with a group of mixed gastrointestinal needs, I'll head to Chinatown.
- My favorites:
- Clydes- This is a good one when your group's budget is mixed. They have oysters on the half-shell alongside some reasonable salads and sandwiches. I will eat the hell out of their buffalo chicken sandwich when under the weather.
- The Chophouse- Steak! Also, an old-school bar that I love to go to.
- Rosa Mexicano- Great Mexican (you probably guessed the Mexican part).
- Oyamel- A tapas place that isn't too bad, wallet-wise.
- Fado- A very pub-like pub. If there's proper football on, you can see it there, along with all the other hooligans.
- Gordon Biersch - Beer and American style food. Try the fried artichoke hearts.
Downtown
If you're visiting the Mall, do yourself a favor and DO NOT eat on the Mall. It's expensive. Walk a few blocks away.
- Fogo de Chao- A Brazillian bar-b-que. They walk around with swords full of meat and cut it off onto your plate. You can just get the salad bar, which was heart enough to feed the vegetarians in our group, the last time I took some there.
- Capital City Brewing Company- They have beer!
- Elephant and Castle- A pub with a nice range of prices
- Old Ebbit Grill- They have oysters and a pretty nice price spread.
- Teaism - Bento boxes! I adore this place. There's seating downstairs, so don't worry if it looks crowded up top.
Also recommended, for those wanting to put on fancy clothes, is The Capital Grille.
Old Town Alexandria
If, for some reason, you don't want to go to the city, take the metro down to King Street. There, you'll be in Old Town Alexandria. Lots of shopping, bars, and privately owned restaurants. This is a quieter option for those that don't want to be in the city (DC really isn't that busy, though, unless you're driving).
Food trucks
We have food trucks! Though the conference provides lunch, it can be fun to go track one of these down instead. Also, many are open for breakfast as well.
They move around from day to day, so check out Food Truck Fiestafor what's going on by us that day.
I'm ________
Paleo: Many restaurants will have some sort of meat 'n' veggies option. Retro Rays seems to be popular with the paleo crowd, though, and the buffalo at Ted's is grass-fed. And really, you can't go wrong with Fago de Chao.
Vegan: I rarely dine with vegans in DC. Most of my friends in the area are vegetarian, and that's completely different when looking for a restaurant. Happily, Urban Spoonhas a list for you! I can highly recommend 2Amys, Clydes, and Busboys and Poets.
Gluten-free: Many places around here offer gluten-free options. Urban Spoononce again comes through with a list of GF places.
Triple D likes...
I need to do this on the cheap
Every year, The Washingtonian Magazine puts together a list of Cheap Eatsfor the DC area. It's seriously worth checking out. I recommend hitting Five Guys if you can. This goes double for you West Coasters, so you can compare it to In-N-Out.
Next time!
How to Metro!
DjangoCon 2012 - Metro
The DC area has a commuter rail called the Metro. It's pretty easy to use in my opinion. Still, I've had to give enough impromptu lessons to tourists to know it's not completely intuitive.
The Map
Do yourself a favor and get a copy of the Metro's rail map now, whether you store it on your phone or in your backpack. They have one of these maps on every car and in every station, but sometimes people stand in front of them.
Lines are indicated by color (that part should be intuitive). Stops are indicated by circles. If a stop appears on more than one line, then you can transfer there.
Why are some of the stops double circles? Because they're the first or last place you can transfer to a new line. They're not the only places you can transfer. Any circle on any color (even those that run under two other lines of color) are transfer stations. For example, at L'Enfant Plaza you can transfer to orange, blue, green, and yellow lines.
Something to keep in mind: The map is not to scale. Some stops, you can get off and walk to the next stop with no issues. Some will land you miles away from where you wanted to be. Unless you know for certain that another stop is a good alternative, do not get off of the Metro at a stop that seems to be 'close'.
Catching a train
Trains are labeled by two things: color and end destination. When you're in a station, look for the platform that matches the color of the train you're trying to get and the end destination of that line. So, if you're at the Crystal City station and you want to go to Gallery Place / Chinatown, get on the platform for the Yellow Line, on the side that ends in Fort Totten (or Greenbelt, if you happen to see a yellow train going to Greenbelt. Either works).
To find your platform, follow the signs and pillars for your line's color AND final destination. In some stations, one line has trains in both direction serviced by one track. In others, you're on completely different levels.
Once you're at your platform, there should be a sign announcing when the next train is, what color it is, what it's final destination is, and how many cars it has. Trains stop at the end of the platform (consider the opening where the train is coming from the beginning).
The stations
On the escalators: Stand on the right, walk on the left. For the love of god, please do this so you don't give some poor Washingtonian a coronary.
The stations in the city are all underground, while the ones outside of the city are a mixed bag (though mostly above ground).
Many of the stations have cell service in the stations. This drops in the tunnels for some carriers. Get your stuff done before the train moves.
There's often more than one exit for a station. Some people look at one of the posted maps to figure out which one would be best for them. Me? I take the closest one, then turn on my GPS to figure out how to get where I'm going.
Tickets and fare
Fare is determined by three things: where you are, your destination, and what time it is. Either use the trip planner tool or look at the fair list in your beginning station (the fare list is almost always right above the rail map). Fares are one-way, and you need to add a dollar each way if you're using a paper ticket.
You can get either a FastPass or a paper ticket. If you're going to do any more Metroing besides going to and from the Regean National Airport, get a FastPass. It costs five dollars (you pay ten, but it comes with five bucks on it). Also, if your FastPass doesn't have enough money to get out of the station, it'll let you go negative. With a paper pass, you simply won't get out until you up the balance.
FastPasses are magnetic cards, so just touch the FastPass card to the FastPass shield, and the gates will open. A screen should show you how much you have on your card.
Transfers are free. Screwing up and going too far is free. Metro doesn't charge you until you leave the station.
We do not run 24 hours
We are not a cool train system that runs at all hours. It was a fight to get them to stay open past eleven, trust me.
Each station closes at a different time, so they each post a sign as you go in, stating when the last train will leave that station. There might be separate times for each line.
Rush+
UGH. Why did you do this to me, Metro? Rush+ was added recently to make trips a bit shorter for people working in the city who work off of one line, but live / park on another.
Anyway, some trains only run on certain tracks and certain times. These are indicated by the dashed lines (for example, a Yellow Rush+ train runs from King Street to Franconia-Springfield).
Rush+ trains only run Monday through Friday in the morning (6:30am - 9:00am) and in the evening (3:30pm - 6:00pm).
Mobility challenged
Every station has escalators and elevators. That said, they go out of service. It's rare that I walk into a Metro station and don't see an escalator / elevator outage warning. If you can't use the stairs, keep an eye on the outages, either on WMATA's site or through one of the Metro apps.
If there is an outage, they'll usually pick another stop and offer a shuttle from that one to the stop with the outage.
Next
I think that's it for now! If you have any questions, hit me up in the comments!
Accessibility: Look at What's Out!
Overnight, my book on making accessible websites was released. It's my first book, and I'm completely over the moon. Not only do I get my own animal, but I get to put a book out on something I'm passionate about. I get to help break the paradigm that accessibility is too expensive, just about the blind, or only benefits a small portion of the population.
Even better is the timing: I'm teaching a tutorial at DjangoCon on making accessible websites. I've been super jazzed about being able to do more than drive-by conversations with people about accessibility. It's one of those topics that many people approach with skeptisism, but leave with a budding interest. This time, I'll be able to take that interest and mold it into a deeper understanding in what it means to be accessible.
We also get to improve a really bad restaurant site, which I think will be cathartic for most of us.
There's space at the tutorial, so if you're at DjangoCon or in the DC area, you can still sign up! And heck, you can say you took a class on accessibility from the woman who wrote a book on it.
Career Day
Note: I half-wrote this way back, but then had to put the blog on hiatus due to commitments that make me money. I've decided to post this now that schools are back in session, hopefully encouraging others to speak at their kids' schools this year.
I did my first career day at my son's school, and it was a blast.
I'd never done one before, because either the timing was poor, or the stinker would hide the flyer from me. This year, I intercepted it and was able to sign up, much to his horror.
The lead-up
Since I signed up for a whole day, I thought I'd be speaking for the whole day. Imagine my level of peevishness when I found out I was only signed up for three talking slots. Was I not cool enough or something? I pouted with another first-time parent (also in IT), as we waited for our turns.
The cool factor of the other parents was also a bit intimidating. We had:
- A bee keeper
- A mobile vet lab
- A freaking SWAT team
- A mounted policeman
- Loads of firemen
- A Redskins cheerleader
Seriously? I'm going to compete with that? I comforted myself that I was at least a step above the nutritionist.
Talking to the kids
Finally, it was my turn.
We were handed a sheet of things to cover. This sheet was very important, because the kids had to write down certain things about you, like how much you can make in your career, what character traits are important, and how much education you need. You know, the boring stuff. After that, you could ad lib all you wanted.
I was tempted to ad lib all the way through, but the kids stopped me cold. "You're not reading the sheet."
"Uh..."
A girl held up her worksheet. It looked like the sheet I'd been handed, except hers was mostly blank. Fine, I read off the sheet, they filled out their paper, and they were happy. Less stressed, they actually paid closer attention to what I had to say after that.
- My spiel was pretty basic:
- I told them I was a developer, a person who makes programs for computers and the Internet. Computers are stupid (the kids loved that I used a 'bad' word, by the way), and we give them instructions to tell them what to do.
- We call those instructions 'code.'
- Many things in our lives are either computers in disguise, or contain computers. Gaming consoles, phones, MP3 players, TVs, and even cars use code.
- Being good at math is a plus, but I know many developers who aren't as good at math who are great developers.
- What you need to be good at is breaking down a problem into parts, and being able to solve those parts.
Oh, and be good at writing and communication, I added. I figured I might as well indoctrinate some of them while I had them, because we totally need more developers who can write coherently.
I also brought up that I was way overdressed for my job, seeing as how I was in jeans and a nice sweater set. I usually wore my pajamas.
My spiel done, I brought up the code for a fairly basic number guessing game.
import random
high = 10
secret_num = random.randint(1, high)
while 1:
guess = input("Give me a number between 1 and %s: " % high)
if guess == secret_num:
print "You got it!"
break
elif guess > secret_num:
print "That's too high. Try again!"
else:
print "That's too low. Try again!"
I walked them through it, and surprisingly, the kids got it right off the bat. We messed around with it, raising the upper limit, changing the inputs, adding how many times the class had guessed. They really, really understood it. They knew what to change, and they knew what the outcome would be.
This should be a lesson to educators: You don't need to start kids off on some fake programming language like Scratch. We have something they can understand and actually use later.
I then brought up my roguelike, toyed around with it, and showed them how much more code was involved. They began to understand that you can do a lot with a little code, but sometimes you need a bunch of code to do something bigger.
Lastly, I made my Mac speak. This, hands-down, was the best trick, and it was one I didn't add until a few minutes before I started my talks. You'd think a bunch of kids who had consoles and computers at home that spouted voice acting all the time wouldn't be into this, but they were amazed. I got requests to do everyone's names, get the computer to sass me, and get it to say bits of songs that I wasn't cool enough to recognize.
Question time!
There was a time set aside for questions, and I got hammered.
- Common questions:
- Do you know anyone who makes video games? (Yes, I know people at EA and BioWare, and a few indie developers. The kids flipped out over this)
- Can I play your game on my XBox/PS3/DS? (No, and then I explained how distribution channels worked, and how it was very expensive to develop for consoles)
- How long does it take for you to make a website? (It varies. I've put one up in an hour, I've had them take me a few weeks. One took a group of us a year to make)
- What games do you play? (I'd list off all the titles I could think of, much to the dismay of the teachers. I think they wanted me to decry all games as evil. The kids, however, were stunned that I played many of the games that they did.)
- Can you hack our teacher's computer? (Uh, no. That's illegal.)
What surprised me
The kids repeat questions. Over and over and over again. Not variations on a question: the same exact question. I would just repeat myself until the teacher intervened.
Every kid has a smart phone. I guess my son wasn't being dramatic when he said he was the only one without one.
I should have reviewed what kids call numbers before I went in. They don't call them integers and floats. They call them whole numbers and decimal numbers. I'd recommend asking the teacher before you go in, since this could vary by district.
One kid asked me if I knew anyone who worked on Grand Theft Auto. "That's rated M. What do you know about that series?" "Um..."
The kids were shocked that me, an old person, played as many video games as I do. They were also shocked that I knew what all the systems were, and that I would actually choose not to buy one.
They were really, really good at the number guessing game. Like, almost perfect. I've known adults who couldn't do the guessing game in the minimum amount of moves.
Not everyone presenting was a parent of a child there. Some had children who graduated long ago, others were friends of a teacher. Some had approached the school and had a standing agreement. I had always assumed that the adults at career day were parents to a child there, but most weren't.
So, should you do a career day?
You absolutely should. I was up against a SWAT team and a vet, and I ended up being one of the more popular presentations. It takes barely any prep: I wrote the number guessing game while I was waiting. They already had the format they needed, and it wasn't anything I had to think terribly hard about.
Getting kids excited about programming today means that one day, you're more likely to have competent juniors. If you plan on being in the workforce eleven years from now, the fifth grader you talk to today is your new hire in the future.
Katie Writes Again!
A few months ago, I signed a contract to write a book in the Teach Yourself X in 24 Hours series. Pearson was looking to do a complete re-write of the one they already had on Python, and my name had ended up on the list.
When I tell people I'm writing a Teach Yourself book, I get one of two reactions:
Let's talk about that.
Why I'm writing a book for beginners
Personally, I think Python is an excellent language for people who want to learn programming. Why?
- It's easy to read. I've had third graders look at a script, and they've been able to tell me what it'll do.
- It's easy to install. I've been able to talk people through installing Python through a Google Hangout while I'm drinking wine and watching Doctor Who.
- It's actually used out in the real world. Nothing can discourage a user more than discovering that the fake language you had them use isn't good for anything, and now they have to go and learn something new.
- It's powerful. In the same vein, you can do so much with Python. It makes games! It makes websites! It chews through data and talks to databases and if you get a robot arm for your Raspberry Pi, I'm pretty sure it'll make you a sandwich! It covers pretty much everything those new to programming have asked me about doing.
Why I'm writing a Teach Yourself book
For starters, they asked.
Secondly, I don't have a huge beef with beginners series. I'm one of those rare developers that actually likes Head First books, and yes, I have owned several Dummies guides (one for photography, and the other for writing romance novels [1]). Hell, I own Manga guides! I think it's important for any field to have something that helps put the novice on track without talking down to them or scaring them off.
Yes, I know there's several free books out there for learning Python. One problem: they're on-line. Even the novice who knows how to google properly may not have the vocabulary to find these books. Many beginners, if they don't have access to someone who already codes, are going to go to the bookstore or library and look for a book.
Many 'Beginning' books are deceptive: They're not always for the beginner in programming. They're often for the person who's decided to learn that language who already has some programming under their belt. With tech books not being cheap, it can be an expensive gamble to see where this book is aiming. Say what you will about beginner series: they leave no doubt as to where they're going to start you: the absolute bottom rung.
It's impossible to know which beginner series a beginner is going to gravitate towards. Some like the Dummies guides, while others like the Head First books. I think we should have an excellent Python book in each of them, and the one in the 24 hours series needed to be redone. So, I'm doing it.
What was this proposal like?
This proposal was completely different than the Accessibility Handbook's proposal. That one, I had no predefined format, so it was completely up to me how I organized my material. If anything, my novel organization helped sell the book to higher-ups.
With the Teach Yourself book, I still had some freedom, but there were definitely some constraints: There had to be 24 chapters. Each chapter had to have certain sections, like quizzes or a FAQ. Each chapter had to be doable in an hour. I had to open and close with a list of what the reader was going to learn / what had been learned. There were a few optional sections, but if I included that section for one chapter, it had to be in all the chapters.
Seriously, their style guide is thirty-five pages long. It's crazy.
Also, they had someone tech-read my outline, and I'm glad they did. It ended up a ton better for the second pair of eyes (Thanks, Doug!).
So, where am I?
I've gathered my tech and test readers, and I've submitted chapters one through ten. Eleven through thirteen are written, and chapter fourteen is being a big ol' pain in my ass. It will either be the chapter that everyone loves, or the chapter that no one actually reads.
When's it coming out?
Uh, next year? Sometime?
I plan on handing in the manuscript in January or February, but that doesn't mean I'll be completely done. I'll still have edits to do, and books take a long time to get from the author's desk to the public. They take a lot of time, making sure you don't look like a complete moron, re-doing your images, double checking your resources, and basically making you sweat bullets for weeks and weeks and weeks.
[1] Note, I have no actual interest in writing romance novels. But Writing a Romance Novel For Dummies was one of the most realistic books ever I've ever read about being an author. While other books tried to nurture your delicate soul, that one flat out told you to network and market yourself, and to do what your editor says.
PyLadies Workshop Wrap-Up
A chilly weekend in October, PyLadies descended upon the American University campus to teach a class for women on programming and Python.
Workshop numbers
We had thirty-five slots (five were for AU students, and the rest were for us). Every one of the Meet-up slots was filled, and we had a small waiting list.
A few days before the workshop, we asked people to drop if they realized they couldn't make it. Some did, so everyone on the wait-list ended up getting in.
We ended up having twenty-four students, and this was a very good thing. Any more, and we would have had students without desks. As it was, our volunteers had to sit against the wall.
Materials
With our first class, we used the MIT materials given to us by other PyLadies chapter. This time, though, we wanted to try doing something on our own. I had some different ideas about teaching order, so we scrapped those materials and started fresh.
Well, 'fresh.' To be honest, I was using the order from a book I'm writing. I wanted to give it a dry run on some actual humans before sending the book off for publication. My theory, when putting together the table of contents for the book, was that going back and forth between data types and functionality offered more chances for reinforcement than diving into data types first, then functionality.
I also decided to use IDLE, since getting paths set up is a pain in the ass, when you're talking 20 windows machines. While I still think IDLE is a great teaching tool, having some people on older versions proved an issue. I may turn set-up idle time into 'learn how to get around your computer' time, since many students don't know how to open a terminal or change directories.
Sign-up
This time, we did sign-up through the DC PyLadies meet-up rather than through the much larger DC Python group. This meant our class was filled within days, rather than hours, and our wait-list was much smaller (last time, if memory serves, our wait-list was three times the number of spots).
We kept the number of slots the same (30), but added a new rule: The class was for women and their friends. Basically, men could attend as long as they came as the guest of a woman.
Why did we do this? Because last time, the waiting list was almost completely male. This made me wonder if the fact that our class was mostly women was only due to chance. Had we posted at a slightly different time, would the class have been mostly guys?
This isn't the case for every PyLadies chapter (or group that's set up for women and programming). Some have never run into this issue. Some have had huge issues with it, and have had to put this rule in place for even their casual meet-ups. We added this clause because I'm not sure yet where we fall on the spectrum.
In our case, only two men signed up, so we didn't have to have any awkward conversations. Huzzah!
Also, we didn't charge for the class. I don't believe in nominal fees. No, not because I'm completely selfless; I don't believe in them because they tend to have the opposite of the intended effect. When people shell out a few bucks for a seat, they feel more justified in not attending. You've basically given them a way to pay off their guilt. If you don't let them do this, then they're more likely to get out of bed early and get to class.
Volunteer meeting
A few days before the class, we had a meeting with the volunteers, which I highly recommend to anyone running an Intro to Python class. We covered the syllabus, schedule, and some ground rules:
- Don't dis the student's ideas, or point out that it's already been done. Everyone has to write a shitty blog app at some point in their career.
- We have two days to teach the students about Python, so we're going to be streamlining. We're not going to mention some things on purpose. We only have so many hours, and their brains are only going to be sponge-like for part of those hours.
- Watch for confusion. Never assume the student will 'totally get it later.' They will not. Back up, try again.
- If you are still running into a wall with a student, grab another teacher / volunteer and let them try.
- If you think of something that we need to go over, ping me during a break, not while I'm teaching. It might be something we're not going to cover, or I might need to whip up an example really fast. Heck, I might need to check the help, because it may have been a while since I used that particular bit of Python.
Day One
Setting-up
I don't know what the hell happened in DC on Saturday, but traffic was horrific. Like, sit in one spot for 30 minutes horrific. I'm a hardened native who thinks anyone who whines about an hour long commute is a wuss, but even I was yelling 'Oh, come ON!' over my steering wheel. I'd planned on being there an hour early, and was there an hour late.
Happily, everyone else where there on time. The first hour was dedicated to setting everyone up, so all I missed was a bunch of cursing at Windows machines.
I recommend setting up in person or over a screen share, because instructions can easily go off the rails. One click installers often aren't. Sometimes students grab the wrong version, or installed a version that was current a while back. Sometimes, they grab Python 3 no matter how many times you say "Get 2.7" because they've been trained to get the latest version of whatever they need.
You also do not want a poor Windows user screwing up their path while they try to get Python to run on the command line.
Wifi was problematic due to some duplicate logins. Some people stayed on fine. Others kept knocking each other off. We weren't doing much with the Internet, but I still recommend bringing in installers on flash drives. I also recommend making sure that the students have wifi before anyone else.
Let's get started!
Once everyone was set-up, I realized we still had a while before lunch, so I went ahead and started covering some introductions and basic concepts.
I talked a bit about who we are (most of the attendees had never been to a PyLadies meeting), gave an overview of the class, and talked about all the stuff that Python can do, and is currently doing. We had a mix of students, from a high school student to people into scientific computing, as well as a hardened Java and PHP developer. I tried to hit on as wide a range of applications I could. I wanted everyone to recognize at least one thing I was tossing out there.
At that point, I pulled up IDLE and started going over some basics.
- What's a variable?
- Numbers (floats and integers)
- Math
- Comparing values
- True and False
At this point, food was ready, so we ate and helped anyone who was still having technical issues.
After lunch, we moved into our next topics.
- if / then statements (with a quick dip into try / except)
- What's a block?
- Storing text
- Playing with text (conversion and math)
- Getting input from the user
- Lessons about trusting the user (never trust the user)
- Working with files
By this time, it was three o'clock, so we introduced the idea of student projects. We asked them to go home and think about what in their life could be automated, or what they were curious about. Sunday afternoon, we'd break into groups and help them realize at least a part of their project.
I then went home and drank.
Day Two
There was a marathon scheduled in DC, so I emailed students, warning them to check their route. I struck out early and managed to get there insanely early.
Note: Nothing is open on the AU campus on Sunday.
I didn't notice any attrition on our second day, which delighted me. Everyone showed up, we cleared up a few technical issues, did a quick review of what we'd gone over the day before, then launched into more Python.
More Python!
We were only teaching that morning, but we crammed quite a bit in.
- Lists
- For loops
- While loops
- Dictionaries
- Imports
- Functions
Brains full, we had lunch, then moved on to student projects.
Student projects
Previously, we had the entire class do a Twitter project. This time, though, we decided to let the students think of their own projects. We broke them up into groups that had roughly the same idea. There was a web group (including a woman who got Django running the night of the first class!), a PyGame group, some OS people, and... other groups. I'm kicking myself that I didn't make a note of all the groups that we had.
Volunteers and teachers moved amongst the groups, helping students focus a big idea into something more manageable. It was amazing watching students work through the logic of a program, then light up when their code started doing what they expected.
The next level?
One request we got from many students was a second class, covering the next level of coding. We didn't get to object oriented programming (because come on, people! We only had two days!), and we were barely got to cover imports or installing other libraries.
I'm putting together materials for a 'Intro to Python II' class now, and trying to figure out a way to screen for people that may be better off in the Intro I class. Ask them to write a bit of code? Quick test? Ask that they finish the Codecademy Python section before they come in? I don't want to put people off, but our time and the seats available are highly limited. I'd rather not have a class full of people who assume that they'll catch up quickly.
Releasing materials
I was asked during the class whether I would be releasing the materials for others to use, and the answer is 'yes.' I'm just considering licensing. Creative commons makes the most sense, so I've been reading over all the variations. After talking with some people who've used CC licensing, I think I'm going to go with CC-NC-SA. So, people are free to use the materials (with attribution), and alter them (as long as they share alike), but any commercial uses will require a waiver.
I may strip off the NC part, but it was pointed out that, once I do that, I can't undo it. I'm not against people charging for a class, but I want err on the side of caution for now.
Last but not least...
A huge thanks to my co-organizer, Jackie Kazil, who made the food and the space happen, DC Python for lending us their non-profit status, all of our wonderful volunteers who made it possible for me to teach without having to debug someone's computer every two minutes, Social Code for the sponsorship, and American University for the space.
Looking back, looking forward
Around this time of year, I like to look back at the past year, and look ahead to what I have planned for the next. It's less of a 'resolutions' post (I suck at keeping them), and more of a way to take stock of where I am and where I'm going.
2012 was crazy busy. I'm actually having trouble remembering all of the things that I did during 2012. It was like 2012 was the year of me finding ways to drive myself crazy.
I decided that it was a super idea to submit a poster, a talk, and a tutorial to PyCon. It was not a super idea. I looked like I was about to fall over by the end of the conference.
I got married. It was a small wedding. I thought this would make it effortless. Ha! Hahahah. Totally worth it, though. Renting a limo to barhop around DC rather than renting a hall and feeding 150 people crappy chicken? Best idea ever!
The book I signed a contract for in 2010 was released, and I officially became a published author. Right before this, though, was a month or so of mental anguish where I convinced myself I was a hack, and had no business anywhere near a book contract.
Not having learned my lesson the first time, I signed a contract for another book. This one is due out in 2013.
I taught classes. I taught so many classes. Two PyLadies intro classes. Part of a tutorial on PyGame. Then a class at DjangoCon, which, hilariously enough, I discovered the day of my class that my time-slot was an hour shorter than I thought it would be. Amazingly, I still made it all fit.
For the first time, one of my blog posts went viral. Then another one did.
I got my first consulting gig, and because of that, had to make a mad dash to create an LLC so it's harder to sue the pants off of me. Many thanks to #django-social for hand-holding me through that.
The boy started middle school. He's fully mainstreamed, now, so this has been our first year of walking the tightrope without a net. It's been... interesting.
The girl started preschool. She's very nearly civilized.
So, what's up for next year?
My second book will be published! My editor will personally strangle me if it isn't, so consider me highly motivated!
I have two secret projects that don't have any signed paper yet, so I can't do any more than drop less-than-subtle clues. Hint: it may involve more books!
The girl will start Kindergarten. If I don't sell the boy as punishment for not turning his classwork in, he will start seventh grade.
I want to get better with my jQuery and Javascript skills, so I'm thinking of projects that involve them. I'm seriously considering an interactive fiction engine.
I really want to finish my roguelike. When I get a chance to chill for a few hours, I'll pick it back up, probably starting with writing some tests for it. I already have some ideas for the next game I want to write (see above).
Okay, I have to go prep for a party in which I alienate most of my friends via Cards Against Humanity. Here's to an excellent 2013!
PyCon: Mani Party!
I've been talking about this on Twitter for a while, but I figure I'd make some sort of official announcement: I'm hosting a mani/pedi BOF at PyCon! What started as a cute joke snowballed into an actual thing, as many of the best things often do!
This will be a DIY mani/pedi party. I do not have the head-space / time to find a manicurist willing to come set up shop at the conference. Maybe in Montreal!
When and where
Friday, March 15th, from 5-6 (and possibly rolling later, but I wanted to start before dinner time)
Room 202
Do I need to sign up?
Nope! Feel free to come on by! Just a warning though... You come into the room, you are leaving with painted nails. It may be clear coat, but there will be nail painting.
What is Katie bringing?
Source: Uploaded by user via Katie on Pinterest
I'm a bit of a nail polish hoarder, so I'll be bringing a chunk of my collection with me. I may do a few last minute substitutions, so I won't be posting a list until right before I leave. I have a few polishes making their way to me that may end up getting traded in.
Rest assured, though, I'll have a nice spread of colors, from office friendly to freak flag, and a selection of top and base coats. I'll also have nail polish remover, files, nail clippers, orange sticks, and cotton balls.
Finally, for those that worry about this sort of stuff, all but one polish that I'm brining is at least 3-Free, and all are cruelty-free.
Can I bring my own stuff?
Totally! Please do! You might want to mark it in some way. I'll bring a few Sharpies and a thing of tape to help with that.
I don't have anything to bring...
Did you see my case of colors? You don't need to bring anything but your hands.
Lastly
This is a dry event. Lacquer and liquor, people. It doesn't mix.
PyCon 2013: Young Coders
This year at PyCon, I had the honor of co-teaching the very first workshop just for children at PyCon. Barbara Shaurette and I taught approximately 35 kids from eight to eighteen the basics of programming Python.
The class was offered for free to all children, and all kids got to take home a Raspberry Pi at the end of the day. The students came from attendees and the local community. Some parents were Python developers, and some had no idea what programming was. They just knew this was a chance to enrich their children's lives.
What did we teach?
Barbara took her materials that she uses for her class and, with minimal tweaks, converted them to a kids' class (I'm lazy, and have no slides for my classes). We tweaked the lesson plan for the second day when we realized that some things were confusing to the kids (or to the teacher who couldn't remember how slices work).
This is roughly what we taught:
- Who are we, and what do we do with Python?
- What is programming?
- What's an algorithm (the peanut butter and jelly example)
- Numbers + math
- Strings
- Lists
- if statements
- Loops (for and while)
- Input + Output
- Functions
- Objects
- Imports
- Let's make some games!
- Number guessing
- Raspberry Rogue
- State capitals
- Care and feeding of your Raspberry Pis
With the first class, we had to take lots of breaks. Every other module, we'd let the kids get up and move around or run to the bathroom. The second class, where the kids were older, we didn't need as many breaks (in fact, they often refused breaks), so we got through way more material. In fact, we ran out, so I ended up live coding a few more examples and running through some code Barbara had written for another class.
Barbara and I swapped back and forth as needed. Sometimes, one teacher needed a break. Other times, the other teacher knew more about that module so wanted to take point.
Volunteers
I'll confess, I was worried when I saw how many volunteers were signed up to help us. Would it be too confusing? Would we have too many chefs in the kitchen? I didn't want to tell anyone that they couldn't come, though, just in case some people couldn't make it (or forgot that they had signed up).
I asked the volunteers to stand or sit at the back of the room and watch the kids' monitors rather than the slides. My reasoning? If they're focusing on what Barbara or I were saying, they were missing what was going on with the students. The one thing we couldn't see was the kids' monitors, so we had no idea if they were lost, if their Pi was throwing errors, or if they were done typing an example. I also needed them to grab the 'off' teacher to tell us if we needed to go over something again, or if they'd noticed something particularly interesting that a student had done.
One thing I made sure to ask was that they not interrupt the class, even if they noticed an error. Having two teachers, though, meant that there was always someone they could grab and talk to quietly.
We ended up having around a 1:3 ratio, and it was perfect. Volunteers were able to jump in if a student was floundering, and many developed a relationship with a few select kids over the course of eight hours. The volunteers were also an invaluable asset to me, letting me know with nods and hand signals if I could move on, or if I needed to chill out for a bit.
Tools
We used IDLE because it's already on Raspian's desktop. Personally, I like IDLE as a teaching tool. It's included in the standard library, it does tab completion and color coding, and it even has a text editor included so you don't have to start your class off by teaching everyone about paths.
Too bad it's broke as hell.
I believe my first contribution to the Python Standard Library will be fixes to IDLE. I really do like it that much. Happily, the kids were flexible. If they needed to do a workaround, or ignore something on our slides (they were written with the standard shell in mind), they did so. They were total champs. My adult students would have been much more upset.
So... what was it like?
It was amazing. It was exhausting. I wanted to do it again. I wanted a drink.
The kids were fabulous (I only had to use Mom Voice once). They stayed on task in a way I've never seen. Hell, at work, if we've been coding for a whole hour we start whining that we need a Starbucks run. They had a network connection and never once did I see Facebook open. They had a link to Python games as well, and only opened them during breaks.
People kept stopping by and peeking in, then giving us little cheers at the sight of rows of kids with their 'coder faces' on. I tried to impress on the kids that they were the stars of PyCon. I'm not quite sure they bought it, but it's true. Everyone was so excited about the classes. I got stopped at least once an hour by someone who wanted to know about it, or how they could do it themselves.
We even had a group of programmers from Mexico approach Jesse and offer to translate our slides so that they could teach the classes in their home country. I believe we're even finding a way to send them Pis so they can copy our set-up exactly.
Lessons learned
How are my son's teachers not alcoholics?
The biggest lesson learned? Two days of teaching, even shared between two teachers, is probably too much. Barbara and I were about to collapse by the end of it. When you teach an eight-hour class, you have to be 'on' for the entire time. Even when we weren't actively teaching, we were looking at the next module, walking the room, talking to a volunteer, or helping a student. Even our break times were spent going over what we needed to change in the slides.
Next time? Two different teachers every day. The optimal pair would be two people who know each other well and can pass the ball back and forth with a minimum amount of effort. Barbara and I have known each other for years, so this was was easy for us. It would have been harder if it was someone I didn't know as well.
Mavis Beacon they aren't
With the exception of a few of the teenagers, the kids were not fast typists. When I was live-coding, some tried to follow along, word for word. As I raced ahead, they became discouraged. We also had long variable names in the examples, which took forever for the kids to type in (and they wanted to type in every code sample).
Oh man, objects...
We were able to get to classes, but it turned out to be something that's a bit difficult to explain. I'm still coming up with a story that will make objects easy for kids to grasp. I don't want to ignore them because many of the kids want to make a game, and that requires understanding what an object is.
Wait, they need to eat?!
We forgot to feed the kids snacks at snack-time. Barbara and I both face-palmed, especially since snacks were provided for tutorial attendees.
The future
I want this class to happen again. I want to see it done at PyCons, big and small. I'd love to see them done in smaller communities, and I even have a few leads back home. People offering to translate our slides has me jumping with joy.
I have ideas for adding to the class, and I'd love to see what other people might add to the material.
I will totally be doing this again and again.
PyCon 2013: Mani Party Wrap-up!
It all started off as a joke.
I've decided I'm bringing all my nail polishes to PyCon so we can have a mani party.
— Katie Cunningham (@kcunning) November 24, 2012
I've decided I'm bringing all my nail polishes to PyCon so we can have a mani party.
— Katie Cunningham (@kcunning) November 24, 2012Flush with my new obsession, nail polish, I told everyone on Twitter that I was totally going to bring some polish along so we could all do our nails at PyCon. I wasn't really going to do it. Who would come? It's not like PyCon was a fashion show. We had to give people explicit instructions to take showers. This is not a mani/pedi crowd.
People responded, though. Yes! Please! Bring some polishes! And yes, people. About half of the people on Twitter asking me to go ahead and do it were men. Apparently, more of my guy friends like nail polish than I thought. Hell, I was surprised most of the women liked nail polish. At conferences, I almost always saw bare, stubby nails.
So... I started planning. I took my ever-patient husband's poker set and turned it into a travel case. I picked out colors. I put them back and picked out completely new colors. I got more nail polish in and had to swap again. I got a BOF room reserved. I blogged about it.
I was sure that no one would show up.
I was wrong.
The BOF
I didn't count how many people showed up. My guess is that it was somewhere over twenty, but I was spending most of my time finding bottles and being very, very excited.
At the last minute, I got contacted by a sponsor. A sponsor! StyleSeat wanted to bring a professional nail artist so this didn't have to be a completely DIY affair.
Doug Napoleone supplied us with the cutest monkey nail blow dryers I've ever seen. We had a few of these left, so gave them away at the PyLadies booth. A Survey Monkey booth guy was enamored with the last one, so I'm wondering if we'll have those sponsored next year as well.
I had planned for the event to last an hour, but I forgot that not everyone is a twitchy freak who does theirs every day (like me). I can do my nails in ten minutes, but most people are going to take way longer. We also had a fairly steady stream of people coming in and out of the room.
We also had a good number of men show up. I had posted a warning, that coming in meant you were getting your nails done, but they all seemed to come in with an idea of exactly what color they were looking for. I don't think I saw a single clear coat leave the room.
In fact, I am now insanely jealous of the infamous Gregory P. Smith. He came in and declared that he was going to do the Python logo on his nails. Pfft, I thought, I've been trying to do that for months. Good luck, dude. What did he do? A perfect Python logo. Okay, fine. I'm going to assume skills like that are why he works at Google.
Next year
I would love to do this again next year. Heck, I might already have a sponsor lined up. I will do a few things differently:
- Have a room with ventilation. I think I have short term memory loss from the fumes
- Budget more time! We obviously weren't done in an hour.
- Make sure to warn anyone coming after us that there may be fumes. A BOF was right after us, and I felt horrible about accidentally booting them from their room.
- Pack a few more quick-dry top coats. We kept running out!
- Remember to bring labels for people who bring nail polish. I ended up with a few extras in my kit. If they're yours, let me know! I'll Amazon you a replacement!
Other than that, I was shocked at how well it turned out. I'm looking forward to doing this again next year!


































