Sokoban solution rotator in Perl This is a project I always meant to do, but now that I've done it, I am wondering if/how I can do it better. I've made some fixes and done general testing on the parsing to make sure it works, but I sense there is more to do that could be very instructive to me in terms of organizing my code and thoughts.

This came about because I played several Sokoban games and wrote solutions only to notice that many levels were rotations of others, so I wanted to save time. (There are Sokoban solvers out there, but I wanted to provide explanations with the solutions.)

The code below takes a text file (it need only be one line - the comments provide one) and user-suggested rotations (argument 1 on the command line) and changes all directions(2U, 3L, 4D, 5R) to how the user wants the board rotated.

Some of it seems a bit brute force, though, especially the for loop and the regular expression (I've only recently moved beyond ending with just /g or /gi). In particular, I am wondering whether I should use an array instead, especially since I may want an option to convert the full words "up"/"right"/"left"/"down" to their rotational equivalents.

Any suggestions big or small are appreciated...whether it's technical or code organization.

This seems like the sort of exercise that might help me learn other languages I'm not familiar with, so I wanted to see how things went with a language I was okay at, first.

Thanks to all who are able to contribute, and I hope my example is somewhat interesting.

################################
#sokoban solution rotator
################################
#sok.txt has lines like so: Your way to the end: 2U 4R 3D 5L 2u 4r 3d 5l.
#the command line argument is any combination of l r h u d v (d and v are equivalent to u, vertical flips. L/R rotate left/right, h = horizontal flip)
#

use strict;
use warnings;

my $file = "sok.txt";
my $dir;
my $anyDif; # checks for differences once we've parsed rotations

# this hash transforms a move to a different direction. By default each move points to itself.
my %rotate = ( 'u' =&gt; 'u',
  'r' =&gt; 'r',
  'd' =&gt; 'd',
  'l' =&gt; 'l');

if (!defined($ARGV[0]))
{
  die ("You need a command line argument (no spaces) to use rotations: R, L, H and V/U/D rotate right, left, horizontally and vertically.");
}

my @adjust = split(//, lc($ARGV[0]));

for $dir (@adjust)
{
  if ($dir eq 'r') # rotate right
  {
    ($rotate{'r'}, $rotate{'d'}, $rotate{'l'}, $rotate{'u'}) = ($rotate{'u'}, $rotate{'r'}, $rotate{'d'}, $rotate{'l'});
    next;
  };
  if ($dir eq 'l') # rotate left
  {
    ($rotate{'l'}, $rotate{'u'}, $rotate{'r'}, $rotate{'d'}) = ($rotate{'u'}, $rotate{'r'}, $rotate{'d'}, $rotate{'l'});
    next;
  };
  if (($dir eq 'v') || ($dir eq 'u') || ($dir eq 'd')) # vertical flip
  {
    ($rotate{'d'}, $rotate{'r'}, $rotate{'u'}, $rotate{'l'}) = ($rotate{'u'}, $rotate{'r'}, $rotate{'d'}, $rotate{'l'});
    next;
  };
  if ($dir eq 'h') # horizontal flip
  {
    ($rotate{'u'}, $rotate{'l'}, $rotate{'d'}, $rotate{'r'}) = ($rotate{'u'}, $rotate{'r'}, $rotate{'d'}, $rotate{'l'});
    next;
  };
  die ("I didn't recognize $dir. RLHVUD (case insensitive) are the only directions I do.");
}

for $dir (sort keys %rotate)
{
  #print "$dir becomes $rotate{$dir}\n";
  $anyDif += ($dir ne $rotate{$dir});
}

#print "Rotation: URDL =&gt; $rotate{'u'}$rotate{'r'}$rotate{'d'}$rotate{'l'}\n";

if (!$anyDif) { die("The rotations you requested don't change the puzzle's orientation."); }

open(A, "sok.txt") || die ("No sok.txt.");

while (my $line = &lt;A&gt;)
{
  print "Orig: $line";
  print "NEW:: ";
  $line =~ s/\b([0-9\*]*)([URDL])\b/$1 . newdir($2)/gei; # I included * with numbers because it means "push to the end"
  chomp($line);
  print "$line\n"; #this is to avoid orig/new appearing on the same line at the end of a file.
}
close(A);

##################################subroutine

sub newdir
{
  if ($_[0] eq lc($_[0]))
  {
    return $rotate{$_[0]};
  }
  return uc($rotate{lc($_[0])});
}
Please check for additional comments in the code,

use strict;
use warnings;

# no need to die() when open()
use autodie;

# this hash transforms a move to a different direction. By default each move points to itself.
my %rotate = (
  'u' =&gt; 'u',
  'r' =&gt; 'r',
  'd' =&gt; 'd',
  'l' =&gt; 'l'
);

if (!defined($ARGV[0]))
{
  die ("You need a command line argument (no spaces) to use rotations: R, L, H and V/U/D rotate right, left, horizontally and vertically.");
}

my @adjust = split(//, lc($ARGV[0]));
#/(fix stackexchange code coloring)

for my $dir (@adjust) {
  my @keys;

  # rotate right
  if ($dir eq 'r') { @keys = qw(r d l u); }
  # rotate left
  elsif ($dir eq 'l') { @keys = qw(l u r d); }
  # vertical flip
  elsif ($dir eq 'v' or $dir eq 'u' or $dir eq 'd') { @keys = qw(d r u l); }
  # horizontal flip
  elsif ($dir eq 'h')  { @keys = qw(u l d r); }
  else {
    die ("I didn't recognize $dir. RLHVUD (case insensitive) are the only directions I do.");
  }

  # check "hash slice"  
  @rotate{@keys} = @rotate{qw(u r d l)};
}

my $anyDif; # checks for differences once we've parsed rotations
for my $dir (keys %rotate) {
  #print "$dir becomes $rotate{$dir}\n";
  $anyDif += ($dir ne $rotate{$dir});
}

#print "Rotation: URDL =&gt; $rotate{'u'}$rotate{'r'}$rotate{'d'}$rotate{'l'}\n";

if (!$anyDif) { die("The rotations you requested don't change the puzzle's orientation."); }

# check "three argument open"
open(my $A, "&lt;", "sok.txt");

while (my $line = &lt;$A&gt;) {
  print "Orig: $line";
  print "NEW:: ";
  $line =~ s/\b([0-9\*]*)([URDL])\b/$1 . newdir($2)/gei; # I included * with numbers because it means "push to the end"
  chomp($line);
  print "$line\n"; #this is to avoid orig/new appearing on the same line at the end of a file.
}
close($A);

##################################subroutine

sub newdir {
  my ($v) = @_;
  my $lc = lc($v);

  # check "ternary operator"
  return ($v eq $lc) ? $rotate{$v} : uc($rotate{$lc});
}