in reply to Re^3: Creating a random generator
in thread Creating a random generator

Since we are not moving any dynamic content over, is the second bit of code necessary?

If you just want to stick with nothing but static data in the data file, the first little loop that reads from the data file is all you need. The rest of it is for applying dynamics to the file content, and would only be needed if you want the file to include specs for the dynamic settings.

The second loop loads a separate hash of code refs, keyed by the names used in the data file in order to invoke one or another function to fill a given data field (i.e. "roll()", "color()", etc). The third loop goes back over the data loaded from the file, and does replacements of "special" field values with suitable strings, based either on a random selection from a given array, or on the output of a given function (from the hash of code refs).

Leaving the dynamic portions of the script in it, there shouldn't be any problems with dependency as far as I can tell.

If you mean "leaving all the dynamic assignments to be done individually, in proper sequence, in the script", then you're right, there are no dependencies involving the data file -- just make sure all the "primary" (static) arrays are set, and make sure the script puts the dynamic assignments in proper order.

But it is still possible for the data file to do more work, and for the perl script to be shorter and simpler, making the system easier to cope with. I've tried studying the other replies (esp. this one by shmem, which taught me a lot), in order to understand how the finished system ought to behave, and here's an approach that simply loads the data, with its various directives for dynamic (randomized) results at run time, and leaves all the random selections to be done at the end, via a simple recursive function that operates on those directives:

#!/usr/bin/perl use strict; use Games::Dice qw(roll); use Data::Dumper; my %Data; my %field_func = ( # functions cited in the data file roll => \&roll, radius => sub { "in a " . Random( "Radius" ) . " radius" }, ); sub Random # selection of an element from a list { my ( $ary_name ) = @_; my $ary_size = scalar @{$Data{$ary_name}}; return $Data{$ary_name}[ rand( $ary_size ) ]; } sub DataString # applies string replacements where needed { local $_ = shift; while ( /(([@&])_(\S+))/ ) { my ( $source, $rep_type, $src_name ) = ( $1, $2, $3 ); my $rep_str; if ( $rep_type eq '@' ) { # need to choose randomly from anoth +er array $rep_str = DataString( Random( $src_name )); } else { # need to get a value from a function call my ( $func, @args ) = split /[=,]/, $src_name; $rep_str = $field_func{$func}->( @args ); } s/\Q$source\E/$rep_str/; } return $_; } # here is where we read the data file of array definitions; # the following block limits the scope for playing with $/ { $/ = ""; # input record separator set to "paragraph mode" while (<DATA>) { next unless ( /^\w+:/ ); chomp; my ( $ary_name, @vals ) = split /\s*[:|]\s*/; $Data{$ary_name} = \@vals; } } # here is where we run the selection (10 times, in this case, # but you can play with the control loop) print DataString( Random( 'Mutation' ))."\n" for ( 1..10 ); # the rest is the sample data file __DATA__ # sample data file based on data snippets from the OP: AbilityName: strength | dexterity | constitution | intelligence | wisdom | charis +ma Class: Warrior | Rogue | Priest | Wizard | Psionist WeapType: bludgeoning | piercing | slashing | missile WeapMat: bone | metal | stone | wooden Dice: 1d4 | 1d6 | 1d8 | 1d10 | 1d12 | 1d20 Eff3: Acid | Cold | Electricity | Fire | Energy Drain | Gas | Poison | Son +ic Radius: 1' or Touch | 5' | 10' | 20' | 50' | 100' Resist: immune | is &_roll=1d100 % magic resistant | has no resistance Spells: all spells | all Wizard spells | all Priest spells | @_Eff3 NWPlearn: @_AbilityName based | @_Class specific Mutation: no unusual effect | general ability | has little @_AbilityName compared to @_Class | @_Resist to @_Spells | unable to learn @_NWPlearn non-weapon proficiencies | unable to learn @_WeapType weapons | cannot use @_WeapMat weapons # end of sample data file
(updates: two changes to the DataString function: removed a pointless "return $_ unless ..." line, and fixed a mistake in the split regex for handling elements of the form " &_func=arg,arg ")

That has just a small sample of data to demonstrate how it works -- you can add more data as you see fit. If you actually want to have two data files -- one for static strings and one for "cross-referenced dynamic" strings, that would be fine -- just read both files the same way, into the same %Data hash.

Note that the file format is pretty flexible as to white-space formatting: the essential feature is that all arrays start with "Name:", have pipes "|" between elements, and are separated from other arrays by at least one blank line (no blank line within an array), because the "paragraph mode" input will read multi-line records up to and including 2 or more line-feeds. Comment "paragraphs" are allowed (ignored).

You'll probably find that it's pretty easy to make mistakes in the data file (misspellings, inconsistent capitalization, missing names, wrong sigils for function vs. array, missing pipe symbols between array elements), but a data file tends to be easier to fix than code, and it wouldn't be very hard to come up with a reasonable "validator" script, to check for common mistakes in the data, and/or add some sensible error checking to the above script.

(UPDATE: One mistake you must be very careful to avoid is a circular reference among two or more arrays -- i.e. an element in List1 uses a reference to @_List2, which in turn points back to that same element in List1. Chains like List1 - List2 - List3 - ... - List1 are nastier in the sense of being harder to track down, but the script will blow up in either type of case.)

Regarding the "%field_func" hash, note that the "radius" function doesn't really need to be there -- it can be defined in the data file as well -- but you can/should put a code ref in there for your "insanity" function (make sure the function returns the string you want, rather than just printing the string to stdout); then include suitable references to it in the data file (" &_insanity ").

The core of the approach is the DataString function, which has two important features: (1) it uses a while loop over a given string to make sure all "sigil" replacements are done in turn, and (2) it uses recursion (calls itself) each time it has to do an embedded Random selection. This makes it possible to lay out the data file without worrying about the relative ordering of dependent arrays; so long as all the data are loaded into %Data before going to work, everything should work.