Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Naming Subs

by eoin (Monk)
on Jan 12, 2003 at 00:09 UTC ( [id://226163]=perlquestion: print w/replies, xml ) Need Help??

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

Ok Monks,
I've got a fairly simple question for you(but not for me) that I was hoping you could shed a bit of light on. I'm writing a rpg text based game(text based because I'm having trouble installing modules on to my Win Me Pc). In this game the program prints out the discription of the area and the compass direction your facing. It then asks you what you want to do. Now here comes the hard bit..
What I want to do is, when the user types in an action the sub for that action is called. But what I want to know is there any way of using the text input to call a sub....
$action = <>; $data = &[$action];
Or somthing like that, instead of going on for ever like this....
$action = <>; if ($action eq "go"){ #GO;} elsif($action eq "pickup"){ #Pick it up buddy} etc, etc....
Because this makes your program quite long indeed. I'm only new so feel free to rip it through me and destroy all my confidence for asking such a stupid question.
All the Best, Eoin...

Replies are listed 'Best First'.
Re: Naming Subs
by Zaxo (Archbishop) on Jan 12, 2003 at 00:21 UTC

    A dispatch table is a neat way to do that. It is a hash, keyed by your input string, with code references as values.

    my %action = ( go => sub { # some code }, turn => \&turn_sub, #... } while (1) { my $act = <>; chomp $act; # parsing can get fancier here exists $action{$act} and $action{$act}->(); }

    After Compline,
    Zaxo

      Thanks I'll give it a try and let you know the results.


      All the Best, Eoin...

      If everything seems to be going well, you obviously don't know what the hell is going on.

Re: Naming Subs
by sauoq (Abbot) on Jan 12, 2003 at 00:25 UTC

    Well, you could do something like that but you don't really want to anyway. You should validate the user's input.

    My suggestion is to use a hash of sub references.

    sub he_eats { print "You eat @_!\n"; } sub he_sleeps { print "You sleep @_!\n"; } sub he_moves { print "You move @_!\n"; } my %actions = ( eat => \&he_eats, sleep => \&he_sleeps, move => \&he_moves, ); while (<>) { chomp; my ($action, @args) = split; if (exists $actions{$action}) { $actions{$action}->(@args); } }

    This is my 600th post.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Naming Subs
by pg (Canon) on Jan 12, 2003 at 05:47 UTC
    sauoq's reply provided the best way of dealing with this type of problems. One of my old post provided exactly the same solution to a similar question with a different face. You may want to check it out.

    However dug's post is definitely unique, although it is not "good", (I know he knew this ;-).

    Let's go deeper with the concept he brought up: symbolic reference.

    There are two types of reference in Perl:
    1. hard reference, the everyday used reference created by using \. Recommended.
    2. symbolic reference. The reference created by "name". Not recommended. Banned by using
      use strict("refs"); or simply use strict;
    However it is worth to understand, although not worth to use. I wrote two pieces of simple code to demo:
    1. Symbolic reference of scalar:
      $a = 666; $b = 888; $name = "a"; print $$name, "\n"; $name = "b"; print $$name, "\n";
    2. Symbolic reference of sub:
      $name = "a"; &$name; $name = "b"; &$name; sub a { print "in sub a\n"; } sub b { print "in sub b\n"; }
      However dug's post is definitely unique, although it is not "good", (I know he knew this ;-).

      Is this just a knee-jerk reaction against using symbolic references? You go on to give a brief explanation of what they are for the OP's benefit, but no explanation of why the are not good in this case (or any case).

      I'm not trying to be obtuse, just curious to hear why you think they are bad in *this* case. There is an interesting thread on comp.lang.perl.misc here that talks about this topic explicitly. I'm sure there are more as well.

      Thanks,
      -- dug

        Symbolic references are loaded with caveats. If you know them and know to avoid them, you're fine, of course.

        Dispatch hashes are still better. A hash is a portable namespace. Using a dispatch hash promotes separation between disjunct parts of your application's logic: in this case, the hash encapsulates the game logic, while the toplevel code handles the user input logistics.

        Not to mention that just understanding that you can "store subs in a hash" is a large conceptual step that will open up an entire world of new possibilities.

        Makeshifts last the longest.

        Certainly I can give you my opinion why it is not good. (When I said, in my prev post, that you knew it was bad, I was sincere, but unfortunately you thought I had an evil mind...Well)
        1. You asked me why it was bad in *this* case. My answer is simple, it is not just bad in this case, it is bad in all cases, including this one. This is something about principle. It is just like you ask me why "goto" is bad in *this* case, I will say, no, it is bad in every case, you should never ever use it.

          Whatever you archieved here, can be easily archieved by using a hash containing all coderef's (hard ones). What is the point to violate principles, when you can complete the same task by following the principles, and gain more maintainablility and flexibilty?

        2. Modern programming no longer appreciates smart tricks that much, on the contrary, we strongly stress the importance of maintainability more than ever.

          To make your piece of code work, it requires to turn off "use strict". You may argure that we can just turn off use strict refs for that particular lexical scope to reduce the danger, but still someone maintain your code may add some bad code into that lexical scope, and miss the chance of correcting it in the first place, all because "use strict" is turned off.

          To be frank, after couple of months, even you may forget that that piece of code is a mine field. Does this not happen all the time to us?

          A really dangerous thinking in programming is to assume everything will go well by its own. That is never true.
Re: Naming Subs
by dug (Chaplain) on Jan 12, 2003 at 03:54 UTC
    Howdy,
      Sounds fun! This is actually one place where not using strict 'refs' may make your code more maintainable. *ducks*

    This was my take:
    #!/usr/bin/perl use warnings; use strict; $|++; use vars qw( $AUTOLOAD $running ); $running = 1; while ($running) { my $input = <>; my ($action, @args) = split ' ', $input; # probably not robust enoug +h $action = lc( $action ); no strict 'refs'; &$action( @args ); # dispatch based upon action } sub go { my @args = @_; print "You go @args\n"; } sub quit { my @args = @_; $running--; print "Thanks for playing\n"; } sub AUTOLOAD # catches calls to non-existent methods { my @args = @_; my ($method) = $AUTOLOAD =~ m/::(:?\w+)$/; print "I don't understand $method @args\n"; }

    My session with this looked like:
    Go west young man
    You go west young man
    Bob Ross is amazing
    I don't understand bob Ross is amazing
    quit
    Thanks for playing
    
    Of course, anything worth its salt would understand that Bob Ross was the man :-)

    TIMTOWTDI,
    -- dug

      No need to shut off strict 'refs'. Take a look at the dispatch table code used in other replys.

      Remember: dispatch tables are sexy.

Re: Naming Subs
by sfink (Deacon) on Jan 12, 2003 at 06:59 UTC
    Try something like this, which is 'use strict'-clean:
    sub cmd_move { my ($package, $direction) = @_; print "You start to walk $direction, but suddenly remember that Gr +ues live there\n"; } while (<STDIN>) { chomp; my ($cmd, @args) = split(/\s+/, $_); $cmd = "cmd_$cmd"; if (($cmd !~ /^\w+$/) || !main->can($cmd)) { print "Unknown command. Try again.\n"; } else { main->$cmd(@args); } }
    The trick is that all commands defined in the top-level are considered to be in the package 'main', and any package can be treated as a class. Hence main->can($x) will tell you whether there is a method named $x in package main, and main->$x() will call it (and pass in the string 'main' as the first argument.)

    You might also want to use __PACKAGE__ instead of main, so that this will work even if you implement it inside of another package. But even better, define all of your commands in a different package just to make extra sure you don't accidentally expose a regular subroutine to the user:

    package Commands; sub cmd_move {....}; . . . package main; while (<STDIN>) { ... if (Commands->can($cmd)) {...} }
    Just to be avoid confusion, I prefix cmd_ to the names of the command subroutines, so that if the user types 'move' then the subroutine 'cmd_move' will be run. Also, I check to be sure the command looks valid. The command is never evaluated or substituted into an external system call or anything, so it doesn't really matter, but you should probably do it anyway just in case you start to use it differently later.
Re: Naming Subs
by Wysardry (Pilgrim) on Jan 12, 2003 at 13:48 UTC

    How about this variation?:-

    #!/usr/bin/perl -w use strict; use warnings; # Set empty strings/arrays my $typed_command = my $cmd_verb = my $cmd_noun = my $action = ""; my @cmd_list = (); # Define subroutines sub misunderstood {print "I don't understand `@cmd_list'\n";} sub go_north {print "You go north\n";} sub go_south {print "You go south\n";} sub go_west {print "You go west\n";} sub go_east {print "You go east\n";} sub get {print "You take the $cmd_noun\n";} sub jump {print "Boing! Boing!\n";} sub quit {print "See you later.\n"; exit;} # Declare valid commands hash table # Pair up the commands with the subroutines that will execute them. # The verb is used as a key in the hash table for quick selection. # This will avoid a whole string of if statements later and keep the # definition near the top of the program for easy editing later. # Don't forget to include synonyms! my %valid_commands = ( 'north', \&go_north, 'n', \&go_north, 'south', \&go_south, 's', \&go_south, 'west', \&go_west, 'w', \&go_west, 'east', \&go_east, 'e', \&go_east, 'get', \&get, 'take', \&get, 'quit', \&quit, 'jump', \&jump, ); # Main loop print "What now?>"; while (defined($typed_command = <STDIN>)) { @cmd_list = split " ", lc($typed_command); $cmd_verb = $cmd_list[0]; $cmd_noun = $cmd_list[1]; if (exists($valid_commands{$cmd_verb})) { $action = $valid_commands{$cmd_verb}; &$action($cmd_noun); } else { &misunderstood(@cmd_list); } print "What now?>"; }

    Normally I would have put more whitespace around my code, but I've squashed this up quite a bit so that people don't have to scroll this page so far.

      And the Once And Only Once version:
      my %valid_commands = ( jump => \&jump, map { my ($code, $cmds) = @$_; map { $_ => $code }, @$cmds; } [ \&go_north => [qw(north n)] ], [ \&go_south => [qw(south s)] ], [ \&go_west => [qw(west w)] ], [ \&go_east => [qw(east e)] ], [ \&get => [qw(get take)] ], [ \&quit => [qw(quit exit end)] ], );
      Update: removed an duplicate copy of the go_north line thanks to pfaut.

      Makeshifts last the longest.

Re: Naming Subs
by John M. Dlugosz (Monsignor) on Jan 12, 2003 at 06:29 UTC
    I have something similar in a quick & dirty piece of code that wraps some version control system code. I give it the verb on the command line as an argument, such as "get" or "co" (for checkout). It jumps to a handler for each known verb exactly like you are asking.

    I don't remember which of the many possible ways I used to turn the variable into an actual function call. I think today $action->() would do the trick.

    But, this is a problem with release code because the user could trigger a jump to any function in the code, not just those you are intending! So to make it more robust I named the option functions something distinct, with a prefix on the verb. E.g. "get" would call sub option_get. If the user typed foo instead, it would complain about the lack of an option_foo, rather than jump to sub foo. Only the intended targets had names of this form.

    —John

Re: Naming Subs
by Phaysis (Pilgrim) on Jan 13, 2003 at 14:46 UTC
    Eoin:

    If you're interested in developing a serious single-user text-based adventure, may I suggest a non-perl method?

    The Text Adventure Development System ("TADS" for short, found here), uses a robust C-like language to define rooms, objects, actions, etc. You actually "compile" your world's code with TADS which you then distribute with the TADS binaries. Your world code is then unreadable and therefore hard to cheat.

    A close friend of mine has used this system to good success; he's developed several text adventures of varying complexities. He swears by it, says it allows him to enjoy the text-adventure genre more because he can get into the process of creation more easily.

    If you're looking at networking your RPG/text adventure, allowing multiple players into the same "world", or anything of the sort, a pure perl solution may be in order. But if a single-player command-prompt environment is all you need, then TADS is a mighty-fine pre-invented wheel indeed.

    Best of luck.

    -Shawn / (Ph) Phaysis
    If idle hands are the tools of the devil, are idol tools the hands of god?

Re: Naming Subs
by Anonymous Monk on Jan 13, 2003 at 04:27 UTC
    Use OOP. Really. There's no reason to use refs.. though that's what objects really are.
    Create a Command object that has all the base things for a "Command" and inheret off of that other commands, implementing the same method over-and-over. As for creating which command to do, put the object names in a hash and "new" the one you need.
      What's the point in creating dozens of single method classes to look up through a hash? You are basically creating a dispatch hash, except with extra buzzword compliance by jumping through a useless OO hoop first.

      Makeshifts last the longest.

        simple, it's more organized and less code to write.
Re: Naming Subs
by MadraghRua (Vicar) on Jan 13, 2003 at 18:24 UTC
    Eoin
    It sounds like you're developing something like a MUD or a MOO. You might want to check out this link for some general pointers on available MUDS and MOOS. Many of them have already dealt with your problem and its always worth looking at the existing code.

    MadraghRua
    yet another biologist hacking perl....

Re: Naming Subs
by Anonymous Monk on Jan 13, 2003 at 11:02 UTC
    You can do almost exactly what your pseudo-code suggests, using code references. (see perlref manpage for more on refs.) The idea is that the variable is evaluated, then execution passes to the subroutine with that name, if one exists:
    $data = &{ $action };

    Of course you'd want to catch actions that haven't been defined; you could do that by comparing $action against a hash of defined actions, or eval'ing the above statement, or, well, you know how it goes ;)

      Of course you'd want to catch actions that haven't been defined; you could do that by comparing $action against a hash of defined actions, or eval'ing the above statement, or, well, you know how it goes ;)

      No need for any of that. If you used a hash, you would need to verify that the element existed using exists. As pointed out elsewhere in thread, the symbol table is just a glorified hash anyway, and you can check for the existance of the action in exactly the same way

      $data = &{ $action } if exists &{ $action };

      Examine what is said, not who speaks.

      The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

Re: Naming Subs
by andyram27 (Initiate) on Jan 13, 2003 at 18:51 UTC
    How about something like this:

    my $class = "a_class"; bless { "action1" => action1(), "action2" => action2() }, $class;
    this creates an anonymous hash of functions; oop style

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://226163]
Approved by vagnerr
Front-paged by diotalevi
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (4)
As of 2024-04-23 20:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found