Advertisement
Guest User

Untitled

a guest
Dec 1st, 2017
234
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.58 KB | None | 0 0
  1. /*
  2.  
  3. Inspiration: http://www.datagenetics.com/blog/december32011/
  4.  
  5. The article above recommends employing a suite of algorithms to win a game of Battleship in the fewest moves. One of these is an algorithm that selects targets based on the probability of a point on the grid being occupied. I was curious how effective an AI opponent would be if it used ONLY this algorithm.
  6.  
  7. Result: The average win occurs after 62 moves.
  8.  
  9. I then attempted to skew the probability on positions adjacent to hits so the AI would focus on areas known to contain a ship. No additional logic was used to take advantage of, for example, two adjacent hits indicating a ship's alignment.
  10.  
  11. Result: The average win occurs after 55 moves.
  12.  
  13. */
  14.  
  15. (function (document) {
  16. 'use strict';
  17.  
  18. var SHIP = 0,
  19. MISS = 1,
  20. HIT = 2,
  21. hitsMade,
  22. hitsToWin,
  23. ships = [5, 4, 3, 3, 2],
  24. // TODO: look into Int8Array on these big matrices for performance
  25. positions = [],
  26. probabilities = [],
  27. hitsSkewProbabilities = true,
  28. skewFactor = 2,
  29. boardSize = 10,
  30. classMapping = ['ship', 'miss', 'hit'],
  31. board,
  32. resultMsg,
  33. volleyButton,
  34. monteCarlo = false;
  35.  
  36. // run immediately
  37. initialize();
  38.  
  39. function initialize() {
  40. board = document.getElementById('board');
  41. resultMsg = document.getElementById('result');
  42. volleyButton = document.getElementById('volley');
  43. volleyButton.onclick = (monteCarlo ? runMonteCarlo : beginVolley);
  44. setupBoard();
  45. }
  46.  
  47. function setupBoard() {
  48. // initialize positions matrix
  49. for (var y = 0; y < boardSize; y++) {
  50. positions[y] = [];
  51. for (var x = 0; x < boardSize; x++) {
  52. positions[y][x] = null;
  53. }
  54. }
  55.  
  56. // determine hits to win given the set of ships
  57. hitsMade = hitsToWin = 0;
  58. for (var i = 0, l = ships.length; i < l; i++) {
  59. hitsToWin += ships[i];
  60. }
  61.  
  62. distributeShips();
  63. recalculateProbabilities();
  64. redrawBoard(true);
  65. }
  66.  
  67. function distributeShips() {
  68. var pos, shipPlaced, vertical;
  69. for (var i = 0, l = ships.length; i < l; i++) {
  70. shipPlaced = false;
  71. vertical = randomBoolean();
  72. while (!shipPlaced) {
  73. pos = getRandomPosition();
  74. shipPlaced = placeShip(pos, ships[i], vertical);
  75. }
  76. }
  77. }
  78.  
  79. function placeShip(pos, shipSize, vertical) {
  80. // "pos" is ship origin
  81. var x = pos[0],
  82. y = pos[1],
  83. z = (vertical ? y : x),
  84. end = z + shipSize - 1;
  85.  
  86. if (shipCanOccupyPosition(SHIP, pos, shipSize, vertical)) {
  87. for (var i = z; i <= end; i++) {
  88. if (vertical) positions[x][i] = SHIP;
  89. else positions[i][y] = SHIP;
  90. }
  91. return true;
  92. }
  93.  
  94. return false;
  95. }
  96.  
  97. function redrawBoard(displayProbability) {
  98. if (monteCarlo) return; // no need to draw when testing thousands of boards
  99. var boardHTML = '';
  100. for (var y = 0; y < boardSize; y++) {
  101. boardHTML += '<tr>';
  102. for (var x = 0; x < boardSize; x++) {
  103. var thisPos = positions[x][y];
  104. boardHTML += '<td class="';
  105. if (thisPos !== null) boardHTML += classMapping[thisPos];
  106. boardHTML += '">';
  107. if (displayProbability && thisPos != MISS && thisPos !== HIT) boardHTML += probabilities[x][y];
  108. boardHTML += '</td>';
  109. }
  110. boardHTML += '</tr>';
  111. }
  112. board.innerHTML = boardHTML;
  113. }
  114.  
  115. function recalculateProbabilities() {
  116. var hits = [];
  117.  
  118. // reset probabilities
  119. for (var y = 0; y < boardSize; y++) {
  120. probabilities[y] = [];
  121. for (var x = 0; x < boardSize; x++) {
  122. probabilities[y][x] = 0;
  123. // we remember hits as we find them for skewing
  124. if (hitsSkewProbabilities && positions[x][y] === HIT) {
  125. hits.push([x, y]);
  126. }
  127. }
  128. }
  129.  
  130. // calculate probabilities for each type of ship
  131. for (var i = 0, l = ships.length; i < l; i++) {
  132. for (var y = 0; y < boardSize; y++) {
  133. for (var x = 0; x < boardSize; x++) {
  134. // horizontal check
  135. if (shipCanOccupyPosition(MISS, [x, y], ships[i], false)) {
  136. increaseProbability([x, y], ships[i], false);
  137. }
  138. // vertical check
  139. if (shipCanOccupyPosition(MISS, [x, y], ships[i], true)) {
  140. increaseProbability([x, y], ships[i], true);
  141. }
  142. }
  143. }
  144. }
  145.  
  146. // skew probabilities for positions adjacent to hits
  147. if (hitsSkewProbabilities) {
  148. skewProbabilityAroundHits(hits);
  149. }
  150. }
  151.  
  152. function increaseProbability(pos, shipSize, vertical) {
  153. // "pos" is ship origin
  154. var x = pos[0],
  155. y = pos[1],
  156. z = (vertical ? y : x),
  157. end = z + shipSize - 1;
  158.  
  159. for (var i = z; i <= end; i++) {
  160. if (vertical) probabilities[x][i]++;
  161. else probabilities[i][y]++;
  162. }
  163. }
  164.  
  165. function skewProbabilityAroundHits(toSkew) {
  166. var uniques = [];
  167.  
  168. // add adjacent positions to the positions to be skewed
  169. for (var i = 0, l = toSkew.length; i < l; i++) {
  170. toSkew = toSkew.concat(getAdjacentPositions(toSkew[i]));
  171. }
  172.  
  173. // store uniques to avoid skewing positions multiple times
  174. // TODO: do A/B testing to see if doing this with strings is efficient
  175. for (var i = 0, l = toSkew.length; i < l; i++) {
  176. var uniquesStr = uniques.join('|').toString();
  177. if (uniquesStr.indexOf(toSkew[i].toString()) === -1) {
  178. uniques.push(toSkew[i]);
  179.  
  180. // skew probability
  181. var x = toSkew[i][0],
  182. y = toSkew[i][1];
  183. probabilities[x][y] *= skewFactor;
  184. }
  185. }
  186. }
  187.  
  188. function getAdjacentPositions(pos) {
  189. var x = pos[0],
  190. y = pos[1],
  191. adj = [];
  192.  
  193. if (y + 1 < boardSize) adj.push([x, y + 1]);
  194. if (y - 1 >= 0) adj.push([x, y - 1]);
  195. if (x + 1 < boardSize) adj.push([x + 1, y]);
  196. if (x - 1 >= 0) adj.push([x - 1, y]);
  197.  
  198. return adj;
  199. }
  200.  
  201. function shipCanOccupyPosition(criteriaForRejection, pos, shipSize, vertical) { // TODO: criteriaForRejection is an awkward concept, improve
  202. // "pos" is ship origin
  203. var x = pos[0],
  204. y = pos[1],
  205. z = (vertical ? y : x),
  206. end = z + shipSize - 1;
  207.  
  208. // board border is too close
  209. if (end > boardSize - 1) return false;
  210.  
  211. // check if there's an obstacle
  212. for (var i = z; i <= end; i++) {
  213. var thisPos = (vertical ? positions[x][i] : positions[i][y]);
  214. if (thisPos === criteriaForRejection) return false;
  215. }
  216.  
  217. return true;
  218. }
  219.  
  220. function beginVolley() {
  221. if (hitsMade > 0) setupBoard();
  222. resultMsg.innerHTML = '';
  223. volleyButton.disabled = true;
  224. var moves = 0,
  225. volley = setInterval(function () {
  226. fireAtBestPosition();
  227. moves++;
  228. if (hitsMade === hitsToWin) {
  229. resultMsg.innerHTML = 'All ships sunk in ' + moves + ' moves.';
  230. clearInterval(volley);
  231. volleyButton.disabled = false;
  232. }
  233. }, 50);
  234. }
  235.  
  236. function fireAtBestPosition() {
  237. var pos = getBestUnplayedPosition(),
  238. x = pos[0],
  239. y = pos[1];
  240.  
  241. if (positions[x][y] === SHIP) {
  242. positions[x][y] = HIT;
  243. hitsMade++;
  244. } else positions[x][y] = MISS;
  245.  
  246. recalculateProbabilities();
  247. redrawBoard(true);
  248. }
  249.  
  250. function getBestUnplayedPosition() {
  251. var bestProb = 0,
  252. bestPos;
  253.  
  254. // so far there is no tie-breaker -- first position
  255. // with highest probability on board is returned
  256. for (var y = 0; y < boardSize; y++) {
  257. for (var x = 0; x < boardSize; x++) {
  258. if (!positions[x][y] && probabilities[x][y] > bestProb) {
  259. bestProb = probabilities[x][y];
  260. bestPos = [x, y];
  261. }
  262. }
  263. }
  264.  
  265. return bestPos;
  266. }
  267.  
  268. function getRandomPosition() {
  269. var x = Math.floor(Math.random() * 10),
  270. y = Math.floor(Math.random() * 10);
  271.  
  272. return [x, y];
  273. }
  274.  
  275. function randomBoolean() {
  276. return (Math.round(Math.random()) == 1);
  277. }
  278.  
  279. function runMonteCarlo() {
  280. var elapsed, sum = 0,
  281. runs = (hitsSkewProbabilities ? 50 : 1000);
  282.  
  283. elapsed = (new Date()).getTime();
  284.  
  285. for (var i = 0; i < runs; i++) {
  286. var moves = 0;
  287. setupBoard();
  288. while (hitsMade < hitsToWin) {
  289. fireAtBestPosition();
  290. moves++;
  291. }
  292. sum += moves;
  293. }
  294.  
  295. elapsed = (new Date()).getTime() - elapsed;
  296. console.log('test duration: ' + elapsed + 'ms');
  297.  
  298. resultMsg.innerHTML = 'Average moves: ' + (sum / runs);
  299. }
  300.  
  301. }(document));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement