bl791's picture
download
raw
15.8 kB
#!/usr/bin/perl
use strict;
use Getopt::Long;
use Pod::Usage;
Usage() unless @ARGV;
# help options
my ($help, $man);
# debugging options
my ($animate, $verbose, $srand);
# run-time configuration
my $room_class = 'Room';
my $prop_class = 'Prop';
# map generation
my $dt = 0.25;
GetOptions('help|?' => \$help, 'man' => \$man,
'animate' => \$animate,
'verbose+' => \$verbose, 'srand=i' => \$srand,
'room=s' => \$room_class, 'prop=s' => \$prop_class,
'dt=f' => \$dt,
) or pod2usage(2);
pod2usage(1) if $help;
pod2usage(-verbose => 2) if $man;
# run-time configuration
my $room = { };
my $prop = { };
my $input = shift;
$input .= '.log' unless -f $input;
(my $base = $input) =~ s/(\.\w+)?$//;
my $output = $base . '.inf';
die "Input file $input is the same as output file $output\n"
if $input eq $output;
my $plotter = $base . '.png';
die "Input file $input is the same as plotter file $plotter\n"
if $input eq $plotter;
open(INPUT, $input)
or die "Unable to open input file $input\n";
warn "input=$input, output=$output, plotter=$plotter\n" if $verbose;
my (%opposite, %sort_by, %xoff, %yoff);
{
my @d1 = qw/n ne e se u in/;
my @d2 = qw/s sw w nw d out/;
@opposite{@d1, @d2} = (@d2, @d1);
@sort_by{@d1, @d2} = qw/a e c f i k b g d h j l/;
splice @d1, 4, 2;
splice @d2, 4, 2;
my $s2 = sqrt(.5);
@xoff{@d1, @d2} = ( .0, $s2, 1.0, $s2, .0, -$s2, -1.0, -$s2, );
@yoff{@d1, @d2} = (1.0, $s2, .0, -$s2, -1.0, -$s2, .0, $s2, );
}
my $expecting = $room_class;
sub trace {
warn sprintf "%d) %s x=%8.5f y=%8.5f fx=%8.5f fy=%8.5f\n", @_,
if $_[1] =~ /^V/;
}
sub gd {
my $hash = shift;
return grep { defined $hash->{$_} } @_;
}
sub cart2rad {
my ($dx, $dy) = @_;
my $d = sqrt($dx*$dx + $dy*$dy);
if (wantarray) {
return ($d, atan2($dy, $dx));
} else {
return $d;
}
}
sub Usage {
die <<"__USAGE__";
Usage: $0 <logfile>
__USAGE__
}
sub move {
my ($prop, $room) = @_;
return if $prop->{parent} == $room;
$prop->{parent} = $room;
$prop->{sibling} = $room->{child};
$room->{child} = $prop;
}
my $next_line;
sub get_description {
my @descr;
while (<INPUT>) {
s/[\n\r]+//;
last if /^>/ or eof(INPUT);
push @descr, $_;
}
$next_line = $_;
return join(' ', @descr);
}
sub fix_description {
my ($property, $descr) = @_;
$descr =~ s/"/~/g; # clean up embedded double-quotes
$descr =~ s/^/\n $property "/; # add header... "\n" is required
$descr =~ s/ *$/"/; # clean up trailing spaces
$descr =~ s/(.*\n[^\n]{1,72}) ([^\n]+)$/$1\n $2/s
while $descr =~ /[^\n]{73,}/; # word-wrap the text
$descr =~ s/\n /\n\t/g; # clean up indentations
return $descr;
}
{
my ($prev, @dir, $title, $property);
do {
if ($expecting eq $room_class) {
my ($label, @descr);
$title = <INPUT>;
$title =~ s/[\n\r]+//;
($label = $title) =~ s/\W+/_/g;
$label = '_'.$label if $label =~ /^\d/;
warn "Processing room '$label'\n" if $verbose > 1;
my $obj = (defined $room->{$label}) ? $room->{$label} : { };
$obj->{title} ||= $title;
my $d = get_description();
$obj->{description} ||= $d;
$room->{$label} ||= $obj;
if ($prev) {
foreach my $d (gd \%opposite, @dir) {
$prev->{$d} ||= $label;
}
}
$prev = $obj;
} elsif ($expecting eq $prop_class) {
warn "Prop found outside room!\n" unless $prev;
my $label;
($label = $title) =~ s/\W+/_/g;
my $suffix = "00";
(++ $suffix) while (defined $prop->{$label.'_'.$suffix}) &&
($prop->{$label.'_'.$suffix}->{parent} != $prev);
$label .= '_'.$suffix;
warn "Processing prop '$label'\n" if $verbose > 1;
$prop->{$label} = {
label => $label,
title => $title,
name => join(' ', map {"'$_'"} split /\s+/, $title),
} if ! defined $prop->{$label};
$prop->{$label}->{$property} = get_description(),
move($prop->{$label}, $prev);
} else {
warn "Don't know how to expect $expecting\n";
}
($_, $next_line) = ($next_line, '');
s/> *//;
$_ = lc $_;
if (/^(x|examine)\s+(.*)/) {
$title = $2;
$expecting = $prop_class;
$property = 'description';
} elsif (/^(listen\s+to|hear)\s+(.*)/) {
$title = $2;
$expecting = $prop_class;
$property = 'sound';
} elsif (/^(smell|sniff)\s+(.*)/) {
$title = $2;
$expecting = $prop_class;
$property = 'aroma';
} elsif (/^(taste)\s+(.*)/) {
$title = $2;
$expecting = $prop_class;
$property = 'flavor';
} elsif (/^(touch|feel)\s+(.*)/) {
$title = $2;
$expecting = $prop_class;
$property = 'texture';
} elsif (/^(\w+)/ && (defined($sort_by{$1}) || $1 eq 'back')) {
@dir = split(/\s*,\s*/);
$expecting = $room_class;
} else {
warn "Ignoring unrecognized command, '$_'\n";
}
} until eof(INPUT);
}
close(INPUT);
warn "Cleaning up directions\n" if $verbose;
foreach my $label (keys %$room) {
my $obj = $room->{$label};
POSSIBLE_EXIT:
foreach my $dir (gd \%sort_by, keys %$obj) {
my $label2 = $obj->{$dir};
my $obj2 = $room->{$label2};
foreach my $dir2 (gd \%sort_by, keys %$obj2) {
if ($obj2->{$dir2} eq $label) {
$obj->{$dir.' '} = $dir2;
$obj2->{$dir2.' '} = $dir;
next POSSIBLE_EXIT;
}
}
my $dir2 = $opposite{$dir};
$obj2->{$dir2} ||= $label;
$obj->{$dir.' '} = $dir2;
$obj2->{$dir2.' '} = $dir;
}
}
open(OUTPUT, ">$output")
or die "Unable to open output file $output\n";
my @senses = qw/description aroma sound taste texture/;
foreach my $label (sort keys %$room) {
my $obj = $room->{$label};
my ($title, $descr) = ($obj->{title}, $obj->{description});
next unless $descr;
warn "printing room $label\n" if $verbose > 1;
my @d =( fix_description('description', $descr) );
foreach my $dir (sort { $sort_by{$a} cmp $sort_by{$b} }
gd \%sort_by, keys %$obj) {
my $label2 = $obj->{$dir};
push @d, qq/ ${dir}_to ${label2}/;
}
print OUTPUT qq/$room_class $label "$title"\n with/,
join(",\n", @d), ";\n\n";
for ($obj = $obj->{child}; $obj; $obj = $obj->{sibling}) {
my ($label, $title) = ($obj->{label}, $obj->{title});
warn " printing prop $label\n" if $verbose > 2;
my @d;
foreach my $sense (gd $obj, @senses) {
warn " printing $sense\n" if $verbose > 3;
push @d, fix_description($sense, $obj->{$sense});
}
#next unless @d;
print OUTPUT qq/$prop_class -> $label "$title"\n with/,
join(",", qq/\n name $obj->{name}/, @d),
";\n\n";
}
}
close(OUTPUT);
warn "Calculating path lengths\n" if $verbose;
my %cost; # cost matrix used by Floyd's algorithm
foreach my $label (keys %$room) {
my $obj = $room->{$label};
foreach my $label2 (keys %$room) {
$cost{$label}->{$label2} = 9999;
}
foreach my $dir (grep {$sort_by{$_}} keys %$obj) {
my $label2 = $obj->{$dir};
$cost{$label}->{$label2} = 1;
}
$cost{$label}->{$label} = 0;
}
warn "Applying Floyd's algorithm\n" if $verbose;
foreach my $k (keys %$room) {
my $cost_k = $cost{$k};
foreach my $i (keys %$room) {
my $cost_i = $cost{$i};
foreach my $j (keys %$room) {
if ($cost_i->{$j} > ($cost_i->{$k} + $cost_k->{$j})) {
$cost_i->{$j} = $cost_i->{$k} + $cost_k->{$j};
}
}
}
}
warn "Calculating map locations\n" if $verbose;
(my $nbr_rooms = scalar %$room) =~ s=/.*==;
$nbr_rooms = 0 + $nbr_rooms;
$srand = int(10000 * rand) unless defined $srand;
srand $srand;
foreach my $label (keys %$room) {
$room->{$label}->{x} = 24*rand;
$room->{$label}->{y} = 24*rand;
}
my (%min, %max);
my ($pass, $prev_f, $iteration) = (1, 9999, '000');
while ($pass != 3) {
%min = (x=>+1e9, y=>+1e9, f=>+1e9);
%max = (x=>-1e9, y=>-1e9, f=>-1e9);
foreach my $label (keys %$room) {
my $obj = $room->{$label};
my ($x, $y, $fx, $fy) = ($obj->{x}, $obj->{y}, 0, 0);
#trace 1, $label, $x, $y, $fx, $fy;
# links between rooms exert attractive force
foreach my $dir (gd \%xoff, keys %$obj) {
my $label2 = $obj->{$dir} or next;
my $obj2 = $room->{$label2}
or die "obj2: label=$label, dir=$dir, label2=$label2";
my $dir2 = $obj->{$dir.' '}
or die "dir2: label=$label, dir=$dir, label2=$label2";
my ($d, $r) = cart2rad(
($x + $xoff{$dir}) - ($obj2->{x} + $xoff{$dir2}),
($y + $yoff{$dir}) - ($obj2->{y} + $yoff{$dir2}),
);
my $f = $d;
$fx += $f * cos($r);
$fy += $f * sin($r);
}
# all rooms exert repulsive force
unless ($pass == 1) {
my $costs = $cost{$label};
foreach my $label2 (keys %$room) {
next if $label2 eq $label;
my $obj2 = $room->{$label2};
my ($d, $r) = cart2rad(
$x - $obj2->{x}, $y - $obj2->{y});
$d /= 3;
my $f = $costs->{$label2} /
(($d < 1) ? $d : ($d*$d));
$f = 10 if $f > 10;
$fx -= $f * cos($r);
$fy -= $f * sin($r);
}
}
$obj->{fx} = $fx;
$obj->{fy} = $fy;
my $f = cart2rad($fx, $fy);
$max{f} = $f if $max{f} < $f;
$min{f} = $f if $min{f} > $f;
}
foreach my $label (keys %$room) {
my $obj = $room->{$label};
#trace 3, $label, $obj->{x}, $obj->{y}, $obj->{fx}, $obj->{fy};
$obj->{x} -= $obj->{fx} * $dt;
$obj->{y} -= $obj->{fy} * $dt;
foreach my $v (qw/x y fx fy/) {
if ($min{$v} > $obj->{$v}) {
$min{$v} = $obj->{$v};
$min{$v.' who'} = $label;
}
if ($max{$v} < $obj->{$v}) {
$max{$v} = $obj->{$v};
$max{$v.' who'} = $label;
}
}
}
#warn sprintf "sqrt((%8.5f-%8.5f)/(%8.5f-%8.5f+%8.5f-%8.5f))\n",
# $max{f}, $min{f}, $max{x}, $min{x}, $max{y}, $min{y};
$dt = 0.1*sqrt(($max{x}-$min{x}+$max{y}-$min{y}) / ($max{f}-$min{f}));
} continue {
warn sprintf "pass $pass, dt = %8.5f, prev. f = %8.5f\n",
$dt, $prev_f if $verbose > 1;
foreach my $v (qw/x y fx fy/) {
warn sprintf "\t$v min=%8.5f @ %-8s max=%8.5f @ %-8s\n",
$min{$v}, $min{$v.' who'}, $max{$v}, $max{$v.' who'}
if $verbose > 2;
}
if ((($prev_f - $max{f}) < 0.01) && ($max{f} < 2)) {
++ $pass;
$prev_f = 9999;
} elsif ($max{x}-$min{x} > 1000) {
++ $pass;
} elsif ($max{y}-$min{y} > 1000) {
++ $pass;
} elsif ($max{f} > 9999) {
++ $pass;
} else {
$prev_f = $max{f};
}
plot($base.'.'.($iteration++).'.png') if $animate;
}
plot($base.'.png');
sub plot {
my $plotter = shift;
my $diam = 1;
foreach my $v (qw/x y/) {
$min{$v} -= $diam + 1;
$max{$v} += $diam + 1;
}
use Graphics::Plotter;
open(PLOTTER, ">$plotter")
or die "Unable to open plotter file $plotter\n";
my $p = Graphics::Plotter::PNG->new(*PLOTTER);
$p->openpl();
my ($dx, $dy) = ($max{x}-$min{x}, $max{y}-$min{y});
if ($dx > $dy) {
$p->fspace($min{x}, $min{y}-($dx-$dy)/2, $max{x}, $max{y}+($dx-$dy)/2);
$p->fmove($min{x}, $min{y}-($dx-$dy)/2);
} else {
$p->fspace($min{x}-($dy-$dx)/2, $min{y}, $max{x}+($dy-$dx)/2, $max{y});
$p->fmove($min{x}-($dy-$dx)/2, $min{y});
}
$p->alabel('l','b',"srand=$srand");
foreach my $label (keys %$room) {
my $obj = $room->{$label};
my ($x, $y) = ($obj->{x}, $obj->{y});
$p->fmove($x,$y);
$p->fcirclerel(0,0,$diam);
my ($title, $descr) = ($obj->{title}, $obj->{description});
if (length($title) > 5) {
$title = substr($title,0,5);
}
$p->pencolorname($descr?"blue":"red");
$p->alabel('c','c',$title);
$p->pencolorname("black");
foreach my $dir (gd \%xoff, keys %$obj) {
my $label2 = $obj->{$dir} or next;
my $obj2 = $room->{$label2}
or die "obj2: label=$label, dir=$dir, label2=$label2";
my $dir2 = $obj->{$dir.' '}
or die "dir2: label=$label, dir=$dir, label2=$label2";
$x + $xoff{$dir}, $y + $yoff{$dir},
$obj2->{x} + $xoff{$dir2}, $obj2->{y} + $yoff{$dir2};
#$p->fline($x + $xoff{$dir}, $y + $yoff{$dir},
# $obj2->{x} + $xoff{$dir2}, $obj2->{y} + $yoff{$dir2});
$p->fbezier3($x + $xoff{$dir}, $y + $yoff{$dir},
$x + 2 * $xoff{$dir}, $y + 2 * $yoff{$dir},
$obj2->{x} + 2 * $xoff{$dir2},
$obj2->{y} + 2 * $yoff{$dir2},
$obj2->{x} + $xoff{$dir2}, $obj2->{y} + $yoff{$dir2});
}
}
$p->closepl();
}
__END__
=head1 NAME
log2inf - Reverse-engineer an IF transcript into an Inform source file.
=head1 SYNOPSIS
sample [options] [file]
Options:
-room <string> name of Class to use for rooms (default 'Room')
-prop <string> name of Class to use for props (default 'Prop')
-dt <float> see below
-animate produce an image after each pass
-verbose increment verbosity level
-srand <int> initialize random number generator
-help brief help message
-man full documentation
=head1 OPTIONS
=over 8
=item B<-room> -I<name>
Name of Class to use for rooms (default 'Room').
=item B<-prop> -I<name>
Name of Class to use for props (default 'Prop').
=item B<-dt> -I<float>
Sets increment amount for map generation.
=item B<-animate>
Graphs are produced by seeding the rooms to random locations, then moving
them around to minimize the forces produced by elastic links between them.
This option causes a graph to be produced after each iteration.
=item B<-verbose>
Increment verbosity level. Use it once to produce a little output,
use it multiple times to produce a lot.
=item B<-srand> -I<int>
Initialize random number generator.
=item B<-help>
Print a brief help message and exits.
=item B<-man>
Prints the manual page and exits.
=back
=head1 DESCRIPTION
In "The Inform Designer's Manual, 4th edition, Graham Nelson says,
"Gareth Rees persuasively advocates writing this sort of
transcript, of an ideal sequence of play, first, and worrying about
how to code up the design afterwards. Other designers prefer to build
from the bottom up, crafting the objects one at a time and finally
bringing them together into the narrative."
I've gone with Rees' idea. First, I wrote a transcript, then I wrote
a Perl script that turns transcripts into Inform source code. So,
something like this:
>n
West Wing
You are in the West Wing of the White House. You see a desk and, on
it, a red telephone.
>x large wooden desk
It has a heavy paperweight on it.
>x paperweight
It says "The buck stops here."
>x red telephone
It's red, without a dial.
>w
Rose Garden
generates something like this:
Room West_Wing "West Wing"
with
description "You are in the West Wing of the White House. You
see a desk and, on it, a red telephone.",
w_to Rose_Garden;
Prop -> large_wooden_desk_00 "large wooden desk"
with
name 'large' 'wooden' 'desk'
description "It has a paperweight on it."
Prop -> paperweight_00 "paperweight"
with
name 'paperweight'
description "It says ~The buck stops here.~"
Prop -> red_telephone_00 "red telephone"
with
name 'red' 'telephone'
description "It's red, without a dial."
Props are numbered, so multiple rooms could contain, for example,
large wooden desks. Note that anything described is assumed to be a
child of the room, so you still have a bit of fixing up to do (both
the paperweight and the telephone, in this instance). Also, note that
I assume the existance of classes named Room and Prop. Right now, I
use the script to generate a file that I then include in my main
Inform source, as both the transcript and the Perl script are still in
a bit of flux.
My next goal is to add a mapping component, which will likely spit out
gnuplot instructions to draw a crude map of just the rooms. You just
pretend that the eight "primary" links between rooms are elastic, and
figure out a layout that keeps the links at their minimal lengths.
Simple iterative physics-based modeling.
I think I can also divide a map into layers by using some coloring
algorithms. It's easy if the only connections between layers are 'up'
and 'down', but I think that by using weighted averaging, I can decide
which connections are important and which just lead to sunken living
rooms and the like.
=head1 AUTHOR
Sam Denton, samwyse (at) email (dot) com
=head1 BUGS
=head1 SEE ALSO
"The Inform Designer's Manual", 4th edition, Graham Nelson.
=head1 COPYRIGHT
Copyright 2004, Sam Denton. All rights reserved.
This program is free software. You may copy or
redistribute it under the same license as Perl itself.
=cut

Xet Storage Details

Size:
15.8 kB
·
Xet hash:
c8b6e272732c879db09c66a344efaa710bf6311746c797855016729cf5df1d49

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.