From aaadd82d319b143a046ad160894fcaadb20772d2 Mon Sep 17 00:00:00 2001 From: garrettmills Date: Tue, 8 Sep 2020 07:39:09 -0500 Subject: [PATCH] Add helper methods to game service for firing on opponent (#2) --- src/services/GameState.service.js | 70 ++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/services/GameState.service.js b/src/services/GameState.service.js index 7a5580e..572a635 100644 --- a/src/services/GameState.service.js +++ b/src/services/GameState.service.js @@ -1,5 +1,5 @@ -import { Player, GridCellState, GameState, clone, ShipType, isShipType, isShipCell } from '../module/util.js' -import { InvalidShipPlacementError, InvalidAdvanceStateError } from '../module/errors.js' +import { Player, GridCellState, GameState, clone, ShipType, isShipType, isShipCell, isValidTargetCell } from '../module/util.js' +import { InvalidShipPlacementError, InvalidAdvanceStateError, InvalidMissileFireAttemptError } from '../module/errors.js' /** * Singleton service for managing the state of the game. @@ -261,6 +261,60 @@ export class GameStateService { } } + /** + * Attempt to fire a missile at the current opponent at the given coordinates. + * The coordinates should be an array of [row_index, column_index] where the missile should fire. + * Returns true if the missile hit an undamaged cell of a ship. + * + * @example + * If I want to fire a missile at row 5 column 7, then: + * game_service.attempt_missile_fire([5, 7]) + * + * @param {[number, number]} coords + * @return {boolean} + */ + attempt_missile_fire([target_row_i, target_col_i]) { + const target_cell = this._get_cell_state(this.current_opponent, target_row_i, target_col_i) + if ( !isValidTargetCell(target_cell.render) ) + throw new InvalidMissileFireAttemptError('Cannot fire on cell with state: ' + target_cell.render) + + if ( target_cell.render === GridCellState.Ship ) { + // We hit an un-hit ship cell! + this._set_cell_state(this.current_opponent, target_row_i, target_col_i, GridCellState.Damaged) + + // set ships to sunk where appropriate + this._sink_damaged_ships(this.current_opponent) + return true + } else if ( target_cell.render === GridCellState.Available ) { + // We missed... + this._set_cell_state(this.current_opponent, target_row_i, target_col_i, GridCellState.Missed) + } + + return false + } + + /** + * Checks the player's ships. If any are fully damaged, it flags that ship's cells + * as "sunk" rather than damaged. + * @param {Player} player + * @private + */ + _sink_damaged_ships(player) { + this.get_ship_entities(player).some(ship => { + const covered_cells = this.get_covered_cells(ship.coords_one, ship.coords_two) + const all_damaged = covered_cells.every(([cell_row, cell_col]) => { + return this._get_cell_state(player, cell_row, cell_col).render === GridCellState.Damaged + }) + + if ( all_damaged ) { + // The entire boat was damaged, so sink it + covered_cells.some(([cell_row, cell_col]) => { + this._set_cell_state(player, cell_row, cell_col, GridCellState.Sunk) + }) + } + }) + } + /** * Attempt to place a ship of the given type at the given coordinates. * Throws an InvalidShipPlacementError if the coordinates are invalid. @@ -454,6 +508,18 @@ export class GameStateService { _set_cell_state(player, row_i, col_i, state) { this.player_x_game_board[player][row_i][col_i].render = state } + + /** + * Get the state of the cell at the given coordinates on the player's board. + * @param {Player} player + * @param {number} row_i + * @param {number} col_i + * @return {object} + * @private + */ + _get_cell_state(player, row_i, col_i) { + return this.player_x_game_board[player][row_i][col_i] + } } // Export a single instance, so it can be shared by all files