diff --git a/src/components/GameBoard.component.js b/src/components/GameBoard.component.js index 3805d73..04fd48b 100644 --- a/src/components/GameBoard.component.js +++ b/src/components/GameBoard.component.js @@ -1,5 +1,6 @@ import {Component} from '../../lib/vues6.js' -import {Player} from '../module/util.js' +import {ShipType, isShipCell} from '../module/util.js' +import game_service from '../services/GameState.service.js' /* * This is the HTML/JavaScript for the game board component. @@ -20,7 +21,7 @@ import {Player} from '../module/util.js' * Battleship grid is 14x14. */ const template = ` -
+

{{ i + 1 }} @@ -28,6 +29,8 @@ const template = ` v-for="(cell,j) of row" v-bind:render="cell.render" @click="on_cell_click(i,j)" + @hover="on_cell_hover(i,j)" + v-bind:has_ghost_ship="is_ghost_cell(i,j)" >
@@ -39,7 +42,7 @@ const template = ` export default class GameBoardComponent extends Component { static get selector() { return 'app-game-board' } static get template() { return template } - static get props() { return ['rows'] } + static get props() { return ['rows', 'is_placement_mode'] } /** * If true, the grid is ready to be rendered. If false, @@ -49,18 +52,150 @@ export default class GameBoardComponent extends Component { ready = false /** - * Array of grid rows. Each element in this array is itself - * an array of grid cell values. - * @type {Array>} + * The various column labels to display. + * @type {string[]} */ - // rows = [] column_labels = ["A", "B", "C", "D", "E", "F", "G", "H", "I"] + /** + * Array of coordinates as [row_index, column_index] of cells which should + * show a ghost ship overlay. + * @type {[number, number][]} + */ + ship_ghost_cells = [] + + /** + * The ship currently being placed. + * @type {string} + */ + current_placement = ShipType.x3 + + /** + * Set to true when the shift key is pressed. + * @type {boolean} + */ + shift_pressed = false + + /** + * Array of functions bound to event listeners. Used to + * remove event listeners on destroy. + * @type {function[]} + */ + bound_fns = [] + async vue_on_create() { this.ready = true + + // We need to listen for keyup/keydown so we can tell when the user has + // pressed/released the shift key. + const keyup_fn = this.on_keyup.bind(this) + const keydown_fn = this.on_keydown.bind(this) + this.bound_fns.push(keyup_fn, keydown_fn) + + window.addEventListener('keyup', keyup_fn) + window.addEventListener('keydown', keydown_fn) + } + + async vue_on_destroy() { + // Remove the event listeners for the shift key + const [keyup_fn, keydown_fn] = this.bound_fns + window.removeEventListener('keyup', keyup_fn) + window.removeEventListener('keydown', keydown_fn) } on_cell_click(row_i, cell_i) { - alert(`${row_i} : ${cell_i}`) + + } + + /** + * Called when the user hovers over a cell. + * When in placement mode, this updates the cells that show the ghost ship. + * @param {number} row_i + * @param {number} cell_i + */ + on_cell_hover(row_i, cell_i) { + if ( this.is_placement_mode ) { + const ghost_cells = [[row_i, cell_i]] + const is_horizontal = this.shift_pressed + let is_valid_hover = true + + if ( !is_horizontal ) { + const num_cells = game_service.get_ship_length(this.current_placement) + for ( let i = row_i + 1; i < row_i + num_cells; i += 1 ) { + ghost_cells.push([i, cell_i]) + if ( i > 8 ) is_valid_hover = false + } + } else { + const num_cells = game_service.get_ship_length(this.current_placement) + for ( let i = cell_i + 1; i < cell_i + num_cells; i += 1 ) { + ghost_cells.push([row_i, i]) + if ( i > 8 ) is_valid_hover = false + } + } + + // Don't allow placing on existing ships + is_valid_hover = !ghost_cells.some(([row_i, col_i]) => this.is_ship_cell(row_i, col_i)) + + if ( is_valid_hover ) { + this.ship_ghost_cells = ghost_cells + } else { + this.ship_ghost_cells = [] + } + } else { + this.ship_ghost_cells = [] + } + } + + /** + * Returns true if the cell at [row_index, column_index] is a ship. + * @param {number} row_i + * @param {number} col_i + * @return {boolean} + */ + is_ship_cell(row_i, col_i) { + return isShipCell(this.rows[row_i][col_i].render) + } + + /** + * Hide the ghost ship when the mouse leaves the grid. + */ + on_mouse_leave() { + this.ship_ghost_cells = [] + } + + /** + * Returns a truthy value if the given cell is a ghost ship. + * @param {number} row_i + * @param {number} col_i + * @return {boolean} + */ + is_ghost_cell(row_i, col_i) { + return !!this.ship_ghost_cells.find(([cell_row_i, cell_col_i]) => cell_row_i === row_i && cell_col_i === col_i) + } + + /** + * When keydown, check if shift was pressed. If so, update the placement. + * @param event + */ + on_keydown(event) { + if ( event.key === 'Shift' ) { + this.shift_pressed = true + if ( this.ship_ghost_cells.length > 0 ) { + this.on_cell_hover(this.ship_ghost_cells[0][0], this.ship_ghost_cells[0][1]) + } + } + } + + /** + * When keyup, check if shift was released. If so, update the placement. + * @param event + */ + on_keyup(event) { + if ( event.key === 'Shift' ) { + this.shift_pressed = false + if ( this.ship_ghost_cells.length > 0 ) { + this.on_cell_hover(this.ship_ghost_cells[0][0], this.ship_ghost_cells[0][1]) + } + } } } diff --git a/src/components/GridCell.component.js b/src/components/GridCell.component.js index 738ad0a..74f6d35 100644 --- a/src/components/GridCell.component.js +++ b/src/components/GridCell.component.js @@ -5,9 +5,11 @@ const template = `
@@ -20,6 +22,7 @@ export default class GridCellComponent extends Component { static get props() { return [ 'render', + 'has_ghost_ship', ] } @@ -29,4 +32,12 @@ export default class GridCellComponent extends Component { on_click() { this.$emit('click') } + + on_hover($event) { + this.$emit('hover', $event) + } + + on_mouse_leave() { + this.$emit('hoverchange') + } } diff --git a/src/components/TopLevel.component.js b/src/components/TopLevel.component.js index ea86f0c..00bc6ae 100644 --- a/src/components/TopLevel.component.js +++ b/src/components/TopLevel.component.js @@ -21,7 +21,7 @@ const template = `
- +
@@ -53,6 +53,12 @@ export default class TopLevelComponent extends Component { instructions = '' + /** + * True if the player should be able to place their ships. + * @type {boolean} + */ + player_is_placing_ships = false + async vue_on_create() { console.log('game service', game_service) this.current_state = game_service.get_game_state() @@ -60,6 +66,7 @@ export default class TopLevelComponent extends Component { this.current_state = next_state this.opponent_rows = game_service.get_current_opponent_state() this.player_rows = game_service.get_current_player_state() + this.player_is_placing_ships = next_state === GameState.PlayerSetup // add code for instructions }) diff --git a/src/style/components.css b/src/style/components.css index 6f5a05b..6f6ec5f 100644 --- a/src/style/components.css +++ b/src/style/components.css @@ -66,6 +66,10 @@ background: #ffbbbb; } +.game-board-cell-component.ghost { + background: #507090; +} + .column_labels { display: flex; margin-top: 5px;