| #!/usr/bin/perl -w | |
| # Tool to create IFM maps from ASCII art maps. Intended to be an easy | |
| # way to draw a map and have it converted to IFM format. | |
| # | |
| # Use "asciimapper -t -i -o template.art" to generate a blank template. | |
| # The -t option specifies a template should be made. | |
| # The -i option specifies it should include instructions in comments. | |
| # The -o option specifies the output file (stdout would be used otherwise). | |
| # A -x ROWS,COLS option can be used to specify the size of the template. | |
| # | |
| # Use "asciimapper -k -i template.art" to generate a key file for a map. | |
| # The input template may be filled in or blank. The output file will be | |
| # "template.key" | |
| # The -t option specifies a template should be made. | |
| # The -i option specifies it should include instructions in comments. | |
| # | |
| # Use "asciimapper -g 4 template.art" to enlarge (grow) a map by 4 squares | |
| # in all directions. Or use any combination of [NSEW]{num} in one string | |
| # to more finely tune the growth. If "template.key" is found, it will be | |
| # enlarged as well. | |
| # | |
| # Use "asciimapper -m template.art" to generate an IFM map from the art | |
| # file, the "template.key" file (if found) and save it to "template.ifm". | |
| # | |
| # Use "asciimapper -d demo.art" to generate a demo ASCII art map, from | |
| # which a key file will be made and filled in, then an IFM map will be | |
| # generated. Template mode, key mode, and generation mode options will | |
| # be honored. | |
| # | |
| # This was written to work with IFM 5.1. Other versions may or may not work. | |
| # | |
| # Get IFM: http://www.ifarchive.org/indexes/if-archiveXmapping-tools.html | |
| # | |
| # 23 June 2005 Eli the Bearded asciimapper@eli.users.panix.com | |
| use strict; | |
| use vars qw( $o_include $o_mode $o_out $o_file $o_rows $o_cols $o_verbose | |
| $o_preserve $o_inup $o_size $o_demo $o_n $o_e $o_s $o_w $nokey | |
| $title $r $c $sr $sc | |
| @map @rooms %names %tasks %items %styles @key @seen %links | |
| $id $VERSION $LONG_VERSION_INFO | |
| ); | |
| $id = $0; | |
| $id =~ s:.*/::; | |
| $VERSION = "1.0.1"; | |
| $LONG_VERSION_INFO = "Revised initial release"; | |
| $o_rows = 10; | |
| $o_cols = 20; | |
| $o_size = 1; | |
| $o_inup = 'in'; | |
| $o_n = $o_e = $o_s = $o_w = 0; | |
| sub dotemplate(); | |
| sub dokeytemplate($); | |
| sub dogrowth(); | |
| sub dodemo(); | |
| sub readfiles($); | |
| sub tagrooms(); | |
| sub makeifm($$$$); | |
| sub findexits($$); | |
| sub usage($); | |
| while(defined($ARGV[0]) and substr($ARGV[0], 0, 1) eq '-') { | |
| if (($ARGV[0] eq '-t') or ($ARGV[0] eq '--template')) { | |
| shift; | |
| $o_mode = 't'; | |
| } elsif (($ARGV[0] eq '-k') or ($ARGV[0] eq '--key')) { | |
| shift; | |
| $o_mode = 'k'; | |
| } elsif (($ARGV[0] eq '-m') or ($ARGV[0] eq '--makemap')) { | |
| shift; | |
| $o_mode = 'm'; | |
| } elsif (($ARGV[0] eq '-g') or ($ARGV[0] eq '--grow')) { | |
| shift; | |
| $o_mode = 'g'; | |
| if($ARGV[0] =~ /^\s*([1-9]\d*)\s*$/) { | |
| $o_n = $o_e = $o_s = $o_w = $1; | |
| } elsif ($ARGV[0] =~ /^\s*((?:[nesw]\d+,?)+)\s*$/i) { | |
| $_ = $1; | |
| if(s/n(\d+)//i) { $o_n = $1; } | |
| if(s/e(\d+)//i) { $o_e = $1; } | |
| if(s/s(\d+)//i) { $o_s = $1; } | |
| if(s/w(\d+)//i) { $o_w = $1; } | |
| } else { | |
| print STDERR "$id: -g (--grow) requires a growth size\n"; | |
| usage(2); | |
| } | |
| shift; | |
| } elsif (($ARGV[0] eq '-d') or ($ARGV[0] eq '--demo')) { | |
| shift; | |
| $o_mode = 'd'; | |
| $o_demo = 1; | |
| } elsif (($ARGV[0] eq '-i') or ($ARGV[0] eq '--include')) { | |
| shift; | |
| $o_include = 1; | |
| } elsif (($ARGV[0] eq '-p') or ($ARGV[0] eq '--preserve')) { | |
| shift; | |
| $o_preserve = 1; | |
| } elsif (($ARGV[0] eq '-u') or ($ARGV[0] eq '--updown')) { | |
| shift; | |
| $o_inup = 'up'; | |
| } elsif (($ARGV[0] eq '-s') or ($ARGV[0] eq '--size')) { | |
| shift; | |
| if($ARGV[0] =~ /^\s*([1-9]\d*)\s*$/) { | |
| $o_size= $1; | |
| shift; | |
| } else { | |
| print STDERR "$id: -s (--size) requires a positive number\n"; | |
| usage(2); | |
| } | |
| } elsif (($ARGV[0] eq '-x') or ($ARGV[0] eq '--rowsxcols')) { | |
| shift; | |
| if($ARGV[0] =~ /^\s*([1-9]\d*)\s*[Xx,]\s*([1-9]\d*)\s*$/) { | |
| $o_rows = $1; | |
| $o_cols = $2; | |
| shift; | |
| } else { | |
| print STDERR "$id: -x (--rowsxcols) requires a pair of positive numbers\n"; | |
| usage(2); | |
| } | |
| } elsif (($ARGV[0] eq '-o') or ($ARGV[0] eq '--out')) { | |
| shift; | |
| $o_out = shift; | |
| if (!defined($o_out)) { | |
| print STDERR "$id: -o (--out) requires an output file\n"; | |
| usage(2); | |
| } | |
| } elsif (($ARGV[0] eq '-v') or ($ARGV[0] eq '--verbose')) { | |
| shift; | |
| $o_verbose = 1; | |
| } elsif ($ARGV[0] eq '--version') { | |
| print "$0 version $VERSION -- $LONG_VERSION_INFO\n"; | |
| print "by Eli the Bearded / Benjamin Elijah Griffin\n"; | |
| exit(0); | |
| } elsif ($ARGV[0] eq '--help') { | |
| &usage(0); | |
| } else { | |
| print STDERR "$0: $ARGV[0] not a recognized option\n"; | |
| &usage(2); | |
| } | |
| } | |
| $o_file = shift; | |
| # Key mode does not use $o_out at all, so try it before creating $o_out | |
| if($o_mode eq 'k') { | |
| if(!defined($o_file)) { | |
| die "$id: No input file specified\n"; | |
| } elsif ($o_verbose) { | |
| print STDERR "Key template mode for $o_file\n"; | |
| } | |
| dokeytemplate($o_file); | |
| exit; | |
| } | |
| if(defined($o_out)) { | |
| if(!open(STDOUT, "> $o_out")) { | |
| die "$id: cannot open $o_out for output: $!\n"; | |
| } elsif ($o_verbose) { | |
| print STDERR "Opened $o_out for output\n"; | |
| } | |
| } | |
| if($o_mode eq 't') { | |
| if($o_verbose) { | |
| print STDERR "Template creation mode\n"; | |
| } | |
| dotemplate(); | |
| exit; | |
| } | |
| if($o_mode eq 'g') { | |
| if($o_verbose) { | |
| print STDERR "Template growth mode: N$o_n,E$o_e,S$o_s,W$o_w\n"; | |
| } | |
| dogrowth(); | |
| exit; | |
| } | |
| if($o_mode eq 'd') { | |
| if($o_verbose) { | |
| print STDERR "Demonstration mode\n"; | |
| } | |
| dodemo(); | |
| $o_mode = 'm'; | |
| } | |
| if($o_mode eq 'm') { | |
| if($o_verbose) { | |
| print STDERR "IFM map creation mode\n"; | |
| } | |
| # readfiles might also open an outfile | |
| readfiles($o_file); | |
| # Gives rooms unique tags based on map co-ordinates, | |
| # sets $r and $c to the map size row & col | |
| # sets $sr and $sc to the starting room row & col | |
| # initializes @seen | |
| tagrooms(); | |
| if($o_verbose) { | |
| print STDERR "Map starting from room at $sr,$sc\n"; | |
| } | |
| # from-tag,direction,current-row,current-col | |
| makeifm( '', '', $sr, $sc); | |
| exit; | |
| } | |
| die "$id: fell through modeselect if()s\n"; | |
| ##################################################################### | |
| # Functions follow | |
| ##################################################################### | |
| # This recursive function is the heart of the mapping process. | |
| # Don't be surprised to get deep recursion warnings on large maps, | |
| # eg an 8x8 chess board. | |
| # | |
| # Reads and writes @seen to know when to print a new room or merely link | |
| # to it. | |
| # | |
| # Reads and writes %links to know if a particular link has been processed, | |
| # in either direction, previously. If all links from a room have been | |
| # processed, we recurse no further for that room. | |
| # | |
| # Reads from @rooms, @key, %names, %items, %tasks, and %styles to | |
| # know what to print with a room. (Calls findexits() which reads @map.) | |
| # | |
| # Return value should be ignored. | |
| sub makeifm($$$$) { | |
| my $from = shift; # previous room tag | |
| my $dir = shift; # direction we entered | |
| my $rr = shift; # room row | |
| my $rc = shift; # room col | |
| my @exits = findexits($rr,$rc); | |
| my $exit; | |
| my $tag = $rooms[$rr][$rc]; | |
| my $key = $key[$rr][$rc]; | |
| my $name; | |
| my $item; | |
| my $task; | |
| my $style; | |
| my $new; | |
| if(length($key) and $key =~ /\S/) { | |
| $key =~ s/\s+//g; | |
| $name = defined($names{$key} ) ? $names{$key} : ''; | |
| $item = defined($items{$key} ) ? $items{$key} : ''; | |
| $task = defined($tasks{$key} ) ? $tasks{$key} : ''; | |
| $style = defined($styles{$key}) ? $styles{$key} : ''; | |
| } | |
| if(!$seen[$rr][$rc]) { | |
| $dir ||= ''; | |
| if($dir) { | |
| # $dir at this point is either a bare direction (eg "n", "sw") | |
| # or a direction and a go clause (eg "n go up", "s go down"). | |
| # The "go up" (etc) clause must appear after the "from ROOM" | |
| # clause. The double parens avoid $2 being uninitialized. | |
| $dir =~ s/(\w+)\s*((?:go\s+\w+)?)/dir $1 from $from $2/; | |
| } | |
| if($tag =~ /\S/) { | |
| if($from) { | |
| $links{"$tag:$from"} = $links{"$from:$tag"} = 1; | |
| } | |
| $tag =~ s/^/tag /; | |
| } | |
| if($style =~ /\S/) { | |
| $style =~ s/^/style /; | |
| } | |
| if($o_verbose) { | |
| print STDERR "adding room $name $dir " . | |
| ($tag? $tag : "($rr,$rc)" ) . | |
| " $style\n"; | |
| } | |
| print qq(room "$name" $dir $tag $style;\n); | |
| if($item =~ /\S/) { | |
| $item =~ s/^/ item "/; | |
| $item =~ s/$/";\n/; | |
| $item =~ s/,/";\n item "/g; | |
| print $item; | |
| } | |
| if($task =~ /\S/) { | |
| $task =~ s/^/ task "/; | |
| $task =~ s/$/";\n/; | |
| $task =~ s/;([^\n])/";\n task "$1/g; | |
| print $task; | |
| } | |
| print "\n"; | |
| $seen[$rr][$rc] = 1; | |
| } else { | |
| # seen this room, just add a link to it | |
| if(($from =~ /\S/) and ($tag =~ /\S/)) { | |
| if($o_verbose) { | |
| print STDERR "adding link $from, $tag, $dir\n"; | |
| } | |
| $links{"$tag:$from"} = $links{"$from:$tag"} = 1; | |
| print "link $from to $tag dir $dir;\n\n"; | |
| } | |
| # No good reason to increment at this time, but... | |
| $seen[$rr][$rc] ++; | |
| } | |
| $tag = $rooms[$rr][$rc]; | |
| if($tag !~ /^\s*$/) { | |
| for $exit (@exits) { | |
| $new = $rooms[$$exit[1]][$$exit[2]]; | |
| if(!$links{"$tag:$new"}) { | |
| if($o_verbose) { | |
| print STDERR "recursing: $tag, $$exit[0], $$exit[1], $$exit[2]\n"; | |
| } | |
| makeifm($tag, $$exit[0], $$exit[1], $$exit[2]); | |
| } | |
| } # for exit | |
| } # if tag | |
| } # end &makeifm | |
| ##################################################################### | |
| # Called by the recursive makeifm() function, this reads from @map | |
| # to find and give the proper names to all exits from a room, | |
| # returning an array of arrays: | |
| # [ [ exit-name, new-room-row, new-room-col ], | |
| # ... | |
| # ] | |
| sub findexits($$) { | |
| my $rr = shift; # room row | |
| my $rc = shift; # room col | |
| my @out; | |
| if(!defined($rr) or !defined($rc) or ($rr < 0) or ($rc < 0)) { | |
| die "$id: room co-ordinates out of bounds!\n"; | |
| } | |
| my $mr = 1+2*$rr; # map row | |
| my $mc = 1+2*$rc; # map col | |
| my $go1; | |
| my $go2; | |
| if($o_inup eq 'in') { | |
| $go1 = 'go in'; | |
| $go2 = 'go out'; | |
| } else { | |
| $go1 = 'go up'; | |
| $go2 = 'go down'; | |
| } | |
| # Search around $mr,$mc in the map file for exit paths, different | |
| # co-ordinates have different exit markers: | |
| # ($mr-1,$mc-1)[\xX] ($mr-1,$mc)[|^vV] ($mr-1,$mc+1)[/xX] | |
| # ( $mr,$mc-1)[-<>] ( $mr,$mc)[#*] ( $mr,$mc+1)[-<>] | |
| # ($mr+1,$mc-1)[/xX] ($mr+1,$mc)[|^vV] ($mr+1,$mc+1)[\xX] | |
| # There are eight 2-d directions possible, but twenty "real" | |
| # directions. | |
| my $n = lc($map[$mr-1][$mc]); | |
| my $ne = lc($map[$mr-1][$mc+1]); | |
| my $e = $map[$mr][$mc+1]; | |
| my $se = lc($map[$mr+1][$mc+1]); | |
| my $s = lc($map[$mr+1][$mc]); | |
| my $sw = lc($map[$mr+1][$mc-1]); | |
| my $w = $map[$mr][$mc-1]; | |
| my $nw = lc($map[$mr-1][$mc-1]); | |
| if($n eq '|') { push(@out, ["n", $rr-1, $rc]); } | |
| if($n eq '^') { push(@out, ["n $go1", $rr-1, $rc]); } | |
| if($n eq 'v') { push(@out, ["n $go2", $rr-1, $rc]); } | |
| if($ne eq '/') { push(@out, ["ne", $rr-1, $rc+1]); } | |
| if($ne eq 'x') { push(@out, ["ne", $rr-1, $rc+1]); } | |
| if($e eq '-') { push(@out, ["e", $rr, $rc+1]); } | |
| if($e eq '>') { push(@out, ["e $go1", $rr, $rc+1]); } | |
| if($e eq '<') { push(@out, ["e $go2", $rr, $rc+1]); } | |
| if($se eq '\\'){ push(@out, ["se", $rr+1, $rc+1]); } | |
| if($se eq 'x') { push(@out, ["se", $rr+1, $rc+1]); } | |
| if($s eq '|') { push(@out, ["s", $rr+1, $rc]); } | |
| if($s eq 'v') { push(@out, ["s $go1", $rr+1, $rc]); } | |
| if($s eq '^') { push(@out, ["s $go2", $rr+1, $rc]); } | |
| if($sw eq '/') { push(@out, ["sw", $rr+1, $rc-1]); } | |
| if($sw eq 'x') { push(@out, ["sw", $rr+1, $rc-1]); } | |
| if($w eq '-') { push(@out, ["w", $rr, $rc-1]); } | |
| if($w eq '<') { push(@out, ["w $go1", $rr, $rc-1]); } | |
| if($w eq '>') { push(@out, ["w $go2", $rr, $rc-1]); } | |
| if($nw eq '\\'){ push(@out, ["nw", $rr-1, $rc-1]); } | |
| if($nw eq 'x') { push(@out, ["nw", $rr-1, $rc-1]); } | |
| if($o_verbose) { | |
| my $i; | |
| my $exits = "Exit(s) for $rr,$rc: "; | |
| for ($i = 0; $i < @out; $i++) { | |
| $exits .= $out[$i][0] . ","; | |
| } | |
| $exits =~ s/.$/\n/; | |
| print STDERR $exits; | |
| print STDERR "[$nw,$n,$ne],[$w,".$map[$mr][$mc].",$e],[$sw,$s,$se]\n"; | |
| } | |
| @out; | |
| } # end &findexits | |
| ##################################################################### | |
| # Gives rooms unique tags based on map co-ordinates, | |
| # sets $r and $c to the map size row & col | |
| # sets $sr and $sc to the starting room row & col | |
| # checks @key dimensions | |
| # initializes @seen | |
| # Exits the program if any errors are found. | |
| sub tagrooms() { | |
| my $i; | |
| my $j; | |
| local $"; | |
| $" = ','; | |
| $r = 1 + $#rooms; | |
| $c = 1 + $#{$rooms[0]}; | |
| if($o_verbose) { | |
| print STDERR "Naming rooms, grid size ${r}x$c\n"; | |
| } | |
| if(!$nokey and $r-1 != $#key) { | |
| die "$id: key map is not $r rows\n"; | |
| } | |
| for($i = 0; $i < $r; $i ++) { | |
| if($c-1 != $#{$rooms[$i]}) { | |
| die "$id: room row $i is not $c long\n"; | |
| } | |
| if(!$nokey and $c-1 != $#{$key[$i]}) { | |
| die "$id: key map room row $i is not $c long\n"; | |
| } | |
| for($j = 0; $j < $c; $j ++) { | |
| $seen[$i][$j] = ''; | |
| if($rooms[$i][$j] eq ' ') { | |
| next; | |
| } | |
| if($rooms[$i][$j] eq '*') { | |
| if(!defined($sr)) { | |
| $sr = $i; | |
| $sc = $j; | |
| } else { | |
| die "$id: Found a second start room ($i,$j), first at $sr,$sc.\n" . | |
| "Only one start room is allowed.\n"; | |
| } | |
| } | |
| $rooms[$i][$j] = "R${i}C$j"; | |
| } # for $j | |
| if($o_verbose) { | |
| print STDERR "@{$rooms[$i]}\n"; | |
| } | |
| } # for $i | |
| } # end &tagrooms | |
| ##################################################################### | |
| # Reads files prior to making a map. From one input file (the map art | |
| # file) probes for the associated key file. | |
| # Initializes @map, @key, %names, %items, %tasks, %styles. | |
| # Opens the output file and starts writing to it. | |
| # Exits the program if any errors are found. | |
| sub readfiles($) { | |
| my $in = shift; | |
| my $key; | |
| my $line; | |
| my $name; | |
| my $value; | |
| my $type; | |
| my $data; | |
| my $seen_title; | |
| local $"; | |
| $" = ','; | |
| if(defined($in) and $in ne '-') { | |
| if(!open(STDIN, "< $in")) { | |
| die "$id: cannot open $in for reading: $!\n"; | |
| } elsif ($o_verbose) { | |
| print STDERR "Reading map from $in\n"; | |
| } | |
| $key = $in; | |
| $key =~ s/([.]art$|$)/.key/i; | |
| if(-f $key) { | |
| if(!open(KEY, "< $key")) { | |
| die "$id: cannot open $key for reading: $!\n"; | |
| } elsif($o_verbose) { | |
| print STDERR "Will read key data from $key\n"; | |
| } | |
| } else { | |
| $key = undef; | |
| $nokey = 1; | |
| } | |
| if(!defined($o_out)) { | |
| $o_out = $in; | |
| $o_out =~ s/([.]art$|$)/.ifm/i; | |
| if(!open(STDOUT, "> $o_out")) { | |
| die "$id: cannot open $o_out for output: $!\n"; | |
| } elsif ($o_verbose) { | |
| print STDERR "Writing to $o_out\n"; | |
| } | |
| } | |
| } elsif($o_verbose) { | |
| print STDERR "Using stdin for input.\n"; | |
| if(!defined($o_out)) { | |
| print STDERR "Using stdout for output.\n"; | |
| } | |
| } | |
| if($o_verbose) { | |
| print STDERR "Starting on the art file\n"; | |
| } | |
| while(<STDIN>) { | |
| if($o_preserve and /^:/) { | |
| s/^:/#/; | |
| print; | |
| next; | |
| } elsif (/^:/) { | |
| next; | |
| } | |
| chomp; | |
| if(!$seen_title) { | |
| $seen_title = 1; | |
| if ($o_verbose) { | |
| print STDERR "Map title: $_\n"; | |
| if($o_preserve) { | |
| print STDERR "Preserving any source comments\n"; | |
| } | |
| } | |
| print qq(title "$_";\n\n); | |
| next; | |
| } | |
| if($_ eq '') { | |
| next; | |
| } | |
| push(@map,[split(//, $_)]); | |
| if($#map % 2) { | |
| # odd lines have rooms | |
| s:^[-|xXvV^/\\<>.]::; | |
| s:\G([#* ])[-|xXvV^/\\<>.]:$1:g; | |
| if(!/^[#* ]+/) { | |
| die "$id: Syntax error line $., found a non-room char when expecting". | |
| " a room\nPartially processed line: $_\n"; | |
| } | |
| push(@rooms,[split(//, $_)]); | |
| if($o_verbose) { | |
| print STDERR "@{$rooms[-1]}\n"; | |
| } | |
| } else { | |
| # even lines have connections only | |
| if(!m:^[-|xXvV^/\\<>.]+$:) { | |
| die "$id: Syntax error line $., found a non-connecting char when". | |
| " expecting a connecting char\nLine: $_\n"; | |
| } | |
| } | |
| } # while STDIN | |
| if(defined($key)) { | |
| $line = 1; | |
| if($o_verbose) { | |
| print STDERR "Starting on the key file\n"; | |
| } | |
| while(<KEY>) { | |
| if($o_preserve and /^:/) { | |
| s/^:/#/; | |
| print; | |
| next; | |
| } elsif (/^:/) { | |
| next; | |
| } | |
| chomp; | |
| if($_ eq '') { | |
| next; | |
| } | |
| if(/^(\S+):\s*(.+)/) { | |
| # key explanation line | |
| $name = $1; | |
| $value = $2; | |
| # Global style stuff should apper before all the room declarations. | |
| if($name eq 'OVERALL') { | |
| # blank is allowed | |
| $value =~ s/style\s*\{(.*)\}/$1\n/; | |
| print $value; | |
| } | |
| while($value =~ /\G\s* # anchor and optional space | |
| (name|item|task|style) # type of data | |
| \s*\{ # space and open brace | |
| ( (?: [^{}\\]+ # non brace or backslash | |
| | \\[{}\\] # escaped brace or backslash | |
| )* ) # zero or more either above | |
| \} # close brace | |
| /xg # extended format, global | |
| ) { | |
| $type = $1; | |
| $data = $2; | |
| if($data =~ /^\s*$/) { | |
| # blank is a non-statement | |
| next; | |
| } | |
| # The different types have different rules for multiple | |
| # appearances. | |
| if($type eq 'name') { | |
| # Multiseen: Last value wins | |
| $names{$name} = $data; | |
| } | |
| if($type eq 'item') { | |
| # Multiseen: concatenate with comma | |
| if(defined($items{$name})) { | |
| $items{$name} .= ",$data"; | |
| } else { | |
| $items{$name} .= $data; | |
| } | |
| } | |
| if($type eq 'task') { | |
| # Multiseen: concatenate with semicolon | |
| if(defined($tasks{$name})) { | |
| $tasks{$name} .= ";$data"; | |
| } else { | |
| $tasks{$name} .= $data; | |
| } | |
| } | |
| if($type eq 'style') { | |
| # Multiseen: Last value wins | |
| $styles{$name} = $data; | |
| } | |
| } # while processing values | |
| next; | |
| } | |
| # Not blank, not key explanation, not comment. Must be the map. | |
| $line ++; | |
| if($line % 2) { | |
| # odd lines have rooms | |
| s:^[.]::; | |
| s:[.]$::; | |
| push(@key,[split(/[.]/, $_)]); | |
| if($o_verbose) { | |
| print STDERR "@{$key[-1]}\n"; | |
| } | |
| } else { | |
| # even lines have connections only | |
| if(!m:^[.]+$:) { | |
| die "$id: Syntax error $name line $., expected a line of periods,". | |
| " got:\n$_\n"; | |
| } | |
| } | |
| } # while KEY | |
| close KEY; | |
| } # if name | |
| elsif ($o_verbose) { | |
| print STDERR "No key file\n"; | |
| } | |
| } # end &readfiles | |
| ##################################################################### | |
| # Print a map template, with or without the instruction comments. | |
| # Output is to the default filehandle, the main program sets that up. | |
| # Return value should be ignored. | |
| sub dotemplate() { | |
| my $i; | |
| my $block; | |
| print "Map Title\n"; | |
| if($o_include) { | |
| if($o_verbose) { | |
| print STDERR "Including instruction comments.\n"; | |
| } | |
| print <<IncludeBit; | |
| : Art file syntax | |
| : The first line is used as the map title. Don't use quotes in it. | |
| : Comments are lines that begin with a colon. Comments cannot start mid-line. | |
| : There are several recognized file types, but the most important one has | |
| : the .art suffix. It is the ASCII art file that defines where the rooms are | |
| : and how they are connected. It is a grid of spaces surrounded by periods | |
| : initially. The spaces are grid squares where a room can be. The periods are | |
| : places for room connectors. All rooms should be connected somehow. | |
| : The other files have .name, .item, .task suffixes, and correspond to the | |
| : room names, items, and tasks. These are space and period grids as well, | |
| : but do not have the room connectors, and room contents are not limited to | |
| : single characters. This makes these files hard to visualize. The best way | |
| : create one is to use a .art file and replace the rooms starting with the | |
| : lower right (southeast) and working up and left (northeast). | |
| : Character Meaning | |
| : space no room there | |
| : * start room (must be exactly one) | |
| : # other rooms | |
| : . no connection | |
| : - connect rooms west-east | |
| : | connect rooms north-south | |
| : \\ connect rooms northwest-southeast | |
| : / connect rooms northeast-southwest | |
| : X or x connect two pairs of rooms nw-se and ne-sw | |
| : ^ connect two rooms in-out, laid out north-south | |
| : v or V connect two rooms out-in, laid out south-north | |
| : < connect two rooms in-out, laid out west-east | |
| : > connect two rooms out-in, laid out east-west | |
| IncludeBit | |
| } | |
| $block = (".." x $o_cols) . ".\n" . (". " x $o_cols) . ".\n"; | |
| for ($i = 0; $i < $o_rows; $i ++) { | |
| print $block; | |
| } | |
| print ".." x $o_cols . ".\n"; | |
| } # end &dotemplate | |
| ##################################################################### | |
| # Reads a map (art) template and converts it to a key template. All | |
| # room exits need to be wiped for a key template, and room sizes may | |
| # be enlarged for larger keys. | |
| # The input and output files are opened here. | |
| # Return value should be ignored. | |
| sub dokeytemplate($) { | |
| my $in = shift; | |
| my $key = $in; | |
| my $seen_title; | |
| my $seen_start; | |
| my $seen_hash; | |
| if(!open(STDIN, "< $in")) { | |
| die "$id: cannot open $in for reading: $!\n"; | |
| } | |
| $key =~ s/([.]art$|$)/.key/i; | |
| if(!open(KEY, "> $key")) { | |
| die "$id: cannot open $key for writing: $!\n"; | |
| } elsif ($o_verbose) { | |
| print STDERR "Opened $key for output\n"; | |
| } | |
| if($o_include) { | |
| # We'll put a comment block with some explanations here. | |
| print KEY <<KeyComments; | |
| : Key file syntax | |
| : In key files room connections are not shown. Rooms are separated always | |
| : by dots. Whitespace only in a room means either no room or no key for | |
| : that room. Keys can be any non-whitespace characters. Explanation lines | |
| : are used to assign attributes to the keys. The format is key at the start | |
| : of the line; a colon; optional spaces; an attribute name; an attribute | |
| : value in a set of {braces}. A key can be used in multiple explanation | |
| : lines. Each explanation line can contain multiple attributes. Multiple | |
| : items are comma (,) delimited. Multiple tasks are semicolon (;) delimited. | |
| : Multiple assignments of name or style will result in the last one being | |
| : used. The special key OVERALL can be used with the style attribute to | |
| : put literal text (eg, style definitions) into the output IFM file. | |
| KeyComments | |
| } | |
| while(<STDIN>) { | |
| if($o_preserve and /^:/) { | |
| print KEY; | |
| next; | |
| } elsif (/^:/) { | |
| next; | |
| } | |
| if(!$seen_title) { | |
| $seen_title = 1; | |
| if ($o_verbose) { | |
| print STDERR "Map title: $_"; | |
| if($o_preserve) { | |
| print STDERR "Preserving any source comments\n"; | |
| } | |
| } | |
| next; | |
| } | |
| tr: #*\n:.:c; | |
| if ($o_size > 1) { | |
| s/[.]([ #*.])/'.' . $1 x $o_size/eg; | |
| } | |
| if(tr:*:*:) { | |
| $seen_start = 1; | |
| } | |
| if(tr:#:#:) { | |
| $seen_hash = 1; | |
| } | |
| print KEY; | |
| } # while STDIN | |
| print KEY "OVERALL: style {}\n"; | |
| if ($seen_start) { | |
| print KEY '*' x $o_size . ": name {}\n"; | |
| print KEY '*' x $o_size . ": item {}\n"; | |
| print KEY '*' x $o_size . ": task {}\n"; | |
| print KEY '*' x $o_size . ": style {}\n"; | |
| } | |
| if ($seen_hash) { | |
| print KEY '#' x $o_size . ": name {}\n"; | |
| print KEY '#' x $o_size . ": item {}\n"; | |
| print KEY '*' x $o_size . ": task {}\n"; | |
| print KEY '#' x $o_size . ": style {}\n"; | |
| } | |
| close KEY; | |
| } # end &dokeytemplate | |
| ##################################################################### | |
| # Reads in an art file (specified or STDIN) and adds extra cells to | |
| # the map. If an outfile is specified, that is used for output. | |
| # Otherwise, if an artfile is specified, that will be rewritten. | |
| # Lastly, if the art file is from STDIN and no outfile given, STDOUT | |
| # will get the grown map. | |
| # The outfile will have already been opened, if being used. | |
| # If an art file was specified, and no outfile specified, and a key | |
| # file is found for it, that will be grown too. | |
| # Existing map features, including comments, will be preserved. | |
| sub dogrowth() { | |
| my $queue; | |
| my $key; | |
| my $line; | |
| my $new1; | |
| my $new2; | |
| my $key1; | |
| my $key2; | |
| my $keyroom; | |
| my $keywall; | |
| my $hold; | |
| my $lart; | |
| my $lkey; | |
| my $i; | |
| my @lines; | |
| my $seen_title; | |
| if(defined($o_file)) { | |
| if(!open(STDIN, "< $o_file")) { | |
| die "$id: cannot open $o_file for reading: $!\n"; | |
| } elsif($o_verbose) { | |
| print STDERR "Opened $o_file for input.\n"; | |
| } | |
| if(!defined($o_out)) { | |
| $queue = 1; # store output for printing after reopen | |
| $key = $o_file; | |
| $key =~ s/([.]art$|$)/.key/i; | |
| if(! -f $key) { | |
| $key = undef; | |
| } | |
| } | |
| } | |
| $line = 0; | |
| while(<STDIN>) { | |
| if(/^:/) { | |
| if($queue) { | |
| push(@lines, $_); | |
| } else { | |
| print; | |
| } | |
| next; | |
| } # if comment | |
| if(!$seen_title) { | |
| $seen_title = 1; | |
| if($queue) { | |
| push(@lines, $_); | |
| } else { | |
| print; | |
| } | |
| if ($o_verbose) { | |
| print STDERR "Map title: $_"; | |
| } | |
| next; | |
| } # if title | |
| if(/^\s*$/) { | |
| if($queue) { | |
| push(@lines, $_); | |
| } else { | |
| print; | |
| } | |
| } # if blank | |
| # Not comment, title, or blank. Must be map data. | |
| if(!defined($new1)) { | |
| # First time through. Figure out current size. | |
| $new1 = $_; | |
| $new1 =~ s/\s+$//; | |
| $new1 =~ tr:.:.:c; | |
| $hold = $new1; | |
| if($o_e) { | |
| $new1 .= '..' x $o_e; | |
| } | |
| if($o_w) { | |
| $new1 .= '..' x $o_w; | |
| } | |
| if($o_n or $o_s) { | |
| $new2 = $new1; | |
| $new2 =~ s/\.\./. /g; | |
| } | |
| $i = $o_n; | |
| while($i) { | |
| if($queue) { | |
| push(@lines, "$new1\n", "$new2\n"); | |
| } else { | |
| print "$new1\n$new2\n"; | |
| } | |
| $i --; | |
| } # while $i | |
| } # if !$new1 | |
| if($o_e) { | |
| chomp; | |
| if($line % 2) { | |
| # odd lines have rooms | |
| $_ = $_ . ' .' x $o_e . "\n"; | |
| } else { | |
| $_ = $_ . '..' x $o_e . "\n"; | |
| } | |
| } # if $o_e | |
| if($o_w) { | |
| if($line % 2) { | |
| # odd lines have rooms | |
| $_ = '. ' x $o_w . $_; | |
| } else { | |
| $_ = '..' x $o_w . $_; | |
| } | |
| } # if $o_w | |
| if($queue) { | |
| push(@lines, $_); | |
| } else { | |
| print; | |
| } | |
| $line ++; | |
| } # while STDIN | |
| $i = $o_s; | |
| while($i) { | |
| if($queue) { | |
| push(@lines, "$new2\n", "$new1\n"); | |
| } else { | |
| print "$new2\n$new1\n"; | |
| } | |
| $i --; | |
| } # while $i | |
| close STDIN; | |
| if($queue) { | |
| if(!open(ART, "> $o_file")) { | |
| die "$id: cannot open $o_file for writing: $!\n"; | |
| } elsif($o_verbose) { | |
| print STDERR "Opened $o_file for output.\n"; | |
| } | |
| print ART @lines; | |
| close ART; | |
| } # if $queue | |
| if($key) { | |
| if(!open(KEY, "< $key")) { | |
| die "$id: cannot open $key for reading: $!\n"; | |
| } elsif($o_verbose) { | |
| print STDERR "Opened $key for input.\n"; | |
| } | |
| undef(@lines); | |
| } else { | |
| if($o_verbose) { | |
| print STDERR "Not processing a key file.\n"; | |
| } | |
| return; | |
| } | |
| $line = 0; | |
| while(<KEY>) { | |
| if(/^:/) { | |
| push(@lines, $_); | |
| next; | |
| } # if comment | |
| if(/^\S+:\s*.+/) { | |
| # key explanation line | |
| next; | |
| } # if key data | |
| if(/^\s*$/) { | |
| push(@lines, $_); | |
| } # if blank | |
| # Not comment, key data, or blank. Must be map data. | |
| if(!defined($key1)) { | |
| # First time through. Figure out current size. | |
| # This is more complicated for a key file, since the rooms | |
| # may be wider than one character. | |
| $key1 = $_; | |
| $key1 =~ s/\s*$//; | |
| $hold =~ s/\s*$//; | |
| # Minus one for final dot. | |
| $lart = length($hold) - 1; | |
| $lkey = length($key1) - 1; | |
| if($lart != $lkey) { | |
| # Figure original number of rooms wide. | |
| $lart /= 2; | |
| # Divide by that and we get the key room size plus one (for the .) | |
| $lkey /= $lart; | |
| $lkey --; | |
| $keyroom = ' ' x $lkey; | |
| $keywall = '.' x $lkey; | |
| } else { | |
| # We still need to figure original number of rooms wide. | |
| $lart /= 2; | |
| $lkey = 1; | |
| $keyroom = ' '; | |
| $keywall = '.'; | |
| } | |
| if($o_verbose) { | |
| print STDERR "Original map is $lart rooms wide, " . | |
| "with $lkey chars per room.\n" | |
| } | |
| if($o_e) { | |
| $key1 .= ".$keywall" x $o_e; | |
| } | |
| if($o_w) { | |
| $key1 .= ".$keywall" x $o_w; | |
| } | |
| if($o_n or $o_s) { | |
| $key2 = ".$keyroom" x ($lart + $o_e + $o_w) . '.'; | |
| } | |
| $i = $o_n; | |
| while($i) { | |
| push(@lines, "$key1\n", "$key2\n"); | |
| $i --; | |
| } # while $i | |
| } # if !$new1 | |
| if($o_e) { | |
| chomp; | |
| if($line % 2) { | |
| # odd lines have rooms | |
| $_ = $_ . "$keyroom." x $o_e . "\n"; | |
| } else { | |
| $_ = $_ . "$keywall." x $o_e . "\n"; | |
| } | |
| } # if $o_e | |
| if($o_w) { | |
| if($line % 2) { | |
| # odd lines have rooms | |
| $_ = ".$keyroom" x $o_w . $_; | |
| } else { | |
| $_ = ".$keywall" x $o_w . $_; | |
| } | |
| } # if $o_w | |
| push(@lines, $_); | |
| $line ++; | |
| } # while KEY | |
| close KEY; | |
| $i = $o_s; | |
| while($i) { | |
| push(@lines, "$key2\n", "$key1\n"); | |
| $i --; | |
| } # while $i | |
| if(!open(KEY, "> $key")) { | |
| die "$id: cannot open $key for writing: $!\n"; | |
| } elsif($o_verbose) { | |
| print STDERR "Opened $key for output.\n"; | |
| } | |
| print KEY @lines; | |
| close KEY; | |
| } # end &dogrowth | |
| ##################################################################### | |
| # Run a demonstration. | |
| sub dodemo() { | |
| my $key; | |
| my $line; | |
| my @lines; | |
| if(!defined($o_out)) { | |
| if(!defined($o_file)) { | |
| die "$id: a file name is needed\n"; | |
| } | |
| if(!open(STDOUT, "> $o_file")) { | |
| die "$id: cannot open $o_file for output: $!\n"; | |
| } elsif ($o_verbose) { | |
| print STDERR "Using $o_file for output.\n"; | |
| } | |
| } else { | |
| $o_file = $o_out; | |
| $o_out = undef; | |
| } | |
| if(($o_rows < 4) or ($o_cols < 7)) { | |
| die "$id: The demo map needs to be at least 4x7.\n"; | |
| } | |
| if($o_verbose) { | |
| print STDERR "Creating a blank template.\n"; | |
| } | |
| dotemplate(); | |
| close STDOUT; | |
| if(!open(BLANK, "< $o_file")) { | |
| die "$id: cannot open $o_file for reading: $!\n"; | |
| } | |
| if($o_verbose) { | |
| print STDERR "Reading back the blank template.\n"; | |
| } | |
| $line = 0; | |
| $_ = <BLANK>; # map title | |
| push(@lines, "Demo Map\n"); | |
| while(<BLANK>) { | |
| if(/^[.]/) { | |
| # line 0 unchanged | |
| if($line == 1) { s{...............}{. . . .#.#. . .}; } | |
| if($line == 2) { s{...............}{.......v.^.....}; } | |
| if($line == 3) { s{...............}{. .#. .#.#.#>#.}; } | |
| if($line == 4) { s{...............}{../.\\...x|/....}; } | |
| if($line == 5) { s{...............}{.#. .#-#-#. . .}; } | |
| if($line == 6) { s{...............}{.....|../|\\....}; } | |
| if($line == 7) { s{...............}{. . .*.#.#.#<#.}; } | |
| # line 8 (and up) unchanged | |
| $line ++; | |
| } | |
| push(@lines, $_); | |
| } # while BLANK | |
| close BLANK; | |
| if(!open(MAP, "> $o_file")) { | |
| die "$id: cannot open $o_file for writing: $!\n"; | |
| } | |
| if($o_verbose) { | |
| print STDERR "Writing back the filled in template.\n"; | |
| } | |
| print MAP @lines; | |
| close MAP; | |
| undef(@lines); | |
| if($o_verbose) { | |
| print STDERR "Creating the key template.\n"; | |
| } | |
| dokeytemplate($o_file); | |
| $key = $o_file; | |
| $key =~ s/([.]art$|$)/.key/i; | |
| if(!open(KEYBLANK, "< $key")) { | |
| die "$id: cannot open $key for reading: $!\n"; | |
| } | |
| if($o_verbose) { | |
| print STDERR "Reading back the blank key template.\n"; | |
| } | |
| $line = 0; | |
| while(<KEYBLANK>) { | |
| if(/^[.]/) { | |
| # line 0 unchanged | |
| if($line == 1) { s/#+/'H'x$o_size/e; s/#+/'T'x$o_size/e; } | |
| # line 2 unchanged | |
| if($line == 3) { s/#+/'C'x$o_size/e; s/#+(\.\s)/'c'x$o_size.$1/e; } | |
| # line 4 unchanged | |
| if($line == 5) { s/#+/'A'x$o_size/e; s/#+(\.\s)/'@'x$o_size.$1/e; } | |
| # line 6 unchanged | |
| if($line == 7) { s/#+\.#+\.#+\.#+/'1'x$o_size.'.'. | |
| '2'x$o_size.'.'. | |
| '3'x$o_size.'.'. | |
| '|'x$o_size/e; } | |
| # map lines 8 (and up) unchanged | |
| $line ++; | |
| } | |
| if(/^OVERALL: style/) { | |
| s/\{\s*\}/{room_colour = "gray60" in style Dark; }/; | |
| } | |
| if(/^([*]+: name)/) { | |
| $_ = "$1 {Cell} item{paperclip} task{bend paperclip;pick lock}\n"; | |
| } elsif(/^[*]/) { | |
| next; | |
| } | |
| push(@lines, $_); | |
| } # while KEYBLANK | |
| close KEYBLANK; | |
| push(@lines, 'H'x$o_size . ": name{Hole} item{flashlight} style {Dark}\n"); | |
| push(@lines, 'H'x$o_size . ": task{search;take flashlight;turn on light}\n"); | |
| push(@lines, 'T'x$o_size . ": name{Tree} item{apple}\n"); | |
| push(@lines, 'T'x$o_size . ": task{eat apple;open cellphone}\n"); | |
| push(@lines, 'T'x$o_size . ": task{put battery in cellphone}\n"); | |
| push(@lines, 'T'x$o_size . ": task{call for help}\n"); | |
| push(@lines, 'C'x$o_size . ": name{Closet} style{Dark}\n"); | |
| push(@lines, 'c'x$o_size . ": name{Cupboard} item{cellphone battery}\n"); | |
| push(@lines, 'A'x$o_size . ": name{Alcove} style{Dark} item{cellphone}\n"); | |
| push(@lines, '@'x$o_size . ": name{Round room}\n"); | |
| push(@lines, '1'x$o_size . ": name{Exam room 1} item {bed,gloves}\n"); | |
| push(@lines, '2'x$o_size . ": name{Exam room 2} item {bed} item{towel}\n"); | |
| push(@lines, '3'x$o_size . ": name{Exam room 3} item {bed,broken sink}\n"); | |
| push(@lines, '|'x$o_size . ": name{Large sewer pipe} item {filthy rags}\n"); | |
| if(!open(KEYCUT, "> $key")) { | |
| die "$id: cannot open $key for writing: $!\n"; | |
| } | |
| if($o_verbose) { | |
| print STDERR "Writing back the filled in key template.\n"; | |
| } | |
| print KEYCUT @lines; | |
| close KEYCUT; | |
| undef(@lines); | |
| } # end &dodemo | |
| ##################################################################### | |
| # Prints a usage message. If the message is from a non-error condition | |
| # it goes to STDOUT. Errors go to STDERR. | |
| # Exits the program. | |
| sub usage($) { | |
| my $exit = shift; | |
| if($exit) { | |
| # Exiting with error, show brief usage | |
| print STDERR "$0: use '$id --help' for usage summary.\n"; | |
| exit $exit; | |
| } | |
| print <<"UsageSummary"; | |
| $0: usage '$id {mode-option} [additional options] [infile]' | |
| Mode options: | |
| -t --template print an art template | |
| -k --key make a key template from an art template | |
| -g --grow SIZE grow template(s) by SIZE (eg "5" or "N2E1S4W7") | |
| -m --makemap make the IFM map | |
| -d --demo make a demo map | |
| Additional options: | |
| -i --include include instruction comments in templates | |
| -p --preserve preserve comments in derived files | |
| -u --updown use up-down instead of in-out (makemap only) | |
| -x --rowsxcols R,C use R rows and C cols for template (template only) | |
| -s --size NUM use NUM as room width (for key template mode only) | |
| -o --out OUT use OUT for output file (unused for key mode) | |
| -v --verbose verbose output to STDERR | |
| --version print version info | |
| --help print this help | |
| STDIN and STDOUT are used when input or output files not specified, except | |
| for --key mode. The demo option will run asciimapper in template mode, | |
| then key template mode, and then makemap mode. Options for each honored. | |
| UsageSummary | |
| exit $exit; | |
| } # end &usage | |
| __END__ | |
| =pod | |
| =head1 NAME | |
| asciimapper - make IFM maps from ascii art | |
| =head1 SYNOPSIS | |
| asciimapper {mode-option} [additional options] [infile] | |
| =head1 DESCRIPTION | |
| A simple tool to create IFM (interactive fiction mapper) maps from | |
| ASCII art maps. Intended to be an easy way to draw a map and have it | |
| converted to IFM format. | |
| There are four principle modes of operation each effected by different | |
| options. | |
| The main mode is the map making mode. In this mode an art map file | |
| (suffix I<.art>) will be read. If a corresponding key file (suffix | |
| I<.key>) is found, it will be read as well. Then a map (suffix I<.ifm>) | |
| file will be created. | |
| There is a template mode which will create a blank art template. | |
| There is a key template mode which will convert an art file, blank or | |
| filled in, to a key template. Using a filled in art file makes matching | |
| rooms to squares easier. | |
| There is a grow template mode which will add extra squares to an | |
| existing art template (and key template if found), in case the original | |
| size was inadequate. | |
| While C<asciimapper> can produce a large variety of maps, it does not | |
| come close to the full range of IFM. In some cases it may be necessary | |
| to edit the IFM file created to achieve the desired map. See the | |
| L<"LIMITATIONS"> section for details. | |
| This was written to work with IFM 5.1. Other versions may or may not work. | |
| =head1 EXAMPLES | |
| asciimapper -t -x 9x4 -i -o template.art | |
| This will create a new, blank art template nine squares tall and four | |
| squares wide, including instruction comments, in "F<template.art>". | |
| asciimapper -k -s 2 -i template.art | |
| This will create a "F<template.key>" file from the art template, and | |
| include instruction comments in it. The squares for the keys will be | |
| two characters wide. | |
| asciimapper -g 4 template.art | |
| This will grow the art template file four squares in each direction. | |
| If a "F<template.key>" file is found, it too, will be so enlarged. | |
| asciimapper -g N2S2W5 template.art | |
| This will grow the art template file two squares in each of the | |
| north and south directions, and five squares on the west side. | |
| If a "F<template.key>" file is found, it too, will be so enlarged. | |
| asciimapper -m -p template.art | |
| This will read the art template, and a corresponding key template if | |
| found, and create a "F<template.ifm>" file with IFM map data. Any comments | |
| in either of the source files will be preserved, as comments, in the | |
| output. | |
| asciimapper -d demo.art | |
| This will run C<asciimapper> through template creation, key template | |
| creation, and map generation modes. It creates a small (4x6) map to | |
| demonstrate all features. All options for the modes exercised will be | |
| honored -- except a request to create a too-small map template. | |
| =head1 USAGE | |
| Options must be specified individually. Order is not important, but | |
| options that take a parameter must be paired with that parameter. | |
| =head2 Mode | |
| =over 4 | |
| =item * | |
| -t --template | |
| Generate an art template. | |
| =item * | |
| -k --key | |
| Generate a key template from an art template. | |
| The key file must be named after the input template. This mode will | |
| not use -o (--out) or STDOUT for output, and requires an I<infile> | |
| to be specified. | |
| =item * | |
| -g --grow SIZE | |
| Enlarge an art template, and if found, a corresponding key template. | |
| The I<SIZE> can be a bare number to grow the same amount on all edges, | |
| or a list of one letter directions (C<N>, C<E>, C<S>, C<W>) followed | |
| by a number, eg C<N3,S2,W4> to grow by three on the north side (top), | |
| two on the south (bottom), and four on the west (left). Commas are | |
| optional in the direction list. | |
| =item * | |
| -m --makemap | |
| This will make an IFM format map from an art file. The art file | |
| specifies the locations of all the rooms and the connections between | |
| them. The key file, if available, will be used to name rooms, place | |
| items, list tasks, and specify styles. On largish maps this may | |
| produce "deep recursion" warning messages. | |
| =item * | |
| -d --demo | |
| Not a true mode, this will generate a demo ASCII art map, from | |
| which a key file will be made and filled in, then an IFM map will be | |
| generated. Template mode, key mode, and generation mode options will | |
| be honored. | |
| =back | |
| =head2 Additional options | |
| =over 4 | |
| =item * | |
| -i --include | |
| Include, as comments, instructions in template files. | |
| =item * | |
| -p --preserve | |
| Preserve comments in template files in derived files. Art file | |
| comments will be copied to key templates. Art and key file | |
| comments will be copied to IFM output, with the comment character | |
| appropriately changed. | |
| =item * | |
| -u --updown | |
| When making maps, use "go up" and "go down" in place of "go in" and | |
| "go out". | |
| =item * | |
| -x --rowsxcols RxC | |
| When making an art template, make it I<R> rows and I<C> columns. The | |
| two values can be separated with a comma or an C<x>. | |
| =item * | |
| -s --size NUM | |
| When making a key template, use I<NUM> characters for each key | |
| square. Art templates always use one character squares, but a map | |
| with a lot of rooms might need more keys than one character would | |
| allow. | |
| =item * | |
| -o --out OUT | |
| Use I<OUT> as the output file. Different modes use this differently. | |
| For art template creation, this is used instead of STDOUT. | |
| For key template creation, this is not used. | |
| For grow mode, a new map file, I<OUT>, will be created instead of | |
| overwriting the existing map or using STDOUT. | |
| For map making mode, I<OUT> will be the IFM file. | |
| For the demo, this will be preferred over I<infile> for naming the | |
| output. | |
| =item * | |
| -v --verbose | |
| Be verbose about what is happening. In the map making mode, this will | |
| produce a lot of output. All verbose comments go to STDERR. | |
| =back | |
| =head2 Special options | |
| =over 4 | |
| =item * | |
| --version | |
| Print version information and exit. | |
| =item * | |
| --help | |
| Print a usage summary and exit. | |
| =back | |
| Except for key mode, C<asciimapper> tries to be good about accepting | |
| STDIN and STDOUT as files. | |
| =head1 ART SYNTAX | |
| The art file starts out with a map title on the first non-comment line. | |
| Comment lines begin with a colon (:), comments must start at the | |
| begining of the line. After the title, blank lines are ignored. | |
| The map section is a block of walls and connectors and room spaces. | |
| Rooms are denoted with either an asterisk (C<*>) for the start room or | |
| hash marks (C<#>) for all other rooms. There must be exactly one | |
| start room. Empty spaces on the map are denoted with space characters. | |
| Walls are periods (C<.>), indicating no connection there. Hyphens and | |
| vertical bars (C<-> and C<|>) are used to indicate west-east and | |
| north-south connections. Slash and backslash (C</> and C<\>) are used | |
| for the diagonal directions. Crossing diagonal exits can be indicated | |
| with an C<X> (case insignificant). | |
| To connect with the special directions of in-out (or up-down) the | |
| angle backets (aka less-than and greater-than, C<E<lt>> and C<E<gt>>) can be | |
| used to point the in (or up) direction to a room laid out to the west | |
| or east. While a caret (C<^>) or a C<V> (case insignificant) can be | |
| used to point the in (or up) direction to a room laid out to the north | |
| or the south. | |
| Except for a map that will be grown, the outer edges must all be periods. | |
| After growth, the outer edges must all be periods. | |
| All map lines must be the same length. | |
| =head1 KEY SYNTAX | |
| The key file is initially very much like the art file, but all edges | |
| must be periods. The spaces are filled with space characters, hashs | |
| and one asterisk room -- at least when a key template is built from | |
| a filled in art template. | |
| Comments have the same format, and blank lines are ignored. There is | |
| no map title line in the key file. There are key explanation lines, | |
| which may be before, after, or interspersed with the map data. | |
| An explanation line starts with the key name (which cannot contain | |
| whitespace, periods, or colons) and is followed by a colon, and a list | |
| of attributes. Attributes can have spaces around them and consist of | |
| an attribute type and a brace (C<{> and C<}>) enclosed block with the | |
| value. | |
| Key names can be used for multiple rooms, and key names can be used | |
| on multiple explanation lines. When there are multiple rooms with | |
| the same key, they will have the same items, tasks, etc. When there | |
| are multiple explanation lines, the attributes will be merged. | |
| The attribute types are: | |
| =over 4 | |
| =item * | |
| name | |
| Specifies the room name. If used multiple times for a key, the last | |
| case is used. | |
| =item * | |
| item | |
| Specifies one or more items in a room. If used multiple times for a | |
| key, they are joined with commas (C<,>). Use commas within the value | |
| field to separate items, too. | |
| =item * | |
| task | |
| Specifies one or more tasks in a room. If used multiple times for a | |
| key, they are joined with semicolons (C<;>). Use semicolons within the | |
| value field to separate tasks, too. | |
| =item * | |
| style | |
| Specifies a room style. If used multiple times for a key, the last | |
| case is used. There are not predefined styles, but the special key | |
| C<OVERALL> can be used to define styles. | |
| =back | |
| Key names are not fixed width except for the convience of the map staying | |
| roughly similar looking. The special key C<OVERALL> can be used with | |
| the style attribute to include literal IFM commands. These will all appear | |
| in the IFM file before the first room is mentioned. | |
| All map lines must have the same number of rooms, and the size of the | |
| map must match the art file. | |
| =head1 MAPPING PROCESS | |
| When generating the IFM map, C<asciimapper> will first read in one or | |
| both template files, storing them in memory and noting the location of | |
| the start room. | |
| It will then recursively wander through the map, starting from the | |
| specified place, describing the path and rooms along the way. When a | |
| path reaches a previously seen room, the link to it is noted. When a | |
| room is reached with no previously used exits, C<asciimapper> backtracks | |
| (returns from the recursed function). This mimicks the way IFM is meant | |
| to be used. | |
| All rooms must be connected for C<asciimapper> to find them. The | |
| recursive nature of the wander can produce deep recursion warnings from | |
| Perl when there are a lot of possible paths. | |
| =head1 LIMITATIONS | |
| Here is a list of some significant limitations in C<asciimapper> not | |
| shared by IFM: | |
| Maps can contain ins and outs or ups and downs, but not both. In either | |
| case, in/out or up/down links cannot be placed in a diagonal direction. | |
| Arbitrary command directions are not possible. | |
| While IFM can easily express room links that are not straight, | |
| C<asciimapper> has no way to do so. This prevents exits from pointing | |
| back into the same room. | |
| There is no way to stylize links, such as oneway paths or directions | |
| that are initially blocked. | |
| Placeholder exits to unexplored areas do not work. | |
| Rooms cannot be anything but the standard shape. | |
| An output file can only contain a single map. | |
| Many IFM commands cannot be specified. | |
| =head1 REVISION HISTORY | |
| Version 1.0 was shown to Glenn Hutchings, author of IFM, but had a | |
| much more awkward method of specifying room contents. 23 June 2005 | |
| Version 1.0.1 now uses key files, has a grow template mode, separates | |
| preserving comments from including them, fixes several bugs (most | |
| notably the proper use of the go clause), includes documentation. | |
| 28 June 2005 | |
| =head1 SEE ALSO | |
| C<ifm> -- interactive fiction mapper | |
| Get IFM: http://www.ifarchive.org/indexes/if-archiveXmapping-tools.html | |
| =head1 PREREQUISITES | |
| This was written against IFM version 5.1, and using Perl 5.6.1. The | |
| Perl modules C<strict> and <vars> are used. | |
| =head1 AUTHOR AND COPYRIGHT | |
| Copyright 2005 by Eli the Bearded / Benjamin Elijah Griffin, | |
| E<lt>asciimapper@eli.users.panix.comE<gt>. | |
| Released under the same license(s) as Perl. | |
| =head1 BUGS | |
| See the L<"LIMITATIONS"> section above. Anything not covered that that | |
| seems wrong might be a bug, and should be reported to the author. | |
| =cut | |
Xet Storage Details
- Size:
- 46.5 kB
- Xet hash:
- dbe524e4bd3c760eef270e55f40b2603b9eead2283272de5beeeb6a507506698
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.