Code Golf: Lasers
Solution 1:
Perl, 166 160 characters
Perl, 251 248 246 222 214 208 203 201 193 190 180 176 173 170 166 --> 160 chars.
Solution had 166 strokes when this contest ended, but A. Rex has found a couple ways to shave off 6 more characters:
s!.!$t{$s++}=$&!ge,$s=$r+=99for<>;%d='>.^1<2v3'=~/./g;($r)=grep$d|=$d{$t{$_}},%t;
{$_=$t{$r+=(1,-99,-1,99)[$d^=3*/\\/+m</>]};/[\/\\ ]/&&redo}die/x/?true:false,$/
The first line loads the input into %t
, a table of the board where $t{99*i+j}
holds the character at row i,column j. Then,
%d=split//,'>.^1<2v3' ; ($r)=grep{$d|=$d{$t{$_}}}%t
it searches the elements of %t
for a character that matches > ^ <
or v
, and simultaneously sets $d
to a value between 0 and 3 that indicates the initial direction of the laser beam.
At the beginning of each iteration in the main loop, we update $d
if the beam is currently on a mirror. XOR'ing by 3 gives the correct behavior for a \
mirror and XOR'ing by 1 gives the correct behavior for a /
mirror.
$d^=3*/\\/+m</>
Next, the current position $r
is updated accoring to the current direction.
$r+=(1,-99,-1,99)[$d] ; $_ = $t{$r}
We assign the character at the current position to $_
to make convenient use of the match operators.
/[\/\\ ]/ && redo
Continue if we are on a blank space or a mirror character. Otherwise we terminate true
if we are on the target ($_ =~ /x/
) and false
otherwise.
Limitation: may not work on problems with more than 99 columns. This limitation could be removed at the expense of 3 more characters,
Solution 2:
Perl, 177 Characters
The first linebreak can be removed; the other two are mandatory.
$/=%d=split//,' >/^\v';$_=<>;$s='#';{
y/v<^/>v</?do{my$o;$o.="
"while s/.$/$o.=$&,""/meg;y'/\\'\/'for$o,$s;$_=$o}:/>x/?die"true
":/>#/?die"false
":s/>(.)/$s$d{$1}/?$s=$1:1;redo}
Explanation:
$/ = %d = (' ' => '>', '/' => '^', '\\' => 'v');
If a right-moving beam runs into an {empty space, up-angled mirror, down-angled mirror} it becomes a {right-moving beam, up-moving beam, down-moving beam}. Initialize $/
along the way -- fortunately "6" is not a valid input char.
$_ = <>;
Read the board into $_
.
$s="#";
$s
is the symbol of whatever the beam is sitting on top of now. Since the laser emitter is to be treated like a wall, set this to be a wall to begin with.
if (tr/v<^/>v</) {
my $o;
$o .= "\n" while s/.$/$o .= $&, ""/meg;
tr,/\\,\\/, for $o, $s;
$_ = $o;
}
If the laser beam is pointing any way except right, rotate its symbol, and then rotate the whole board in place (also rotating the symbols for the mirrors). It's a 90 degree left rotation, accomplished effectively by reversing the rows while transposing rows and columns, in a slightly fiendish s///e
with side effects. In the golfed code, the tr is written in the form y'''
which allows me to skip backslashing one backslash.
die "true\n" if />x/; die "false\n" if />#/;
Terminate with the right message if we hit the target or a wall.
$s = $1 if s/>(.)/$s$d{$1}/;
If there's an empty space in front of the laser, move forward. If there's a mirror in front of the laser, move forward and rotate the beam. In either case, put the "saved symbol" back into the old beam location, and put the thing we just overwrote into the saved symbol.
redo;
Repeat until termination. {...;redo}
is two characters less than for(;;){...}
and three less than while(1){...}
.