Thursday, July 15, 2010

Let's make a small text game in Ruby

Want to do some stuff with Ruby? So do I! I'm going to make a small text game, where you control a robot and blow up buildings.

So let's start by thinking about how the game will work. Pretty much, I want it to be command-line interactive and turn based. So you enter a command, and then something happens, and then you can enter another thing, and so on. You should be able to turn left or right and move forward and backward, and check what's in front of you. You should also be able to shoot.

I'm thinking sensible commands would be:

left
right
forward (maybe also go)
back
shoot (also pew or pewpew)
look (or check)

everything should be lower case for now. There'll be buildings randomly placed about, and when you are facing them and shoot, they should blow up. Maybe an extra command,

map

would be useful so you can see there's a building (demarcated by a B) somewhere, and a blown up building (demarcated by an X) somewhere else, and you the player, demarcated by a P. Later, I could make you be one of ^ v > < depending on your facing or something. A score for how many buildings you blown up would be neat too.

COOL!

So where do we begin? Well, if I want it to be interactive I'm going to have to have some kind of game-loop. I *could* just make this a game you play by issuing Class.command commands in irb or something, but that feels cheap. So it makes sense to me that I'd have some sort of controller class I'll call Game. I'm also going to need a Player.. or a Robot who moves around. Yeah, I like the thought of Robot being the thing that shoots. Then I'm gonna need Buildings, and a Map.

So, the Game will have a Robot, Buildings and a Map.
each Building will have a location and a status: either it exists or it doesn't!
the Robot will have a location, a direction, and maybe a name.
the Map will have to show the Robot and the Buildings, and eventually (I hope) the facing.

So let's get started already!!!

First, Iteration 1: Let's make the Game class and the main loop inside it, which will receive commands. What do we do now? That's right, we write some tests!

I'm going to lift some code pretty closely out of Ruby Best Practices here, because it does just about exactly what we want:
class GameTest < Test::Unit::TestCase
def setup
@commandlist = ["left", "right", "forward",
"go", "back", "shoot", "pew",
"pewpew", "map", "look", "check"]
@input = StringIO.new
@game = Game.new(@input)
end

commandlist.each do |command|
must "set the inputted command correctly when parsing #{command}" do
provide_input(command)
Game.get_command
assert_equal Game.command, command
end
end

def provide_input(string)
@input << string
@input.rewind
end
end
not having run this yet or ever done testing in ruby before, I have /no idea/ if this will actually work. Having written it though, I'm going to throw my Game class together (which now essentially writes itself)
class Game
attr_reader :command

def initialize(readin=STDIN, output=STDOUT)
@input = readin
@output = output
@command_list = ["left", "right", "go", "back",
"shoot", "check", "look", "pew",
"pewpew", "exit", "map", "forward"]
end

def get_command
temp_command = @input.gets
temp_command.chomp!
if @command_list.index(temp_command) != nil then
@command = temp_command
end
end

def loop
while @command != "exit"
get_command
puts @command
end
end
end
I don't have rake or rubygems on this computer, so I can't test this yet! I'll have to do so a little later - after a quick manual test of the Game class, it looks like it is in the right place. I realized while making it though, that I forgot to test that I'm not taking inappropriate commands! Fortunately I built it into the game class, and I'll modify the test soon to check for it.

Also of note, my directory structure at the moment:
/
/Rakefile
/src/
/src/game.rb
/test/
/test/test_game.rb
ah, and what exactly is in the rakefile? I've never used rake before! But again lifting from Ruby Best Practices (a fantastic book!), I've got
require "rake/testtask"

task :default => [:test]

Rake::TestTask.new do |test|
test.libs << "test"
test.test_files = Dir[ "test/test_*.rb" ]
test.verbose = true
end
And that's where I'll leave it for now. More later when I can actually run tests and find out all the things I've done incorrectly. :)

No comments: