fork download
  1. (function() {
  2.  
  3. // Global Constants
  4. var CONST = {};
  5. CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
  6. // You are player 0 and the computer is player 1
  7. // The virtual player is used for generating temporary ships
  8. // for calculating the probability heatmap
  9. CONST.HUMAN_PLAYER = 0;
  10. CONST.COMPUTER_PLAYER = 1;
  11. CONST.VIRTUAL_PLAYER = 2;
  12. // Possible values for the parameter `type` (string)
  13. CONST.CSS_TYPE_EMPTY = 'empty';
  14. CONST.CSS_TYPE_SHIP = 'ship';
  15. CONST.CSS_TYPE_MISS = 'miss';
  16. CONST.CSS_TYPE_HIT = 'hit';
  17. CONST.CSS_TYPE_SUNK = 'sunk';
  18. // Grid code:
  19. CONST.TYPE_EMPTY = 0; // 0 = water (empty)
  20. CONST.TYPE_SHIP = 1; // 1 = undamaged ship
  21. CONST.TYPE_MISS = 2; // 2 = water with a cannonball in it (missed shot)
  22. CONST.TYPE_HIT = 3; // 3 = damaged ship (hit shot)
  23. CONST.TYPE_SUNK = 4; // 4 = sunk ship
  24.  
  25. Game.usedShips = [CONST.UNUSED, CONST.UNUSED, CONST.UNUSED, CONST.UNUSED, CONST.UNUSED];
  26. CONST.USED = 1;
  27. CONST.UNUSED = 0;
  28.  
  29. // Game Statistics
  30. function Stats(){
  31. this.shotsTaken = 0;
  32. this.shotsHit = 0;
  33. this.totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
  34. this.totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
  35. this.gamesPlayed = parseInt(localStorage.getItem('gamesPlayed'), 10) || 0;
  36. this.gamesWon = parseInt(localStorage.getItem('gamesWon'), 10) || 0;
  37. this.uuid = localStorage.getItem('uuid') || this.createUUID();
  38. if (DEBUG_MODE) {
  39. this.skipCurrentGame = true;
  40. }
  41. }
  42. Stats.prototype.incrementShots = function() {
  43. this.shotsTaken++;
  44. };
  45. Stats.prototype.hitShot = function() {
  46. this.shotsHit++;
  47. };
  48. Stats.prototype.wonGame = function() {
  49. this.gamesPlayed++;
  50. this.gamesWon++;
  51. if (!DEBUG_MODE) {
  52. ga('send', 'event', 'gameOver', 'win', this.uuid);
  53. }
  54. };
  55. Stats.prototype.lostGame = function() {
  56. this.gamesPlayed++;
  57. if (!DEBUG_MODE) {
  58. ga('send', 'event', 'gameOver', 'lose', this.uuid);
  59. }
  60. };
  61. // Saves the game statistics to localstorage, also uploads where the user placed
  62. // their ships to Google Analytics so that in the future I'll be able to see
  63. // which cells humans are disproportionately biased to place ships on.
  64. Stats.prototype.syncStats = function() {
  65. if(!this.skipCurrentGame) {
  66. var totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
  67. totalShots += this.shotsTaken;
  68. var totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
  69. totalHits += this.shotsHit;
  70. localStorage.setItem('totalShots', totalShots);
  71. localStorage.setItem('totalHits', totalHits);
  72. localStorage.setItem('gamesPlayed', this.gamesPlayed);
  73. localStorage.setItem('gamesWon', this.gamesWon);
  74. localStorage.setItem('uuid', this.uuid);
  75. } else {
  76. this.skipCurrentGame = false;
  77. }
  78.  
  79. var stringifiedGrid = '';
  80. for (var x = 0; x < Game.size; x++) {
  81. for (var y = 0; y < Game.size; y++) {
  82. stringifiedGrid += '(' + x + ',' + y + '):' + mainGame.humanGrid.cells[x][y] + ';\n';
  83. }
  84. }
  85.  
  86. if (!DEBUG_MODE) {
  87. ga('send', 'event', 'humanGrid', stringifiedGrid, this.uuid);
  88. }
  89. };
  90. // Updates the sidebar display with the current statistics
  91. Stats.prototype.updateStatsSidebar = function() {
  92. var elWinPercent = document.getElementById('stats-wins');
  93. var elAccuracy = document.getElementById('stats-accuracy');
  94. elWinPercent.innerHTML = this.gamesWon + " of " + this.gamesPlayed;
  95. elAccuracy.innerHTML = Math.round((100 * this.totalHits / this.totalShots) || 0) + "%";
  96. };
  97. // Reset all game vanity statistics to zero. Doesn't reset your uuid.
  98. Stats.prototype.resetStats = function(e) {
  99. // Skip tracking stats until the end of the current game or else
  100. // the accuracy percentage will be wrong (since you are tracking
  101. // hits that didn't start from the beginning of the game)
  102. Game.stats.skipCurrentGame = true;
  103. localStorage.setItem('totalShots', 0);
  104. localStorage.setItem('totalHits', 0);
  105. localStorage.setItem('gamesPlayed', 0);
  106. localStorage.setItem('gamesWon', 0);
  107. localStorage.setItem('showTutorial', true);
  108. Game.stats.shotsTaken = 0;
  109. Game.stats.shotsHit = 0;
  110. Game.stats.totalShots = 0;
  111. Game.stats.totalHits = 0;
  112. Game.stats.gamesPlayed = 0;
  113. Game.stats.gamesWon = 0;
  114. Game.stats.updateStatsSidebar();
  115. };
  116. Stats.prototype.createUUID = function(len, radix) {
  117. /*!
  118. Math.uuid.js (v1.4)
  119. http://w...content-available-to-author-only...a.com
  120. mailto:robert@broofa.com
  121.  
  122. Copyright (c) 2010 Robert Kieffer
  123. Dual licensed under the MIT and GPL licenses.
  124. */
  125. var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''),
  126. uuid = [], i;
  127. radix = radix || chars.length;
  128.  
  129. if (len) {
  130. // Compact form
  131. for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
  132. } else {
  133. // rfc4122, version 4 form
  134. var r;
  135.  
  136. // rfc4122 requires these characters
  137. uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
  138. uuid[14] = '4';
  139.  
  140. // Fill in random data. At i==19 set the high bits of clock sequence as
  141. // per rfc4122, sec. 4.1.5
  142. for (i = 0; i < 36; i++) {
  143. if (!uuid[i]) {
  144. r = 0 | Math.random()*16;
  145. uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
  146. }
  147. }
  148. }
  149.  
  150. return uuid.join('');
  151. };
  152.  
  153. // Game manager object
  154. // Constructor
  155. function Game(size) {
  156. Game.size = size;
  157. this.shotsTaken = 0;
  158. this.createGrid();
  159. this.init();
  160. }
  161. Game.size = 10; // Default grid size is 10x10
  162. Game.gameOver = false;
  163. // Checks if the game is won, and if it is, re-initializes the game
  164. Game.prototype.checkIfWon = function() {
  165. if (this.computerFleet.allShipsSunk()) {
  166. alert('Congratulations, you win!');
  167. Game.gameOver = true;
  168. Game.stats.wonGame();
  169. Game.stats.syncStats();
  170. Game.stats.updateStatsSidebar();
  171. this.showRestartSidebar();
  172. } else if (this.humanFleet.allShipsSunk()) {
  173. alert('Yarr! The computer sank all your ships. Try again.');
  174. Game.gameOver = true;
  175. Game.stats.lostGame();
  176. Game.stats.syncStats();
  177. Game.stats.updateStatsSidebar();
  178. this.showRestartSidebar();
  179. }
  180. };
  181. // Shoots at the target player on the grid.
  182. // Returns {int} Constants.TYPE: What the shot uncovered
  183. Game.prototype.shoot = function(x, y, targetPlayer) {
  184. var targetGrid;
  185. var targetFleet;
  186. if (targetPlayer === CONST.HUMAN_PLAYER) {
  187. targetGrid = this.humanGrid;
  188. targetFleet = this.humanFleet;
  189. } else if (targetPlayer === CONST.COMPUTER_PLAYER) {
  190. targetGrid = this.computerGrid;
  191. targetFleet = this.computerFleet;
  192. } else {
  193. // Should never be called
  194. console.log("There was an error trying to find the correct player to target");
  195. }
  196.  
  197. if (targetGrid.isDamagedShip(x, y)) {
  198. return null;
  199. } else if (targetGrid.isMiss(x, y)) {
  200. return null;
  201. } else if (targetGrid.isUndamagedShip(x, y)) {
  202. // update the board/grid
  203. targetGrid.updateCell(x, y, 'hit', targetPlayer);
  204. // IMPORTANT: This function needs to be called _after_ updating the cell to a 'hit',
  205. // because it overrides the CSS class to 'sunk' if we find that the ship was sunk
  206. targetFleet.findShipByCoords(x, y).incrementDamage(); // increase the damage
  207. this.checkIfWon();
  208. return CONST.TYPE_HIT;
  209. } else {
  210. targetGrid.updateCell(x, y, 'miss', targetPlayer);
  211. this.checkIfWon();
  212. return CONST.TYPE_MISS;
  213. }
  214. };
  215. // Creates click event listeners on each one of the 100 grid cells
  216. Game.prototype.shootListener = function(e) {
  217. var self = e.target.self;
  218. // Extract coordinates from event listener
  219. var x = parseInt(e.target.getAttribute('data-x'), 10);
  220. var y = parseInt(e.target.getAttribute('data-y'), 10);
  221. var result = null;
  222. if (self.readyToPlay) {
  223. result = self.shoot(x, y, CONST.COMPUTER_PLAYER);
  224.  
  225. // Remove the tutorial arrow
  226. if (gameTutorial.showTutorial) {
  227. gameTutorial.nextStep();
  228. }
  229. }
  230.  
  231. if (result !== null && !Game.gameOver) {
  232. Game.stats.incrementShots();
  233. if (result === CONST.TYPE_HIT) {
  234. Game.stats.hitShot();
  235. }
  236. // The AI shoots iff the player clicks on a cell that he/she hasn't
  237. // already clicked on yet
  238. self.robot.shoot();
  239. } else {
  240. Game.gameOver = false;
  241. }
  242. };
  243. // Creates click event listeners on each of the ship names in the roster
  244. Game.prototype.rosterListener = function(e) {
  245. var self = e.target.self;
  246. // Remove all classes of 'placing' from the fleet roster first
  247. var roster = document.querySelectorAll('.fleet-roster li');
  248. for (var i = 0; i < roster.length; i++) {
  249. var classes = roster[i].getAttribute('class') || '';
  250. classes = classes.replace('placing', '');
  251. roster[i].setAttribute('class', classes);
  252. }
  253.  
  254. // Move the highlight to the next step
  255. if (gameTutorial.currentStep === 1) {
  256. gameTutorial.nextStep();
  257. }
  258.  
  259. // Set the class of the target ship to 'placing'
  260. Game.placeShipType = e.target.getAttribute('id');
  261. document.getElementById(Game.placeShipType).setAttribute('class', 'placing');
  262. Game.placeShipDirection = parseInt(document.getElementById('rotate-button').getAttribute('data-direction'), 10);
  263. self.placingOnGrid = true;
  264. };
  265. // Creates click event listeners on the human player's grid to handle
  266. // ship placement after the user has selected a ship name
  267. Game.prototype.placementListener = function(e) {
  268. var self = e.target.self;
  269. if (self.placingOnGrid) {
  270. // Extract coordinates from event listener
  271. var x = parseInt(e.target.getAttribute('data-x'), 10);
  272. var y = parseInt(e.target.getAttribute('data-y'), 10);
  273.  
  274. // Don't screw up the direction if the user tries to place again.
  275. var successful = self.humanFleet.placeShip(x, y, Game.placeShipDirection, Game.placeShipType);
  276. if (successful) {
  277. // Done placing this ship
  278. self.endPlacing(Game.placeShipType);
  279.  
  280. // Remove the helper arrow
  281. if (gameTutorial.currentStep === 2) {
  282. gameTutorial.nextStep();
  283. }
  284.  
  285. self.placingOnGrid = false;
  286. if (self.areAllShipsPlaced()) {
  287. var el = document.getElementById('rotate-button');
  288. el.addEventListener(transitionEndEventName(),(function(){
  289. el.setAttribute('class', 'hidden');
  290. if (gameTutorial.showTutorial) {
  291. document.getElementById('start-game').setAttribute('class', 'highlight');
  292. } else {
  293. document.getElementById('start-game').removeAttribute('class');
  294. }
  295. }),false);
  296. el.setAttribute('class', 'invisible');
  297. }
  298. }
  299. }
  300. };
  301. // Creates mouseover event listeners that handles mouseover on the
  302. // human player's grid to draw a phantom ship implying that the user
  303. // is allowed to place a ship there
  304. Game.prototype.placementMouseover = function(e) {
  305. var self = e.target.self;
  306. if (self.placingOnGrid) {
  307. var x = parseInt(e.target.getAttribute('data-x'), 10);
  308. var y = parseInt(e.target.getAttribute('data-y'), 10);
  309. var classes;
  310. var fleetRoster = self.humanFleet.fleetRoster;
  311.  
  312. for (var i = 0; i < fleetRoster.length; i++) {
  313. var shipType = fleetRoster[i].type;
  314.  
  315. if (Game.placeShipType === shipType &&
  316. fleetRoster[i].isLegal(x, y, Game.placeShipDirection)) {
  317. // Virtual ship
  318. fleetRoster[i].create(x, y, Game.placeShipDirection, true);
  319. Game.placeShipCoords = fleetRoster[i].getAllShipCells();
  320.  
  321. for (var j = 0; j < Game.placeShipCoords.length; j++) {
  322. var el = document.querySelector('.grid-cell-' + Game.placeShipCoords[j].x + '-' + Game.placeShipCoords[j].y);
  323. classes = el.getAttribute('class');
  324. // Check if the substring ' grid-ship' already exists to avoid adding it twice
  325. if (classes.indexOf(' grid-ship') < 0) {
  326. classes += ' grid-ship';
  327. el.setAttribute('class', classes);
  328. }
  329. }
  330. }
  331. }
  332. }
  333. };
  334. // Creates mouseout event listeners that un-draws the phantom ship
  335. // on the human player's grid as the user hovers over a different cell
  336. Game.prototype.placementMouseout = function(e) {
  337. var self = e.target.self;
  338. if (self.placingOnGrid) {
  339. for (var j = 0; j < Game.placeShipCoords.length; j++) {
  340. var el = document.querySelector('.grid-cell-' + Game.placeShipCoords[j].x + '-' + Game.placeShipCoords[j].y);
  341. classes = el.getAttribute('class');
  342. // Check if the substring ' grid-ship' already exists to avoid adding it twice
  343. if (classes.indexOf(' grid-ship') > -1) {
  344. classes = classes.replace(' grid-ship', '');
  345. el.setAttribute('class', classes);
  346. }
  347. }
  348. }
  349. };
  350. // Click handler for the Rotate Ship button
  351. Game.prototype.toggleRotation = function(e) {
  352. // Toggle rotation direction
  353. var direction = parseInt(e.target.getAttribute('data-direction'), 10);
  354. if (direction === Ship.DIRECTION_VERTICAL) {
  355. e.target.setAttribute('data-direction', '1');
  356. Game.placeShipDirection = Ship.DIRECTION_HORIZONTAL;
  357. } else if (direction === Ship.DIRECTION_HORIZONTAL) {
  358. e.target.setAttribute('data-direction', '0');
  359. Game.placeShipDirection = Ship.DIRECTION_VERTICAL;
  360. }
  361. };
  362. // Click handler for the Start Game button
  363. Game.prototype.startGame = function(e) {
  364. var self = e.target.self;
  365. var el = document.getElementById('roster-sidebar');
  366. var fn = function() {el.setAttribute('class', 'hidden');};
  367. el.addEventListener(transitionEndEventName(),fn,false);
  368. el.setAttribute('class', 'invisible');
  369. self.readyToPlay = true;
  370.  
  371. // Advanced the tutorial step
  372. if (gameTutorial.currentStep === 3) {
  373. gameTutorial.nextStep();
  374. }
  375. el.removeEventListener(transitionEndEventName(),fn,false);
  376. };
  377. // Click handler for Restart Game button
  378. Game.prototype.restartGame = function(e) {
  379. e.target.removeEventListener(e.type, arguments.callee);
  380. var self = e.target.self;
  381. document.getElementById('restart-sidebar').setAttribute('class', 'hidden');
  382. self.resetFogOfWar();
  383. self.init();
  384. };
  385. // Debugging function used to place all ships and just start
  386. Game.prototype.placeRandomly = function(e){
  387. e.target.removeEventListener(e.type, arguments.callee);
  388. e.target.self.humanFleet.placeShipsRandomly();
  389. e.target.self.readyToPlay = true;
  390. document.getElementById('roster-sidebar').setAttribute('class', 'hidden');
  391. this.setAttribute('class', 'hidden');
  392. };
  393. // Ends placing the current ship
  394. Game.prototype.endPlacing = function(shipType) {
  395. document.getElementById(shipType).setAttribute('class', 'placed');
  396.  
  397. // Mark the ship as 'used'
  398. Game.usedShips[CONST.AVAILABLE_SHIPS.indexOf(shipType)] = CONST.USED;
  399.  
  400. // Wipe out the variable when you're done with it
  401. Game.placeShipDirection = null;
  402. Game.placeShipType = '';
  403. Game.placeShipCoords = [];
  404. };
  405. // Checks whether or not all ships are done placing
  406. // Returns boolean
  407. Game.prototype.areAllShipsPlaced = function() {
  408. var playerRoster = document.querySelectorAll('.fleet-roster li');
  409. for (var i = 0; i < playerRoster.length; i++) {
  410. if (playerRoster[i].getAttribute('class') === 'placed') {
  411. continue;
  412. } else {
  413. return false;
  414. }
  415. }
  416. // Reset temporary variables
  417. Game.placeShipDirection = 0;
  418. Game.placeShipType = '';
  419. Game.placeShipCoords = [];
  420. return true;
  421. };
  422. // Resets the fog of war
  423. Game.prototype.resetFogOfWar = function() {
  424. for (var i = 0; i < Game.size; i++) {
  425. for (var j = 0; j < Game.size; j++) {
  426. this.humanGrid.updateCell(i, j, 'empty', CONST.HUMAN_PLAYER);
  427. this.computerGrid.updateCell(i, j, 'empty', CONST.COMPUTER_PLAYER);
  428. }
  429. }
  430. // Reset all values to indicate the ships are ready to be placed again
  431. Game.usedShips = Game.usedShips.map(function(){return CONST.UNUSED;});
  432. };
  433. // Resets CSS styling of the sidebar
  434. Game.prototype.resetRosterSidebar = function() {
  435. var els = document.querySelector('.fleet-roster').querySelectorAll('li');
  436. for (var i = 0; i < els.length; i++) {
  437. els[i].removeAttribute('class');
  438. }
  439.  
  440. if (gameTutorial.showTutorial) {
  441. gameTutorial.nextStep();
  442. } else {
  443. document.getElementById('roster-sidebar').removeAttribute('class');
  444. }
  445. document.getElementById('rotate-button').removeAttribute('class');
  446. document.getElementById('start-game').setAttribute('class', 'hidden');
  447. if (DEBUG_MODE) {
  448. document.getElementById('place-randomly').removeAttribute('class');
  449. }
  450. };
  451. Game.prototype.showRestartSidebar = function() {
  452. var sidebar = document.getElementById('restart-sidebar');
  453. sidebar.setAttribute('class', 'highlight');
  454.  
  455. // Deregister listeners
  456. var computerCells = document.querySelector('.computer-player').childNodes;
  457. for (var j = 0; j < computerCells.length; j++) {
  458. computerCells[j].removeEventListener('click', this.shootListener, false);
  459. }
  460. var playerRoster = document.querySelector('.fleet-roster').querySelectorAll('li');
  461. for (var i = 0; i < playerRoster.length; i++) {
  462. playerRoster[i].removeEventListener('click', this.rosterListener, false);
  463. }
  464.  
  465. var restartButton = document.getElementById('restart-game');
  466. restartButton.addEventListener('click', this.restartGame, false);
  467. restartButton.self = this;
  468. };
  469. // Generates the HTML divs for the grid for both players
  470. Game.prototype.createGrid = function() {
  471. var gridDiv = document.querySelectorAll('.grid');
  472. for (var grid = 0; grid < gridDiv.length; grid++) {
  473. gridDiv[grid].removeChild(gridDiv[grid].querySelector('.no-js')); // Removes the no-js warning
  474. for (var i = 0; i < Game.size; i++) {
  475. for (var j = 0; j < Game.size; j++) {
  476. var el = document.createElement('div');
  477. el.setAttribute('data-x', i);
  478. el.setAttribute('data-y', j);
  479. el.setAttribute('class', 'grid-cell grid-cell-' + i + '-' + j);
  480. gridDiv[grid].appendChild(el);
  481. }
  482. }
  483. }
  484. };
  485. // Initializes the Game. Also resets the game if previously initialized
  486. Game.prototype.init = function() {
  487. this.humanGrid = new Grid(Game.size);
  488. this.computerGrid = new Grid(Game.size);
  489. this.humanFleet = new Fleet(this.humanGrid, CONST.HUMAN_PLAYER);
  490. this.computerFleet = new Fleet(this.computerGrid, CONST.COMPUTER_PLAYER);
  491.  
  492. this.robot = new AI(this);
  493. Game.stats = new Stats();
  494. Game.stats.updateStatsSidebar();
  495.  
  496. // Reset game variables
  497. this.shotsTaken = 0;
  498. this.readyToPlay = false;
  499. this.placingOnGrid = false;
  500. Game.placeShipDirection = 0;
  501. Game.placeShipType = '';
  502. Game.placeShipCoords = [];
  503.  
  504. this.resetRosterSidebar();
  505.  
  506. // Add a click listener for the Grid.shoot() method for all cells
  507. // Only add this listener to the computer's grid
  508. var computerCells = document.querySelector('.computer-player').childNodes;
  509. for (var j = 0; j < computerCells.length; j++) {
  510. computerCells[j].self = this;
  511. computerCells[j].addEventListener('click', this.shootListener, false);
  512. }
  513.  
  514. // Add a click listener to the roster
  515. var playerRoster = document.querySelector('.fleet-roster').querySelectorAll('li');
  516. for (var i = 0; i < playerRoster.length; i++) {
  517. playerRoster[i].self = this;
  518. playerRoster[i].addEventListener('click', this.rosterListener, false);
  519. }
  520.  
  521. // Add a click listener to the human player's grid while placing
  522. var humanCells = document.querySelector('.human-player').childNodes;
  523. for (var k = 0; k < humanCells.length; k++) {
  524. humanCells[k].self = this;
  525. humanCells[k].addEventListener('click', this.placementListener, false);
  526. humanCells[k].addEventListener('mouseover', this.placementMouseover, false);
  527. humanCells[k].addEventListener('mouseout', this.placementMouseout, false);
  528. }
  529.  
  530. var rotateButton = document.getElementById('rotate-button');
  531. rotateButton.addEventListener('click', this.toggleRotation, false);
  532. var startButton = document.getElementById('start-game');
  533. startButton.self = this;
  534. startButton.addEventListener('click', this.startGame, false);
  535. var resetButton = document.getElementById('reset-stats');
  536. resetButton.addEventListener('click', Game.stats.resetStats, false);
  537. var randomButton = document.getElementById('place-randomly');
  538. randomButton.self = this;
  539. randomButton.addEventListener('click', this.placeRandomly, false);
  540. this.computerFleet.placeShipsRandomly();
  541. };
  542.  
  543. // Grid object
  544. // Constructor
  545. function Grid(size) {
  546. this.size = size;
  547. this.cells = [];
  548. this.init();
  549. }
  550.  
  551. // Initialize and populate the grid
  552. Grid.prototype.init = function() {
  553. for (var x = 0; x < this.size; x++) {
  554. var row = [];
  555. this.cells[x] = row;
  556. for (var y = 0; y < this.size; y++) {
  557. row.push(CONST.TYPE_EMPTY);
  558. }
  559. }
  560. };
  561.  
  562. // Updates the cell's CSS class based on the type passed in
  563. Grid.prototype.updateCell = function(x, y, type, targetPlayer) {
  564. var player;
  565. if (targetPlayer === CONST.HUMAN_PLAYER) {
  566. player = 'human-player';
  567. } else if (targetPlayer === CONST.COMPUTER_PLAYER) {
  568. player = 'computer-player';
  569. } else {
  570. // Should never be called
  571. console.log("There was an error trying to find the correct player's grid");
  572. }
  573.  
  574. switch (type) {
  575. case CONST.CSS_TYPE_EMPTY:
  576. this.cells[x][y] = CONST.TYPE_EMPTY;
  577. break;
  578. case CONST.CSS_TYPE_SHIP:
  579. this.cells[x][y] = CONST.TYPE_SHIP;
  580. break;
  581. case CONST.CSS_TYPE_MISS:
  582. this.cells[x][y] = CONST.TYPE_MISS;
  583. break;
  584. case CONST.CSS_TYPE_HIT:
  585. this.cells[x][y] = CONST.TYPE_HIT;
  586. break;
  587. case CONST.CSS_TYPE_SUNK:
  588. this.cells[x][y] = CONST.TYPE_SUNK;
  589. break;
  590. default:
  591. this.cells[x][y] = CONST.TYPE_EMPTY;
  592. break;
  593. }
  594. var classes = ['grid-cell', 'grid-cell-' + x + '-' + y, 'grid-' + type];
  595. document.querySelector('.' + player + ' .grid-cell-' + x + '-' + y).setAttribute('class', classes.join(' '));
  596. };
  597. // Checks to see if a cell contains an undamaged ship
  598. // Returns boolean
  599. Grid.prototype.isUndamagedShip = function(x, y) {
  600. return this.cells[x][y] === CONST.TYPE_SHIP;
  601. };
  602. // Checks to see if the shot was missed. This is equivalent
  603. // to checking if a cell contains a cannonball
  604. // Returns boolean
  605. Grid.prototype.isMiss = function(x, y) {
  606. return this.cells[x][y] === CONST.TYPE_MISS;
  607. };
  608. // Checks to see if a cell contains a damaged ship,
  609. // either hit or sunk.
  610. // Returns boolean
  611. Grid.prototype.isDamagedShip = function(x, y) {
  612. return this.cells[x][y] === CONST.TYPE_HIT || this.cells[x][y] === CONST.TYPE_SUNK;
  613. };
  614.  
  615. // Fleet object
  616. // This object is used to keep track of a player's portfolio of ships
  617. // Constructor
  618. function Fleet(playerGrid, player) {
  619. this.numShips = CONST.AVAILABLE_SHIPS.length;
  620. this.playerGrid = playerGrid;
  621. this.player = player;
  622. this.fleetRoster = [];
  623. this.populate();
  624. }
  625. // Populates a fleet
  626. Fleet.prototype.populate = function() {
  627. for (var i = 0; i < this.numShips; i++) {
  628. // loop over the ship types when numShips > Constants.AVAILABLE_SHIPS.length
  629. var j = i % CONST.AVAILABLE_SHIPS.length;
  630. this.fleetRoster.push(new Ship(CONST.AVAILABLE_SHIPS[j], this.playerGrid, this.player));
  631. }
  632. };
  633. // Places the ship and returns whether or not the placement was successful
  634. // Returns boolean
  635. Fleet.prototype.placeShip = function(x, y, direction, shipType) {
  636. var shipCoords;
  637. for (var i = 0; i < this.fleetRoster.length; i++) {
  638. var shipTypes = this.fleetRoster[i].type;
  639.  
  640. if (shipType === shipTypes &&
  641. this.fleetRoster[i].isLegal(x, y, direction)) {
  642. this.fleetRoster[i].create(x, y, direction, false);
  643. shipCoords = this.fleetRoster[i].getAllShipCells();
  644.  
  645. for (var j = 0; j < shipCoords.length; j++) {
  646. this.playerGrid.updateCell(shipCoords[j].x, shipCoords[j].y, 'ship', this.player);
  647. }
  648. return true;
  649. }
  650. }
  651. return false;
  652. };
  653. // Places ships randomly on the board
  654. // TODO: Avoid placing ships too close to each other
  655. Fleet.prototype.placeShipsRandomly = function() {
  656. var shipCoords;
  657. for (var i = 0; i < this.fleetRoster.length; i++) {
  658. var illegalPlacement = true;
  659.  
  660. // Prevents the random placement of already placed ships
  661. if(this.player === CONST.HUMAN_PLAYER && Game.usedShips[i] === CONST.USED) {
  662. continue;
  663. }
  664. while (illegalPlacement) {
  665. var randomX = Math.floor(10*Math.random());
  666. var randomY = Math.floor(10*Math.random());
  667. var randomDirection = Math.floor(2*Math.random());
  668.  
  669. if (this.fleetRoster[i].isLegal(randomX, randomY, randomDirection)) {
  670. this.fleetRoster[i].create(randomX, randomY, randomDirection, false);
  671. shipCoords = this.fleetRoster[i].getAllShipCells();
  672. illegalPlacement = false;
  673. } else {
  674. continue;
  675. }
  676. }
  677. if (this.player === CONST.HUMAN_PLAYER && Game.usedShips[i] !== CONST.USED) {
  678. for (var j = 0; j < shipCoords.length; j++) {
  679. this.playerGrid.updateCell(shipCoords[j].x, shipCoords[j].y, 'ship', this.player);
  680. Game.usedShips[i] = CONST.USED;
  681. }
  682. }
  683. }
  684. };
  685. // Finds a ship by location
  686. // Returns the ship object located at (x, y)
  687. // If no ship exists at (x, y), this returns null instead
  688.  
  689.  
  690. Fleet.prototype.findShipByCoords = function(x, y) {
  691. for (var i = 0; i < this.fleetRoster.length; i++) {
  692. var currentShip = this.fleetRoster[i];
  693. if (currentShip.direction === Ship.DIRECTION_VERTICAL) {
  694. if (y === currentShip.yPosition &&
  695. x >= currentShip.xPosition &&
  696. x < currentShip.xPosition + currentShip.shipLength) {
  697. return currentShip;
  698. } else {
  699. continue;
  700. }
  701. } else {
  702. if (x === currentShip.xPosition &&
  703. y >= currentShip.yPosition &&
  704. y < currentShip.yPosition + currentShip.shipLength) {
  705. return currentShip;
  706. } else {
  707. continue;
  708. }
  709. }
  710. }
  711. return null;
  712. };
  713. // Finds a ship by its type
  714. // Param shipType is a string
  715. // Returns the ship object that is of type shipType
  716. // If no ship exists, this returns null.
  717. Fleet.prototype.findShipByType = function(shipType) {
  718. for (var i = 0; i < this.fleetRoster.length; i++) {
  719. if (this.fleetRoster[i].type === shipType) {
  720. return this.fleetRoster[i];
  721. }
  722. }
  723. return null;
  724. };
  725. // Checks to see if all ships have been sunk
  726. // Returns boolean
  727. Fleet.prototype.allShipsSunk = function() {
  728. for (var i = 0; i < this.fleetRoster.length; i++) {
  729. // If one or more ships are not sunk, then the sentence "all ships are sunk" is false.
  730. if (this.fleetRoster[i].sunk === false) {
  731. return false;
  732. }
  733. }
  734. return true;
  735. };
  736.  
  737. // Ship object
  738. // Constructor
  739. function Ship(type, playerGrid, player) {
  740. this.damage = 0;
  741. this.type = type;
  742. this.playerGrid = playerGrid;
  743. this.player = player;
  744.  
  745. switch (this.type) {
  746. case CONST.AVAILABLE_SHIPS[0]:
  747. this.shipLength = 5;
  748. break;
  749. case CONST.AVAILABLE_SHIPS[1]:
  750. this.shipLength = 4;
  751. break;
  752. case CONST.AVAILABLE_SHIPS[2]:
  753. this.shipLength = 3;
  754. break;
  755. case CONST.AVAILABLE_SHIPS[3]:
  756. this.shipLength = 3;
  757. break;
  758. case CONST.AVAILABLE_SHIPS[4]:
  759. this.shipLength = 2;
  760. break;
  761. default:
  762. this.shipLength = 3;
  763. break;
  764. }
  765. this.maxDamage = this.shipLength;
  766. this.sunk = false;
  767. }
  768. // Checks to see if the placement of a ship is legal
  769. // Returns boolean
  770. Ship.prototype.isLegal = function(x, y, direction) {
  771. // first, check if the ship is within the grid...
  772. if (this.withinBounds(x, y, direction)) {
  773. // ...then check to make sure it doesn't collide with another ship
  774. for (var i = 0; i < this.shipLength; i++) {
  775. if (direction === Ship.DIRECTION_VERTICAL) {
  776. if (this.playerGrid.cells[x + i][y] === CONST.TYPE_SHIP ||
  777. this.playerGrid.cells[x + i][y] === CONST.TYPE_MISS ||
  778. this.playerGrid.cells[x + i][y] === CONST.TYPE_SUNK) {
  779. return false;
  780. }
  781. } else {
  782. if (this.playerGrid.cells[x][y + i] === CONST.TYPE_SHIP ||
  783. this.playerGrid.cells[x][y + i] === CONST.TYPE_MISS ||
  784. this.playerGrid.cells[x][y + i] === CONST.TYPE_SUNK) {
  785. return false;
  786. }
  787. }
  788. }
  789. return true;
  790. } else {
  791. return false;
  792. }
  793. };
  794. // Checks to see if the ship is within bounds of the grid
  795. // Returns boolean
  796. Ship.prototype.withinBounds = function(x, y, direction) {
  797. if (direction === Ship.DIRECTION_VERTICAL) {
  798. return x + this.shipLength <= Game.size;
  799. } else {
  800. return y + this.shipLength <= Game.size;
  801. }
  802. };
  803. // Increments the damage counter of a ship
  804. // Returns Ship
  805. Ship.prototype.incrementDamage = function() {
  806. this.damage++;
  807. if (this.isSunk()) {
  808. this.sinkShip(false); // Sinks the ship
  809. }
  810. };
  811. // Checks to see if the ship is sunk
  812. // Returns boolean
  813. Ship.prototype.isSunk = function() {
  814. return this.damage >= this.maxDamage;
  815. };
  816. // Sinks the ship
  817. Ship.prototype.sinkShip = function(virtual) {
  818. this.damage = this.maxDamage; // Force the damage to exceed max damage
  819. this.sunk = true;
  820.  
  821. // Make the CSS class sunk, but only if the ship is not virtual
  822. if (!virtual) {
  823. var allCells = this.getAllShipCells();
  824. for (var i = 0; i < this.shipLength; i++) {
  825. this.playerGrid.updateCell(allCells[i].x, allCells[i].y, 'sunk', this.player);
  826. }
  827. }
  828. };
  829. /**
  830.  * Gets all the ship cells
  831.  *
  832.  * Returns an array with all (x, y) coordinates of the ship:
  833.  * e.g.
  834.  * [
  835.  * {'x':2, 'y':2},
  836.  * {'x':3, 'y':2},
  837.  * {'x':4, 'y':2}
  838.  * ]
  839.  */
  840. Ship.prototype.getAllShipCells = function() {
  841. var resultObject = [];
  842. for (var i = 0; i < this.shipLength; i++) {
  843. if (this.direction === Ship.DIRECTION_VERTICAL) {
  844. resultObject[i] = {'x': this.xPosition + i, 'y': this.yPosition};
  845. } else {
  846. resultObject[i] = {'x': this.xPosition, 'y': this.yPosition + i};
  847. }
  848. }
  849. return resultObject;
  850. };
  851. // Initializes a ship with the given coordinates and direction (bearing).
  852. // If the ship is declared "virtual", then the ship gets initialized with
  853. // its coordinates but DOESN'T get placed on the grid.
  854. Ship.prototype.create = function(x, y, direction, virtual) {
  855. // This function assumes that you've already checked that the placement is legal
  856. this.xPosition = x;
  857. this.yPosition = y;
  858. this.direction = direction;
  859.  
  860. // If the ship is virtual, don't add it to the grid.
  861. if (!virtual) {
  862. for (var i = 0; i < this.shipLength; i++) {
  863. if (this.direction === Ship.DIRECTION_VERTICAL) {
  864. this.playerGrid.cells[x + i][y] = CONST.TYPE_SHIP;
  865. } else {
  866. this.playerGrid.cells[x][y + i] = CONST.TYPE_SHIP;
  867. }
  868. }
  869. }
  870.  
  871. };
  872. // direction === 0 when the ship is facing north/south
  873. // direction === 1 when the ship is facing east/west
  874. Ship.DIRECTION_VERTICAL = 0;
  875. Ship.DIRECTION_HORIZONTAL = 1;
  876.  
  877. // Tutorial Object
  878. // Constructor
  879. function Tutorial() {
  880. this.currentStep = 0;
  881. // Check if 'showTutorial' is initialized, if it's uninitialized, set it to true.
  882. this.showTutorial = localStorage.getItem('showTutorial') !== 'false';
  883. }
  884. // Advances the tutorial to the next step
  885. Tutorial.prototype.nextStep = function() {
  886. var humanGrid = document.querySelector('.human-player');
  887. var computerGrid = document.querySelector('.computer-player');
  888. switch (this.currentStep) {
  889. case 0:
  890. document.getElementById('roster-sidebar').setAttribute('class', 'highlight');
  891. document.getElementById('step1').setAttribute('class', 'current-step');
  892. this.currentStep++;
  893. break;
  894. case 1:
  895. document.getElementById('roster-sidebar').removeAttribute('class');
  896. document.getElementById('step1').removeAttribute('class');
  897. humanGrid.setAttribute('class', humanGrid.getAttribute('class') + ' highlight');
  898. document.getElementById('step2').setAttribute('class', 'current-step');
  899. this.currentStep++;
  900. break;
  901. case 2:
  902. document.getElementById('step2').removeAttribute('class');
  903. var humanClasses = humanGrid.getAttribute('class');
  904. humanClasses = humanClasses.replace(' highlight', '');
  905. humanGrid.setAttribute('class', humanClasses);
  906. this.currentStep++;
  907. break;
  908. case 3:
  909. computerGrid.setAttribute('class', computerGrid.getAttribute('class') + ' highlight');
  910. document.getElementById('step3').setAttribute('class', 'current-step');
  911. this.currentStep++;
  912. break;
  913. case 4:
  914. var computerClasses = computerGrid.getAttribute('class');
  915. document.getElementById('step3').removeAttribute('class');
  916. computerClasses = computerClasses.replace(' highlight', '');
  917. computerGrid.setAttribute('class', computerClasses);
  918. document.getElementById('step4').setAttribute('class', 'current-step');
  919. this.currentStep++;
  920. break;
  921. case 5:
  922. document.getElementById('step4').removeAttribute('class');
  923. this.currentStep = 6;
  924. this.showTutorial = false;
  925. localStorage.setItem('showTutorial', false);
  926. break;
  927. default:
  928. break;
  929. }
  930. };
  931.  
  932. // AI Object
  933. // Optimal battleship-playing AI
  934. // Constructor
  935. function AI(gameObject) {
  936. this.gameObject = gameObject;
  937. this.virtualGrid = new Grid(Game.size);
  938. this.virtualFleet = new Fleet(this.virtualGrid, CONST.VIRTUAL_PLAYER);
  939.  
  940. this.probGrid = []; // Probability Grid
  941. this.initProbs();
  942. this.updateProbs();
  943. }
  944. AI.PROB_WEIGHT = 5000; // arbitrarily big number
  945. // how much weight to give to the opening book's high probability cells
  946. AI.OPEN_HIGH_MIN = 20;
  947. AI.OPEN_HIGH_MAX = 30;
  948. // how much weight to give to the opening book's medium probability cells
  949. AI.OPEN_MED_MIN = 15;
  950. AI.OPEN_MED_MAX = 25;
  951. // how much weight to give to the opening book's low probability cells
  952. AI.OPEN_LOW_MIN = 10;
  953. AI.OPEN_LOW_MAX = 20;
  954. // Amount of randomness when selecting between cells of equal probability
  955. AI.RANDOMNESS = 0.1;
  956. // AI's opening book.
  957. // This is the pattern of the first cells for the AI to target
  958. AI.OPENINGS = [
  959. {'x': 7, 'y': 3, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  960. {'x': 6, 'y': 2, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  961. {'x': 3, 'y': 7, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  962. {'x': 2, 'y': 6, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  963. {'x': 6, 'y': 6, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  964. {'x': 3, 'y': 3, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  965. {'x': 5, 'y': 5, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  966. {'x': 4, 'y': 4, 'weight': getRandom(AI.OPEN_LOW_MIN, AI.OPEN_LOW_MAX)},
  967. {'x': 9, 'y': 5, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
  968. {'x': 0, 'y': 4, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
  969. {'x': 5, 'y': 9, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
  970. {'x': 4, 'y': 0, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
  971. {'x': 0, 'y': 8, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
  972. {'x': 1, 'y': 9, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)},
  973. {'x': 8, 'y': 0, 'weight': getRandom(AI.OPEN_MED_MIN, AI.OPEN_MED_MAX)},
  974. {'x': 9, 'y': 1, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)},
  975. {'x': 9, 'y': 9, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)},
  976. {'x': 0, 'y': 0, 'weight': getRandom(AI.OPEN_HIGH_MIN, AI.OPEN_HIGH_MAX)}
  977. ];
  978. // Scouts the grid based on max probability, and shoots at the cell
  979. // that has the highest probability of containing a ship
  980. AI.prototype.shoot = function() {
  981. var maxProbability = 0;
  982. var maxProbCoords;
  983. var maxProbs = [];
  984.  
  985. // Add the AI's opening book to the probability grid
  986. for (var i = 0; i < AI.OPENINGS.length; i++) {
  987. var cell = AI.OPENINGS[i];
  988. if (this.probGrid[cell.x][cell.y] !== 0) {
  989. this.probGrid[cell.x][cell.y] += cell.weight;
  990. }
  991. }
  992.  
  993. for (var x = 0; x < Game.size; x++) {
  994. for (var y = 0; y < Game.size; y++) {
  995. if (this.probGrid[x][y] > maxProbability) {
  996. maxProbability = this.probGrid[x][y];
  997. maxProbs = [{'x': x, 'y': y}]; // Replace the array
  998. } else if (this.probGrid[x][y] === maxProbability) {
  999. maxProbs.push({'x': x, 'y': y});
  1000. }
  1001. }
  1002. }
  1003.  
  1004. maxProbCoords = Math.random() < AI.RANDOMNESS ?
  1005. maxProbs[Math.floor(Math.random() * maxProbs.length)] :
  1006. maxProbs[0];
  1007.  
  1008. var result = this.gameObject.shoot(maxProbCoords.x, maxProbCoords.y, CONST.HUMAN_PLAYER);
  1009.  
  1010. // If the game ends, the next lines need to be skipped.
  1011. if (Game.gameOver) {
  1012. Game.gameOver = false;
  1013. return;
  1014. }
  1015.  
  1016. this.virtualGrid.cells[maxProbCoords.x][maxProbCoords.y] = result;
  1017.  
  1018. // If you hit a ship, check to make sure if you've sunk it.
  1019. if (result === CONST.TYPE_HIT) {
  1020. var humanShip = this.findHumanShip(maxProbCoords.x, maxProbCoords.y);
  1021. if (humanShip.isSunk()) {
  1022. // Remove any ships from the roster that have been sunk
  1023. var shipTypes = [];
  1024. for (var k = 0; k < this.virtualFleet.fleetRoster.length; k++) {
  1025. shipTypes.push(this.virtualFleet.fleetRoster[k].type);
  1026. }
  1027. var index = shipTypes.indexOf(humanShip.type);
  1028. this.virtualFleet.fleetRoster.splice(index, 1);
  1029.  
  1030. // Update the virtual grid with the sunk ship's cells
  1031. var shipCells = humanShip.getAllShipCells();
  1032. for (var _i = 0; _i < shipCells.length; _i++) {
  1033. this.virtualGrid.cells[shipCells[_i].x][shipCells[_i].y] = CONST.TYPE_SUNK;
  1034. }
  1035. }
  1036. }
  1037. // Update probability grid after each shot
  1038. this.updateProbs();
  1039. };
  1040. // Update the probability grid
  1041. AI.prototype.updateProbs = function() {
  1042. var roster = this.virtualFleet.fleetRoster;
  1043. var coords;
  1044. this.resetProbs();
  1045.  
  1046.  
  1047. for (var k = 0; k < roster.length; k++) {
  1048. for (var x = 0; x < Game.size; x++) {
  1049. for (var y = 0; y < Game.size; y++) {
  1050. if (roster[k].isLegal(x, y, Ship.DIRECTION_VERTICAL)) {
  1051. roster[k].create(x, y, Ship.DIRECTION_VERTICAL, true);
  1052. coords = roster[k].getAllShipCells();
  1053. if (this.passesThroughHitCell(coords)) {
  1054. for (var i = 0; i < coords.length; i++) {
  1055. this.probGrid[coords[i].x][coords[i].y] += AI.PROB_WEIGHT * this.numHitCellsCovered(coords);
  1056. }
  1057. } else {
  1058. for (var _i = 0; _i < coords.length; _i++) {
  1059. this.probGrid[coords[_i].x][coords[_i].y]++;
  1060. }
  1061. }
  1062. }
  1063. if (roster[k].isLegal(x, y, Ship.DIRECTION_HORIZONTAL)) {
  1064. roster[k].create(x, y, Ship.DIRECTION_HORIZONTAL, true);
  1065. coords = roster[k].getAllShipCells();
  1066. if (this.passesThroughHitCell(coords)) {
  1067. for (var j = 0; j < coords.length; j++) {
  1068. this.probGrid[coords[j].x][coords[j].y] += AI.PROB_WEIGHT * this.numHitCellsCovered(coords);
  1069. }
  1070. } else {
  1071. for (var _j = 0; _j < coords.length; _j++) {
  1072. this.probGrid[coords[_j].x][coords[_j].y]++;
  1073. }
  1074. }
  1075. }
  1076.  
  1077. // Set hit cells to probability zero so the AI doesn't
  1078. // target cells that are already hit
  1079. if (this.virtualGrid.cells[x][y] === CONST.TYPE_HIT) {
  1080. this.probGrid[x][y] = 0;
  1081. }
  1082. }
  1083. }
  1084. }
  1085. };
  1086. // Initializes the probability grid for targeting
  1087. AI.prototype.initProbs = function() {
  1088. for (var x = 0; x < Game.size; x++) {
  1089. var row = [];
  1090. this.probGrid[x] = row;
  1091. for (var y = 0; y < Game.size; y++) {
  1092. row.push(0);
  1093. }
  1094. }
  1095. };
  1096. // Resets the probability grid to all 0.
  1097. AI.prototype.resetProbs = function() {
  1098. for (var x = 0; x < Game.size; x++) {
  1099. for (var y = 0; y < Game.size; y++) {
  1100. this.probGrid[x][y] = 0;
  1101. }
  1102. }
  1103. };
  1104. AI.prototype.metagame = function() {
  1105. // Inputs:
  1106. // Proximity of hit cells to edge
  1107. // Proximity of hit cells to each other
  1108. // Edit the probability grid by multiplying each cell with a new probability weight (e.g. 0.4, or 3). Set this as a CONST and make 1-CONST the inverse for decreasing, or 2*CONST for increasing
  1109. };
  1110. // Finds a human ship by coordinates
  1111. // Returns Ship
  1112. AI.prototype.findHumanShip = function(x, y) {
  1113. return this.gameObject.humanFleet.findShipByCoords(x, y);
  1114. };
  1115. // Checks whether or not a given ship's cells passes through
  1116. // any cell that is hit.
  1117. // Returns boolean
  1118. AI.prototype.passesThroughHitCell = function(shipCells) {
  1119. for (var i = 0; i < shipCells.length; i++) {
  1120. if (this.virtualGrid.cells[shipCells[i].x][shipCells[i].y] === CONST.TYPE_HIT) {
  1121. return true;
  1122. }
  1123. }
  1124. return false;
  1125. };
  1126. // Gives the number of hit cells the ships passes through. The more
  1127. // cells this is, the more probable the ship exists in those coordinates
  1128. // Returns int
  1129. AI.prototype.numHitCellsCovered = function(shipCells) {
  1130. var cells = 0;
  1131. for (var i = 0; i < shipCells.length; i++) {
  1132. if (this.virtualGrid.cells[shipCells[i].x][shipCells[i].y] === CONST.TYPE_HIT) {
  1133. cells++;
  1134. }
  1135. }
  1136. return cells;
  1137. };
  1138.  
  1139. // Global constant only initialized once
  1140. var gameTutorial = new Tutorial();
  1141.  
  1142. // Start the game
  1143. var mainGame = new Game(10);
  1144.  
  1145. })();
  1146.  
  1147.  
  1148. if (!Array.prototype.indexOf) {
  1149. Array.prototype.indexOf = function (searchElement, fromIndex) {
  1150.  
  1151. var k;
  1152.  
  1153. // 1. Let O be the result of calling ToObject passing
  1154. // the this value as the argument.
  1155. if (this === null || this === undefined) {
  1156. throw new TypeError('"this" is null or not defined');
  1157. }
  1158.  
  1159. var O = Object(this);
  1160.  
  1161. // 2. Let lenValue be the result of calling the Get
  1162. // internal method of O with the argument "length".
  1163. // 3. Let len be ToUint32(lenValue).
  1164. var len = O.length >>> 0;
  1165.  
  1166. // 4. If len is 0, return -1.
  1167. if (len === 0) {
  1168. return -1;
  1169. }
  1170.  
  1171. // 5. If argument fromIndex was passed let n be
  1172. // ToInteger(fromIndex); else let n be 0.
  1173. var n = +fromIndex || 0;
  1174.  
  1175. if (Math.abs(n) === Infinity) {
  1176. n = 0;
  1177. }
  1178.  
  1179. // 6. If n >= len, return -1.
  1180. if (n >= len) {
  1181. return -1;
  1182. }
  1183.  
  1184. // 7. If n >= 0, then Let k be n.
  1185. // 8. Else, n<0, Let k be len - abs(n).
  1186. // If k is less than 0, then let k be 0.
  1187. k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
  1188.  
  1189. while (k < len) {
  1190. var kValue;
  1191.  
  1192. if (k in O && O[k] === searchElement) {
  1193. return k;
  1194. }
  1195. k++;
  1196. }
  1197. return -1;
  1198. };
  1199. }
  1200.  
  1201. if (!Array.prototype.map) {
  1202.  
  1203. Array.prototype.map = function(callback, thisArg) {
  1204.  
  1205. var T, A, k;
  1206.  
  1207. if (this == null) {
  1208. throw new TypeError(" this is null or not defined");
  1209. }
  1210.  
  1211. // 1. Let O be the result of calling ToObject passing the |this|
  1212. // value as the argument.
  1213. var O = Object(this);
  1214.  
  1215. // 2. Let lenValue be the result of calling the Get internal
  1216. // method of O with the argument "length".
  1217. // 3. Let len be ToUint32(lenValue).
  1218. var len = O.length >>> 0;
  1219.  
  1220. // 4. If IsCallable(callback) is false, throw a TypeError exception.
  1221. // See: http://e...content-available-to-author-only...b.com/#x9.11
  1222. if (typeof callback !== "function") {
  1223. throw new TypeError(callback + " is not a function");
  1224. }
  1225.  
  1226. // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
  1227. if (arguments.length > 1) {
  1228. T = thisArg;
  1229. }
  1230.  
  1231. // 6. Let A be a new array created as if by the expression new Array(len)
  1232. // where Array is the standard built-in constructor with that name and
  1233. // len is the value of len.
  1234. A = new Array(len);
  1235.  
  1236. // 7. Let k be 0
  1237. k = 0;
  1238.  
  1239. // 8. Repeat, while k < len
  1240. while (k < len) {
  1241.  
  1242. var kValue, mappedValue;
  1243. if (k in O) {
  1244. kValue = O[k];
  1245. mappedValue = callback.call(T, kValue, k, O);
  1246. A[k] = mappedValue;
  1247. }
  1248.  
  1249. k++;
  1250. }
  1251.  
  1252. // 9. return A
  1253. return A;
  1254. };
  1255. }
  1256.  
  1257.  
  1258. function transitionEndEventName() {
  1259. var i,
  1260. undefined,
  1261. el = document.createElement('div'),
  1262. transitions = {
  1263. 'transition':'transitionend',
  1264. 'OTransition':'otransitionend', // oTransitionEnd in very old Opera
  1265. 'MozTransition':'transitionend',
  1266. 'WebkitTransition':'webkitTransitionEnd'
  1267. };
  1268.  
  1269. for (i in transitions) {
  1270. if (transitions.hasOwnProperty(i) && el.style[i] !== undefined) {
  1271. return transitions[i];
  1272. }
  1273. }
  1274. }
  1275.  
  1276. // Returns a random number between min (inclusive) and max (exclusive)
  1277. function getRandom(min, max) {
  1278. return Math.random() * (max - min) + min;
  1279. }
  1280.  
  1281. // Toggles on or off DEBUG_MODE
  1282. function setDebug(val) {
  1283. DEBUG_MODE = val;
  1284. localStorage.setItem('DEBUG_MODE', val);
  1285. localStorage.setItem('showTutorial', 'false');
  1286. window.location.reload();
  1287. }
  1288.  
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
Main.java:1: error: class, interface, or enum expected
(function() {
^
Main.java:1: error: class, interface, or enum expected
(function() {
 ^
Main.java:5: error: class, interface, or enum expected
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                         ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                 ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                    ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                               ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                                  ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                                            ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                                               ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                                                         ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                                                            ^
Main.java:5: error: unclosed character literal
CONST.AVAILABLE_SHIPS = ['carrier', 'battleship', 'destroyer', 'submarine', 'patrolboat'];
                                                                                       ^
Main.java:9: error: class, interface, or enum expected
CONST.HUMAN_PLAYER = 0;
^
Main.java:10: error: class, interface, or enum expected
CONST.COMPUTER_PLAYER = 1;
^
Main.java:11: error: class, interface, or enum expected
CONST.VIRTUAL_PLAYER = 2;
^
Main.java:13: error: class, interface, or enum expected
CONST.CSS_TYPE_EMPTY = 'empty';
^
Main.java:13: error: unclosed character literal
CONST.CSS_TYPE_EMPTY = 'empty';
                       ^
Main.java:13: error: unclosed character literal
CONST.CSS_TYPE_EMPTY = 'empty';
                             ^
Main.java:14: error: unclosed character literal
CONST.CSS_TYPE_SHIP = 'ship';
                      ^
Main.java:14: error: unclosed character literal
CONST.CSS_TYPE_SHIP = 'ship';
                           ^
Main.java:15: error: unclosed character literal
CONST.CSS_TYPE_MISS = 'miss';
                      ^
Main.java:15: error: unclosed character literal
CONST.CSS_TYPE_MISS = 'miss';
                           ^
Main.java:16: error: unclosed character literal
CONST.CSS_TYPE_HIT = 'hit';
                     ^
Main.java:16: error: unclosed character literal
CONST.CSS_TYPE_HIT = 'hit';
                         ^
Main.java:17: error: unclosed character literal
CONST.CSS_TYPE_SUNK = 'sunk';
                      ^
Main.java:17: error: unclosed character literal
CONST.CSS_TYPE_SUNK = 'sunk';
                           ^
Main.java:20: error: class, interface, or enum expected
CONST.TYPE_SHIP = 1; // 1 = undamaged ship
^
Main.java:21: error: class, interface, or enum expected
CONST.TYPE_MISS = 2; // 2 = water with a cannonball in it (missed shot)
^
Main.java:22: error: class, interface, or enum expected
CONST.TYPE_HIT = 3; // 3 = damaged ship (hit shot)
^
Main.java:23: error: class, interface, or enum expected
CONST.TYPE_SUNK = 4; // 4 = sunk ship
^
Main.java:25: error: class, interface, or enum expected
Game.usedShips = [CONST.UNUSED, CONST.UNUSED, CONST.UNUSED, CONST.UNUSED, CONST.UNUSED];
^
Main.java:26: error: class, interface, or enum expected
CONST.USED = 1;
^
Main.java:27: error: class, interface, or enum expected
CONST.UNUSED = 0;
^
Main.java:30: error: class, interface, or enum expected
function Stats(){
^
Main.java:32: error: class, interface, or enum expected
	this.shotsHit = 0;
	^
Main.java:33: error: class, interface, or enum expected
	this.totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
	^
Main.java:33: error: unclosed character literal
	this.totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
	                                                ^
Main.java:33: error: unclosed character literal
	this.totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
	                                                           ^
Main.java:34: error: class, interface, or enum expected
	this.totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
	^
Main.java:34: error: unclosed character literal
	this.totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
	                                               ^
Main.java:34: error: unclosed character literal
	this.totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
	                                                         ^
Main.java:35: error: class, interface, or enum expected
	this.gamesPlayed = parseInt(localStorage.getItem('gamesPlayed'), 10) || 0;
	^
Main.java:35: error: unclosed character literal
	this.gamesPlayed = parseInt(localStorage.getItem('gamesPlayed'), 10) || 0;
	                                                 ^
Main.java:35: error: unclosed character literal
	this.gamesPlayed = parseInt(localStorage.getItem('gamesPlayed'), 10) || 0;
	                                                             ^
Main.java:36: error: class, interface, or enum expected
	this.gamesWon = parseInt(localStorage.getItem('gamesWon'), 10) || 0;
	^
Main.java:36: error: unclosed character literal
	this.gamesWon = parseInt(localStorage.getItem('gamesWon'), 10) || 0;
	                                              ^
Main.java:36: error: unclosed character literal
	this.gamesWon = parseInt(localStorage.getItem('gamesWon'), 10) || 0;
	                                                       ^
Main.java:37: error: class, interface, or enum expected
	this.uuid = localStorage.getItem('uuid') || this.createUUID();
	^
Main.java:37: error: unclosed character literal
	this.uuid = localStorage.getItem('uuid') || this.createUUID();
	                                 ^
Main.java:37: error: unclosed character literal
	this.uuid = localStorage.getItem('uuid') || this.createUUID();
	                                      ^
Main.java:38: error: class, interface, or enum expected
	if (DEBUG_MODE) {
	^
Main.java:40: error: class, interface, or enum expected
	}
	^
Main.java:44: error: class, interface, or enum expected
};
^
Main.java:45: error: class, interface, or enum expected
Stats.prototype.hitShot = function() {
^
Main.java:47: error: class, interface, or enum expected
};
^
Main.java:48: error: class, interface, or enum expected
Stats.prototype.wonGame = function() {
^
Main.java:50: error: class, interface, or enum expected
	this.gamesWon++;
	^
Main.java:51: error: class, interface, or enum expected
	if (!DEBUG_MODE) {
	^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		   ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		        ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		           ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		                 ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		                    ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		                             ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		                                ^
Main.java:52: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'win', this.uuid);
		                                    ^
Main.java:53: error: class, interface, or enum expected
	}
	^
Main.java:55: error: class, interface, or enum expected
Stats.prototype.lostGame = function() {
^
Main.java:57: error: class, interface, or enum expected
	if (!DEBUG_MODE) {
	^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		   ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		        ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		           ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		                 ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		                    ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		                             ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		                                ^
Main.java:58: error: unclosed character literal
		ga('send', 'event', 'gameOver', 'lose', this.uuid);
		                                     ^
Main.java:59: error: class, interface, or enum expected
	}
	^
Main.java:64: error: class, interface, or enum expected
Stats.prototype.syncStats = function() {
^
Main.java:66: error: unclosed character literal
		var totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
		                                               ^
Main.java:66: error: unclosed character literal
		var totalShots = parseInt(localStorage.getItem('totalShots'), 10) || 0;
		                                                          ^
Main.java:67: error: class, interface, or enum expected
		totalShots += this.shotsTaken;
		^
Main.java:68: error: class, interface, or enum expected
		var totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
		^
Main.java:68: error: unclosed character literal
		var totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
		                                              ^
Main.java:68: error: unclosed character literal
		var totalHits = parseInt(localStorage.getItem('totalHits'), 10) || 0;
		                                                        ^
Main.java:69: error: class, interface, or enum expected
		totalHits += this.shotsHit;
		^
Main.java:70: error: class, interface, or enum expected
		localStorage.setItem('totalShots', totalShots);
		^
Main.java:70: error: unclosed character literal
		localStorage.setItem('totalShots', totalShots);
		                     ^
Main.java:70: error: unclosed character literal
		localStorage.setItem('totalShots', totalShots);
		                                ^
Main.java:71: error: class, interface, or enum expected
		localStorage.setItem('totalHits', totalHits);
		^
Main.java:71: error: unclosed character literal
		localStorage.setItem('totalHits', totalHits);
		                     ^
Main.java:71: error: unclosed character literal
		localStorage.setItem('totalHits', totalHits);
		                               ^
Main.java:72: error: class, interface, or enum expected
		localStorage.setItem('gamesPlayed', this.gamesPlayed);
		^
Main.java:72: error: unclosed character literal
		localStorage.setItem('gamesPlayed', this.gamesPlayed);
		                     ^
Main.java:72: error: unclosed character literal
		localStorage.setItem('gamesPlayed', this.gamesPlayed);
		                                 ^
Main.java:73: error: class, interface, or enum expected
		localStorage.setItem('gamesWon', this.gamesWon);
		^
Main.java:73: error: unclosed character literal
		localStorage.setItem('gamesWon', this.gamesWon);
		                     ^
Main.java:73: error: unclosed character literal
		localStorage.setItem('gamesWon', this.gamesWon);
		                              ^
Main.java:74: error: class, interface, or enum expected
		localStorage.setItem('uuid', this.uuid);
		^
100 errors
stdout
Standard output is empty