battleship.pl
1	#!/usr/bin/perl
2
3 # TODO: More work on AI, make it smarter and less random
4 # ** Keep track of where it's already missed and whether or not opponent moves
5 # TODO: Handle situation where player or AI can place ships that 'wrap' around the map, ie coordinates
6 # like 20,21,22 which would place the end of a cruiser in the first row, and the next two sections of it
7 # in the second row. This doesn't really break the game at all, but it does look weird on the map and doesn't seem
8 # to be a mature implimentation if it exists
9 # TODO: Handle the fact that player can input random coordinates so that they could potentially have 1 third
10 # of a ship in 3 different coordinates, or just have a ship occupy 1 tile by entering the same coordinate
11 # TODO: 'Productionize' the code: error handling, more input sanitation, etc
12 # ** Optimze placement so we dont have to check it each time, ie check at placement
13 # ** Consolidate redundant subs
14 # TODO: Improve readability, game play feel
15 #
16 # KNOWN BUGS:
17 # TODO: &clearUnocTiles issue -- see sub comment
18 # ** Not sure this is really an issue, but leaving it here to remind myself anyways
19
20 # Basic implimentation of 'battleship' to teach myself more about programming
21 # I don't know the actual rules of the game, this is my stab at
22 # something in the 'spirit' of it
23 #
24 # Player takes turns against computer trying to hit one of their ships.
25 # Can only perform 1 action per turn:
26 # - Move
27 # - Attack
28 #
29 # Three types of ships:
30 # * Cruiser
31 # - Hull Points: 2
32 # - Size: 3x1
33 # - Attack Power: 1
34 # * Carrier
35 # - Hull Points: 3
36 # - Size: 5x1
37 # - Attack Power: 2
38 # * Submarine
39 # - Hull Points: 1
40 # - Size 2x1
41 # - Attack Power: 3
42 #
43 # 5x5 map grid for each player
44 # Cruiser = *
45 # Carrier = @
46 # Submarine = ~
47 # Ocean/Empty Space = .
48
49 use strict;
50 use warnings;
51 use lib "/home/swatson/Repos/battleship-perl";
52 #use MapTools;
53 use Term::ANSIColor qw(:constants);
54
55 my $version = 0.1;
56 if ( $ARGV[0] && $ARGV[0] =~ /version/ ) {
57 print "$version\n";
58 exit 0;
59 }
60
61 # Maps
62 my %p1map;
63 my %p2map;
64
65 # Stats trackers
66 my @p1Attacks;
67 my @p2Attacks;
68
69 # Ships - surely there is a better way to do this
70 my %p1cruiser = ( 'hp' => '2', 'size' => '3', 'ap' => '1', 'loc' => '', 'sym' => '*', 'mc' => 0 );
71 my %p1carrier = ( 'hp' => '3', 'size' => '5', 'ap' => '2', 'loc' => '', 'sym' => '@', 'mc' => 0 );
72 my %p1subm = ( 'hp' => '1', 'size' => '2', 'ap' => '3', 'loc' => '', 'sym' => '~', 'mc' => 0 );
73
74 my %p1ships = ( 'cru' => \%p1cruiser, 'car' => \%p1carrier, 'subm' => \%p1subm );
75
76 my %p2cruiser = ( 'hp' => '2', 'size' => '3', 'ap' => '1', 'loc' => '', 'sym' => '*', 'mc' => 0 );
77 my %p2carrier = ( 'hp' => '3', 'size' => '5', 'ap' => '2', 'loc' => '', 'sym' => '@', 'mc' => 0 );
78 my %p2subm = ( 'hp' => '1', 'size' => '2', 'ap' => '3', 'loc' => '', 'sym' => '~', 'mc' => 0 );
79
80 my %p2ships = ( 'cru' => \%p2cruiser, 'car' => \%p2carrier, 'subm' => \%p2subm );
81
82 sub initMap {
83
84 foreach my $number ( 1 .. 50 ) {
85
86 $p1map{$number} = ".";
87 $p2map{$number} = ".";
88 }
89
90 }
91
92 sub clearUnocTiles {
93
94 # Bug where sometimes after a ship is moved one of the old tiles it was on
95 # is not reset despite the &shipPosition function reporting that it is
96 # Thus far, I've been unable to figure out why that is happening, so
97 # for now am providing this function, which will check the location of all ships
98 # and reset any incorrect tiles for both the player and the AI
99
100 my @p1usedTiles;
101 my @p2usedTiles;
102
103 # Get in use tiles for ship hashes
104 foreach my $ship ( keys %p1ships ) {
105
106 if ( ! $p1ships{$ship} ) {
107 next;
108 }
109 my $shipRef = $p1ships{$ship};
110 my $location = ${$shipRef}{loc};
111 my @inUseTiles = split(",", $location);
112 foreach my $iut ( @inUseTiles ) {
113 push(@p1usedTiles, $iut);
114 }
115
116 }
117
118 # Clean the tiles
119 foreach my $key ( keys %p1map ) {
120 if ( grep { $_ eq $key } @p1usedTiles ) {
121 next;
122 } else {
123 $p1map{$key} = ".";
124 }
125
126 }
127
128 # Now the same for the AI map
129 foreach my $ship ( keys %p2ships ) {
130
131 if ( ! $p2ships{$ship} ) {
132 next;
133 }
134 my $shipRef = $p2ships{$ship};
135 my $location = ${$shipRef}{loc};
136 my @inUseTiles = split(",", $location);
137 foreach my $iut ( @inUseTiles ) {
138 push(@p2usedTiles, $iut);
139 }
140
141 }
142
143 # Clean the tiles
144 foreach my $key ( keys %p2map ) {
145 if ( grep { $_ eq $key } @p2usedTiles ) {
146 next;
147 } else {
148 $p2map{$key} = ".";
149 }
150 }
151 }
152
153 sub printMap {
154
155 my $count = 1;
156 print "^ Player Map ^\n";
157 foreach my $key ( sort { $a <=> $b } keys %p1map ) {
158 # Probably a better way to do this
159 if ( $count != 10 && $count != 20 && $count != 30 && $count != 40 && $count != 50 ) {
160 if ( $p1map{$key} eq "*" ) {
161 print YELLOW, "$p1map{$key}", RESET;
162 } elsif ( $p1map{$key} eq "@" ) {
163 print RED, "$p1map{$key}", RESET;
164 } elsif ( $p1map{$key} eq "~" ) {
165 print CYAN, "$p1map{$key}", RESET;
166 } else {
167 print "$p1map{$key}";
168 }
169 } else {
170 if ( $p1map{$key} eq "*" ) {
171 print YELLOW, "$p1map{$key}\n", RESET;
172 } elsif ( $p1map{$key} eq "@" ) {
173 print RED, "$p1map{$key}\n", RESET;
174 } elsif ( $p1map{$key} eq "~" ) {
175 print CYAN, "$p1map{$key}\n", RESET;
176 } else {
177 print "$p1map{$key}\n";
178 }
179 }
180 $count++;
181
182 }
183
184 }
185
186 sub printPlayerStats {
187
188 # Print stats from main turn menu
189
190 print "\n";
191
192 foreach my $key ( keys %p1ships ) {
193 my $shipHref = $p1ships{$key};
194 if ( ! defined $p1ships{$key} ) {
195 print MAGENTA, "^^^ Ship: $key ^^^ \n", RESET;
196 print RED, "| SUNK! | \n", RESET;
197 } else {
198 print MAGENTA, "^^^ Ship: $key ^^^ \n", RESET;
199 print RED, "| HP: ${$shipHref}{hp} | AP: ${$shipHref}{ap} | Location: ${$shipHref}{loc} |\n", RESET;
200 }
201 }
202
203 print MAGENTA, "Coordinates attacked since last AI move:\n", RESET;
204 my $atkArSize = scalar @p1Attacks;
205 if ( $atkArSize > 0 ) {
206 foreach my $coor ( @p1Attacks ) {
207 print RED, "$coor ", RESET;
208 }
209 } else {
210 print "No attacks since last AI move";
211 }
212
213 print "\n";
214
215 }
216
217 sub shipPosition {
218
219 # Map ship to position via grid mapping
220 # 1 2 3 4 5 6 7 8 9 10
221 # . . . . . . . . . .
222 # 11 12 13 14 15 16 17 18 19 20
223 # . . . . . . . . . .
224 # Etc.
225
226 # Function should recieve ship hashRef and new grid location as input
227 my $shipHref = shift;
228
229 my $newLocation = shift;
230 my $currentLocation = ${$shipHref}{loc};
231
232 my @currentLoc = split(/,/, $currentLocation);
233 my @newLoc = split(/,/, $newLocation);
234
235 # This ended up working better than old loop
236 &clearUnocTiles;
237
238
239 # Now update new positon
240 foreach my $tile ( @newLoc ) {
241 $p1map{$tile} = ${$shipHref}{sym};
242 }
243
244 # Update shipHref with valid location
245 ${$shipHref}{loc} = join(',', @newLoc);
246
247 # Update move counter -- CANT DO THIS HERE AS WE USE THIS SUB IN INIT
248 # ${$shipHref}{mc} = 1;
249
250
251 }
252
253 # TODO: Consolidate with above sub
254 sub AiShipPosition {
255
256 # Map ship to position via grid mapping
257 # 1 2 3 4 5 6 7 8 9 10
258 # . . . . . . . . . .
259 # 11 12 13 14 15 16 17 18 19 20
260 # . . . . . . . . . .
261 # Etc.
262
263 # Function should recieve ship hashRef and new grid location as input
264 my $shipHref = shift;
265
266 my $newLocation = shift;
267 my $currentLocation = ${$shipHref}{loc};
268
269 my @currentLoc = split(/,/, $currentLocation);
270 my @newLoc = split(/,/, $newLocation);
271
272 # This ended up working better than old loop
273 &clearUnocTiles;
274
275 # Now update new positon
276 foreach my $tile ( @newLoc ) {
277 $p2map{$tile} = ${$shipHref}{sym};
278 }
279
280 # Update shipHref with valid location
281 ${$shipHref}{loc} = join(',', @newLoc);
282
283 # Update move counter -- CANT DO THIS HERE AS WE USE THIS SUB IN INIT
284 # ${$shipHref}{mc} = 1;
285
286
287 }
288
289 sub updateMap {
290
291 foreach my $key ( keys %p1ships ) {
292 my $shipHref = $p1ships{$key};
293 my @mapPoints = split(/,/, ${$shipHref}{loc});
294 foreach my $mpoint ( @mapPoints ) {
295 my $symbol = ${$shipHref}{sym};
296 $p1map{$mpoint} = $symbol;
297
298 }
299 }
300
301 foreach my $key ( keys %p2ships ) {
302 my $shipHref = $p2ships{$key};
303 my @mapPoints = split(/,/, ${$shipHref}{loc});
304 foreach my $mpoint ( @mapPoints ) {
305 my $symbol = ${$shipHref}{sym};
306 $p1map{$mpoint} = $symbol;
307 }
308 }
309 }
310
311 sub checkLocation {
312
313 # Given a set of coordinates, determine if they are already occupied
314 my $taken = 0;
315 my $coordinates = shift;
316 if ( $coordinates !~ /^[0-9]*,[0-9]*$/ && $coordinates !~ /^[0-9]*,[0-9]*,[0-9]*$/ && $coordinates !~ /^[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*$/ ) {
317 print "These coordinates look incorrect, you shouldnt see this error...\n";
318 $taken = $taken + 1;
319 }
320
321 my @coors = split(/,/, $coordinates);
322 foreach my $coor ( @coors ) {
323 if ( $p1map{$coor} ne "." ) {
324 print "coordinate $coor contains $p1map{$coor}\n";
325 $taken = $taken + 1;
326 }
327 }
328
329 if ( $taken >= 1 ) {
330 return 1;
331 } else {
332 return 0;
333 }
334
335
336 }
337
338 sub placeShips {
339
340 while() {
341
342 # Init map at the top as failure will kick you back here
343 &initMap;
344
345 print "Where do you want to place your cruiser? : ";
346 my $cruLoc = <STDIN>;
347 chomp $cruLoc;
348
349 ###
350 ### TODO : Not actually checking location on any of the below blocks
351 ### For whatever reason, it doesn't work as expected, and return coordinates that
352 ### are taken despite them being empty. I don't understand the behavior, and need to revisit this
353 ###
354
355 if ( $cruLoc !~ /^[0-9]*,[0-9]*,[0-9]*$/ ) {
356 #|| ! eval &checkLocation($cruLoc) ) {
357 print "Input looks wrong, or coordinates are taken, try again\n";
358 next;
359 }
360
361 print "Where do you want to place your carrier? : ";
362 my $carLoc = <STDIN>;
363 chomp $carLoc;
364 if ( $carLoc !~ /^[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*$/ ) {
365 # || ! eval &checkLocation($carLoc) ) {
366 print "Input looks wrong, or coordiantes are taken, try again\n";
367 next;
368 }
369
370 print "Where do you want to place your submarine? : ";
371 my $submLoc = <STDIN>;
372 chomp $submLoc;
373 if ( $submLoc !~ /^[0-9]*,[0-9]*$/ ) {
374 # || ! eval &checkLocation($submLoc) ) {
375 print "Input looks wrong, I need 2 comma seperated coordinates, try again\n";
376 next;
377 }
378
379 print "Coordinates are:\n";
380 print "Cruiser: $cruLoc\n";
381 print "Carrier: $carLoc\n";
382 print "Submarine: $submLoc\n";
383 print GREEN, "Type yes to confirm or type redo to redo: ", RESET;
384 my $confirm = <STDIN>;
385 chomp $confirm;
386 if ( $confirm eq "redo" ) {
387 next;
388 } elsif ( $confirm eq "yes" ) {
389
390 my $cruRef = $p1ships{cru};
391 my $carRef = $p1ships{car};
392 my $submRef = $p1ships{subm};
393
394 if ( ! eval &checkLocation($cruLoc) ) {
395 &shipPosition($cruRef, $cruLoc);
396 } else {
397 print "Cruiser eval check failed\n";
398 &printMap;
399 next;
400 }
401 if ( ! eval &checkLocation($carLoc) ) {
402 &shipPosition($carRef, $carLoc);
403 } else {
404 print "Carrier eval check failed\n";
405 &printMap;
406 next;
407 }
408 if ( ! eval &checkLocation($submLoc) ) {
409 &shipPosition($submRef, $submLoc);
410 } else {
411 print "Submarine eval check failed\n";
412 &printMap;
413 next;
414 }
415
416 last;
417 }
418 }
419
420 }
421
422 sub randomLocation {
423
424 # Used by AI
425 # Pass in ship type and come up with a random location
426 my $shipType = shift;
427 my $size;
428 if ( $shipType eq "cru" ) { $size = 3; }
429 if ( $shipType eq "car" ) { $size = 5; }
430 if ( $shipType eq "subm" ) { $size = 2; }
431
432 # Where to randomly look in the map index ( keys %p2map ) - between 1 and 50
433 my @fakeMap = ( 1 .. 50 );
434 my $random_num = int(1 + rand(50 - 1));
435 # Need to use splice so that numbers are sequential
436 # TODO: Can still cause a situation where ships 'wrap' around edges of the map
437 my @newLocs = splice(@fakeMap, $random_num, $size);
438 # Make sure we don't end up with an empty/short location set
439 while ( scalar(@newLocs) < $size ) {
440 print "Re-rolling AI ship position due to conflict\n";
441 $random_num = int(1 + rand(50 - 1));
442 @newLocs = splice(@fakeMap, $random_num, $size);
443 }
444
445 my $newLocs = join(",", @newLocs);
446
447 return $newLocs;
448
449 }
450
451 # TODO: This is stupid, main subroutine should be adjusted to take player map arg
452 sub checkAILocation {
453 my $coor = shift;
454 my @coors = split(/,/, $coor);
455 my $taken = 0;
456 foreach my $coor ( @coors ) {
457 if ( $p2map{$coor} ne "." ) {
458 print "coordinate $coor contains $p2map{$coor}\n";
459 $taken = $taken + 1;
460 }
461 }
462 if ( $taken >= 1 ) {
463 return 1;
464 } else {
465 return 0;
466 }
467 }
468
469
470 sub initAI {
471
472 print MAGENTA, "Initialzing opponent..\n", RESET;
473 # AI equivelant of placeShips()
474 my $cruLoc = &randomLocation("cru");
475 my $carLoc = &randomLocation("car");
476 my $submLoc = &randomLocation("subm");
477
478 #print "AI cru loc = $cruLoc\n";
479 #print "AI car loc = $carLoc\n";
480 #print "AI subm loc = $submLoc\n";
481
482 # Hash refs for ships
483 my $cruHref = $p2ships{cru};
484 my $carHref = $p2ships{car};
485 my $submHref = $p2ships{subm};
486
487 # Update Locations with new locations
488 if ( ! eval &checkAILocation($cruLoc) ) {
489 ${$cruHref}{loc} = $cruLoc;
490 } else { print "Something went wrong with AI init, exiting\n"; exit 0; }
491 if ( ! eval &checkAILocation($carLoc) ) {
492 ${$carHref}{loc} = $carLoc;
493 } else { print "Something went wrong with AI init, exiting\n"; exit 0; }
494 if ( ! eval &checkAILocation($carLoc) ) {
495 ${$submHref}{loc} = $submLoc;
496 } else { print "Something went wrong with AI init, exiting\n"; exit 0; }
497
498 print "Done\n";
499
500 }
501
502 sub AiTurn {
503
504 # General subroute to have the AI do something after the player takes their turn
505 # Main AI turn logic lives here -- extremely basic to start
506 # Should not take any arguments
507
508 print MAGENTA, "Starting AI's turn\n", RESET;
509 sleep 1;
510 # This used to be 50/50, but testing has found having the AI
511 # constantly moving around makes the game pretty boring, so make it less likely the AI will move
512 my @outcomes = (0,1,2,3,4);
513 my $randomNum = int(rand(@outcomes));
514 #my $randomNum = 1;
515
516 # Get random ship key and href
517 my @availShips;
518 foreach my $key ( keys %p2ships ) {
519 if ( ! defined $p2ships{$key} ) {
520 next;
521 } else {
522 push(@availShips,$key);
523 }
524 }
525 my $randomShipKey = $availShips[rand @availShips];
526 #print "AI's random ship is : $randomShipKey\n";
527 my $shipHref = $p2ships{$randomShipKey};
528
529 # Make sure AI doesn't try to 'move' if it has no available moves left
530 print "Checking available AI moves\n";
531 my @availMovers;
532 foreach my $key ( keys %p2ships ) {
533 my $shipRef = $p2ships{$key};
534 if ( ! defined $p2ships{$key} ) {
535 next;
536 } elsif ( ${$shipRef}{mc} == 1 ) {
537 next;
538 } else {
539 push(@availMovers, $key);
540 }
541 }
542
543 my $availM = scalar @availMovers;
544 if ( $availM == 0 ) {
545 #print "Bumping random number because we're out of moves\n";
546 $randomNum = 1;
547 }
548
549 if ( $randomNum == 0 ) {
550 # Move
551 print MAGENTA, "AI is moving!\n", RESET;
552
553 # Get new random location
554 my $newRandomLocation = &randomLocation($randomShipKey);
555 while ( eval &checkAILocation($newRandomLocation) ) {
556 #print "Conflict in AI random location, rerolling\n";
557 $newRandomLocation = &randomLocation($randomShipKey);
558 }
559
560 #print "AI's new random location is : $newRandomLocation\n";
561
562 # Move ship to that location
563 if ( ! eval &checkAILocation($newRandomLocation) ) {
564 #print "Setting AI's new location to $newRandomLocation\n";
565 ${$shipHref}{loc} = $newRandomLocation;
566 ${$shipHref}{mc} = 1;
567 print "Updating/cleaning maps\n";
568 @p1Attacks = ("Coors: ");
569 &clearUnocTiles;
570 }
571 } else {
572 # Attack
573 # Same logic copy and pasted from player attack sub, with vars changed
574 print RED, "AI is attacking!\n", RESET;
575 my $randomCoor = int(1 + rand(50 - 1));
576 print RED, "AI's chosen attack coordinate is $randomCoor\n", RESET;
577 my $ap = ${$shipHref}{ap};
578 foreach my $key ( keys %p1ships ) {
579 if ( ! $p1ships{$key} ) {
580 next;
581 }
582 my $playerShipRef = $p1ships{$key};
583 my $playerShipLocation = ${$playerShipRef}{loc};
584 my @playerShipCoors = split(",", $playerShipLocation);
585 if ( grep { $_ eq $randomCoor } @playerShipCoors ) {
586 # Hit !
587 print RED, "Hit!\n", RESET;
588 print RED, "The AI hit your $key for $ap !\n", RESET;
589 # Deterime damage to hull
590 my $playerShipHp = ${$playerShipRef}{hp};
591 my $newPlayerHullValue = $playerShipHp - $ap;
592 if ( $newPlayerHullValue <= 0 ) {
593 print RED, "The AI sunk your $key !\n", RESET;
594 # Clear player map of ship and then set ship key to undef
595 my @sunkenLocation = split(",", ${$playerShipRef}{loc});
596 foreach my $tile (@sunkenLocation) {
597 $p1map{$tile} = ".";
598 }
599 $p1ships{$key} = undef;
600 } else {
601 ${$playerShipRef}{hp} = $newPlayerHullValue;
602 print RED, "Your $key now has ${$playerShipRef}{hp} hp !\n", RESET;
603 }
604
605 last;
606
607 } else {
608 # Miss
609 print GREEN, "AI Miss\n", RESET;
610 }
611 }
612
613 }
614 print "\n";
615
616 }
617
618 sub playerAttackAI {
619
620 # Perform attack against AI. Takes a coordinate, and ship hashRef as an arg
621 # atkCoor is the coordinate to attack
622 # $shipHref is a href to the ship that * is attacking *
623 #
624 # NOTE: This was a more generalized &attack subroutine, but perl
625 # didn't like me trying to iterate over a scalar hash dereference, so
626 # figured seperate subroutes for each player attack would be the 'easiest' way to
627 # do this, as opposed to building a working hash and then repopulating
628 # the real map/ships hashes with the updated values from the working hash
629 # ... open to suggestions for better ways to do this
630 #
631
632 my $atkCoor = shift;
633 my $shipHref = shift;
634
635 # Grab attack power
636 my $ap = ${$shipHref}{ap};
637
638 # Look at opponents ships and figure out where they are --
639 # if the supplied coordinate matches any ship location, start the 'hit' logic, else, miss
640 foreach my $key ( keys %p2ships ) {
641 if ( ! $p2ships{$key} ) {
642 next;
643 }
644 my $aiShipRef = $p2ships{$key};
645 my $aiShipLocation = ${$aiShipRef}{loc};
646 my @AiShipCoors = split(",", $aiShipLocation);
647 if ( grep { $_ eq $atkCoor } @AiShipCoors ) {
648 # Hit !
649 print GREEN, "Hit!\n", RESET;
650 print "You hit the AI's $key for $ap !\n";
651 # Deterime damage to hull
652 my $aiShipHp = ${$aiShipRef}{hp};
653 my $newAiHullValue = $aiShipHp - $ap;
654 if ( $newAiHullValue <= 0 ) {
655 print "You sunk the AI's $key !\n";
656 $p2ships{$key} = undef;
657 } else {
658 ${$aiShipRef}{hp} = $newAiHullValue;
659 print "AI's $key now has ${$aiShipRef}{hp} hp !\n";
660 }
661
662 last;
663
664
665 } else {
666 # Miss
667 print RED, "Player Miss\n", RESET;
668 }
669 }
670
671
672 }
673
674 sub printMenu {
675
676 print <<EOF
677 Swatson Battleship
678 Type 'start','help', or 'quit'
679
680 EOF
681
682 }
683
684 sub printHelp {
685 print <<EOF
686
687 How To Play:
688 This is a turn based battleship game. Your objective is to destory the AI ships.
689 Each turn you can either attack with 1 ship or move 1 ship.
690 To attack type: attack
691 To move type: move
692 To see stats type: stats
693 Press Ctrl+C to exit any time.
694
695 You have 3 ships:
696 * Cruiser - Hull Points 2, Size 3, Attack Power 1
697 * Carrier - Hull Points 3, Size 5, Attack Power 2
698 * Submarine - Hull Points 1, Size 2, Attack Power 3
699
700 Each turn you will be prompted to either move or attack.
701 * When attacking, provide a coordinate number ( 1 - 50 ) to fire at
702 * When moving, provide a comma seperated list of coordinates to move to
703 * * For cruiser, provide 3 coordinates
704 * * For carrier, provide 5 coordinates
705 * * For submarine, provide 2 coordinates
706
707 EOF
708
709 }
710
711
712 &initMap;
713 &printMap;
714 &updateMap;
715 &printMap;
716
717 # Menu loop
718 while () {
719
720 my $count = 0;
721 if ( $count == 0 ) {
722 &printMenu;
723 }
724 print "Select option: ";
725 my $input = <STDIN>;
726 chomp $input;
727 if ( $input eq "quit" ) {
728 print "Quitting\n";
729 exit 0;
730 }
731 if ( $input eq "help" ) {
732 &printHelp;
733 }
734 if ( $input eq "start" ) {
735 my $gameCounter = 0;
736 my $aiCounter = 1;
737 while () {
738 print "\n\n";
739 # Main game loop
740 if ( $gameCounter == 0 ) {
741 &initAI;
742 &placeShips;
743 &clearUnocTiles;
744 $gameCounter++;
745 next;
746 }
747
748 if ( ! defined $p2ships{cru} && ! defined $p2ships{subm} && ! defined $p2ships{car} ) {
749 print "You won! Exiting...\n";
750 exit 0;
751 } elsif ( ! defined $p1ships{cru} && ! defined $p1ships{subm} && ! defined $p1ships{car} ) {
752 print "The brain dead AI beat you! Exiting...\n";
753 exit 0;
754 }
755 print GREEN, "! TURN: $gameCounter !\n", RESET;
756 sleep 1;
757 my @opponentRemaining;
758 foreach my $key ( keys %p2ships ) {
759 if ( defined $p2ships{$key} )
760 { push(@opponentRemaining, $key)
761 }
762 }
763
764 # Make sure the AI doesn't take an additional turn if
765 # the player makes a typing mistake or calls the stats sub
766 if ( $aiCounter == $gameCounter ) {
767 &AiTurn;
768 $aiCounter++;
769 }
770
771 my $opShipsLeft = scalar @opponentRemaining;
772 print "\n";
773 print GREEN, "--AI has $opShipsLeft ships left--\n", RESET;
774 &printMap;
775 print "Move or attack: ";
776 my $gameInput = <STDIN>;
777 chomp $gameInput;
778 if ( $gameInput eq "quit" ) {
779 print "Are you sure? : ";
780 my $answer = <STDIN>;
781 chomp $answer;
782 if ( $answer eq "yes" ) {
783 exit 0;
784 } else {
785 next;
786 }
787 }
788 if ( $gameInput eq "move" ) {
789 print "What ship do you want to move? : ";
790 my $shipInput = <STDIN>;
791 chomp $shipInput;
792
793 my @validInputs;
794 foreach my $key ( keys %p1ships ) {
795 my $shipHref = $p1ships{$key};
796 my $moveCounter = ${$shipHref}{mc};
797 if ( ! defined $p1ships{$key} ) {
798 next;
799 } elsif ( $moveCounter == 1 ) {
800 next;
801 } else {
802 push(@validInputs,$key);
803 }
804 }
805 if ( ! grep { $_ eq $shipInput } @validInputs ) {
806 print "That input looks wrong, try again\n";
807 next;
808 } else {
809 print "New coordinates: ";
810 my $newCoor = <STDIN>;
811 chomp $newCoor;
812 if ( $shipInput eq "cru" && $newCoor !~ /^[0-9]*,[0-9]*,[0-9]*$/ ) {
813 print "Bad coordinates, try again\n";
814 next;
815 } elsif ( $shipInput eq "car" && $newCoor !~ /^[0-9]*,[0-9]*,[0-9]*,[0-9]*,[0-9]*$/ ) {
816 print "Bad coordiantes, try again\n";
817 next;
818 } elsif ( $shipInput eq "subm" && $newCoor !~ /^[0-9]*,[0-9]*$/ ) {
819 print "Bad coordinates, try again\n";
820 next;
821 }
822 if ( eval &checkLocation($newCoor) ) {
823 print "Coordinates occupied, try again\n";
824 next;
825 }
826
827 my $shipHref = $p1ships{$shipInput};
828 &shipPosition($shipHref, $newCoor);
829 ${$shipHref}{mc} = 1;
830 &clearUnocTiles;
831 print "\n";
832
833 }
834
835 } elsif ( $gameInput eq "attack" ) {
836 print "What ship do you want to attack with? : ";
837 my $attackShip = <STDIN>;
838 chomp $attackShip;
839
840 my @validInputs;
841 foreach my $key ( keys %p1ships ) {
842 if ( ! defined $p1ships{$key} ) {
843 next;
844 } else {
845 push(@validInputs,$key);
846 }
847 }
848
849 if ( ! grep { $_ eq $attackShip } @validInputs ) {
850 print "That input looks wrong, try again\n";
851 next;
852 } else {
853 print "Select a single coordinate to attack: ";
854 my $atkCoor = <STDIN>;
855 chomp $atkCoor;
856 my @validCoors = ( 0 .. 50 );
857 if ( ! grep { $_ eq $atkCoor } @validCoors ) {
858 print "That doesn't look like a real coordinate, try again\n";
859 next;
860 } else {
861 &playerAttackAI($atkCoor,$p1ships{$attackShip});
862 push(@p1Attacks,$atkCoor);
863 print "\n";
864 }
865 }
866 } elsif ( $gameInput eq "stats" ) {
867 &printPlayerStats;
868 next;
869 } elsif ( $gameInput eq "help" ) {
870 &printHelp;
871 print "\n";
872 next;
873 } else {
874 next;
875 }
876 $gameCounter++;
877 }
878 }
879
880 $count++;
881
882 }