import _ from 'lodash' import { lacuna, call } from '@tlecommunity/super-core' // `get_star_map` rejects a rectangle whose area exceeds 3001, so each window is a // 54x54 square (2916). Rings step outward by a full window width. const HALF = 27 const WINDOW = HALF * 2 /** * Euclidean distance between two `{ x, y }` points. */ export function distance(a, b) { let dx = a.x - b.x let dy = a.y - b.y return Math.sqrt(dx * dx + dy * dy) } // Window centres for the square Chebyshev ring `k` around the origin. function ringCentres(origin, k) { if (k === 0) { return [{ x: origin.x, y: origin.y }] } let centres = [] for (let dx = -k; dx <= k; dx++) { for (let dy = -k; dy <= k; dy++) { if (Math.max(Math.abs(dx), Math.abs(dy)) === k) { centres.push({ x: origin.x + dx * WINDOW, y: origin.y + dy * WINDOW }) } } } return centres } /** * Scan the star map outward from `origin`, one 54-wide ring at a time (closest * ring first), calling `collect(star)` for every distinct star returned. `collect` * should return truthy when the star yielded a usable candidate; scanning stops * once `enough(candidateCount)` is truthy or `maxRing` rings have been scanned. * * @param {{x: number, y: number}} origin * @param {Object} opts * @param {Function} opts.collect * @param {?Function} opts.enough * @param {?number} opts.maxRing */ export async function scanOutward(origin, { collect, enough, maxRing = 2 }) { let seen = new Set() let count = 0 for (let k = 0; k <= maxRing; k++) { for (const centre of ringCentres(origin, k)) { let res = await call(() => lacuna.map.getStarMap({ left: Math.round(centre.x - HALF), right: Math.round(centre.x + HALF), bottom: Math.round(centre.y - HALF), top: Math.round(centre.y + HALF), }) ) for (const star of res.stars || []) { if (seen.has(star.id)) { continue } seen.add(star.id) if (collect(star)) { count++ } } } if (typeof enough === 'function' && enough(count)) { return } } } /** * Clamp a desired ship-build quantity to the room actually available in a * planet's Shipyard queue and Space Port docks. * * @param {object} shipyard - a Shipyard building (needs `.id`) * @param {number} desired - how many ships the caller wants to build * @returns {Promise} the number that will actually fit (>= 0) */ export async function buildRoom(shipyard, desired) { if (desired <= 0) { return 0 } let buildable = await call(() => lacuna.shipyard.getBuildable({ building_id: shipyard.id })) let dockSpace = parseInt(buildable.docks_available, 10) || 0 let queueSpace = (parseInt(buildable.build_queue_max, 10) || 0) - (parseInt(buildable.build_queue_used, 10) || 0) return _.max([0, _.min([desired, dockSpace, queueSpace])]) }