http://qs321.pair.com?node_id=951764


in reply to ASCII Battleship Program

perlStuck:

Very fun! For an early effort, it's pretty decent.

Anyway, I'm stuck on a conference call as the $day_job is doing some software installs and may need to ask questions. So to amuse myself, I played the game, and then started tuning up the code.

The first thing I noticed was this bit:

given ($let) { when (/A/i) { $let = 0; } when (/B/i) { $let = 1; } when (/C/i) { $let = 2; } when (/D/i) { $let = 3; } when (/E/i) { $let = 4; } when (/F/i) { $let = 5; } when (/G/i) { $let = 6; } when (/H/i) { $let = 7; } when (/I/i) { $let = 8; } when (/J/i) { $let = 9; } }

which I replaced with a subroutine:

sub letter_to_coord { # Converts A .. J into 0 .. 9, assuming valid data entered. my $letter = uc(shift); my $num = ord($letter) - ord('A'); return $num if $num < 10 and $num >= 0; die "invalid input!"; }

Since that was repeated six more times, it reduced the line count somewhat. I also noticed the board printing logic was used multiple times, so I wrote a couple subroutines:

sub print_both_boards { print "USER BOARD:"; print_board(\@main::user_ships::user_board); print "COMPUTER BOARD:"; print_board(\@main::com_ships::com_board); } sub print_board { my $ar = shift; print "\n 1 2 3 4 5 6 7 8 9 10\n"; print " --------------------\n"; for (my $i=0; $i<@$ar; ++$i) { print "$main::letters::letters[$i]|"; my @t = map { $_ eq '0' ? '-' : $_ } @{$$ar[$i]}; print " @t\n"; } }

Between those two changes, I cut out roughly 300 lines. I'll put the rest in readmore tags, so you don't have to suffer through all of it.

The next bit I fiddled with was ship generation. There was a separate routine for each type of ship, each doing roughly the same thing. I replaced it with:

sub generate_ship_pos::any_ship { my $size = shift; my @ship; while (1) { # Randomly place a ship @ship = (); my $x = int(rand(10)); my $y = int(rand(10)); my $orient = int(rand(2)); given ($orient) { when (0) { push @ship, [ $x, $y++ ] for 1 .. $size; } when (1) { push @ship, [ $x++, $y ] for 1 .. $size; } } redo if $x>9 or $y>9; # Try again if we go past the edge # Verify that it doesn't intersect another ship my $collisions=0; for my $P (@ship) { ++$collisions if $main::com_ships::com_board[$$P[0]][$$P[1 +]] != 0; } last unless $collisions; } for my $ar (@ship) { $main::com_ships::com_board[$$ar[0]][$$ar[1]] = 1; push @main::com_ships::com_all_ships, "$$ar[0]$$ar[1]"; } #push @main::com_ships::com_all_ships, map { "$$_[0]$$_[1]" } @shi +p; }

That gets rid of 150 lines. I noticed that the user ship placement was similarly duplicated, and the code was hard to slog through. A lot of effort was spent in making sure the user placed the ships properly, so I changed the input scheme so they need only choose the starting point and the direction the ship lies. I read it enough to figure out what results I needed, and then wrote these subroutines:

sub place_ship { my ($x, $y, $size, $orient, $type) = @_; my @ship; print "$x, $y\n"; given (uc($orient)) { when ('U') { push @ship, [--$x, $y] for 1 .. $size } when ('D') { push @ship, [++$x, $y] for 1 .. $size } when ('L') { push @ship, [$x, --$y] for 1 .. $size } when ('R') { push @ship, [$x, $y++] for 1 .. $size } when ('X') { push @ship, [$x++, $y++] for 1 .. $size } when ('Y') { push @ship, [$x--, $y++] for 1 .. $size } } print "$x, $y\n"; return 0 if $x<0 or $y<0 or $x>9 or $y>9; my $collisions=0; for my $P (@ship) { ++$collisions if $main::user_ships::user_board[$$P[0]][$$P[1]] + != 0; } print "col=$collisions\n"; return 0 if $collisions; for my $P (@ship) { $main::user_ships::user_board[$$P[0]][$$P[1]] = substr($type,0 +,1); push @main::user_ships::all_ships, "$$P[0]$$P[1]"; } 1; } sub get_user_ship_pos::any_size { my ($name, $size) = @_; print_board(\@main::user_ships::user_board); while (1) { print "Position your $name: ('A5 r' starts at A5 and goes righ +t):"; chomp(my $input = <STDIN>); if ($input =~ m/([A-J]\d0?)\s*([UDLRXY])/i) { # Coordinates look good, try placing the ship my ($start, $dir) = ($1, $2); my ($let, $num) = split '', $start; $let = letter_to_coord($let); last if place_ship($let, $num, 3, $dir, $name); } print "You have entered invalid input. Please try again.\n\n"; } }

This cut the program down to under 550 lines. But I notice that the computer ship placement and user ship placement is pretty similar, so I should factor a bit of that out, too. But the conference call is heating up and I have to stop here.

Current version:

#!/usr/bin/perl use v5.10.1; use strict; use warnings; use Data::Dumper; my %Pieces = ( Battleship => 4, Carrier => 5, Destroyer => 3, Patrol => 2, Submarine => 3, ); startup(); #********************************************************************* +********* #*******************************STARTUP FUNCTION********************** +********* #********************************************************************* +********* sub main::startup { local $main::startup::name = ""; print "\n\nPlease enter your name: "; chomp($main::startup::name = <STDIN>); main::call(); } #********************************************************************* +********** #******************************MAIN CALL FUNCTION********************* +********** #********************************************************************* +********** sub main::call { srand; local @main::com_ships::com_all_ships = (); local @main::user_ships::user_all_ships = (); #local @main::user_ships::user_carrier = (); #local @main::user_ships::user_battleship = (); #local @main::user_ships::user_destroyer = (); #local @main::user_ships::user_submarine = (); #local @main::user_ships::user_patrol = (); local @main::com_ships::com_board = generate_board(); local @main::user_ships::user_board = generate_board(); local @main::letters::letters = ("A" .. "J"); local $main::game_play::whos_first = ''; local $main::game_play::mode = ''; local @main::game_play::com_shots_check; local @main::game_play::user_shots_check; local $main::com_ships::hits = ''; local $main::user_ships::hits = ''; generate_ship_pos::start(); get_user_ship_pos::start(); game_play::start(); } #********************************************************************* +********** #***************************SECONDARY CALL FUNCTIONS****************** +********** #********************************************************************* +********** sub generate_ship_pos::start { for my $v (values %Pieces) { generate_ship_pos::any_ship($v); } generate_ship_pos::correctional(@main::com_ships::com_all_ships); # Reset the board, we used it as a scratchpad @main::com_ships::com_board = generate_board(); } sub get_user_ship_pos::start { for my $k (keys %Pieces) { get_user_ship_pos::any_size($k, $Pieces{$k}); } } sub game_play::start { $main::game_play::whos_first = game_play::decide(); $main::game_play::mode = game_play::mode(); game_play::tree_branches($main::game_play::whos_first, $main::game_pl +ay::mode); } #********************************************************************* +********** #*********************COMPUTER SHIP GENERATION FUNCTIONS************** +********** #********************************************************************* +********** sub generate_ship_pos::any_ship { my $size = shift; my @ship; while (1) { # Randomly place a ship @ship = (); my $x = int(rand(10)); my $y = int(rand(10)); my $orient = int(rand(2)); given ($orient) { when (0) { push @ship, [ $x, $y++ ] for 1 .. $size; } when (1) { push @ship, [ $x++, $y ] for 1 .. $size; } } redo if $x>9 or $y>9; # Try again if we go past the edge # Verify that it doesn't intersect another ship my $collisions=0; for my $P (@ship) { ++$collisions if $main::com_ships::com_board[$$P[0]][$$P[1 +]] != 0; } last unless $collisions; } for my $ar (@ship) { $main::com_ships::com_board[$$ar[0]][$$ar[1]] = 1; push @main::com_ships::com_all_ships, "$$ar[0]$$ar[1]"; } } sub generate_ship_pos::correctional { foreach my $el (@main::com_ships::com_all_ships) { if (length $el == 1) { $el = '0' . "$el"; } } } #********************************************************************* +********** #************************USER SHIP GENERATION FUNCTIONS*************** +********** #********************************************************************* +********** sub place_ship { my ($x, $y, $size, $orient, $type) = @_; my @ship; print "$x, $y\n"; given (uc($orient)) { when ('U') { push @ship, [--$x, $y] for 1 .. $size } when ('D') { push @ship, [++$x, $y] for 1 .. $size } when ('L') { push @ship, [$x, --$y] for 1 .. $size } when ('R') { push @ship, [$x, $y++] for 1 .. $size } when ('X') { push @ship, [$x++, $y++] for 1 .. $size } when ('Y') { push @ship, [$x--, $y++] for 1 .. $size } } print "$x, $y\n"; return 0 if $x<0 or $y<0 or $x>9 or $y>9; my $collisions=0; for my $P (@ship) { ++$collisions if $main::user_ships::user_board[$$P[0]][$$P[1]] + != 0; } print "col=$collisions\n"; return 0 if $collisions; for my $P (@ship) { $main::user_ships::user_board[$$P[0]][$$P[1]] = substr($type,0 +,1); push @main::user_ships::all_ships, "$$P[0]$$P[1]"; } 1; } sub get_user_ship_pos::any_size { my ($name, $size) = @_; print_board(\@main::user_ships::user_board); while (1) { print "Position your $name: ('A5 r' starts at A5 and goes righ +t):"; chomp(my $input = <STDIN>); if ($input =~ m/([A-J]\d0?)\s*([UDLRXY])/i) { # Coordinates look good, try placing the ship my ($start, $dir) = ($1, $2); my ($let, $num) = split '', $start; $let = letter_to_coord($let); last if place_ship($let, $num, $size, $dir, $name); } print "You have entered invalid input. Please try again.\n\n"; } } #********************************************************************* +********** #*******************************GAME-PLAY FUNCTIONS******************* +********** #********************************************************************* +********** sub game_play::decide { my $decide = ''; while (!$decide) { print "\n1. You ($main::startup::name)"; print "\n2. Computer"; print "\n3. Random"; print "\n\nPlease enter digit to decide who goes first: "; chomp($decide = <STDIN>); if ($decide == 1 or $decide == 2) { return $decide; } elsif ($decide = 3) { return int(rand(1000)); } else { print "Your slection is not recognized. Please try again.\n"; $decide = ''; } } } sub game_play::mode { my $mode = ''; while (!$mode) { print "\n1. Regular"; print "\n2. Three-shot salvo"; print "\n\nPlease enter mode selection: "; chomp($mode = <STDIN>); if ($mode == 1 or $mode == 2) { return $mode; } else { print "Your slection is not recognized. Please try again.\n"; $mode = ''; } } } sub game_play::tree_branches { my @deciders = @_; if ($deciders[0]/2 == int($deciders[0]/2)){ game_play::com_start($deciders[1]); } else { game_play::user_start($deciders[1]); } } sub game_play::com_start { my ($decider) = @_; print "COMPUTER STARTS . . . .\n\n"; sleep 1; if ($decider == 2) { game_play::com_start::salvo_mode(); } elsif ($decider == 1) { game_play::com_start::reg_mode(); } } sub game_play::user_start { my ($decider) = @_; print "$main::startup::name STARTS . . . .\n\n"; if ($decider == 2) { game_play::user_start::salvo_mode(); } else { game_play::user_start::reg_mode(); } } sub game_play::com_start::salvo_mode { while (!game_play::dead_yet()) { game_play::salvo_mode::com_salvo(); game_play::dead_yet(); print_both_boards(); game_play::salvo_mode::user_salvo(); game_play::dead_yet(); } } sub game_play::com_start::reg_mode { while (!game_play::dead_yet()) { game_play::reg_mode::com_shot(); game_play::dead_yet(); print_both_boards(); game_play::reg_mode::user_shot(); game_play::dead_yet(); } } sub game_play::user_start::salvo_mode { while (!game_play::dead_yet()) { print_both_boards(); game_play::salvo_mode::user_salvo(); game_play::dead_yet(); game_play::salvo_mode::com_salvo(); game_play::dead_yet(); } } sub game_play::user_start::reg_mode { while (!&game_play::dead_yet()) { print_both_boards(); game_play::reg_mode::user_shot(); game_play::dead_yet(); game_play::reg_mode::com_shot(); game_play::dead_yet(); } } sub game_play::salvo_mode::com_salvo { my $x1 = int(rand(10)); my $y1 = int(rand(10)); my $x2 = int(rand(10)); my $y2 = int(rand(10)); my $x3 = int(rand(10)); my $y3 = int(rand(10)); my @temp_checker = ("$x1" . "$y1", "$x2" . "$y2", "$x3" . "$y3"); if (($main::user_ships::user_board[$x1][$y1] eq 'X') or ($main::user_ +ships::user_board[$x1][$y1] eq 'O')) { game_play::salvo_mode::com_salvo(); return; } if (($main::user_ships::user_board[$x3][$y3] eq 'X') or ($main::user_ +ships::user_board[$x3][$y3] eq 'O')) { game_play::salvo_mode::com_salvo(); return; } if (($main::user_ships::user_board[$x2][$y2] eq 'X') or ($main::user_ +ships::user_board[$x2][$y2] eq 'O')) { game_play::salvo_mode::com_salvo(); return; } push @main::game_play::com_shots_check, @temp_checker; if ($main::user_ships::user_board[$x1][$y1] eq '#') { print "You've been hit!\n"; $main::user_ships::hits++; $main::user_ships::user_board[$x1][$y1] = 'X'; } elsif ($main::user_ships::user_board[$x1][$y1] eq 'X') { game_play::salvo_mode::com_salvo(); return; } else { $main::user_ships::user_board[$x1][$y1] = 'O'; } if ($main::user_ships::user_board[$x2][$y2] eq '#') { print "You've been hit!\n"; $main::user_ships::hits++; $main::user_ships::user_board[$x2][$y2] = 'X'; } elsif ($main::user_ships::user_board[$x2][$y2] eq 'X') { game_play::salvo_mode::com_salvo(); return; } else { $main::user_ships::user_board[$x2][$y2] = 'O'; } if ($main::user_ships::user_board[$x3][$y3] eq '#') { print "You've been hit!\n"; $main::user_ships::hits++; $main::user_ships::user_board[$x3][$y3] = 'X'; } elsif ($main::user_ships::user_board[$x3][$y3] eq 'X') { game_play::salvo_mode::com_salvo(); return; } else { $main::user_ships::user_board[$x3][$y3] = 'O'; } } sub game_play::salvo_mode::user_salvo { my $input = ''; print "\n Please enter your three salvo shots: "; chomp($input = <STDIN>); my @user_shots = split(" ", $input); if ($input !~ /^[A-J]\d0?\s[A-J]\d0?\s[A-J]\d0?$/i) { print "You've entered invalid input. Please try again.\n"; game_play::salvo_mode::user_salvo(); return 0; } if ($user_shots[0] eq ($user_shots[1] or $user_shots[2]) or $user_sho +ts[1] eq $user_shots[2]) { print "You have entered a co-ordinate twice. Please try again.\n"; &game_play::salvo_mode::user_salvo(); return 0; } foreach my $el (@user_shots) { my ($let, $one, $two) = split('', $el); $let = letter_to_coord($let); my $num = 0; if ($two ne "") { $num = "$one" . "$two"; $num = $num - 1; } else { $num = $one - 1; } $el = "$let" . "$num"; given ($num) { when ($num > 9) { print "Your input is invalid. Please try again.\n\n"; @user_shots = (); &game_play::salvo_mode::user_salvo(); return; } } foreach my $lee (@main::game_play::user_shots_check) { if ($el eq $lee) { print "You have entered invalid input. Please try again.\n"; &game_play::salvo_mode::user_salvo(); return; } } foreach my $lee (@main::com_ships::com_all_ships) { if ($el eq $lee) { print "Computer ship hit!\n"; $main::com_ships::com_board[$let][$num] = 'X'; $main::com_ships::hits++; last; } elsif ($el ne $lee) { $main::com_ships::com_board[$let][$num] = 'O'; } } } push @main::game_play::user_shots_check, @user_shots; } sub game_play::reg_mode::com_shot { my $x1 = int(rand(10)); my $y1 = int(rand(10)); my $temp_checker = ("$x1" . "$y1"); if (($main::user_ships::user_board[$x1][$y1] eq 'X') or ($main::user_ +ships::user_board[$x1][$y1] eq 'O')) { game_play::reg_mode::com_shot(); return; } push @main::game_play::com_shots_check, $temp_checker; if ($main::user_ships::user_board[$x1][$y1] eq '#') { print "You've been hit!\n"; $main::user_ships::hits++; $main::user_ships::user_board[$x1][$y1] = 'X'; } elsif ($main::user_ships::user_board[$x1][$y1] eq 'X') { game_play::reg_mode::com_shot(); return; } else { $main::user_ships::user_board[$x1][$y1] = 'O'; } } sub game_play::reg_mode::user_shot { my $input = ''; print "\n Please enter your target: "; chomp($input = <STDIN>); if ($input !~ /^[A-J]\d0?$/i) { print "You've entered invalid input. Please try again.\n"; game_play::reg_mode::user_shot(); return 0; } my ($let, $one, $two) = split('', $input); $let = letter_to_coord($let); my $num = 0; if ($two ne "") { $num = "$one" . "$two"; $num = $num - 1; } else { $num = $one - 1; } $input = "$let" . "$num"; given ($num) { when ($num > 9) { print "Your input is invalid. Please try again.\n\n"; $input = ''; game_play::reg_mode::user_shot(); return; } } foreach my $lee (@main::game_play::user_shots_check) { if ($input eq $lee) { print "You have entered invalid input. Please try again.\n"; game_play::reg_mode::user_shot(); return; } } foreach my $lee (@main::com_ships::com_all_ships) { if ($input eq $lee) { print "Computer ship hit!\n"; $main::com_ships::com_board[$let][$num] = 'X'; $main::com_ships::hits++; last; } elsif ($input ne $lee) { $main::com_ships::com_board[$let][$num] = 'O'; } } push @main::game_play::user_shots_check, $input; } sub game_play::dead_yet { if ($main::com_ships::hits eq '17') { print "Congratulations! You won, $main::startup::name!\n\n"; exit; } if ($main::user_ships::hits eq '17') { print "Computer won. Better luck next time!\n"; print "Computer used:\n"; print_board(\@main::com_ships::com_board); exit; } } sub letter_to_coord { # Converts A .. J into 0 .. 9, assuming valid data entered. my $letter = uc(shift); my $num = ord($letter) - ord('A'); return $num if $num < 10 and $num >= 0; die "invalid input!"; } sub print_both_boards { print "USER BOARD:"; print_board(\@main::user_ships::user_board); print "COMPUTER BOARD:"; print_board(\@main::com_ships::com_board); } sub print_board { my $ar = shift; print "\n 1 2 3 4 5 6 7 8 9 10\n"; print " --------------------\n"; for (my $i=0; $i<@$ar; ++$i) { print "$main::letters::letters[$i]|"; my @t = map { $_ eq '0' ? '-' : $_ } @{$$ar[$i]}; print " @t\n"; } } sub generate_board { my @t = ( [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ); return @t; }

There's a good bit more refactoring that could be done. Perhaps if the call cools down some, I'll revisit it.

...roboticus

When your only tool is a hammer, all problems look like your thumb.