skip to Main Content
$combatblock = "$name:$dex:$db:$mp:$hp";

I’m using Perl 5.36 on Debian Linux 12 and am writing a utility for a roleplaying game. This isn’t a work-related project, as I’m more of a hobbyist than a serious programmer, so I apologise if this isn’t really an appropriate request.

My input file looks like this:

Name: orc of power normal
mw_monsters normal
Stats: STR [14] CON [10] SIZ [10] INT [8] POW [8] DEX [15] APP [8] EDU [5] 
SCB: Physical=7 Communication=4 Knowledge=4 Manipulation=8 Perception=5 
Combat: Hit Points=10 Major Wound Level=5 Armour=
Initiative:
Stat Rolls: Effort 70% Stamina 50% Idea 40% Luck 40% Agility 75% Charisma 40% Know 25% 
Derived: MP=8 XP=4 FP=24 SAN=40% DB=NONE  MOV [10]
Skills:  Scimitar 42% Composite Bow 42% Short Spear 42% Short Spear (thrown) 42% Spiked Shield 42% Climb 62% Dodge 47% Hide 42% Lore (Orc-lore) 39% Language (Human) 29% Language (Orcish) 64% Ride (warboar) 57% Sense 40% Search 50% Move Quietly 47% Track 35%
 ++ Distribute 40% to ONE skill and 20% to THREE skills
Powers: 
This orc is not a sorcerer.
Possessions:
Notes:

I want to select certain parts of these lines and produce output like this:

Name:DEX:DB:MP:HP:Current:
orc:[15]:DB=NONE:MP=8:Points=10

This is my code so far:

#!/usr/bin/env perl
use strict;
use warnings;

my $line;
my $combatblock = "";
my $name = "";
my $dex = "";
my $db = "";
my $mp = "";
my $hp = "";
my @combatarray;
my @nameline;
my @statline;
my @hpline;
my @derivedline;

print "Name:DEX:DB:MP:HP:Current:n";
while ($line = <>) {
    chomp $line;
    if ($line =~ "Name") {
        @nameline = split (/ /,$line);
        $name=$nameline[1]; # Name
    };

    if ($line =~ "STR") {
        @statline = split (/ /,$line);
        $dex=$statline[12]; # DEX for initiative
    };

    if ($line =~ "Derived") {
        @derivedline = split (/ /,$line);
        $db=$derivedline[5]; # Damage Bonus
        $mp=$derivedline[1]; # Magic Points
    };

    if ($line =~ "Hit Points") {
        @hpline = split (/ /,$line);
        $hp=$hpline[2]; # Hitpoints
    };
    $combatblock = "$name:$dex:$db:$mp:$hp";
    print "$combatblockn"; # This can be commented out to get rid of unnecessary output
};
print "Result $combatblockn"; # Result can be taken out of this line

What it generates is this:

61 colin@kuu> combatstatblock.pl < orc.txt
Name:DEX:DB:MP:HP:Current:
orc::::
orc::::
orc:[15]:::
orc:[15]:::
orc:[15]:::Points=10
orc:[15]:::Points=10
orc:[15]:::Points=10
orc:[15]:DB=NONE:MP=8:Points=10
orc:[15]:DB=NONE:MP=8:Points=10
orc:[15]:DB=NONE:MP=8:Points=10
orc:[15]:DB=NONE:MP=8:Points=10
orc:[15]:DB=NONE:MP=8:Points=10
orc:[15]:DB=NONE:MP=8:Points=10
orc:[15]:DB=NONE:MP=8:Points=10
Result orc:[15]:DB=NONE:MP=8:Points=10
62 colin@kuu> 

Now, I understand this is actually doing what it’s written to do. As noted in a comment string above, I can take out "print "$combatblockn";" to get rid of all the other output and just leave the "Result" line.

This script is supposed to process many more "orc" stats and produce output like this:

Name:DEX:DB:MP:HP:Current:
orc:[15]:DB=NONE:MP=8:Points=10
orc:[19]:DB=NONE:MP=13:Points=10
orc:[12]:DB=+1D4:MP=10:Points=12

… and so on through the input file.

What’s happening is that only the last result (orc:[12] in this case) is printed: the while loop overwrites the previous value of $combatblock.

How can I break out of the while loop, print the "Result" line, then go back to the top of the while loop to process the next set of "orc" stats?

I’ve tried:

  • Writing the $combatblock to a file so I can "cat" the output on the console
  • Nesting the "if ($line =~" statements inside another foreach or while loop
  • Using an @combatarray structure to push the $combatblock onto
  • Generating $combatblock inside the "if" statements with lines like "$combatblock = "$combatblock:$db:$mp:"" (which are commented out in the script above)

Nothing seems to get past the "while overwrites the previous value" problem. I’m pretty sure the solution is quite simple but I simply can’t find a way forward.

Any help would be greatly appreciated.

2

Answers


  1. you can achieve that by : push your variables to an array at each if condition and print at end of the all if conditions inside the while loop and reset the array.

    but you need to revisit your code for the how you are extracting the desired strings from input file.

     #!/usr/bin/env perl
        use strict;
        use warnings;
    
        my $line;
        my $combatblock = "";
        my $name = "";
        my $dex = "";
        my $db = "";
        my $mp = "";
        my $hp = "";
    
        my @combatarray;
        my @nameline;
        my @statline;
        my @hpline;
        my @derivedline;
    
        print "Name:DEX:DB:MP:HP:Current:n";
    
        while ($line = <>)
        {
    
        chomp $line;
    
        if ($line =~ "Name") {
            @nameline = split (/ /,$line);
            $name=$nameline[1]; # Name
        #   print " ++ $namen";
            $combatblock = "$name:";
        push (@combatarray, $combatblock)
        
        };
    
        if ($line =~ "STR") {
            @statline = split (/ /,$line);
            $dex=$statline[12]; # DEX for initiative
        #   print " ++ $dexn";
           $combatblock = "$dex:";
        push (@combatarray, $combatblock)
        };
    
        if ($line =~ "Derived") {
            @derivedline = split (/ /,$line);
            $db=$derivedline[5]; # Damage Bonus
            $mp=$derivedline[1]; # Magic Points
        #   print " ++ $dbn";
        #   print " ++ $mpn";
           $combatblock = "$db:$mp:";
        push (@combatarray, $combatblock)
        };
    
        if ($line =~ "Hit Points") {
            @hpline = split (/ /,$line);
            $hp=$hpline[2]; # Hitpoints
        #   print " ++ $hpn";
           $combatblock = "$hp:";
        push (@combatarray, $combatblock)
        };
    #$combatblock = "$name:$dex:$db:$mp:$hp";
    print "@combatarray";
    @combatarray = ();
    };
    #print "Result $combatblockn"; # Result can be taken out of this line
    
    Login or Signup to reply.
  2. I’ve mentioned a lot of things in the comments, and basically what it all comes down to is that this is an XY-problem, by which I mean that your problem comes from choosing a bad design.

    First of all, the save file for the orc npc feels very spontaneous and not very thought through. You have a number of inconsistencies that break the format, and it seems you allow for free style comments.

    It is almost like this is a combination of a character sheet (e.g. 100% human readable) and a data sheet, which can be read by a program. The simplest solution is to pick a pre-existing storage format, such as XML or json. Then if you need to write to the file, or read it, you use a parser, or a program (e.g. excel). Then store the npc data as e.g "orc.xml" and just double click it.

    Second, your main problem in the code you show is that you try to parse and print at the same time. And you are using global variables. My suggestions are:

    • Separate parsing and print/process
    • Make a subroutine for parsing
    • Use a pre-existing data format for save files
    • Store the npc data in an object (hash)
    • Declare variables in the smallest scope possible

    Here is a small example of how you could handle your current save file format:

    use strict;
    use warnings;
    use Data::Dumper;
    use feature 'say';
    
    # main code
    my $orc = parse_npc("orc.txt");
    print Dumper $orc;
    
    # subroutines
    sub parse_npc { # file name as argument
        my $file = shift;
        my $npc;
        if (!-e $file) {
            warn "File not found: '$file'";
        }
        elsif (! open my $fh, "<", $file) {
            warn "Cannot open file: '$file': $!";
        } else {
            while (<$fh>) {
                chomp;
                next if /^ ++/; # comment?
                if (/^([ws]+):s*(.*)/) {
                    $npc->{$1} = $2;
                } else {
                    warn "Unknown entry in '$file': '$_'";
                }
            }
        }
        return $npc;     # will return undef for failures
    }
    

    Output:

    Unknown entry in 'orc.txt': 'mw_monsters normal' at C:UsersPeterperlfoo.pl line 27, <$fh> line 2.
    Unknown entry in 'orc.txt': 'This orc is not a sorcerer.' at C:UsersPeterperlfoo.pl line 27, <$fh> line 12.
    $VAR1 = {
              'SCB' => 'Physical=7 Communication=4 Knowledge=4 Manipulation=8 Perception=5 ',
              'Derived' => 'MP=8 XP=4 FP=24 SAN=40% DB=NONE  MOV [10]',
              'Stats' => 'STR [14] CON [10] SIZ [10] INT [8] POW [8] DEX [15] APP [8] EDU [5] ',
              'Initiative' => '',
              'Name' => 'orc of power normal',
              'Stat Rolls' => 'Effort 70% Stamina 50% Idea 40% Luck 40% Agility 75% Charisma 40% Know 25% ',
              'Powers' => '',
              'Possessions' => '',
              'Combat' => 'Hit Points=10 Major Wound Level=5 Armour=',
              'Skills' => 'Scimitar 42% Composite Bow 42% Short Spear 42% Short Spear (thrown) 42% Spiked Shield 42% Climb 62% Dodge 47% Hide 42% Lore (Orc-lore) 39% Language (Human) 29% Language (Orcish) 64% Ride (warboar) 57% Sense 40% Search 50% Move Quietly 47% Track 35%',
              'Notes' => ''
            };
    

    Note that this program completely skips comments. Comments like this inside a data file with a home made format tend to get lost. If you want to secure them, put them in a special field like Comments: .

    Note also the warnings for parsing errors. I have not attempted to parse your fields, because they are so inconsistent they would need their own parser. If you want to access the fields of this object, you just do print $orc->{Combat} for example. Or for a slice: print @{$orc}{qw(Combat Name Derived)};

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search