Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Puzzle

by Anonymous Monk
on Sep 17, 2003 at 19:47 UTC ( [id://292240]=sourcecode: print w/replies, xml ) Need Help??
Category: Fun Stuff
Author/Contact Info
Description: A simple puzzle game.
#!/usr/bin/perl -w
srand;
$a1 = 1;
$a2 = 2;
$a3 = 3;
$b1 = 4;
$b2 = 5;
$b3 = 6;
$c1 = 7;
$c2 = 8;
$c3 = "";
$win = 0;
print "\n" x 100;
draw();
print "Your goal is to match this pattern.\n";
print "You may move any square that is next to the blank spot.\n";
print "Press enter when ready. ";
$pause = <STDIN>;
randomize();
{
%squares = ("a1", $a1, "a2", $a2, "a3", $a3, "b1", $b1, "b2", $b2, "b3
+", $b3, "c1", $c1, "c2", $c2, "c3", $c3);
draw();
win();
(exit) if ($win eq 1);
{
  $bad_move = 0;
  print "Which square will you move? ";
  chomp ($response = <STDIN>);
  $move = $squares{$response};
  check_move();
  (redo) if ($bad_move eq 1);
}
fill_blank();
#$squares{$response} = "";
make_blank();
redo;
}

#checks to make sure a proper piece is selected
sub check_move() {
  foreach $i (keys %squares) {
    if ($squares{$i} eq "") {
      if ($response eq 'a1') {
        if ($i ne 'a2' && $i ne 'b1') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'a2') {
        if ($i ne 'a1' && $i ne 'a3' && $i ne 'b2') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'a3') {
        if ($i ne 'a2' && $i ne 'b3') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'b1') {
        if ($i ne 'b2' && $i ne 'a1' && $i ne 'c1') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'b2') {
        if ($i ne 'b1' && $i ne 'b3' && $i ne 'a2' && $i ne 'c2') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'b3') {
        if ($i ne 'b2' && $i ne 'a3' && $i ne 'c3') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'c1') {
        if ($i ne 'c2' && $i ne 'b1') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'c2') {
        if ($i ne 'c1' && $i ne 'c3' && $i ne 'b2') {
          ($bad_move = 1);
        }
      } elsif ($response eq 'c3') {
        if ($i ne 'c2' && $i ne 'b3') {
          ($bad_move = 1);
        }
      }
    }
  }
}
# makes the moved piece blank
sub make_blank() {
  $i = $response;
  ($a1 = "") if ($i eq 'a1');
  ($a2 = "") if ($i eq 'a2');
  ($a3 = "") if ($i eq 'a3');
  ($b1 = "") if ($i eq 'b1');
  ($b2 = "") if ($i eq 'b2');
  ($b3 = "") if ($i eq 'b3');
  ($c1 = "") if ($i eq 'c1');
  ($c2 = "") if ($i eq 'c2');
  ($c3 = "") if ($i eq 'c3');
}

#  randomizes order of pieces for initial game play
sub randomize() {
  $b2 = "";
  @numbers = qw(1 2 3 4 5 6 7 8);

  $new = rand($#numbers);
  $a1 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $a2 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $a3 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $b1 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $b3 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $c1 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $c2 = $numbers[$new];
  splice(@numbers, $new, 1);

  $new = rand($#numbers);
  $c3 = $numbers[$new];
  splice(@numbers, $new, 1);

}

# sets number to blank position
sub fill_blank() {
  foreach $i (keys %squares) {
    if ($squares{$i} eq "") {
      ($a1 = $move) if ($i eq 'a1');
      ($a2 = $move) if ($i eq 'a2');
      ($a3 = $move) if ($i eq 'a3');
      ($b1 = $move) if ($i eq 'b1');
      ($b2 = $move) if ($i eq 'b2');
      ($b3 = $move) if ($i eq 'b3');
      ($c1 = $move) if ($i eq 'c1');
      ($c2 = $move) if ($i eq 'c2');
      ($c3 = $move) if ($i eq 'c3');

#      #delete $squares{$i};
#      $squares{$i} = $move;
#      print $move;
    }
  }
}

# checks for winning sequence
sub win() {
  if ($a1 eq 1 && $a2 eq 2 && $a3 eq 3 && $b1 eq 4 && $b2 eq 5 && $b3 
+eq 6 && $c1 eq 7 && $c2 eq 8) {
    print "YOU WIN!\n";
    $win = 1;
  }
}
# draws board
sub draw() {
  print "\n" x 100;
  printf "                                    1   2   3\n";
  printf "                                  -------------\n";
  printf "                                a | %1s | %1s | %1s |\n", $a
+1, $a2, $a3;
  printf "                                  -------------\n";
  printf "                                b | %1s | %1s | %1s |\n", $b
+1, $b2, $b3;
  printf "                                  -------------\n";
  printf "                                c | %1s | %1s | %1s |\n", $c
+1, $c2, $c3;
  printf "                                  -------------\n";
  print "\n" x13;
}

edit by thelenm: added code tags

Replies are listed 'Best First'.
Re: Puzzle
by davido (Cardinal) on Sep 18, 2003 at 08:13 UTC
    Thanks for posting the game. Honestly, I've never had the patience to work through one of those slider games. But it's fun to see the game presented in Perl.

    I did want to contribute a few suggestions that might both improve the game as well as your code overall. You'll find that to implement my suggestions probably means a complete rewrite. But if you take a few of these suggestions with you to your next project you'll end up with cleaner, more robust, easier to handle code.

    • use strict; If it doesn't execute under strictures you should dig into understanding why. Strict will force you to declare your variables before you use them. It will force other WISE practices as well, and you'll find that development time and bug tracking will become much easier. 'strict' is your friend.
    • Pay attention to warnings. The code isn't done until you've squished every last one. You clear the screen by scrolling 100 '\n' (newlines). But that masks the fact that just starting the program up gives you about ten warnings. Some of them are significant.
    • Use an array of arrays (a two-dimensional array) instead of $a1, $a2, $b1, $c3, etc. It's cleaner, easier to maintain, easier to grow, better all around, less kludgy, and less confusing. It's simply the better way of handling a two-dimensional entity. Since this particular program uses rows A through C, and columns 1 through 3, you might consider a hash of arrays (keys being A through C, for the rows, and columns being 0 through 2 internally). See perldoc perllol and perldoc perlref for info.
    • Input parsing should be more robust. Entering 1a should be just as good as entering a1. Why not? And 1A should be acceptible too.
    • Your validity checking isn't working properly. Somehow undefs are still slipping through, and causing warnings to be generated right in the middle of the printing of the grid, thus distorting and wrecking the grid. If a user enters an empty cell, you could use the "if (defined....){" construct to check against that possibility. And if a user enters a cell that is immobile (blocked by other tiles) that shouldn't cause the program to spit out a warning right in the middle of the grid.
    • Don't use seven or eight print statements in a row when a HERE document would be more efficient, cleaner to write, easier to read in the source code, etc. I understand that you wanted to use printf. But there's still probably a better way, using HERE docs than having six or eight printf's in a row.
    • Pass arguments into your subroutines, and return values from them. Don't let your subs rely on global variables. Subs should rely on what they find in their own local namespace. If you think of a subroutine as a black box that does a particular job, you don't want it to do its job based on all sorts of unnamed things going on in the world. One good way of writing obfuscated code is to let your subs deal entirely with global variables rather than passing data in through the more traditional parameter list method. See perldoc perlsub. Also see perldoc perlref to understand passing hashes, arrays of arrays, and other more complex datastructures into subs.
    • Rather than use nine nested if/elsif statements to probe a hash, use the hash itself as the "switch" handler. Give the hash subroutines as values. Or use one of the other cleaner 'switch' idioms described in perldoc perlsyn.
    • Declare your variables (strict will force you to do this). Declare the variables that subs use internally, within the sub. Always try to limit scope to where it is applicable.

    There are probably other things to discuss, but these are the ones that jumped out at me first. Again, I'm not trying to criticize your code. I think it's a fun implementation of a well known game. I just offer these tips so that you might have an easier time of making a more robust program next time around. I'm no expert myself. But as someone who was at the level of your code not too long ago, I just wanted to help point you in the right direction in tackling the learning curve.

    Keep up the good work. As long as we're all learning all of our efforts are worthwhile. Keep having fun with it.

    Dave

    "If I had my life to do over again, I'd be a plumber." -- Albert Einstein

      Thanks for the great advice.
Re: Puzzle (rewrite of posted code)
by dragonchild (Archbishop) on Sep 18, 2003 at 14:44 UTC
    Speaking of that rewrite ... (This compiles in 5.6.0 and higher, due to use warnings; ... replace that with the -w switch if in 5.0x.)

    The major differences are:

    • This compiles under strict and generates no warnings.
    • I don't have a variable for each square. This allows for any size board. (Though, I did not implement that feature. I left it as an exercise for the reader.)
    • I calculated the neighbors instead of hard-coding them, making the check for a bad move much simpler (and verifiable).
    • I used array slices, map, and grep to deal with the board array.
    • I added the ability to type 'quit' to leave the game (instead of making the user type Ctrl-C).
    • I scoped all the variables, meaning that changes in one place aren't (necessarily) going to screw with another place.
    • This code ended up being object-oriented (even though I don't explicitly use any objects). I'll leave that conversion also as an exercise for the reader.

    Looks like you got your homework cut out for you, AnonyMonk! Good original posting. Your code did satisfy the First Criterion - it worked. The Second Criterion is much harder - making it maintainable. Good luck!

    If you have any questions about what I did, please let me know. I deliberately didn't comment my code so that you'd have to dig around in the literature to understand the more advanced features I used.

    ------
    We are the carpenters and bricklayers of the Information Age.

    The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://292240]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2024-04-19 20:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found