2020-09-05 02:42:09 +00:00
|
|
|
import {Component} from '../../lib/vues6.js'
|
2020-09-10 12:44:53 +00:00
|
|
|
import {ShipType, isShipCell} from '../module/util.js'
|
|
|
|
import game_service from '../services/GameState.service.js'
|
2020-09-05 02:42:09 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This is the HTML/JavaScript for the game board component.
|
|
|
|
* The "template" variable at the top defines the HTML for this component. It can contain references
|
|
|
|
* to methods and properties on the "GameBoardComponent" class shown below.
|
|
|
|
*
|
|
|
|
* For example, the "greeting" property is referenced in the template as "{{ greeting }}". When
|
|
|
|
* the page loads, this will be replaced by the value of the "greeting" property.
|
|
|
|
*
|
|
|
|
* The class below manages the logic referenced by the template. Then, we can use the component
|
|
|
|
* in the application by creating an HTML tag with the value of the "selector()" getter.
|
|
|
|
*
|
|
|
|
* In this case, that's the <app-game-board></app-game-board> tag in index.html.
|
|
|
|
*
|
|
|
|
* We can also use components w/in components, to keep code clean. For example, we could have
|
|
|
|
* a battleship component that we reference inside this game board component.
|
2020-09-06 22:38:29 +00:00
|
|
|
*
|
|
|
|
* Battleship grid is 14x14.
|
2020-09-05 02:42:09 +00:00
|
|
|
*/
|
|
|
|
const template = `
|
2020-09-10 12:44:53 +00:00
|
|
|
<div class="game-board-component" v-if="ready" @mouseleave="on_mouse_leave">
|
2020-09-06 22:38:29 +00:00
|
|
|
<div class="grid-container">
|
2020-09-08 02:06:06 +00:00
|
|
|
<div class="grid-row" v-for="(row,i) of rows">
|
2020-09-09 16:23:50 +00:00
|
|
|
<br> <span class="label">{{ i + 1 }}</span>
|
2020-09-06 22:38:29 +00:00
|
|
|
<app-game-cell
|
2020-09-10 00:44:09 +00:00
|
|
|
v-for="(cell,j) of row"
|
2020-09-06 22:38:29 +00:00
|
|
|
v-bind:render="cell.render"
|
2020-09-10 00:44:09 +00:00
|
|
|
@click="on_cell_click(i,j)"
|
2020-09-10 12:44:53 +00:00
|
|
|
@hover="on_cell_hover(i,j)"
|
|
|
|
v-bind:has_ghost_ship="is_ghost_cell(i,j)"
|
2020-09-06 22:38:29 +00:00
|
|
|
></app-game-cell>
|
|
|
|
</div>
|
2020-09-09 16:23:50 +00:00
|
|
|
<div class="column_labels">
|
|
|
|
<div class="label" v-for="(label,i) of column_labels">{{ label }}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-09-05 02:42:09 +00:00
|
|
|
</div>
|
|
|
|
`
|
|
|
|
export default class GameBoardComponent extends Component {
|
|
|
|
static get selector() { return 'app-game-board' }
|
|
|
|
static get template() { return template }
|
2020-09-12 19:04:17 +00:00
|
|
|
static get props() { return ['rows', 'is_placement_mode', 'ships_to_place', 'is_missile_mode'] }
|
2020-09-05 02:42:09 +00:00
|
|
|
|
2020-09-06 22:38:29 +00:00
|
|
|
/**
|
|
|
|
* If true, the grid is ready to be rendered. If false,
|
|
|
|
* the grid will be hidden.
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
|
|
|
ready = false
|
|
|
|
|
|
|
|
/**
|
2020-09-10 12:44:53 +00:00
|
|
|
* The various column labels to display.
|
|
|
|
* @type {string[]}
|
2020-09-06 22:38:29 +00:00
|
|
|
*/
|
2020-09-08 02:06:06 +00:00
|
|
|
column_labels = ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
|
2020-09-06 22:38:29 +00:00
|
|
|
|
2020-09-10 12:44:53 +00:00
|
|
|
/**
|
|
|
|
* 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 = []
|
|
|
|
|
2020-09-05 02:42:09 +00:00
|
|
|
async vue_on_create() {
|
2020-09-09 16:34:13 +00:00
|
|
|
this.ready = true
|
2020-09-10 12:44:53 +00:00
|
|
|
|
|
|
|
// 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)
|
2020-09-05 02:42:09 +00:00
|
|
|
}
|
2020-09-10 00:44:09 +00:00
|
|
|
|
|
|
|
on_cell_click(row_i, cell_i) {
|
2020-09-10 13:17:48 +00:00
|
|
|
if ( this.is_placement_mode && this.ships_to_place[0] ) {
|
|
|
|
// We should try to place a ship here
|
|
|
|
if ( this.ship_ghost_cells.length > 0 ) {
|
|
|
|
// We have some valid ship placement coordinates
|
|
|
|
const coord_one = this.ship_ghost_cells[0]
|
|
|
|
const coord_two = this.ship_ghost_cells.slice(-1)[0]
|
2020-09-10 12:44:53 +00:00
|
|
|
|
2020-09-10 13:17:48 +00:00
|
|
|
game_service.place_ship(this.ships_to_place[0], coord_one, coord_two)
|
|
|
|
this.$emit('shipplaced')
|
2020-09-12 19:04:17 +00:00
|
|
|
this.$emit('missilefired',[row_i ,cell_i])
|
2020-09-10 13:17:48 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-10 12:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 ) {
|
2020-09-10 13:17:48 +00:00
|
|
|
// If we're in placement mode, determine if the cell the user is hovering
|
|
|
|
// over is a valid place to place the ship.
|
2020-09-10 12:44:53 +00:00
|
|
|
const ghost_cells = [[row_i, cell_i]]
|
|
|
|
const is_horizontal = this.shift_pressed
|
|
|
|
let is_valid_hover = true
|
|
|
|
|
|
|
|
if ( !is_horizontal ) {
|
2020-09-10 13:17:48 +00:00
|
|
|
const num_cells = game_service.get_ship_length(this.ships_to_place[0])
|
2020-09-10 12:44:53 +00:00
|
|
|
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 {
|
2020-09-10 13:17:48 +00:00
|
|
|
const num_cells = game_service.get_ship_length(this.ships_to_place[0])
|
2020-09-10 12:44:53 +00:00
|
|
|
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
|
2020-09-10 12:48:11 +00:00
|
|
|
is_valid_hover = is_valid_hover && !ghost_cells.some(([row_i, col_i]) => this.is_ship_cell(row_i, col_i))
|
2020-09-10 12:44:53 +00:00
|
|
|
|
|
|
|
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) {
|
2020-09-10 12:48:11 +00:00
|
|
|
return this.rows[row_i] && this.rows[row_i][col_i] && isShipCell(this.rows[row_i][col_i].render)
|
2020-09-10 12:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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])
|
|
|
|
}
|
|
|
|
}
|
2020-09-10 00:44:09 +00:00
|
|
|
}
|
2020-09-05 02:42:09 +00:00
|
|
|
}
|