UNPKG

battleships-engine

Version:
339 lines (332 loc) 9.93 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Coords: () => Coords, GameBoard: () => GameBoard, Ship: () => Ship }); module.exports = __toCommonJS(src_exports); // src/consts.ts var shipsLength = { aircraft_carrier: 5, battleship: 4, destroyer: 3, submarine: 3, cruiser: 2 }; var directionTypes = ["vert", "hor"]; var coordsType = ["x", "y"]; var numberRegExp = /\d+/; // src/coords.ts var import_random = __toESM(require("lodash/random"), 1); var Coords = class { x = (0, import_random.default)(1, 10); y = (0, import_random.default)(1, 10); constructor(coords) { if (coords) { const { x, y } = coords; if (x > 10) { throw new Error("X should be less than or equal to 10"); } else { this.x = x; } if (y > 10) { throw new Error("Y should be less than or equal to 10"); } else { this.y = y; } if (x < 1) { throw new Error("X should be greater than 0"); } else { this.x = x; } if (y < 1) { throw new Error("Y should be greater than 0"); } else { this.y = y; } } } toString() { return `(${this.x},${this.y})`; } *[Symbol.iterator]() { for (const coordType of coordsType) { yield { type: coordType, number: this[coordType] }; } } }; // src/ship.ts var Ship = class { length = 0; type; beenHitTimes = 0; coords; direction = "hor"; constructor({ coords, type, direction }) { if (!Object.keys(shipsLength).includes(type)) throw new Error("Invalid ship type"); this.type = type; this.length = shipsLength[type]; if (this.length < 2) throw new Error("Length should more than or equal to 2"); if (!directionTypes.includes(direction)) throw new Error("Invalid direction type"); this.coords = new Coords(coords); this.direction = direction; } hit() { if (this.beenHitTimes >= this.length) { throw new Error("This ship has already sunk."); } this.beenHitTimes++; } isSunk() { return this.beenHitTimes === this.length; } *[Symbol.iterator]() { for (let i = 0; i < this.length; i++) { let obj = { x: this.coords.x, y: this.coords.y + i }; if (this.direction === "hor") obj = { x: this.coords.x + i, y: this.coords.y }; yield { toString: new Coords(obj).toString, ...obj }; } } }; // src/utils.ts var import_random2 = __toESM(require("lodash/random"), 1); var generateRandomCoords = () => new Coords({ x: (0, import_random2.default)(1, 10), y: (0, import_random2.default)(1, 10) }); var generateRandomDir = () => directionTypes[(0, import_random2.default)(1)]; var generateRandomShip = ({ gameboard, shipType }) => { let coords = generateRandomCoords(); const direction = generateRandomDir(); if (direction === "hor" && coords.x + shipsLength[shipType] > 10) { const { x, y } = coords; coords.x = x - 1; } else if (direction === "vert" && coords.y + shipsLength[shipType] > 10) { const { x, y } = coords; coords.y = y - 1; } try { gameboard.placeShip({ coords, direction, type: shipType }); } catch (e) { generateRandomShip({ gameboard, shipType }); } }; var generateGameBoardCells = () => { const map = /* @__PURE__ */ new Map(); coordsType.forEach((coordsType2) => { for (let i = 1; i <= 10; i++) { for (let j = 1; j <= 10; j++) { map.set(`(${i},${j})`, false); } } }); return map; }; var convertStringToCoords = (str) => { const [x, y] = str.split(",").map((word) => { if (word) { const matches = word.match(numberRegExp); if (!matches) return; return Number(matches[0]); } }); if (!x || !y) throw new Error("Invalid string provided"); return { x, y }; }; // src/gameboard.ts var import_random3 = __toESM(require("lodash/random"), 1); var GameBoard = class { ships = /* @__PURE__ */ new Map(); takenCells = /* @__PURE__ */ new Map(); missed = generateGameBoardCells(); hitCells = generateGameBoardCells(); constructor(ships) { if (ships) { ships.forEach((ship) => this.ships.set(ship.type, ship)); this.ships.forEach( (...params) => this.fillTakenCellsWithShip(...params) ); } } fillTakenCellsWithShip(ship, shipType, _map) { for (const coord of ship) this.takenCells.set(coord.toString(), shipType); } inspectCoordsInShips({ coords: paramCoords, missCb, matchCb }) { if (this.ships.size > 0) { const coords = new Coords(paramCoords); const shipType = this.takenCells.get(coords.toString()); if (shipType) { const ship = this.ships.get(shipType); if (!ship) throw new Error(`${shipType} does not exist`); matchCb(ship); } else missCb(); } else missCb(); } placeShip(params) { this.inspectCoordsInShips({ coords: params.coords, missCb: () => { const newShip = new Ship(params); for (const coords of newShip) { if (this.takenCells.has(coords.toString())) { throw new Error( "Ship placement error: The ship overlaps with another ship." ); } } this.ships.set(params.type, newShip); this.fillTakenCellsWithShip(newShip, params.type); }, matchCb: () => { throw new Error( "Ship placement error: The ship overlaps with another ship." ); } }); } removeShip(ship) { this.ships.delete(ship.type); for (const coords of ship) { this.takenCells.delete(coords.toString()); } } moveShip(startingShip, newShipInfo) { this.removeShip(startingShip); this.placeShip({ type: startingShip.type, ...newShipInfo }); } receiveAttack(coords) { const coordsClass = new Coords(coords); if (this.missed.get(coordsClass.toString()) === true) throw new Error( `The coordinate (X: ${coords.x}, Y: ${coords.y}) has already been targeted and missed.` ); const fromTaken = this.takenCells.get(coordsClass.toString()); if (fromTaken) { const ship = this.ships.get(fromTaken); if (!ship) throw new Error(`${fromTaken} does not exist`); else { ship.hit(); this.hitCells.set(coordsClass.toString(), true); } } else this.missed.set(coordsClass.toString(), true); } hasLost() { const currShips = Array.from(this.ships.keys()); if (currShips.length > 0) { return !currShips.map((ship) => this.ships.get(ship)?.isSunk()).includes(false); } else return false; } randomlyPlaceShip({ type, direction = generateRandomDir() }) { if (this.takenCells.size > 0) { const allCells = generateGameBoardCells(); const emptyCells = []; for (const [cell] of allCells) { if (!this.takenCells.has(cell)) emptyCells.push(cell); } const possibleStarts = emptyCells.filter((str) => { const { x, y } = convertStringToCoords(str); const newShip = new Ship({ coords: { x, y }, direction, type }); let isValid = true; if (direction === "hor") isValid = x + shipsLength[type] <= 10; else isValid = y + shipsLength[type] <= 10; if (isValid) { for (const coord of newShip) { isValid = !this.takenCells.has(coord.toString()); if (!isValid) break; } } else { return false; } return isValid; }); if (possibleStarts.length === 0) { this.randomlyPlaceShip({ type, direction: directionTypes.find((dir) => dir !== direction) }); } else { const randomStart = possibleStarts[(0, import_random3.default)(possibleStarts.length - 1)]; if (!randomStart) throw new Error("No available space"); this.placeShip({ type, coords: convertStringToCoords(randomStart), direction }); } } else { generateRandomShip({ gameboard: this, shipType: type }); } } randomlyPlaceShips() { this.ships = /* @__PURE__ */ new Map(); this.takenCells = /* @__PURE__ */ new Map(); Object.keys(shipsLength).forEach( (type) => this.randomlyPlaceShip({ type }) ); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Coords, GameBoard, Ship });