<commit_before>import EventEmitter from './event-emitter.js';
import PlayerState from './player-state.js';
import Grid from './grid.js';
import Ship from './ship.js';
import Matrix from './matrix.js';
import { toMap, getRelativeItem } from './utils.js';

/**
 * @typedef BattleshipRules
 * @type {object}
 * @property {array} grid - a tuple representing the grid size in columns and rows.

 * @typedef BattleshipGameInit
 * @type {object}
 * @property {array} players - an array of exactly two players.
 * @property {BattleshipRules} [rules] - the rules for the battleship game.
 */

/** Class representing a round of battleships. */
export default class BattleshipGame extends EventEmitter {

    /**
     * Create a battleship game with specified players and rules.
     * @param  {BattleshipGameInit} init - the initialization options for the game.
     */
    constructor({ players, rules = BattleshipGame.DEFAULT_RULES } = {}) {

        if (Array.isArray(players) !== true || players.length !== 2) {
            throw new TypeError(`The init object must specify an array of exactly two 'players'.`);
        }

        if (rules.grid.some(size => 0 >= size)) {
            throw new TypeError(`The rules grid dimensions must be greater than 0.`);
        }

        super();

        this.rules = rules;
        this.players = players;

        this._state = BattleshipGame.STATE_STARTING;

        // build a map of all player states, whist we do this, let's hookup listeners for the
        // player state's state changing so we can automatically change our game state.
        this.playerStates = players::toMap(player => player, player => {

            const ships = rules.ships.map(shape => new Ship(shape));

            const grid = new Grid(rules.grid, ships);

            const playerState = new PlayerState({ player, grid });

            playerState.addEventListener('state-change', ::this._onPlayerStateChange);
            playerState.addEventListener('request-fire', ::this._onPlayerFire);

            player.dispatchEvent({ type: 'joined-game', game: this, state: playerState });

            return playerState;

        });

        this.state = BattleshipGame.STATE_PLACEMENT;

    }

    get state() {
        return this._state;
    }

    set state(value) {

        // todo: validate the state change is legit

        this._state = value;
        this.dispatchEvent({ type: 'state-change' });

        // if we're changing to the attack phase then we need to start the player turns loop
        if (value === BattleshipGame.STATE_ATTACK) {
            this._nextTurn();
        }

    }

    _nextTurn() {

        // here we establish a running order for each player, this will be based on the
        // order the players were provided to the game; any player state actions must
        // be validated to be acceptable only when they are the active player.

        const playerStates = [...this.playerStates.values()];<commit_msg>don't auto start the battlefield game, add start method to do so<commit_after>import EventEmitter from './event-emitter.js';
import PlayerState from './player-state.js';
import Grid from './grid.js';
import Ship from './ship.js';
import Matrix from './matrix.js';
import { toMap, getRelativeItem } from './utils.js';

/**
 * @typedef BattleshipRules
 * @type {object}
 * @property {array} grid - a tuple representing the grid size in columns and rows.

 * @typedef BattleshipGameInit
 * @type {object}
 * @property {array} players - an array of exactly two players.
 * @property {BattleshipRules} [rules] - the rules for the battleship game.
 */

/** Class representing a round of battleships. */
export default class BattleshipGame extends EventEmitter {

    /**
     * Create a battleship game with specified players and rules.
     * @param  {BattleshipGameInit} init - the initialization options for the game.
     */
    constructor({ players, rules = BattleshipGame.DEFAULT_RULES } = {}) {

        if (Array.isArray(players) !== true || players.length !== 2) {
            throw new TypeError(`The init object must specify an array of exactly two 'players'.`);
        }

        if (rules.grid.some(size => 0 >= size)) {
            throw new TypeError(`The rules grid dimensions must be greater than 0.`);
        }

        super();

        this.rules = rules;
        this.players = players;

        this._state = BattleshipGame.STATE_STARTING;

        // build a map of all player states, whist we do this, let's hookup listeners for the
        // player state's state changing so we can automatically change our game state.
        this.playerStates = players::toMap(player => player, player => {

            const ships = rules.ships.map(shape => new Ship(shape));

            const grid = new Grid(rules.grid, ships);

            const playerState = new PlayerState({ player, grid });

            playerState.addEventListener('state-change', ::this._onPlayerStateChange);
            playerState.addEventListener('request-fire', ::this._onPlayerFire);

            player.dispatchEvent({ type: 'joined-game', game: this, state: playerState });

            return playerState;

        });

    }

    start() {

        if (this.state !== BattleshipGame.STATE_STARTING) {
            throw new Error(`ATTEMPT_TO_START_ALREADY_STARTED_GAME`);
        }

        this.state = BattleshipGame.STATE_PLACEMENT;
    }

    get state() {
        return this._state;
    }

    set state(value) {

        // todo: validate the state change is legit

        this._state = value;
        this.dispatchEvent({ type: 'state-change' });

        // if we're changing to the attack phase then we need to start the player turns loop
        if (value === BattleshipGame.STATE_ATTACK) {
            this._nextTurn();
        }

    }

    _nextTurn() {

        // here we establish a running order for each player, this will be based on the
        // order the players were provided to the game; any player state actions must
        // be validated to be acceptable only when they are the active player.

        const playerStates = [...this.playerStates.values()];
{'fraction_non_alphanumeric': 0.10704408654628274, 'fraction_numerical': 0.0009760858955588092, 'mean_word_length': 2.5414746543778803, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2949062', 'n_tokens_mistral': 1662, 'n_tokens_neox': 1593, 'n_words': 583}