hsweet has asked for the wisdom of the Perl Monks concerning the following question:

Last year I wrote a sketchy version of a simple text game. I want to turn the thing into an object so my students can use it without having to deal with the innards and to have a (hopefully) fun way to introduce the ideas and syntax.

The game is built of three files, the code, a room map and a room description file. They are detailed at the end of the code. The whole game runs in a while loop and keystrokes that match a command get passed to an appropriate sub.

I've never written anything more than simple objects and this one is confusing me.

Some of my questions right now are;

#!/usr/bin/perl -w use strict; my @things=qw(); my %things; my %things_to_take; my $points=0; my %directions=( e => 'East', n => 'North', w =>'West', s => 'South'); my($r, $c)=(0,0); #row, column on map my @gamemap; #my $room; #current room #**************************Get the rooms****************** #uses a hash of arrays structure, room=>[description, exits, objects, +points] @ARGV=("rooms.txt"); #file to read, or rea +d from stdin #file structure; room name: description; directions; objects; points my %rooms; #$room is room name, @fields is all the rest of the things in the line # while ( my $line = <> ) { (my $room, my $rest) = split /:\s*/, $line, 2; #split at : follo +wed by whitespace. 2 is limit my @fields = split ';', $rest; #delimiter for v +alues $rooms{$room} = [ @fields ]; } #*********************************** Get the Map **** #gamemap and room hash above gotta match so if you end up in a room li +sted in #gamemap you have the key to the room hash which will get you the desc +ription #and the ways out. @ARGV=("map.txt"); # read map from file while ( <> ) { push @gamemap, [ split ]; } my $play=1; #flag to end game my $room; #might not need this one if I use the syntax on +the next line showroom($gamemap[$r]->[$c]); #0,0 on map row, column #*************************** Main Loop ************* while ($play ==1){ print "\n? "; chomp (my $command = <STDIN>); #for starters l(ook), t(ake), i(nventory), nsew, q(uit), p(oints) if ($command=~/(^[nsew])/){ #send n, s, e, or w to +sub using &changeroom($1); }elsif ($command eq "i"){ &inventory; }elsif ($command eq "t"){ print "Take what?"; chomp(my $item=<STDIN>); take($item); }elsif ($command eq "l"){ showroom($gamemap[$r]->[$c]); }elsif ($command eq "q"){ $play = 0; }elsif ($command =~/p|poi\w+/ ){ #p or points or print "You have $points points\n" }else{print"I don't know what you are talking about."} } #********************* subroutines ************************ sub changeroom{ #change the value of $r(ow) and $c(olumn). $gamemap[$r]->[$c] is t +he room # Also checks for legal directions which are in $room{[whichever]} +->[1] #print "Coming from $gamemap[$r]->[$c]\n"; my ($oldr, $oldc)=($r, $c); #change room coordinates if ($_[0] eq "n"){ $r--; }elsif($_[0] eq "s"){ $r++; }elsif($_[0] eq "e"){ $c--; }elsif($_[0] eq "w"){ $c++ } #check if legal move; if (! defined $gamemap[$r]->[$c] ||$gamemap[$r]->[$c] eq "-" ){ print "You can't go that way!\n"; ($r, $c)=($oldr, $oldc); #Stay where you were if you ca +n't go there } #print "Going to $gamemap[$r]->[$c]\n"; showroom($gamemap[$r]->[$c]); } sub inventory{ if ($things[0]){ #if you have anything at all #if (exists $things{'dish'}){ print "you have a "; print join ",",@things; print"\n"; }else{print"You're busted, Jack\n"} print "You have $points points\n"; } sub things_to_take{ #hash of things that can be taken. #key = object, value=points $things_to_take{$_[0]}=$rooms{$gamemap[$r]->[$c]}->[3]; } sub take{ foreach my $key(keys %things_to_take){ if ($key eq $_[0]){ #$things{$_[0]}=1; #figure out hash push @things, $_[0]; #adds object if it matches + list of things in game. $points += $things_to_take{$key}; }else{print" There is no $_[0] here.\n" } } #print @things;print $points; #you still can take a +n item more than once. hash is better } sub showroom { print "You are in a $_[0]. "; #room name print $rooms{$_[0]}->[0]; #description my @directions=split(//,$rooms{$_[0]}->[1]); #ways out print "\nThere are exits to the "; foreach my $way (@directions) { # n=>North, + etc. Uses %directions hash $way=~/([nsew])/g; print "$directions{$1} "; # the ways ou +t. } if($rooms{$_[0]}->[2]){ #things to take chomp $rooms{$_[0]}->[2]; print "\nThere is a " . ($rooms{$_[0]}->[2]) . " here.\n"; things_to_take ($rooms{$_[0]}->[2]); } } =cut =HEAD1 Name A sketch of a single person text adventure game. =HEAD1 Description maps.txt is the game map file and rooms.txt describes the room. Room names in the maps file are entered in rows separated by space(s) The "-" character is an empty room. These map to an array of arrays +inside the code. example: dark_hall - steep_hill windy_plateau river - forest The rooms file is a delimited list of name: Description;Exits;Object to take;Points steep_hill: A hard scramble up to the top.;nes;grass;10 The first delimiter is a colon: followed by a space, the rest are semi +colons. =end

Time flies like an arrow, fruit flies like banannas

Edit by tye, add READMORE

Replies are listed 'Best First'.
Re: Adventure Game ->object
by bigj (Monk) on May 14, 2003 at 06:49 UTC
    In object oriented design, you first want to find out what are the given (natural) objects and the "doings" that have to be done. I normally start with a simple description of the used objects and actions done that shall be implemented to the application.
    First of all, you have rooms in your advanture game. Every room has a name and there are several items in there. Then there is a map. The map can be loaded and initializes all room objects. It also can tell (in the initialization) to the rooms what are their neighbours.
    Then there is of course a player. The player has an inventory of items, he/she knows where he/she is (his/her current room), he/she has a point score. He/She can do one of the following actions: move(to north/south/west/east), take an item, look to an item (both perhaps only described by its name).
    The whole is united as game, consisting of map, one or several players and the possibility to initialize a game, to start and end it.
    After this description it should be easy to find out an object oriented design. Thus we have the following objects and methods (might be not full complete, but it's you who have the overview of your game, not me :-))
    Item + new($name, $points) + getName, getPoints Room + new($name, $description, $room_north, $room_south, $room_west, $room +_east, @item) + addItem($item), removeItem($name) + getName, getDescription, ... Map + load($filename) + initialize_rooms($filename) + getRoom($name), getRoom($row, $col) Player + new($name, $current_room, $points) + move($direction) + take($item_name) + drop($item_name) # perhaps a good addition + throw_away($item_name) # perhaps also a good addition + look($item_name) + getName, getCurrentRoom, getPoints, getInventory : returns @item Game + new($map, %player_name_and_current_rooms) + quit + getMap, getPlayer : return @player
    Having all these objects, now it is easy to write someting like a main server. In pseudo code it's quite that simple:
    my %player_names_and_start_room_names = ( bob => 'dark_hall', jay => 'forest', ); $map = Map->load($map_filename); $map->initialize_rooms($room_filename); $game->new($map, %player_names...);
    While a client (one player) would now only do the graphics (pseudocode):
    while (1) { print "You are in room", $player->getCurrentRoom->getName; print "There are several items in this room:", map {$_->getName}, $player->getCurrentRoom->getItems; print "In the north, there is ", $player->getCurrentRoom->getNorthRoom->getName; ... print "Choose: move (N),(S),(W),(E); (T)ake, (L)ook, (I)nventory, ( +Q)uit"; my $action = read_action; if ($action =~ /[NSWE]/) { $player->move($action) or print "Couldn't move to $action"; } elsif ($action =~ /[TL]/) { my $item = read_item; if ($action eq 'T') { $player->take($item) or print "Couldn't take $item"; } else { print $player->getCurrentRoom->getItem($item)->getDescripti +on || "No description available"; } } }
    Of course, that's quite only my idea of an object oriented design. But to answer your main question, in my opinion, it's important to seperate the parts that are valid in all adventure games (rules, basic actions, a map) from them that are only valid in your (your current map, nr of players, ...) specific adventure games.

    Greetings,
    Janek

Re: Adventure Game ->object
by chaoticset (Chaplain) on May 14, 2003 at 13:11 UTC


    • What parts of this thing become the module and what bits should be in package::main?
    • Should the map and description files be read into the object as instance or a class variables
    • Could this be expanded to support multiple players without doing much to it other than adding the network code?
    In order:
    • None of the bits "should" be in the main. If you want it really really opaque, then make it so that the command text is passed in the sub call from main, and the reply text is spit back. It would look something like
      my $game = Game::Adventure->new($file) #file provides data while (<>) { print $game->($_); }
      if I'm not on crack.
    • I'd do game data as instance variables, mainly because it seems more, well, OO. (I could be very wrong here, of course.)
    • I think you'd essentially have to write a wrapper containing the network code, where the network code is called by the UI and the game code's called by the network. (Maybe that's how it's always done. Network programming is something I know doodly about.) You are going to need to add player data, and you'll need that in the game code and not the network code, but that's the only major addition I can see. (Or, if you don't care, you could just have a generic player description. That seems less fun, though.)

    -----------------------
    You are what you think.