Recent Entries

Getting more women to your meet-up May 07, 2012

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.

PyGame: Returning to Combat April 30, 2012

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

Katie sits before a desk with crumpled paper on it. She holds up another piece of paper, a joyous look on her face. Caption: A plan! Now, surely things won't go to shit!

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!

PyGame: Returning to Items April 23, 2012

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!

Rogue guy has found some pickles in a treasure chest. He doesn't look happy.

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: Hallways April 17, 2012

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

Rogue guy is wielding a sledgehammer, looking determined.

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:

A sketch showing where doors would be okay, and where they would not be possible.

  • 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

A screenshot of the Roguelike, with rooms and hallways implemented

Next

We make our items worth getting!

PyGame: Rooms! April 09, 2012

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?

The rogue and the slime monster are in a very badly constructed room. The rogue looks worried. The monster is wearing a hard hat.

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

A small room is almost completely filled by slimes and chests.

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: View seperation and throwing away code April 02, 2012

Where am I? Here!

View seperation

Katie looks distressed. Caption: How have I not been eaten by wolves?

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!

The rogue and the derpy monster have been put out with the trash. The rogue doesn't look happy.

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!

Fab for All March 29, 2012

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: FIGHT FIGHT FIGHT March 27, 2012

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!

A flowchart outlining a possible combat scenario.

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

Rogue and derpy monster are messed up, with weapons sticking out of them everywhere. The rogue is still cheerful, giving the viewer a thumbs up.

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.

Lighten Up: A follow up March 26, 2012

The Numbers

In 48 hours, Lighten Up got over 62k views. 

It stayed on HackerNews's front page for two days. It generated almost 800 comments.

It was syndicated by Buzzfeed, and quoted by The Guardian, TechCrunch, and IT World

Jaqui Chang and Jeff Atwood mentioned the post, as well as one of the people from Mojang, and Phil Haacked.

There were so many comments on the post, it broke my commenting module and I had to close comments.

It was mentioned in over 1500 tweets.

The Reactions

There were people who blew off the post, but I expected that. After all, the post is about being blown off.

There were heart wrenching posts by men and women detailing how it had happened to them, or to someone they loved.

There were people who admitted that things like this caused them to leave the industry.

Some admitted that they were discouraging their daughters from careers in development due to the culture.

The most powerful posts were from men who realized that they did this, and that they needed to stop.

Leaders of tech groups reached out to me, asking how they could attract more women to their groups.

Owners of companies reached out to me, offering me work.

I was stunned.

Some clarifications

While I left the tech industrry for some time, I've returned to it, and am determined to stay. I was out of it completely for five years, though, and was only drawn back in when I realized that coding was what made me happy. 

I'm stronger now at 31 than I was at 21. This time, I'll make a stand.

I'm very happy with my current job, and haven't had a whiff of what happened to me at other places happen here, at Cox. They're an awesome bunch of people, and proof that you can joke and have fun without having a hostile work environment.

And finally...

The reactions were a powerful indication that, even though our community has its issues, there are people that are willing to work through them and make this a better place for everyone.

Thank you.

Lighten Up March 21, 2012

Recently, I was asked why a woman that loves coding would ever leave the field. It's true: at one point in my life, I decided that coding would be something I'd do only in private. I was only slowly pulled back into the fold.

Let me tell you, I love coding. Been doing it since before I hit puberty. I did it when I barely had the money to keep a server up. I do it on the weekends and evenings, and I'm teaching my kids how to do it. I've spent thousands of dollars to go to conferences so I can learn more about it. Why would I ever leave the profession where I got paid real money to do what I love?

In short, I got tired of being told to 'lighten up.'

This industry is one of subtle sexism. I almost prefer outright sexism, because at least that you can point out. The subtle barbs are usually dismissed as something I need to not care about. It was a joke! Sheesh. Why are you so sensitive?! All I did was make a joke about you needing to be in the kitchen!

Lighten up.

The barbs aren't always jokes, either. Sometimes, it's attempts to push me into a traditionally 'female' role. As the woman, I've been the only person in the group asked to put together a pot luck (presumably, this work is beneath the males). I've been the only one asked to take notes in a meeting... even if I'm the one who's presenting (because my title really should be 'secretary who we let on the servers'). I once had a boss who wanted to turn me into a personal assistant so badly, it ended up in a meeting with HR (he, as white and male, should be allowed to rein in the only female on the team!). 

Why did I have to take all of the above so personally? Sheesh. 

Lighten up.

Sometimes, even the unsubtle jabs are hard to combat. What do you say to the guy who sits across from you when you dress up and makes a comment to everyone about it? "Oop, Katie's got the low cut dress on today! I know where I'm sitting!" Say something, and derail the meeting? Go to HR and get stuck with his work when they move or can him? Get transferred off the best team and languish somewhere else? Start wearing sweaters, even though my breasts feel like they're boiling in there (yup, that's one reason women like low tops, guys)? Which label do I want to be stuck with today?  Ice Queen or Slut? 

What is wrong with you? It was one comment! I bet you'd sue him if he complimented your shoes. 

Lighten up.

Every time I spoke up about the above crap, I got some sympathy, but I also got some guy who didn't understand what the big deal was. If I wasn't in the middle of being raped or beaten or threatened or fired, guess what I needed to do?

Lighten up.

How long would you put up with it? Do you love anything that much? If your spouse subtly treated you like crap every day, how long would your marriage last? If you saw a friend being treated this way by their boss, wouldn't you tell them to quit? 

Or would you tell them to lighten up?

You, person who told me to lighten up, saw one little thing. It didn't seem like a big deal, did it? One little line! One joke! One comment! But it's not just one thing to me: it's one of thousands that I've had to endure since I was old enough to be told that 'X is for boys!' It's probably not even the first thing I've had to deal with that day, unless you've gotten to me pretty early. 

That's the main problem with subtle discrimination. It leaves those that it affects the most powerless against it, quietly discouraging them. If they speak up, they're treated to eye rolls at the least, and at the worst, are called oppressors themselves. We're accused of not wanting equal rights, but of wanting tyranny. 

I would just like the million little barbs to stop, and I would like to not be told to 'lighten up'.

(Update - Have to close comments! There's some issue with them not posting, and I can't look into it right now. Sorry!)

The Littlest Intern March 21, 2012

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.

Hannah, with red eyes and sharp, angry teeth, sneaks up on her brother.

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.

TaskSkill
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: Animation and Active Gaming March 19, 2012

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.

Rogue dude sliding across the floor.

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.

Katie looks disheartened. Caption: Well... hell.

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!

PyCon 2012 - Thoughts on the Poster Session March 13, 2012

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: Fog of War and Organization March 05, 2012

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:

A diagram showing imports in PyGame.

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

Rogue guy, looking at a dying torch, surrounded by darkness. He looks very worried.

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.

Fog of war examples: Hard shadows, field of war with memory, and field of war with light sources.

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!

Black March February 28, 2012

Katie, dancing, badly. Caption: Awkward Dancing Forever!

March will be an interesting time to watch the entertainment industry. Anonymous is calling it Black March, and is requesting that during this month, no music is purchased, no movies patronized, no DVDs purchased. Basically, no patronizing of the entertainment industry whatsoever.

While boycotts can be effective, they're only really effective if it ends up changing people's habits in the long run. Just asking people to put off buying Iron Man 2 for a month isn't going to have a long term impact. That's why gas boycotts are silly. We're just going to buy it the next day. We haven't changed our behaviors, and the companies in charge know it.

So, how do we change our behaviors when it comes to the entertainment industry? I don't really patronize the movie industry, but I do love me some music.

The RIAA has its fingers in a lot of companies, but not all of them. Plenty of independent labels choose not to join the RIAA, and many artists opt to self-publish. There's tons of awesome music out there that isn't touched by RIAA.

My proposal: every day, during the month of March, try to post an album that is published by a company that is not a member of the RIAA.

Why an album? Why not an artist? I found while digging through my digital stash that many independent artists often had one or two albums under an RIAA label, usually after their first release. They usually returned to an independent label after an album or two. I'm not sure why this happens, but I'm willing to chalk it up to youthful indiscresion.

I love to find new music, and I hope others will join me. I'd love to see what other people would recommend. I'd love for people to try out new bands, and see that life outside of the mainstream isn't all inaccessible German techno and caterwaulling.

Pygame: Monsters and Walls! February 27, 2012

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.

A Diablo 2 screenshot of the mini map. The user has never been to a certain area, but they can see the outline of the room. Caption: How do I know about this?

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!

The rogue has encountered an ooze, and is terrified.

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.

Three kinds of inventories: slot based, volume based, and weight based

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

Rogue guy, with a cunning hat, a rainbow, a sword, and a book. He is proud of his epic loots.

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: Treasure! February 20, 2012

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:

A screen shot of a Gimp color picker, with the RBG values highlighted

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?

Rogue discovering a chest. This delights him. Caption: Finally! Purpose!

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.

Screenshot, showing rogue's path through the darkness. There are shadows where he can see, but it's still kind of dark. Caption, with arrow pointing to shadows: Sort of dark here!

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]
Lesson learned: test for your bounds.

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! 

Life with Aspergers February 16, 2012

I get asked what it's like to have a son with Aspergers. Sometimes, the question comes from a parent who's child just got diagnosed. Other times, it's from the genuinely curious. Other times, it's from those that wonder if, had they been a bit odder or a bit later, would have been diagnosed.

For every one person that asks, I'm pretty sure there's four or five that would want to know, but don't bring it up. They think I might be offended. They think it might be prying. They worry that it'll come off as morbid curiosity.

I've never been offended by anyone asking about Jake. I'm very open about his Aspergers, mostly because I want people to see that people on the spectrum aren't some strange alien race to be avoided, but that they're people. I believe that hiding it only leads people to think that disability of any kind is a dark mark on a household. It isn't.

The Early Years

I thought Jake had colic when he was born, but, looking back, I wonder if his sensitivities were on the surface from day one. He would scream (not cry) for hours while his father and I would pace around with him. If you set him off, your day was screwed.

If he wasn't screaming, however, he was a really good baby. He smiled. He engaged. He entertained himself. In fact, he entertained himself really well. While he was always happy have the attention of someone, he didn't require it. He was just as happy messing with his toys or watching Thomas the Train.

In fact, sometimes it was really hard to play with him. I would try to play with him, but if I wasn't performing, he just wasn't interested. At one, he would shoo me from the room if I wasn't being enough of a dork.

The fits didn't stop, even though he was well past the colic age. They came on for no reason, it seemed. He'd be playing, and suddenly he'd pitch the toy he was playing with across the room and start screaming. It just became one of those things that we learned to deal with. I was neurotic about always having a quick way out of any social situation, because I never knew when he'd go off.

Around two and a half, I realized that he wasn't talking. He said 'ma' and 'da', but that was it. He didn't seem to understand commands. I mentioned it to his doctor, and she pushed me to contact Child Find, and to ignore anyone that was telling me that this was 'normal'.

After some hemming and hawing, I called them, and the tests started.

Jacob. Hated. Tests.

He would scream and cry. He'd throw himself against walls beg to go home (He'd finally picked up a third word: GO). He refused to even look at the cards or the tester. If it required equipment, he'd do his best to destroy it. One tester looked at me after a particularly trying session and said "Wow. You must be really tired."

After we figured out that tests weren't working, the state decided to send someone to his daycare, once a week, to work with him. There was no official diagnosis besides the obvious: language delay.

Hyperlexia

He really liked the lady they sent to work with him. She was gentle and non-threatening, and brought special toys that were just for him (he's still not much of a sharer). Around this time, on his own, he picked up the alphabet. While his vocabulary wasn't growing, he was learning to spell words. If he wanted milk, he'd say "M-I-L-K". If he didn't want something, he'd spell out N-O. He learned to spell everything he could.

It wasn't talking, but hey, I'd take it as a step forward.

Excited, I shared it with his teacher. She noted it, but didn't seem as thrilled.

After a year of testing, she came to me, outlining where he was doing quite well, and where he was still having issues.

    The good:
  • He was talking more!
  • He was having fewer freak-outs as he was able to communicate more
  • He'd finally gotten through the horror show of potty training
  • He was playing with other children more smoothly
    The bad:
  • He spoke using mostly Gestalt (stock) phrases.
  • He was still doing that alphabet thing
  • He had odd ways of holding his body
  • He was having issues with fine motor control

"We think he has something called hyperlexia."

"I've... never heard of it."

"It's usually a precursor to a diagnosis of Autism."

That hit me in the stomach. I had hoped that his issues could be solved. If we could get him up to speed by Kindergarten, then we would be fine.

You can't solve Autism, though. You can treat it, you can shape it, you can do your best, but it never goes away.

I started reading up on hyperlexia, and was stunned. These other kids, they sounded just like Jake. When they drew, they did so in letters. They could read long passages without effort, but had no clue as the meaning. They disliked toys that were people, preferring trains or cars or magnetic letters. Changes in routine caused them to throw world-ending fits.

They also had the same behavioral issues. They threw fits. They didn't follow commands. They were not sharers.

Though I felt like I had been punched in the stomach when I first got the diagnosis, I suddenly felt a weight lifting. I had Jake when I was twenty. I was uncertain about my parenting skills. I had thought, all along, that I was a bad parent. Just like that, it wasn't my fault anymore. He had a condition, and we were getting him treatment. I had done just what every other parent before me had done.

I was a pretty good mom, and I was going to make this better.

Autism

Jake started kindergarten with the label of hyperlexia, but it was understood that it would morph into an autism label before long. Hyperlexia isn't normally something that sticks around. It's a way the child organizes the world when their brain isn't doing it the normal way. Once they get the skills to organize the world in a more efficient way, the hyperlexia goes away.

Once Jake was in school, this happened pretty rapidly. He was talking more and more, and using unique phrases almost exclusively. His social skills were picking up. The fits had all but disappeared.

Lack of language makes us super cranky.

He'd changed so much that, upon his re-evaluation, the woman who had once commented that I must be tired didn't recognize Jacob at first. It wasn't until I related the tale of him throwing himself against the door that the light bulb went on.

Aspergers

Somewhere around age seven, Jacob's diagnosis morphed again, this time to Aspergers. This didn't feel as dramatic as his first diagnosis. By now, I was used to the shifting sands that is the life of having a special needs child. Does he still get services? Awesome, fine, where do I sign again? You can call him a giraffe if it means he gets the services he needs.

Jake, now able to talk just fine, was making up for lost time. He never stopped talking. Ever. If I needed him to be quiet, I would make him put a hand over his mouth. After a few minutes, he'd need both hands. By ten minutes, he looked like he was going to burst if he didn't get to say something right now.

The last traces of hyperlexia also faded around this time. The kid that, at three, could spell Zimbabwe and xylophone was flubbing spelling tests. I bet I'm the only parent that was happy to see a 'C'. He'd learned to organize his thoughts in a different way.

Around this time, Jake got into hockey, football, and video games. Unlike most parents, I pushed this. You see, Jake, and so many Aspergers people, will focus on strange topics and want to talk about them obsessively. With Jake, it was production companies.

No one wants to talk about production companies. No. One.

Video games and sports, however, are perfect for the detail oriented mind. They're practically nothing but details! At a family gathering, he struck up a conversation with another boy about the Redskins, and it sounded so utterly normal, I wanted to cry.

This was also when the school got serious about mainstreaming him. He'd always been a little mainstreamed, but it was now clear that he could take regular classes without too much trouble.

While he was still in a regular class with special aides, they now left him on his own and helped the other students, some diagnosed, others not.

My proudest day was the day that some educators for Harvard came down to see how his school was doing mainstreaming. His teacher asked them to pick out the four children in the class who were receiving services.

They skipped Jake, and picked out a child that was likely on the spectrum, but whose parents refused testing.

Today

These days, Aspergers is something we trip over more than treat. It's becoming harder to see where it's shaped his mind in peculiar ways.

One thing I realized recently is that Jake can't do proportions. After going around and around over a puppet for a project, I finally realized that ratios had no meaning for him. No matter how many times I told him to make the arms bigger, they were still going to end up the same size. I ended up doing the cutouts myself. I could have taught him, but honestly, this is probably the only puppet he'll ever have to make.

He's also very literal, and takes fears very much to heart. The current bully-awareness campaigns have made him certain that he shall fall at the hands of hordes of bullies. This is coming from a child that's never been bullied, and the one time a kid was mean to him, it went over his head completely.

He has trouble with grace under fire. The second something makes him anxious, his language skills disintegrate. He forgets all of his training regarding eye contact and stim. You can watch him revert, and it's a reminder that the issues are still there. We just painted over them.

Frequently Asked Questions

Doesn't he just see the world differently?

It would be really nice if it were just that simple.

Yes, Jake can sometimes have a sideways view of the world. While this will occasionally give him an advantage, it's more common that it trips him up. He processes words differently, so he often has trouble with verbal commands. He's very detail oriented, but has trouble seeing the big picture.

This world is built by, and occupied by, neuro-typicals. He has to navigate this world, like it or not, and he has to develop the skill set to do so. It's been a hard trek, and the work isn't over yet. It's not about seeing the world differently. He's missing parts of the world that we have to teach him how to see.

Do Autistic kids have emotions?

Oh my god, this one cracks me up. No, really. Anyone who's ever gotten in the path of an autistic child in one of their mood swings knows for certain that they have emotions. Those emotions will burst your eardrums.

I think what they mean is, do they have interpersonal emotions. Do they love? Do they feel attachment?

Yes, they do. Even the most severe cases (non-verbal, non-communicative) will show preference to some people over others. There was one little boy at the special needs day care where I worked that showed this, perfectly. He was quite severe, never talking, stimming all the time, never making eye contact. He obviously adored his mother. He was happy when his father picked him up, but if mom walked through the door, he was besides himself.

As for Jake, it became very clear that he craved our love and approval. If we were ever disappointed in him, he would throw himself back and moan that we'd broken his heart. To this day, I have to keep the phrase 'I'm disappointed in you' put away, except for occasions when I really need to get through to him how bad something was.

So, that vaccination thing...

No. Autism is not caused by the MMR vaccine. The paper this was based on was hugely flawed, and has a lot of nefarious backstory to it.

But what causes it?

Interesting question.

Remember, Autism is a cluster of symptoms. Some spectrum cases are clearly caused by genetics (Fragile X). Some show signs of being a combination of genetic factors. There's a few cases that pointed to environmental factors, though they're rather rare.

Think of it like a broken arm. You can break your arm all kinds of ways: a car accident, falling down the steps, a bar fight, or just due to crap genetics that have left your bones brittle. The treatment, though, is often the same: get the bone to repair itself.

There can be many causes for Autism, but that doesn't affect the result. I view the causes as interesting trivia, but since 99% of the causes can't be helped, I rarely worry about them.

Did Jacob regress?

Many parents report regression with Autistic children, but I have to say, no, Jake didn't regress. He didn't lose language. He never lost social skills. I know it happens, but it's actually less common than the media makes it out to be.

What do you think about the DSM-V's re-categorization?

Eh. My background is in Psychology, and most of the doctors I worked with didn't give two farts about the DSM.

I will say this: re-categorizing it as a personality type, rather than a disorder, isn't a bad thing. Many Aspergers people have lived a full life without intervention. A wise professor once told me that a disorder is only a disorder if it keeps you from being happy, and being a productive member of society. Do those people have a disorder?

Did you worry about having another child?

Yes. I knew it was a risk. There are strong indicators that autism is genetic. I won't lie: I was in a state of mild panic the entire pregnancy, and well into her first year. It wasn't until she started talking that I began to relax.

Why did I risk it, then?

I had an older brother who died when I was four. Being an only child, for me, was a terribly lonely experience. Maybe it was because I had seen, briefly, what life with a sibling might have been. Being autistic is already lonely. I didn't want to add on to that loneliness if I could help it.

How does he get along with his sister?

Jacob ignored Hannah until she became mobile. After that, she would be ignored no longer and demanded his attention.

These days, they play surprisingly well together (when they're not narcing on each other). It's mostly physical stuff, like races and wrestling, but that's a language that Jacob's always understood.

If she says something that's inaccurate, it drives him up the wall. This delights her, of course. I try not to step in, though. Life is about challenge, and how we deal with it. He's learning to ignore her pushing his buttons.

More questions?

Hit me up in the comments.

Pygame: Getting started February 13, 2012

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

Katie, giving her laptop a dark look. Caption: Please make this code sentient, so when I kill it, it will feel pain.

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.

A rogue character standing with a large red D behind him.

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?

Here's the commit!

Next

I get serious about splitting things up, and we find Treasure!

Writing - Lesson Two - Finding the Time February 06, 2012

Katie, I get asked every now and then, Where do you find the time to write?

Katie, in a superhero pose, in a superhero mask. Caption: Fear not, citizen. I shall organize the shit out of this.

I have to admit, I love that question. It makes me feel like a god-damned Wonder Woman. Why yes, I work full time, go to conferences, raise two kids, attend to my SO+, and write a book. What did you do last weekend?

There's another side my situation, though, that I don't often share. I love video games. I cannot live without seeing the latest My Little Pony or Dowton Abbey. I give a lot of money to Comixology every month. I read books filled with explosions and sex and improbable complications and I often lose myself in them for hours and hours. In short, I'm a distractable lay-about, too.

Obviously, though, I found the time to write about something that I'm excited about. So, how did I do it?

Priorities

In no particular order, we all have priorities:

  • Ourselves
  • Our kids
  • Our spouse
  • Other random relatives
  • Our friends
  • Our job
  • Other organizations

If you want to write, you're going to have to start evaluating those priorities. There are things on that list that, giving up, would cost you quality of life. Do you really want to spend less time with your kids? Do you want to lose date night? Are you ready to call your gaming group quits? Will you deprive yourself of anything pleasurable? Will you quit your job? You probably shouldn't, lest you become some bitter shut-in who can only ponder about the good life, rather than live it.

There are, however, some things implied on that list that you can give up without pain. Writing a book gives you the best excuse ever to get out of a baby shower. It works for getting off the 'volunteer to make cupcakes' list, too. You don't want to slack at your job, but maybe you could write on your lunch break instead of eating out, and shave a few inches off of your waist. The kids could so use an earlier bedtime. You know what? That one show you really liked last season is just crap this season? Maybe you can drop it.

Saying no

Katie, happy, with a thumbs up, holding a manuscript. Caption: Tired of random functions? Write a book!

I started saying no a lot more when I started writing. No, I'm not killing myself to make sure my daughter has a birthday party when she'd already had two celebrations in her honor. No, I'm not going to organize a weekly Python meeting, and no, even the monthly one will not have presenters or a rented out space. You'll drink your coffee and like it. No, I'm not killing myself to make dinner for friends when they come over. Indian takeout will not kill them. No, leftovers will not kill the kids. No no no.

My life isn't lessened by cutting the above out of my life. I get more time, and I now have more time to apply to the book and less time spent doing something that would have annoyed the hell out of me in the first place.

Every day

Go read my last post. You're going to have to write every day, not only because it gives you better momentum, but because you're not going to be able to lock yourself away for huge blocks of time. Locking a person in a room is what they do to crazy people. You do not want to be a crazy person. That's what you'll become though, after the third weekend of barricading yourself in the bedroom with your laptop, while the world beckons from the window (or Twitter, if you forgot to block it while in your Fortress of Solitude).

I've seen the people who wrote a book by locking themselves away for months at a time. There's a look in their eye that says 'keep me away from sharp things.' Really.

Finding the small blocks of time is easier, anyway. I was able to write on Thanksgiving and Christmas, two crazy busy days. Both days had small respites in the morning, while I was drinking my coffee, during which I could bang out a few words. I was even able to find time when my family went on a cruise. I can easily make the kids go away for a half-hour by turning on their favorite show. I don't know how I'd shut off the world for a whole weekend. It's like daring the universe to throw obligations at me.

Family

Katie, bags under her eyes, staring off into space. Her daughter has climbed the back of her chair. Caption: I will give you five thousand ponies if you go away.

I admit, when I decided to write a book, I just announced it to the family as a thing I was going to do, hell or high-water. That probably wasn't wise, but I'm lucky in that they're very supportive, and used to me doing things like this.

Still, even though I was the one doing all the writing, they were still going to end up paying for my new obsession. It was uncertain how much time I would need in order to write, and that was time that was being taken from helping to run a household, hang out with the family, or go do cool stuff.

It took us a while, but we finally came to a system that works: I don't go into crazy-write-all-day mode, and they understand if I have to take a half-hour to write without being disturbed. Looking back, I wish we'd just laid this out explicitly before I set a single word to paper.

Had they not been on board, I would have had to find my chunks of time when they're not around, like my work lunch or extremely early in the morning. Not impossible, but having the freedom to write when I want has made this project a lot easier.

TL;DR

Start saying no. Find small chunks of time. Get your loved ones on board. Write every day.

Older