<commit_before>import assert from 'assert';
import PanelsActionCreator from '../actions/panels-action-creator';
import SculptureActionCreator from '../actions/sculpture-action-creator';
import MoleGameActionCreator from '../actions/mole-game-action-creator';
import TrackedPanels from '../utils/tracked-panels';
import COLORS from '../constants/colors';

export default class MoleGameLogic {
  // These are automatically added to the sculpture store
  static trackedProperties = {
    panelCount: 0, // Game progress (0..30)
    panels: new TrackedPanels()  // panel -> state
  };

  constructor(store, config) {
    this.store = store;
    this.config = config;
    this.gameConfig = config.MOLE_GAME;

    this._complete = false;

    // Unique panel objects: panelKey -> panel
    this._panels = {};
    this.config.GAME_STRIPS.forEach(stripId => {
      this._lights.get(stripId).panelIds.forEach((panelId) => {
        const key = this._hash(stripId, panelId);
        this._panels[key] = { stripId, panelId, key };
      });
    });

    // Force RGB strips to black.
    // FIXME: This is a temporary fix for a firmware bug not respecting intensity
    this._lights.setColor(this.config.LIGHTS.RGB_STRIPS, null, COLORS.BLACK);

    this.moleGameActionCreator = new MoleGameActionCreator(this.store.dispatcher);
  }

  get data() {
    return this.store.data.get('mole');
  }

  /*!
   * Starts the game logic. Only one of the sculptures should call this method.
   */
  start() {
    this._initRemainingPanels();
    this._complete = false;
    this.data.set('panelCount', 0);
    this.data.get('panels').clear();
    this._registerMoveDelay(0); // Request a new active panel immediately
  }

  /**
   * _remainingPanels is a set of panel keys for panels with STATE_OFF,
   * and are used to select random panels.
   */
  _initRemainingPanels() {
    this._remainingPanels = new Set();
    Object.keys(this._panels).forEach((panelkey) => this._remainingPanels.add(panelkey));
  }

  end() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.deactivateAll(stripId));
  }

  /**
   * handleActionPayload must _synchronously_ change tracked data in sculpture store.
   * Any asynchronous behavior must happen by dispatching actions.
   * We're _not_ allowed to dispatch actions synchronously.
   */
  handleActionPayload(payload) {
    if (this._complete) return;

    const actionHandlers = {
      [PanelsActionCreator.PANEL_PRESSED]: this._actionPanelPressed.bind(this),
      [MoleGameActionCreator.AVAIL_PANEL]: this._actionAvailPanel.bind(this),
      [MoleGameActionCreator.DEAVAIL_PANEL]: this._actionDeavailPanel.bind(this),
      [SculptureActionCreator.FINISH_STATUS_ANIMATION]: this._actionFinishStatusAnimation.bind(this),
      [SculptureActionCreator.MERGE_STATE]: this._actionMergeState.bind(this),
    };

    const actionHandler = actionHandlers[payload.actionType];
    if (actionHandler) actionHandler(payload);
  }

  /**
   * We only have a status animation at the end of the game
   */
  _actionFinishStatusAnimation() {
    this._complete = true;
    // There is currently no transition out, so we can synchronously start the next game
    this.store.moveToNextGame();
  }

  /**
   * Asynchronous panel activation
   */
  _actionAvailPanel(panel) {
    this._availPanel(panel);
  }

  /**
   * Asynchronous panel deactivation
   */
  _actionDeavailPanel(panel) {
    this._deavailPanel(panel);
  }

  /**
   * Local action:
   * 
   * If an active panel is pressed:
   * 1) Turn panel to location color
   * 2) Wait a short moment
   * 3) Avail the next panel
   * 4) increase/decrease # of simulaneously active panels
   */
  _actionPanelPressed(payload) {
    let {stripId, panelId, pressed} = payload;

    const state = this.data.get('panels').getPanelState(stripId, panelId);
    if (state === TrackedPanels.STATE_OFF) {
      if (pressed) {
        this._lights.setColor(stripId, panelId, this.store.locationColor);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.ACTIVE_INTENSITY);
      }
      else {
        this._lights.setToDefaultColor(stripId, panelId);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.INACTIVE_INTENSITY);
      }
      return;
    }

    // If an active panel was touched
    if (state === TrackedPanels.STATE_ON) {
      this._colorPanel(payload);

      // Only for master:
      if (this.store.isMaster()) {
        this._remainingPanels.delete(this._getPanelKey(payload));
        // If we have a timeout on this panel, kill the timeout
        this._removeTimeout(this._getPanelKey(payload));
        this._advanceGame();
      }
    }
  }

  /**
   * Advance game. Should only be called by master
   */
  _advanceGame() {
    let panelCount = this.data.get("panelCount") + 1;
    if (panelCount === this.gameConfig.GAME_END) {
      this._lights.deactivateAll();
      this.store.setSuccessStatus();
    }
    else {
      this.data.set('panelCount', panelCount);
      // Determine whether to add, remove of keep # of simultaneous panels
      const addPanels = 1 + (this.gameConfig.NUM_ACTIVE_PANELS[panelCount] ? this.gameConfig.NUM_ACTIVE_PANELS[panelCount] : 0);
      
      for (let i=0; i<addPanels; i++) {
        // Wait before next panel
        this._registerMoveDelay(this.gameConfig.PANEL_SUCCESS_DELAY);
      }
    }
  }

  /*!
   * Remote action
   * If we're master, we're responsible for correctly advancing the game
   */
  _actionMergeState(payload) {
    if (!payload.mole) return; // Only handle mole state

    const moleData = this.data;
    const moleChanges = payload.mole;
    const moleProps = payload.metadata.props.mole;

    // Master owns the panelCount field
    if (!this.store.isMaster()) {
      if (moleChanges.hasOwnProperty('panelCount')) {
        moleData.set('panelCount', moleChanges.panelCount, moleProps.panelCount);
      }
    }<commit_msg>Always set panelCount in tracked data<commit_after>import assert from 'assert';
import PanelsActionCreator from '../actions/panels-action-creator';
import SculptureActionCreator from '../actions/sculpture-action-creator';
import MoleGameActionCreator from '../actions/mole-game-action-creator';
import TrackedPanels from '../utils/tracked-panels';
import COLORS from '../constants/colors';

export default class MoleGameLogic {
  // These are automatically added to the sculpture store
  static trackedProperties = {
    panelCount: 0, // Game progress (0..30)
    panels: new TrackedPanels()  // panel -> state
  };

  constructor(store, config) {
    this.store = store;
    this.config = config;
    this.gameConfig = config.MOLE_GAME;

    this._complete = false;

    // Unique panel objects: panelKey -> panel
    this._panels = {};
    this.config.GAME_STRIPS.forEach(stripId => {
      this._lights.get(stripId).panelIds.forEach((panelId) => {
        const key = this._hash(stripId, panelId);
        this._panels[key] = { stripId, panelId, key };
      });
    });

    // Force RGB strips to black.
    // FIXME: This is a temporary fix for a firmware bug not respecting intensity
    this._lights.setColor(this.config.LIGHTS.RGB_STRIPS, null, COLORS.BLACK);

    this.moleGameActionCreator = new MoleGameActionCreator(this.store.dispatcher);
  }

  get data() {
    return this.store.data.get('mole');
  }

  /*!
   * Starts the game logic. Only one of the sculptures should call this method.
   */
  start() {
    this._initRemainingPanels();
    this._complete = false;
    this.data.set('panelCount', 0);
    this.data.get('panels').clear();
    this._registerMoveDelay(0); // Request a new active panel immediately
  }

  /**
   * _remainingPanels is a set of panel keys for panels with STATE_OFF,
   * and are used to select random panels.
   */
  _initRemainingPanels() {
    this._remainingPanels = new Set();
    Object.keys(this._panels).forEach((panelkey) => this._remainingPanels.add(panelkey));
  }

  end() {
    this.config.GAME_STRIPS.forEach(stripId => this._lights.deactivateAll(stripId));
  }

  /**
   * handleActionPayload must _synchronously_ change tracked data in sculpture store.
   * Any asynchronous behavior must happen by dispatching actions.
   * We're _not_ allowed to dispatch actions synchronously.
   */
  handleActionPayload(payload) {
    if (this._complete) return;

    const actionHandlers = {
      [PanelsActionCreator.PANEL_PRESSED]: this._actionPanelPressed.bind(this),
      [MoleGameActionCreator.AVAIL_PANEL]: this._actionAvailPanel.bind(this),
      [MoleGameActionCreator.DEAVAIL_PANEL]: this._actionDeavailPanel.bind(this),
      [SculptureActionCreator.FINISH_STATUS_ANIMATION]: this._actionFinishStatusAnimation.bind(this),
      [SculptureActionCreator.MERGE_STATE]: this._actionMergeState.bind(this),
    };

    const actionHandler = actionHandlers[payload.actionType];
    if (actionHandler) actionHandler(payload);
  }

  /**
   * We only have a status animation at the end of the game
   */
  _actionFinishStatusAnimation() {
    this._complete = true;
    // There is currently no transition out, so we can synchronously start the next game
    this.store.moveToNextGame();
  }

  /**
   * Asynchronous panel activation
   */
  _actionAvailPanel(panel) {
    this._availPanel(panel);
  }

  /**
   * Asynchronous panel deactivation
   */
  _actionDeavailPanel(panel) {
    this._deavailPanel(panel);
  }

  /**
   * Local action:
   * 
   * If an active panel is pressed:
   * 1) Turn panel to location color
   * 2) Wait a short moment
   * 3) Avail the next panel
   * 4) increase/decrease # of simulaneously active panels
   */
  _actionPanelPressed(payload) {
    let {stripId, panelId, pressed} = payload;

    const state = this.data.get('panels').getPanelState(stripId, panelId);
    if (state === TrackedPanels.STATE_OFF) {
      if (pressed) {
        this._lights.setColor(stripId, panelId, this.store.locationColor);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.ACTIVE_INTENSITY);
      }
      else {
        this._lights.setToDefaultColor(stripId, panelId);
        this._lights.setIntensity(stripId, panelId, this.config.PANEL_DEFAULTS.INACTIVE_INTENSITY);
      }
      return;
    }

    // If an active panel was touched
    if (state === TrackedPanels.STATE_ON) {
      this._colorPanel(payload);

      // Only for master:
      if (this.store.isMaster()) {
        this._remainingPanels.delete(this._getPanelKey(payload));
        // If we have a timeout on this panel, kill the timeout
        this._removeTimeout(this._getPanelKey(payload));
        this._advanceGame();
      }
    }
  }

  /**
   * Advance game. Should only be called by master
   */
  _advanceGame() {
    let panelCount = this.data.get("panelCount") + 1;
    this.data.set('panelCount', panelCount);
    if (panelCount === this.gameConfig.GAME_END) {
      this._lights.deactivateAll();
      this.store.setSuccessStatus();
    }
    else {
      // Determine whether to add, remove of keep # of simultaneous panels
      const addPanels = 1 + (this.gameConfig.NUM_ACTIVE_PANELS[panelCount] ? this.gameConfig.NUM_ACTIVE_PANELS[panelCount] : 0);
      
      for (let i=0; i<addPanels; i++) {
        // Wait before next panel
        this._registerMoveDelay(this.gameConfig.PANEL_SUCCESS_DELAY);
      }
    }
  }

  /*!
   * Remote action
   * If we're master, we're responsible for correctly advancing the game
   */
  _actionMergeState(payload) {
    if (!payload.mole) return; // Only handle mole state

    const moleData = this.data;
    const moleChanges = payload.mole;
    const moleProps = payload.metadata.props.mole;

    // Master owns the panelCount field
    if (!this.store.isMaster()) {
      if (moleChanges.hasOwnProperty('panelCount')) {
        moleData.set('panelCount', moleChanges.panelCount, moleProps.panelCount);
      }
    }
{'fraction_non_alphanumeric': 0.11485348602222971, 'fraction_numerical': 0.0023576961940047153, 'mean_word_length': 3.164446002805049, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 15, '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': '4900845', 'n_tokens_mistral': 3747, 'n_tokens_neox': 3641, 'n_words': 839}