function mkArray(size, dflt) { return (new Array(size)).fill(null).map(x => typeof dflt == 'function' ? dflt() : dflt) } function boardEmpty(sz, dflt) { sz = sz || 10 dflt = dflt === undefined? { type: 'empty' } : dflt return mkArray(sz, () => mkArray(sz, dflt)) } function boardSliceIsEmpty(board, start, end) { for (let y = start[1]; y < end[1]; y += 1) { for (let x = start[0]; x < end[0]; x += 1) { if (board[y] === undefined) return false const val = board[y][x] const isEmpty = ( val !== undefined && val.type == 'empty' ) if (!isEmpty) return false } } return true } function boardPlace(board, start, end, value) { const res = board.map((row, idx) => { const needsCopy = idx >= start[1] && idx <= end[1] return needsCopy ? row.concat() : row }) for (let y = start[1]; y < end[1]; y += 1) { const row = res[y] for (let x = start[0]; x < end[0]; x += 1) { res[y][x] = value } } return res } const SHIPS = [ { "type": "ship", "size": 2, "num": 1, }, { "type": "ship", "size": 3, "num": 2, }, { "type": "ship", "size": 4, "num": 3, }, { "type": "ship", "size": 5, "num": 4, }, ] function generateBoards(board, ships, filter, res) { if (res === undefined) res = [] if (!ships.length) { if (filter && !filter(board)) return res res.push(board) return res } const ship = ships[0] const shipsRest = ships.slice(1) for (let y = 0; y < board.length; y += 1) { const row = board[y] for (let x = 0; x < row.length; x += 1) { const start = [x, y] const endHoriz = [x + ship.size, y + 1] if (boardSliceIsEmpty(board, start, endHoriz)) { const placed = boardPlace(board, start, endHoriz, ship) const subboards = generateBoards(placed, shipsRest, filter, res) } const endVert = [x + 1, y + ship.size] if (boardSliceIsEmpty(board, start, endVert)) { const placed = boardPlace(board, start, endVert, ship) const subboards = generateBoards(placed, shipsRest, filter, res) } } } return res } function boardScan(board, callback) { for (let y = 0; y < board.length; y += 1) { const row = board[y] for (let x = 0; x < row.length; x += 1) { const res = callback(x, y, row[x]) if (res !== undefined) return res } } } function boardShoot(board, pos) { const [x, y] = pos const val = board[y][x] const newVal = ( val.type == 'empty' ? { type: 'miss', isKnown: true } : val.type == 'ship' ? { ...val, isHit: true, isKnown: true } : null ) if (!newVal) throw new Error("Huh?") const res = boardPlace(board, [x, y], [x + 1, y + 1], newVal) if (newVal.type == 'ship') { // check for sink let shipParts = [] boardScan(res, (x, y, val) => { if (val.type == 'ship' && val.num == newVal.num) { if (!val.isHit) { shipParts = [] return false } shipParts.push([x, y, val]) if (shipParts.length == val.size) { return false } } }) shipParts.forEach(([x, y, ship]) => { res[y][x] = {...ship, isSunk: true} }) } return res } function playHuntAndTarget(board) { // 1. See if there are any hits that have not been sunk const firstHits = [] boardScan(board, (x, y, val) => { if (val.type == 'ship' && val.isHit && !val.isSunk) firstHits.push([x, y, val]) }) while (firstHits.length > 0) { const [x, y, val] = firstHits.pop() const targets = [ [x - 1, y], [x + 1, y], [x, y - 1], [x, y + 1], ] for (const [x, y] of targets) { const val = board[y] && board[y][x] const doNotShoot = ( val === undefined || val.isKnown ) if (doNotShoot) continue return boardShoot(board, [x, y]) } } // 2. Pick the first odd non-hit spot const spot = boardScan(board, (x, y, val) => { if (x % 2 + y % 2 != 1) return if (val.type == 'empty' || val.type == 'ship' && !val.isHit) return [x, y] }) if (spot) { const [x, y] = spot return boardShoot(board, [x, y]) } return null } function playProbabilistic(board, ships) { const heatmap = boardEmpty(board.length, 0) const boardBitmask = boardEmpty(board.length, 0) const sunkShips = {} const hits = [] boardScan(board, (x, y, val) => { if (val.isSunk) sunkShips[val.num] = true if (val.isHit && !val.isSunk) hits.push([x, y]) boardBitmask[y][x] = ( val.isSunk || val.type == 'miss' ? { type: 'not-empty' } : { type: 'empty' } ) }) let maxCount = 0 const allPositions = generateBoards( boardBitmask, ships.filter(s => !sunkShips[s.num]), filterBoard => { let coversAllHits = hits.filter(([x, y]) => { return filterBoard[y][x].type == 'ship' }) return coversAllHits.length == hits.length }, ) allPositions.forEach(b => { boardScan(b, (x, y, val) => { if (val.type == 'ship' && !board[y][x].isHit) { heatmap[y][x] += 1 const val = heatmap[y][x] if (val > maxCount) maxCount = val } }) }) if (maxCount == 0) { return null } let maxLoc = null boardScan(heatmap, (x, y, val) => { heatmap[y][x] = val / maxCount if (val == maxCount) maxLoc = [x, y] }) return [boardShoot(board, maxLoc), heatmap] } function boardRender(board) { if (!board) return "(no board)" const res = [``] board.forEach(row => { res.push(``) res.push(...row.map(space => { const classes = [ space.type, space.isHit && 'hit', space.isSunk && 'sunk', ] return `` })) res.push(``) }) res.push(...'
${space.size || ''}
') return res.join('') } function heatmapRender(board) { if (!board) return "(no board)" const res = [``] board.forEach(row => { res.push(``) res.push(...row.map(space => { return `` })) res.push(``) }) res.push(...'
\n') return res.join('') } (() => { const ships = SHIPS.slice(0, 4) const boards = generateBoards(boardEmpty(7), ships) let b = boards[parseInt(Math.random() * boards.length)] let heatmap let count = 0 while (b) { document.write(boardRender(b)) const x = playProbabilistic(b, ships) if (!x) break b = x[0] heatmap = x[1] document.write(heatmapRender(heatmap)) document.write("
") if (count++ > 100) break } })()