battleship.pl 23 KB

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