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.
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:
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.