Recent Entries

DjangoCon - DC Tips August 07, 2012

Mad props to Jackie Kazil, who helped me write this post!

So, coming to DjangoCon in September? Let's have a chat about DC.

The location

DjangoCon DC is actually in Northern Virginia, not in the heart of the Capitol. Specifically, it's being held in Crystal City at the Hyatt Regency, which is close to the Crystal City Metro Station (the yellow and blue metro lines) and one stop away from Reagan National Airport (DCA).

I worked in the area for many years, and while it doesn't have as much of the DC grit and history, it makes up for in being a great nexus between convenience and cost. As someone who's been to several conferences in DC, I can tell you this is a good thing. It is a safe, clean, affordable, and transportation-accessible part of the DC metro area that is littered with Django developers.

If, for some reason, you aren't going to be staying at the conference's hotel, you're in luck. DC has a LOT of hotels. In general, the closer they are to a metro stop, the more expensive they are, but it's still possible to find no-frills hotels very close to a metro stop. If you want to remain close to the main action, I would get a hotel at L'Enfant (which is close to the Mall), Crystal City, or National Airport. Franconia/Springfield may look convenient, but it's actually not close to anything.

Getting to DC

Flying


View DC airports in a larger map

There are three airports in the D.C. region. Travel sites like Hipmunk and Kayak try to make all our local airports equivalent, as if it's just a short jaunt from each to DC. This is a dirty, nasty lie.

The airport visible from the hotel is Reagan National. It is only one metro stop from the hotel (or just under a mile walk if you are crazy). The other airports, Dulles and BWI, are inconvenient but usually cheaper if you are on a budget and willing to spend an extra hour to two hours using a combination of shuttle services, Amtrak, buses, metro. If you choose to stomach one of the other options, then add about $15 to your trip. If you take a taxi, you could end up spending more than the cost of your savings.

If you're coming in on an international flight, it may be worth looking for a flight that lands at some other international airport like Atlanta or New York, then catch a flight into Reagan. Trust me, you will not regret this.

Train

The train station is located at Union Station. It can be nice way to travel if you are on the Eastern Seaboard. The commute from Union Station to the conference isn't bad, since Union Station is also a metro stop.

Chinatown buses and other bus services

For those on a budget or looking for adventure and located on the Eastern Seaboard you might check look into the Chinatown buses. Various companies in Chinatown shuttle to Baltimore, Philideplia, New York, and Boston. D.C. to New York is between three and five hours (depending on your driver aherance to the speed limit) and costs around $20 each way. They will drop you off in Chinatown near the metro, which is the same metro line that you would need to get to the conference. Sometimes you can find a service that offers wifi and plugs and sometimes there are live animals and sketchy late night stops. Adventure!

Other buses services will drop you off in other parts, but they are usually close to a metro stop. Bolt Bus is probably one of the nicest.

Driving

Don't. It's not worth it unless you are car pooling. You won't need your car while visiting. If you do though, you can park you car at the Hotel (expect to pay a pretty hefty amount per night, though). If you want to bring your car, bring a GPS and some antacids. I've been told we're pretty bad drivers around here.

Transportation

DC's metro is stupidly simple and is probably going to all the places you want to go. DC metro area cabs are priced pretty well as a back up. I wouldn't bother to rent a car.

However, if you want to rent a bike, I have good news! There's a Revolution Cycles in Crystal City that rents bikes by the week. I spoke with the owners, and they have about 100 bikes available. Given how many people are going to be in town, they recommended reserving one ahead of time.

I'm no bike enthusiast (I still think my green Huffy was pretty rad), but I have been informed by bike geeks that these are good bikes.

There is also BikeShare, which is like Zipcars for bikes, through DC and in the conference hotel neighbor. Bring your own helmet, though. Don't be one of those guys. But there are only 10 spots at the hotel, so don't count on easily finding a bike at the conference. This is a better option used to tool around D.C. for a couple hours.

At the moment, I don't have much information regarding bike parking at the hotel. Hotels in the region are starting to have bike parking sections available, but if you're renting a bike for the week, keep in mind that you might be keeping it in your room (I hope I'm wrong).

More soon!

I'm going to post something about our tourist scene, if anyone is interested in seeing the sites while you're here.

Accessibility in Video Games - Hearing July 24, 2012

This group is more than just the Deaf. It includes people who use hearing aids as well, since most hearing aids do not work well with headphones and commercial electronics. Sometimes, there's feedback. Sometimes, they pick up too much. Some people that wear hearing aids are still deaf to certain pitches, no matter how much they crank them up.

As a side benefit, it includes anyone who forgets their headphones at home at least once a week, like yours truly.

Dialog

Your cut scenes and dialog should always, always have subtitles. Many developers like to simply put the text at the bottom of the screen, with no background. This can be an issue if your text is light, but you have a scene where it happens to be snowing. You can either test every single scene to make sure your contrast remains high, or you can use banding.

Screenshot of a snowy scene. The white text at the bottom is almost impossible to read.

If you use banding, you put a band of color at the bottom of the screen that's used exclusively for captioning. Ideally, it would be placed below your screen, not covering part of the screen, so that the user isn't missing out on any of the action.

A snowy scene, with the captions in a box beneath the game. The text reads: See? This is better. Everyone can read this!

A few notes about styling: If you can, let people customize this. Me, I'm a white-background-dark-text person, but I know others who hate that combination. Also, ALL CAPS ARE BAD. DO NOT USE THEM.

Another side-benefit of using subtitles: If your voice acting is cringe-worthy, I can mute it. I've had to do this with so many triple-A titles, it's not funny.

Good captions

Good captioning is not transcribing. Usually, in games, since there's a script, this isn't an issue. When people speak off the cuff, they pepper their language with mis-fires, um's, er's, and restarts. An actor probably isn't going to do this unless someone's trying to be super edgy.

However, there is one place where I've seen captioning go off the rails: wordiness. People can only read so fast, so try to keep the words on screen down around ten to twelve. After all, they need time to read and watch the action. This might mean cutting words out of a particularly verbose scene.

Also, make it clear who's speaking. Adding their name to the text can get a bit bulky, so a common solution is to use a slightly different font color for the different speakers. Just remember to keep an eye on color contrast. Does that dark green look different enough from that dark red? Just because someone has issues with hearing doesn't mean that can't be color blind, too!

Cues

Also, dialog isn't the only important thing going on in your game. Off-screen cues are incredibly important. True story: the first time I played Plants vs. Zombies, I played it on my Touch with the sound off. I thought it was a pretty easy game until the stage where they introduced balloon zombies. I felt like it jumped several levels in difficulty. What the hell?! It wasn't until I put my headphones on that I realized how important the off-screen sounds were. Hearing the balloon fill up with air was a cue for me to start laying down cacti. Without it, I was suddenly stunned when I was being bombed with zombies behind my lines of defense.

If you have an off-screen cue, make sure you caption it, or at least add a visual bell that can be turned off and on. With PvZ, I would have added an icon that would flash with the a zombie icon whenever a zombie made a noise off-screen. If the balloon icon flashed, then I would know to start adjusting my strategy.

Screenshot of Plants vs Zombies, with where I would put a sound icon circled.

Voice chat

Live transcription is a long way off, so if your game features voice chat, a deaf user is probably going to miss out on that. We can live with that for now (though do keep an eye on that technology).

If you offer voice chat, though, make sure to offer text chat as well. Yes, they'll still be missing out on some of the game content, but at least there'll be a way for them to talk to others, and have them talk to others.

Options

Remember how I said that hearing aids pick up everything? That's one of the biggest complaints I've heard from those that depend on them, and it's one of the main reason why they get turned off. My grandmother only wore hers if she absolutely had to, and my father-in-law switches his off whenever he's out of the house. All sounds are blasted at equal volume, whether it's the person sitting across from you, or someone dropping a glass from the other side of the restaurant.

Someone wearing a hearing aid may want to turn off your ambient sounds. Most games include a slider for sound effects, but that includes important sounds, like the sound of a creeper walking up behind you. Consider adding a slider for environmental sounds, like water, wind, or merchants chattering, and then another slider for important ambient clues, like monster growls, clues for quests, or signs that you're running out of life.

Next Time

The physically disabled!

Accessibility: Video Games July 16, 2012

Anyone who follows the rest of my passions knows that I spend quite a bit of time advocating for accessibility on websites. I've given talks, done posters, and I'm even working on a book about it for O'Reilly. Recently, however, I've been thinking more and more about accessibility in video games.

Within the past year, two games made a special effort make their games more accessible. Dungeons of Dredmor added a mode especially for colorblind people. Legend of Grimrock added an on-screen movement pad for a disabled gamer who used a mouth stick to use his computer. I admit it: I still get a bit choked up when I read those stories. I just wish there were more of them.

Without a doubt, video games have made some strides in accessibility in the past few years. Audio is now almost always captioned, and inversely, dialog is almost nearly spoken. Controls are customizable. Controllers specifically for the disabled are being marketed more. But as long as we're making games, it's something that developers and designers will have to keep thinking about. It's not a problem that can be 'solved'. That would be like saying we solved the 'usability' problem, or the 'fun' problem. It's something that's going to keep growing and changing as technology and the way we interact with it keeps changing.

Why bother?

This is the first question I get from people. Why add to your development time and overhead, when you could be putting that money towards another level, or slightly better art? Many people see the time and effort put towards accessibility as wasted.

First, the number of people that need accessibility is not as small as most people think. Seven percent of all males are colorblind, to some extent. Twenty-one million people in the US suffer from arthritic issues that can affect how well they can hold a controller, or how quickly they can use a keyboard. This year, a million people will break their arm in the US, while 300,000 more will be treated for repetitive stress injuries. Approximately 10% of the world's population is left-handed.

Second, a side benefit of designing for accessibility is that the usability for everyone often goes up. The things that made a game impossible for one person are often still annoying for someone with no issues. No one likes a bad font choice, or all caps, and most people want a choice when it comes to subtitles, or listening to the voice acting.

Guiding principle

There's one rule for accessibility:

For every player, all the data, and all the functionality

It's up to you how many groups you want to cover, but for everyone you've decided to include, they should be able to not only do everything that everyone else can do in your game, but they should be able to access all the information you're exposing. If a player has to mouse over something figure out what its stats are, but they can't use their mouse, your game isn't really accessible. If you force users to use their arrow keys to move, but a player needs to use WASD, once again, your game isn't accessible. All the stuffs for everyone.

It's not just blind people

I've already alluded to this, but accessibility isn't just about blind people. There's generally four groups:

  • Visually disabled
  • Hearing disabled
  • Physically disabled
  • Cognitively disabled

Unlike a website, you aren't going to be able to make your game accessible for all the people in all of those groups. By knowing what annoys them, however, you can start considering what you can add to your game to make it more accessible.

Visually disabled

Being visually disabled is more than being blind. If someone is color blind, or needs glasses, they fit into this category as well. Yes, most games cannot be tweaked for someone who's completely blind (though they may surprise you), but that's no reason not to consider the other sub-categories.

Color-blindness

This is the group that should never be ignored. One, it's a huge group, no matter what your age range (7% of males). Two, it's one of the easiest groups to test and make adjustments for.

Your goal, when adjusting for color blindness, should be making sure that your interface doesn't get muddled or ambiguous if someone can't differentiate certain colors. If you're developing on a Mac, Color Oracle makes this dead simple. Start up Color Oracle, start up your game, then select the type of color blindness you want to emulate.

Things you should look for:

  • If you change color schemes on your models, can you still tell the difference between them? Do your mana potions look like your health potions?
  • Do things that are important still stand out? That red book on the bookshelf that's vital to moving the plot along: does it still look different?
  • Is your text still readable? Would you want to look at that color combination for several hours, or would you rage quit after fifteen minutes?
  • Is something that should be subtle now really obvious? Some colors are actually more intense for certain types of color blindness.

If you find you have an issue, you don't need to choose a drastically different color: usually, a minor tweak is enough. Normally, I nudge the colors around the color wheel until I find two that seem different. Adjusting the darkness or lightness of the colors usually has the most impact, and has the least impact on the feel of the game.

And if it's really impossible? Steal Dredmor's solution, and have a theme just for the color blind.

Corrected vision

As someone with a rather strong astigmatism can tell you, suffering through bad interface choices can be painful. Sure, I can see pretty well (my vision is close to perfect, otherwise), but after a while, I'll start to get headaches and teary eyes, even with my glasses on.

This happened to me a few years back with a game called Eternal Sonata. I downloaded the demo... and discovered that the font / color / text size combo made it impossible to play. Sure, there was voice acting for most of the dialog... but not for the menus! The crazy fancy font plus the brilliant blue background and teeny size were just too much. I didn't buy the game.

Unlike the color-blind group, this group doesn't have any nifty tools to use for testing. Instead, it's better to follow some guidelines:

  • Dark text on a light background is easier to read. Period. If you must, for aesthetic reasons, have a dark background with light text, at least offer an option for the user to change it.
  • Some people prefer dark backgrounds with light text (migraine sufferers often ask for this). If you have a light background and dark text, give them the option to swap.
  • Use a sans-serif font for screens. No mono-space. No serifs. No cursive. No... whatever the hell Eternal Sonata was using. Don't be afraid to be bland, and choose a common font that is well-known for being highly legible. No one ever played a game, logged forty hours, beat the big bad, and said "Dude! Did you SEE that awesome font they picked?!"
  • Make the font size big. On a computer screen, this would be around 12px. On a TV, it might be bigger. Some people play with their noses pressed against the screen, while others are on a couch across the room. If you want to cover all your bases, you make this customizable as well.

And there's more!

In the next article, I'll discuss the hearing disabled, which ranges from the completely deaf to those who have to wear hearing aids, and people who forgot their headphones.

The Homebody Diaries: Moving More July 09, 2012

When you work in an office, you move. You move quite a bit. You get coffee. You do Starbucks runs. You pack up and walk to meeting rooms. You walk to go grab lunch. You grab something from the printer. You at least walk from your car to the building, and you may walk from a metro or train station. You get up and you pester people while your server is building.

When you work from home, you don't move. My coffee station is three steps from my desk. Same with my printer. I have the space on my desk to move them even closer if I wanted. My lunch? Twenty-two steps from my desk. I don't do Starbucks runs, and even if I did, I'd have to drive. My meetings are all held at my desk. There is no one else to pester. There is no commute.

I'm not moving at all. And I feel it.

Attempt one: Walking

My first thought was to start walking. I live in a shady neighborhood with respectable paths and some nice hills. I see people walking all the time! I could do this over my lunch break: put some podcasts on the headphones, head out, walk for fifteen minutes, turn around, and walk back.

I even had the equipment already. I walked for exercise quite a bit at my old job, so I had some nice walking shoes and good socks. I set out.

For a few weeks, it was great. The weather was nice and I got caught up on my podcasts. Then, disaster struck.

Summer.

When I speak of summer in DC, I affectionately compare it to spelunking in a hobo's a-hole (you don't want to know what I call it when I'm angry). We're built on a swamp. The heat here is oppressive. I've seen it melt Texans. I'm already a delicate Southern flower.

How did I do it at the old job? Simple: they had an underground system of hallways that made it possible to walk several miles and never go outside. I'd retreat to those after the temps went over 90 degrees. I had no such luxury in the burbs. I'd have to figure something else out.

Attempt two: Standing

Standing desks are all the rage at the moment, and they appealed to me. I liked the idea of having fitness seamlessly worked into my day. Also, the testimonies were pretty impressive: less back pain, lost weight, better posture, better concentration.

Problem: I really like my desk, and it's not a standing desk. I like my storage cubbies, I like how it fits into the space we have, and its color really helps disguise what a klutz I am with my coffee. Also, I don't want to spend $900 on something that I may not end up liking.

I looked into systems for 'converting' a desk, but they all seemed to be expensive, not work with my current set-up, or be big steaming piles of one-star reviews. I ended up stealing one of my daughter's play stools and putting my laptop on it.

Upside: It was the perfect height! And free!

Downside: I seriously feared for my life when she realized I was repurposing her stuff.

Standing went well for the first few days. I wore nice shoes, and would stand for an hour at a time. If I was in the middle of a crunch, though, I realized I couldn't stand. I had to have my butt in a chair. Maybe with time I would be able to stand and deal with a fix that had to be out in the next thirty minutes, but that day wasn't today.

I needed to supplement.

Attempt three: The Gym

When I worked in the city, I went to the gym. I'm one of those rare birds that loves the gym. I love running on the treadmill and watching crap daytime TV, especially if the captions are off, so I can make up my own plots. I love the machines. I love taking notes about how much I lifted last week versus this week.

At my old job, we worked right above a gym, go I could easily go over on my lunch break, and still get in a shower afterwards. There are a number of gyms in my area. Why not go to one of them?

I took a hard look at myself. How likely was I to actually leave the house and go to the gym? At the office, I looked forward to getting away from my desk. At home... well, leaving required pants. And leaving the very functional AC. And my comfy chair. I could barely drag myself out of the house to drop the kids off at the sitter, much less anything else more ambitious.

I had to be honest: A gym membership was going to be a waste of money.

Attempt four: Wii Fit

I gave in. However I moved more, it was going to be in my house. Not wanting to spend money, I drug out my Wii Fit. I had gotten it when it first came out, and actually liked it quite a bit at the time. I tried to remember why I had stopped using it. I popped in the disc and started it up.

And I remembered why I stopped using it.

In theory, I like the idea of a Wii Fit. I liked the fitness game, especially after they released an improved version. But a few things got to me:

  • It's slow. It's so slow. You start it up, the little balance board dude has to talk to you. It has to calibrate between each exercise. The exercise lady has to talk you through each exercise. When you're done with each exercise, it has to congratulate you and tell you how much awesomer your life will be now that you did the tree pose for two minutes. There are no transitions, just lots of starting and stopping.
  • What the hell do you do with the controller? You always need to have it close at hand to confirm things, but you also need room to do the exercise. If you keep it on your wrist, you take out your kneecaps.
  • It doesn't autocorrect. Start a bit off balance (because you were putting your controller on the floor), and you're boned.
  • It yells at you for not being perfectly balanced. Yes, I know I'm a bit shaky. I'm working on it, okay?!
  • It yells at you for not showing up every day. Why do Nintendo games do this? What the hell?
  • It's big on the fat shaming. A bit overweight? It makes your Mii fat. Doesn't that make you feel better?

After getting mad at the game and realizing that I was STILL only getting fifteen minutes of movement for every thirty put in, the Wii Fit got put away again.

Attempt five: The Kinect

My son got an XBox with a Kinect for his birthday. The Kinect wasn't my idea. My mother wanted wanted to get him something, and I wanted the XBox with the bigger hard drive, which was paired with the Kinect. Seemed like a fit.

Jake was more excited about playing his NHL games again than the Kinect. In fact, I don't think he's used the Kinect once.

I'd seen some workout games for the Kinect, and decided to look some of them up. To my surprise, some of them were not only highly rated, but had inspired people to write crazy detailed reviews. I found one that offered a demo (Your Shape: Fitness Evolved) and downloaded it.

Fifteen minutes later, I couldn't get my credit card out fast enough to buy it.

Everything that annoyed me about the Wii Fit is fixed. The set-up time is nil, and you never need to touch a controller to get everything set up. It groups expercises so that you move smoothly from one to the next. It doesn't yell at you. It doesn't call you fat. It doesn't guilt you into playing the game.

These days, I generally do a half-hour of yoga at lunch, and I've found it really helps me break out of the afternoon blahs. I thought I would get bored with it, four weeks in, but I'm still entertained by doing the exercises every day. I'll probably get the 2012 version of the game as well.

Oh, Kinect: I'm sorry for every snarky thing I ever said about you.

Just a quick note! May 21, 2012

Not dead! Just super busy! Once I get a few deadlines tidied up, I'll be back!

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.

Older | Newer