<commit_before>var consoleLogger = require('../../util/logger.js')
var http = require('http')

function InputCloudFoundry (config, eventEmitter) {
  this.config = config
  this.eventEmitter = eventEmitter
  this.config.cfhttp = config.cfhttp || config.port
  consoleLogger.log('value '+this.config.cfhttp )
  if (config && config.blacklist) {
    this.config.blacklist = config.blacklist
  } else {
    this.config.blacklist = {}
  }
  if (config.workers) {
    this.config.cloudFoundryWorkers = config.workers
  } else {
    this.config.cloudFoundryWorkers = undefined
  }
}
InputCloudFoundry.prototype.start = function () {
  if (this.config.cfhttp) {
    this.throng = require('throng')
    this.throng({
      workers: this.config.cloudFoundryWorkers || this.WORKERS || 2,
      lifetime: Infinity
    }, this.startCloudfoundryServer.bind(this))
  }
}

InputCloudFoundry.prototype.stop = function (cb) {
  cb()
}

InputCloudFoundry.prototype.getHttpServer = function (aport, handler) {
  var _port = aport || process.env.PORT
  if (aport === true) { // a commadn line flag was set but no port given
    _port = process.env.PORT
  }
  var server = http.createServer(handler)
  try {
    server = server.listen(_port)
    consoleLogger.log('Logagent listening (http): ' + _port + ', process id: ' + process.pid)
    return server
  } catch (err) {
    consoleLogger.log('Port in use (' + _port + '): ' + err)
  }
}

function filterCloudfoundryMessage (data, context) {
  if (data) {
    data['_type'] = context.sourceName.replace('_' + context.index, '')
    data['logSource'] = ('' + data['logSource']).replace('_' + context.index, '')
    if (!data['@timestamp']) {
      data['@timestamp'] = new Date()
    }
  }
  return data
}

InputCloudFoundry.prototype.cloudFoundryHandler = function (req, res) {
  try {
    var self = this
    var eventEmitter = this.eventEmitter
    var path = req.url.split('/')
    var token = null
    if (path.length > 1) {
      if (path[1] && path[1].length > 31 && /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(path[1])) {
        token = path[1]
      }
    }
    if (!token) {
      res.end('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
Error: Missing Logsene Token ' +
        req.url + '. Please use /LOGSENE_TOKEN. More info:

CloudFoundry Log Drain for Logsene

Logsene Log Management by Sematext

')
      return
    }
    if (self.config && self.config.blacklist && self.config.blacklist[token]) {
      if (self.config.debug) {
        consoleLogger.log('blacklisted request for' + token)
      }
      res.statusCode = 404
      return res.end()
    }
    var body = ''
    req.on('data', function (data) {
      body += data
    })
    req.on('end', function () {
      var lines = body.split('\n')
      lines.forEach(function (line) {
        if (!line) {
          return
        }
        eventEmitter.emit('data.raw', line.trim(), {sourceName: 'cloudfoundry_' + token, index: token, filter: filterCloudfoundryMessage})
      })
      res.end('ok\n')
    })
  } catch (err) {
    consoleLogger.error('Error in Cloud Foundry (http): ' + err)
  }
}

// start function for WEB_CONCURENCY
InputCloudFoundry.prototype.startCloudfoundryServer = function (id) {
  consoleLogger.log('start cloudfoundry worker: ' + id + ', pid:' + process.pid)
  this.getHttpServer(Number(this.config.cfhttp), this.cloudFoundryHandler.bind(this))
  var exitInProgress = false
  var terminate = function (reason) {
    return function () {
      if (exitInProgress) {
        return
      }
      exitInProgress = true
      consoleLogger.log('stop Cloud Foundry worker: ' + id + ', pid:' + process.pid + ', terminate reason: ' + reason + ' memory rss: ' + (process.memoryUsage().rss / (1024 * 1024)).toFixed(2) + ' MB')
      setTimeout(process.exit, 250)
    }
  }
  process.once('SIGTERM', terminate('SIGTERM'))
  process.once('SIGINT', terminate('SIGINT'))
  process.once('exit', terminate('exit'))
}

module.exports = InputCloudFoundry
<commit_msg>add /ping /health and JSON parsing for message field<commit_after>var consoleLogger = require('../../util/logger.js')
var http = require('http')

function jsonParse (text) {
  try {
    return JSON.parse(text)
  } catch (err) {
    return null
  }
}

function extractJson (line, source) {
  var parsed = {}
  if (/^\[{0,1}\{.*\}]{0,1}$/.test(line)) {
    parsed = jsonParse(line)
    if (!parsed) {
      return null
    }
    return parsed
  }
}

function InputCloudFoundry (config, eventEmitter) {
  this.config = config
  this.eventEmitter = eventEmitter
  this.config.cfhttp = config.cfhttp || config.port
  consoleLogger.log('value ' + this.config.cfhttp)
  if (config && config.blacklist) {
    this.config.blacklist = config.blacklist
  } else {
    this.config.blacklist = {}
  }
  if (config.workers) {
    this.config.cloudFoundryWorkers = config.workers
  } else {
    this.config.cloudFoundryWorkers = undefined
  }
}
InputCloudFoundry.prototype.start = function () {
  if (this.config.cfhttp) {
    this.throng = require('throng')
    this.throng({
      workers: this.config.cloudFoundryWorkers || this.WORKERS || 2,
      lifetime: Infinity
    }, this.startCloudfoundryServer.bind(this))
  }
}

InputCloudFoundry.prototype.stop = function (cb) {
  cb()
}

InputCloudFoundry.prototype.getHttpServer = function (aport, handler) {
  var _port = aport || process.env.PORT
  if (aport === true) { // a commadn line flag was set but no port given
    _port = process.env.PORT
  }
  var server = http.createServer(handler)
  try {
    server = server.listen(_port)
    consoleLogger.log('Logagent listening (http): ' + _port + ', process id: ' + process.pid)
    return server
  } catch (err) {
    consoleLogger.log('Port in use (' + _port + '): ' + err)
  }
}

function filterCloudfoundryMessage (data, context) {
  if (data) {
    data['_type'] = context.sourceName.replace('_' + context.index, '')
    data['logSource'] = ('' + data['logSource']).replace('_' + context.index, '')
    if (!data['@timestamp']) {
      data['@timestamp'] = new Date()
    }
    if (data.message) {
      data.json = extractJson(data.message)
    }
  }
  return data
}

InputCloudFoundry.prototype.cloudFoundryHandler = function (req, res) {
  try {
    var self = this
    var eventEmitter = this.eventEmitter
    var path = req.url.split('/')
    var token = null
    if (path.length > 1) {
      if (path[1] && path[1].length > 31 && /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(path[1])) {
        token = path[1]
      } else if (path[1] === 'health' || path[1] === 'ping') {
        res.statusCode = 200
        res.end('ok\n')
        return
      }
    }

    if (!token) {
      res.end('<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
Error: Missing Logsene Token ' +
        req.url + '. Please use /LOGSENE_TOKEN. More info:

CloudFoundry Log Drain for Logsene

Logsene Log Management by Sematext

')
      return
    }

    if (self.config && self.config.blacklist && self.config.blacklist[token]) {
      if (self.config.debug) {
        consoleLogger.log('blacklisted request for' + token)
      }
      res.statusCode = 404
      return res.end()
    }
    var body = ''
    req.on('data', function (data) {
      body += data
    })
    req.on('end', function () {
      var lines = body.split('\n')
      lines.forEach(function (line) {
        if (!line) {
          return
        }
        eventEmitter.emit('data.raw', line.trim(), {sourceName: 'cloudfoundry_' + token, index: token, filter: filterCloudfoundryMessage})
      })
      res.end('ok\n')
    })
  } catch (err) {
    consoleLogger.error('Error in Cloud Foundry (http): ' + err)
  }
}

// start function for WEB_CONCURENCY
InputCloudFoundry.prototype.startCloudfoundryServer = function (id) {
  consoleLogger.log('start cloudfoundry worker: ' + id + ', pid:' + process.pid)
  this.getHttpServer(Number(this.config.cfhttp), this.cloudFoundryHandler.bind(this))
  var exitInProgress = false
  var terminate = function (reason) {
    return function () {
      if (exitInProgress) {
        return
      }
      exitInProgress = true
      consoleLogger.log('stop Cloud Foundry worker: ' + id + ', pid:' + process.pid + ', terminate reason: ' + reason + ' memory rss: ' + (process.memoryUsage().rss / (1024 * 1024)).toFixed(2) + ' MB')
      setTimeout(process.exit, 250)
    }
  }
  process.once('SIGTERM', terminate('SIGTERM'))
  process.once('SIGINT', terminate('SIGINT'))
  process.once('exit', terminate('exit'))
}

module.exports = InputCloudFoundry
<|endoftext|>


<commit_before>// Copyright 2020-2021, University of Colorado Boulder

/**
 * Uses the Web Speech API to produce speech from the browser. This is a prototype, DO NOT USE IN PRODUCTION CODE.
 * There is no speech output until the webSpeaker has been initialized. Supported voices will depend on platform.
 * For each voice, you can customize the rate and pitch. Only one webSpeaker should be active at a time and so this
 * type is a singleton.
 *
 * @author Jesse Greenberg
 */

import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import Emitter from '../../../../axon/js/Emitter.js';
import NumberProperty from '../../../../axon/js/NumberProperty.js';
import Property from '../../../../axon/js/Property.js';
import stepTimer from '../../../../axon/js/stepTimer.js';
import Range from '../../../../dot/js/Range.js';
import merge from '../../../../phet-core/js/merge.js';
import platform from '../../../../phet-core/js/platform.js';
import stripEmbeddingMarks from '../../../../phet-core/js/stripEmbeddingMarks.js';
import Utterance from '../../../../utterance-queue/js/Utterance.js';
import VoicingUtterance from '../../../../utterance-queue/js/VoicingUtterance.js';
import scenery from '../../scenery.js';
import globalKeyStateTracker from '../globalKeyStateTracker.js';
import KeyboardUtils from '../KeyboardUtils.js';

class WebSpeaker {
  constructor() {

    // @public {null|SpeechSynthesisVoice}
    this.voiceProperty = new Property( null );

    // @public {NumberProperty} - controls the speaking rate of Web Speech
    this.voiceRateProperty = new NumberProperty( 1.0, { range: new Range( 0.75, 2 ) } );

    // {NumberProperty} - controls the pitch of the synth
    this.voicePitchProperty = new NumberProperty( 1.0, { range: new Range( 0.5, 2 ) } );

    // @public {Emitter} - emits events when the speaker starts/stops speaking, with the Utterance that is
    // either starting or stopping
    this.startSpeakingEmitter = new Emitter( { parameters: [ { valueType: Utterance } ] } );
    this.endSpeakingEmitter = new Emitter( { parameters: [ { valueType: Utterance } ] } );

    // @public {Emitter} - emits whenever the voices change for SpeechSynthesis
    this.voicesChangedEmitter = new Emitter();

    // @public - whether or not the synth is speaking - perhaps this should
    // replace the emitters above?
    this.speakingProperty = new BooleanProperty( false );

    // @private {SpeechSynthesis|null} - synth from Web Speech API that drives speech, defined on initialize
    this._synth = null;

    // @public {SpeechSynthesisVoice[]} - possible voices for Web Speech synthesis
    this.voices = [];

    // @public {boolean} - is the WebSpeaker initialized for use? This is prototypal so it isn't always initialized
    this.initialized = false;

    // whether or ot the webSpeaker is enabled - if false, there will be no speech
    this.enabledProperty = new BooleanProperty( false );

    // @private {Property|DerivedProperty|null} - Controls whether or not speech is allowed with synthesis.
    // Null until initialized, and can be set by options to initialize().
    this._canSpeakProperty = null;

    // @private {function} - bound so we can link and unlink to this.canSpeakProperty when the webSpeaker becomes
    // initialized.
    this.boundHandleCanSpeakChange = this.handleCanSpeakChange.bind( this );

    // @private {Utterance} - A reference to the last utterance spoken, so we can determine
    // cancelling behavior when it is time to speak the next utterance. See VoicingUtterance options.
    this.previousUtterance = null;

    // @public {boolean} - a more interal way to disable speaking - the enabledProperty
    // can be set by the user and is publicly observable for other things - but if
    // you need to temporarily shut down speaking without changing that observable
    // you can set onHold to true to prevent all speaking. Useful in cases like
    // the ResetAllButton where you want to describe the reset without
    // any of the other changing Properties in that interaction
    this.onHold = false;

    // fixes a bug on Safari where the `start` and `end` Utterances don't fire! The
    // issue is (apparently) that Safari internally clears the reference to the
    // Utterance on speak which prevents it from firing these events at the right
    // time - fix borrowed from
    // https://stackoverflow.com/questions/23483990/speechsynthesis-api-onend-callback-not-working
    // Unfortunately, this also introduces a memory leak, we should be smarter about
    // clearing this, though it is a bit tricky since we don't have a way to know
    // when we are done with an utterance - see #215
    this.utterances = [];
  }

  get enabled() {
    return this.enabledProperty.get();
  }

  /**
   * Indicate that the webSpeaker is ready for use, and attempt to populate voices (if they are ready yet). Adds
   * listeners that control speech.
   * @public
   */
  initialize( options ) {
    assert && assert( this.initialized === false, 'can only be initialized once' );
    assert && assert( this.isSpeechSynthesisSupported(), 'trying to initialize speech, but speech is not supported on this platform.' );

    options = merge( {

      // {BooleanProperty|DerivedProperty.<boolean>} - Controls whether speech is allowed with speech synthesis.
      // Combined into another DerivedProperty with this.enabledProperty so you don't have to use that as one
      // of the Properties that derive speechAllowedProperty, if you are passing in a DerivedProperty.
      speechAllowedProperty: new BooleanProperty( true )
    }, options );

    this._synth = window.speechSynthesis;

    this._canSpeakProperty = DerivedProperty.and( [ options.speechAllowedProperty, this.enabledProperty ] );
    this._canSpeakProperty.link( this.boundHandleCanSpeakChange );

    // browsers tend to generate the list of voices lazily, so the list of voices may be empty until speech is
    // first requested
    this.getSynth().onvoiceschanged = () => {
      this.populateVoices();
    };

    // try to populate voices immediately in case the browser populates them eagerly and we never get an
    // onvoiceschanged event
    this.populateVoices();

    // The control key will stop the synth from speaking if there is an active utterance. This key was decided because
    // most major screen readers will stop speech when this key is pressed
    globalKeyStateTracker.keyupEmitter.addListener( domEvent => {
      if ( KeyboardUtils.isKeyEvent( domEvent, KeyboardUtils.KEY_CTRL ) ) {
        this.cancel();
      }
    } );

    this.initialized = true;
  }

  /**
   * When we can no longer speak, cancel all speech to silence everything.
   * @private
   *
   * @param {boolean} canSpeak
   */
  handleCanSpeakChange( canSpeak ) {
    if ( !canSpeak ) { this.cancel(); }
  }

  /**
   * Update the list of voices available to the synth, and notify that the list has changed.
   * @private
   */
  populateVoices() {
    this.voices = this.getSynth().getVoices();
    this.voicesChangedEmitter.emit();
  }

  /**
   * Implements announce so the webSpeaker can be a source of output for utteranceQueue.
   * @public
   *
   * @param {Utterance} utterance
   */
  announce( utterance ) {
    let withCancel = true;
    if ( utterance instanceof VoicingUtterance ) {
      if ( this.previousUtterance && this.previousUtterance === utterance ) {
        withCancel = utterance.cancelSelf;
      }
      else {
        withCancel = utterance.cancelOther;
      }
    }

    this.speak( utterance, withCancel );
  }

  /**
   * Use speech synthesis to speak an utterance. No-op unless webSpeaker is initialized.
   * @public
   *
   * @param {Utterance} utterance
   * @param {boolean} withCancel - if true, any utterances remaining in the queue will be removed and this utterance
   *                               will take priority. Hopefully this works on all platforms, if it does not we
   *                               need to implement our own queing system.
   */
  speak( utterance, withCancel = true ) {
    if ( this.initialized && this._canSpeakProperty.value && !this.onHold ) {
      assert && assert( this.isSpeechSynthesisSupported(), 'trying to speak with speechSynthesis, but it is not supported on this platform' );

      // only cancel the previous alert if there is something new to speak
      if ( withCancel && utterance.alert ) {
        this.cancel();
      }

      // since the "end" event doesn't come through all the time after cancel() on
      // safari, we broadcast this right away to indicate that any previous speaking
      // is done
      if ( this.speakingProperty.get() && this.previousUtterance && withCancel ) {
        this.endSpeakingEmitter.emit( this.previousUtterance );
        this.speakingProperty.value = false;
      }

      // embeddding marks (for i18n) impact the output, strip before speaking
      const speechSynthUtterance = new SpeechSynthesisUtterance( stripEmbeddingMarks( utterance.getTextToAlert() ) );
      speechSynthUtterance.voice = this.voiceProperty.value;
      speechSynthUtterance.pitch = this.voicePitchProperty.value;
      speechSynthUtterance.rate = this.voiceRateProperty.value;

      // keep a reference to teh WebSpeechUtterance or Safari, so the browser
      // doesn't dispose of it before firing, see #215
      this.utterances.push( speechSynthUtterance );

      // Keep this out of the start listener so that it can be synchrounous to the UtteranceQueue draining/announcing, see bug in https://github.com/phetsims/sun/issues/699#issuecomment-831529485
      this.previousUtterance = utterance;

      const startListener = () => {
        this.startSpeakingEmitter.emit( utterance );
        this.speakingProperty.set( true );
        speechSynthUtterance.removeEventListener( 'start', startListener );
      };

      const endListener = () => {
        this.endSpeakingEmitter.emit( utterance );
        this.speakingProperty.set( false );
        speechSynthUtterance.removeEventListener( 'end', endListener );
      };

      speechSynthUtterance.addEventListener( 'start', startListener );
      speechSynthUtterance.addEventListener( 'end', endListener );

      // on safari, giving a bit of a delay to the speak request makes the `end`
      // SpeechSynthesisUtterance event come through much more consistently
      if ( platform.safari ) {
        stepTimer.setTimeout( () => {
          this.getSynth().speak( speechSynthUtterance );
        }, 500 );
      }
      else {
        this.getSynth().speak( speechSynthUtterance );
      }
    }
  }

  /**
   * Speak something initially and synchronously after some user interaction. This is helpful for a couple of cases:
   *   1) Browsers require that speech happen in response to some user interaction, with absolutely no delay.
   *   announce() is used with the utteranceQueue, which does not speak things instantly. Use this when speech is
   *   enabled, then use speak for all other usages.
   *   2) There are rare cases where we need to speak even when when canSpeakProperty is false (like speaking
   *   that voicing has been successfully turned off).
   * @public
   *
   * @param {string} utterThis
   */
  speakImmediately( utterThis ) {
    if ( this.initialized ) {
      assert && assert( this.isSpeechSynthesisSupported(), 'Trying to speak, but speech synthesis is not supported on this platform' );

      // embidding marks (for i18n) impact the output, strip before speaking
      const utterance = new SpeechSynthesisUtterance( stripEmbeddingMarks( utterThis ) );
      utterance.voice = this.voiceProperty.value;
      utterance.pitch = this.voicePitchProperty.value;
      utterance.rate = this.voiceRateProperty.value;

      // Keep this synchrounous as it pertains to the UtteranceQueue draining/announcing, see bug in https://github.com/phetsims/sun/issues/699#issuecomment-831529485
      this.previousUtterance = utterance;

      this.getSynth().speak( utterance );
    }
  }

  /**
   * Returns true if SpeechSynthesis is available on the window. This check is sufficient for all of
   * webSpeaker. On platforms where speechSynthesis is available, all features of it are available, with the
   * exception of the onvoiceschanged event in a couple of platforms. However, the listener can still be set
   * without issue on those platforms so we don't need to check for its existence. On those platforms, voices
   * are provided right on load.
   * @public
   *
   * @returns {boolean}
   */
  isSpeechSynthesisSupported() {
    return !!window.speechSynthesis && !!window.SpeechSynthesisUtterance;
  }

  /**
   * Returns a references to the SpeechSynthesis of the webSpeaker that is used to request speech with the Web
   * Speech API. Every references has a check to ensure that the synth is available.
   * @private
   *
   * @returns {null|SpeechSynthesis}
   */
  getSynth() {
    assert && assert( this.isSpeechSynthesisSupported(), 'Trying to use SpeechSynthesis, but it is not supported on this platform.' );
    return this._synth;
  }

  /**
   * Stops any current speech and removes all utterances in the queue internal to the SpeechSynthesis
   * (not the UtteranceQueue).
   * @public
   */
  cancel() {
    if ( this.initialized ) {
      this.getSynth().cancel();
    }
  }
}

const webSpeaker = new WebSpeaker();

scenery.register( 'webSpeaker', webSpeaker );
export default webSpeaker;<commit_msg>Patch a memory leak in webSpeaker<commit_after>// Copyright 2020-2021, University of Colorado Boulder

/**
 * Uses the Web Speech API to produce speech from the browser. This is a prototype, DO NOT USE IN PRODUCTION CODE.
 * There is no speech output until the webSpeaker has been initialized. Supported voices will depend on platform.
 * For each voice, you can customize the rate and pitch. Only one webSpeaker should be active at a time and so this
 * type is a singleton.
 *
 * @author Jesse Greenberg
 */

import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import Emitter from '../../../../axon/js/Emitter.js';
import NumberProperty from '../../../../axon/js/NumberProperty.js';
import Property from '../../../../axon/js/Property.js';
import stepTimer from '../../../../axon/js/stepTimer.js';
import Range from '../../../../dot/js/Range.js';
import merge from '../../../../phet-core/js/merge.js';
import platform from '../../../../phet-core/js/platform.js';
import stripEmbeddingMarks from '../../../../phet-core/js/stripEmbeddingMarks.js';
import Utterance from '../../../../utterance-queue/js/Utterance.js';
import VoicingUtterance from '../../../../utterance-queue/js/VoicingUtterance.js';
import scenery from '../../scenery.js';
import globalKeyStateTracker from '../globalKeyStateTracker.js';
import KeyboardUtils from '../KeyboardUtils.js';

class WebSpeaker {
  constructor() {

    // @public {null|SpeechSynthesisVoice}
    this.voiceProperty = new Property( null );

    // @public {NumberProperty} - controls the speaking rate of Web Speech
    this.voiceRateProperty = new NumberProperty( 1.0, { range: new Range( 0.75, 2 ) } );

    // {NumberProperty} - controls the pitch of the synth
    this.voicePitchProperty = new NumberProperty( 1.0, { range: new Range( 0.5, 2 ) } );

    // @public {Emitter} - emits events when the speaker starts/stops speaking, with the Utterance that is
    // either starting or stopping
    this.startSpeakingEmitter = new Emitter( { parameters: [ { valueType: Utterance } ] } );
    this.endSpeakingEmitter = new Emitter( { parameters: [ { valueType: Utterance } ] } );

    // @public {Emitter} - emits whenever the voices change for SpeechSynthesis
    this.voicesChangedEmitter = new Emitter();

    // @public - whether or not the synth is speaking - perhaps this should
    // replace the emitters above?
    this.speakingProperty = new BooleanProperty( false );

    // @private {SpeechSynthesis|null} - synth from Web Speech API that drives speech, defined on initialize
    this._synth = null;

    // @public {SpeechSynthesisVoice[]} - possible voices for Web Speech synthesis
    this.voices = [];

    // @public {boolean} - is the WebSpeaker initialized for use? This is prototypal so it isn't always initialized
    this.initialized = false;

    // whether or ot the webSpeaker is enabled - if false, there will be no speech
    this.enabledProperty = new BooleanProperty( false );

    // @private {Property|DerivedProperty|null} - Controls whether or not speech is allowed with synthesis.
    // Null until initialized, and can be set by options to initialize().
    this._canSpeakProperty = null;

    // @private {function} - bound so we can link and unlink to this.canSpeakProperty when the webSpeaker becomes
    // initialized.
    this.boundHandleCanSpeakChange = this.handleCanSpeakChange.bind( this );

    // @private {Utterance} - A reference to the last utterance spoken, so we can determine
    // cancelling behavior when it is time to speak the next utterance. See VoicingUtterance options.
    this.previousUtterance = null;

    // @public {boolean} - a more interal way to disable speaking - the enabledProperty
    // can be set by the user and is publicly observable for other things - but if
    // you need to temporarily shut down speaking without changing that observable
    // you can set onHold to true to prevent all speaking. Useful in cases like
    // the ResetAllButton where you want to describe the reset without
    // any of the other changing Properties in that interaction
    this.onHold = false;

    // fixes a bug on Safari where the `start` and `end` Utterances don't fire! The
    // issue is (apparently) that Safari internally clears the reference to the
    // Utterance on speak which prevents it from firing these events at the right
    // time - fix borrowed from
    // https://stackoverflow.com/questions/23483990/speechsynthesis-api-onend-callback-not-working
    // Unfortunately, this also introduces a memory leak, we should be smarter about
    // clearing this, though it is a bit tricky since we don't have a way to know
    // when we are done with an utterance - see #215
    this.utterances = [];
  }

  get enabled() {
    return this.enabledProperty.get();
  }

  /**
   * Indicate that the webSpeaker is ready for use, and attempt to populate voices (if they are ready yet). Adds
   * listeners that control speech.
   * @public
   */
  initialize( options ) {
    assert && assert( this.initialized === false, 'can only be initialized once' );
    assert && assert( this.isSpeechSynthesisSupported(), 'trying to initialize speech, but speech is not supported on this platform.' );

    options = merge( {

      // {BooleanProperty|DerivedProperty.<boolean>} - Controls whether speech is allowed with speech synthesis.
      // Combined into another DerivedProperty with this.enabledProperty so you don't have to use that as one
      // of the Properties that derive speechAllowedProperty, if you are passing in a DerivedProperty.
      speechAllowedProperty: new BooleanProperty( true )
    }, options );

    this._synth = window.speechSynthesis;

    this._canSpeakProperty = DerivedProperty.and( [ options.speechAllowedProperty, this.enabledProperty ] );
    this._canSpeakProperty.link( this.boundHandleCanSpeakChange );

    // browsers tend to generate the list of voices lazily, so the list of voices may be empty until speech is
    // first requested
    this.getSynth().onvoiceschanged = () => {
      this.populateVoices();
    };

    // try to populate voices immediately in case the browser populates them eagerly and we never get an
    // onvoiceschanged event
    this.populateVoices();

    // The control key will stop the synth from speaking if there is an active utterance. This key was decided because
    // most major screen readers will stop speech when this key is pressed
    globalKeyStateTracker.keyupEmitter.addListener( domEvent => {
      if ( KeyboardUtils.isKeyEvent( domEvent, KeyboardUtils.KEY_CTRL ) ) {
        this.cancel();
      }
    } );

    this.initialized = true;
  }

  /**
   * When we can no longer speak, cancel all speech to silence everything.
   * @private
   *
   * @param {boolean} canSpeak
   */
  handleCanSpeakChange( canSpeak ) {
    if ( !canSpeak ) { this.cancel(); }
  }

  /**
   * Update the list of voices available to the synth, and notify that the list has changed.
   * @private
   */
  populateVoices() {
    this.voices = this.getSynth().getVoices();
    this.voicesChangedEmitter.emit();
  }

  /**
   * Implements announce so the webSpeaker can be a source of output for utteranceQueue.
   * @public
   *
   * @param {Utterance} utterance
   */
  announce( utterance ) {
    let withCancel = true;
    if ( utterance instanceof VoicingUtterance ) {
      if ( this.previousUtterance && this.previousUtterance === utterance ) {
        withCancel = utterance.cancelSelf;
      }
      else {
        withCancel = utterance.cancelOther;
      }
    }

    this.speak( utterance, withCancel );
  }

  /**
   * Use speech synthesis to speak an utterance. No-op unless webSpeaker is initialized.
   * @public
   *
   * @param {Utterance} utterance
   * @param {boolean} withCancel - if true, any utterances remaining in the queue will be removed and this utterance
   *                               will take priority. Hopefully this works on all platforms, if it does not we
   *                               need to implement our own queing system.
   */
  speak( utterance, withCancel = true ) {
    if ( this.initialized && this._canSpeakProperty.value && !this.onHold ) {
      assert && assert( this.isSpeechSynthesisSupported(), 'trying to speak with speechSynthesis, but it is not supported on this platform' );

      // only cancel the previous alert if there is something new to speak
      if ( withCancel && utterance.alert ) {
        this.cancel();
      }

      // since the "end" event doesn't come through all the time after cancel() on
      // safari, we broadcast this right away to indicate that any previous speaking
      // is done
      if ( this.speakingProperty.get() && this.previousUtterance && withCancel ) {
        this.endSpeakingEmitter.emit( this.previousUtterance );
        this.speakingProperty.value = false;
      }

      // embeddding marks (for i18n) impact the output, strip before speaking
      const speechSynthUtterance = new SpeechSynthesisUtterance( stripEmbeddingMarks( utterance.getTextToAlert() ) );
      speechSynthUtterance.voice = this.voiceProperty.value;
      speechSynthUtterance.pitch = this.voicePitchProperty.value;
      speechSynthUtterance.rate = this.voiceRateProperty.value;

      // keep a reference to WebSpeechUtterances in Safari, so the browser doesn't dispose of it before firing, see #215
      this.utterances.push( speechSynthUtterance );

      // Keep this out of the start listener so that it can be synchrounous to the UtteranceQueue draining/announcing, see bug in https://github.com/phetsims/sun/issues/699#issuecomment-831529485
      this.previousUtterance = utterance;

      const startListener = () => {
        this.startSpeakingEmitter.emit( utterance );
        this.speakingProperty.set( true );
        speechSynthUtterance.removeEventListener( 'start', startListener );
      };

      const endListener = () => {

        // remove the reference to the SpeechSynthesisUtterance so we don't leak memory
        const indexOfUtterance = this.utterances.indexOf( speechSynthUtterance );
        if ( indexOfUtterance > -1 ) {
          this.utterances.splice( indexOfUtterance, 1 );
        }

        this.endSpeakingEmitter.emit( utterance );
        this.speakingProperty.set( false );
        speechSynthUtterance.removeEventListener( 'end', endListener );
      };

      speechSynthUtterance.addEventListener( 'start', startListener );
      speechSynthUtterance.addEventListener( 'end', endListener );

      // on safari, giving a bit of a delay to the speak request makes the `end`
      // SpeechSynthesisUtterance event come through much more consistently
      if ( platform.safari ) {
        stepTimer.setTimeout( () => {
          this.getSynth().speak( speechSynthUtterance );
        }, 500 );
      }
      else {
        this.getSynth().speak( speechSynthUtterance );
      }
    }
  }

  /**
   * Speak something initially and synchronously after some user interaction. This is helpful for a couple of cases:
   *   1) Browsers require that speech happen in response to some user interaction, with absolutely no delay.
   *   announce() is used with the utteranceQueue, which does not speak things instantly. Use this when speech is
   *   enabled, then use speak for all other usages.
   *   2) There are rare cases where we need to speak even when when canSpeakProperty is false (like speaking
   *   that voicing has been successfully turned off).
   * @public
   *
   * @param {string} utterThis
   */
  speakImmediately( utterThis ) {
    if ( this.initialized ) {
      assert && assert( this.isSpeechSynthesisSupported(), 'Trying to speak, but speech synthesis is not supported on this platform' );

      // embidding marks (for i18n) impact the output, strip before speaking
      const utterance = new SpeechSynthesisUtterance( stripEmbeddingMarks( utterThis ) );
      utterance.voice = this.voiceProperty.value;
      utterance.pitch = this.voicePitchProperty.value;
      utterance.rate = this.voiceRateProperty.value;

      // Keep this synchrounous as it pertains to the UtteranceQueue draining/announcing, see bug in https://github.com/phetsims/sun/issues/699#issuecomment-831529485
      this.previousUtterance = utterance;

      this.getSynth().speak( utterance );
    }
  }

  /**
   * Returns true if SpeechSynthesis is available on the window. This check is sufficient for all of
   * webSpeaker. On platforms where speechSynthesis is available, all features of it are available, with the
   * exception of the onvoiceschanged event in a couple of platforms. However, the listener can still be set
   * without issue on those platforms so we don't need to check for its existence. On those platforms, voices
   * are provided right on load.
   * @public
   *
   * @returns {boolean}
   */
  isSpeechSynthesisSupported() {
    return !!window.speechSynthesis && !!window.SpeechSynthesisUtterance;
  }

  /**
   * Returns a references to the SpeechSynthesis of the webSpeaker that is used to request speech with the Web
   * Speech API. Every references has a check to ensure that the synth is available.
   * @private
   *
   * @returns {null|SpeechSynthesis}
   */
  getSynth() {
    assert && assert( this.isSpeechSynthesisSupported(), 'Trying to use SpeechSynthesis, but it is not supported on this platform.' );
    return this._synth;
  }

  /**
   * Stops any current speech and removes all utterances in the queue internal to the SpeechSynthesis
   * (not the UtteranceQueue).
   * @public
   */
  cancel() {
    if ( this.initialized ) {
      this.getSynth().cancel();
    }
  }
}

const webSpeaker = new WebSpeaker();

scenery.register( 'webSpeaker', webSpeaker );
export default webSpeaker;<|endoftext|>


<commit_before>console.log('This would be the main JS file.');
<commit_msg>Delete main.js<commit_after><|endoftext|>


<commit_before>'use strict';

const get = require('lodash.get');
const h = require('../../support/helpers');

function getMetric(metric) {
  if (metric.median) {
    return metric.median + ' ms' + ' (' + metric.max + ')'
  } else {
    return metric;
  }
}

module.exports = function(dataCollection, slackOptions) {
  const attachments = [];

  const resultsPerUrl = dataCollection.urlPages;

  for (let url of Object.keys(resultsPerUrl)) {
    const results = resultsPerUrl[url];
    const base = results.data;
    const metrics = {
      firstPaint: {
        name: 'First paint',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.firstPaint')
      },
      speedIndex: {
        name: 'Speed Index',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.SpeedIndex')
      },
      firstVisualChange: {
        name: 'First Visual Change',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.FirstVisualChange')
      },
      visualComplete85: {
        name: 'Visual Complete 85%',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.VisualComplete85')
      },
      lastVisualChange: {
        name: 'Last Visual Change',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.LastVisualChange')
      },
      fullyLoaded: {
        name: 'Fully Loaded',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.fullyLoaded')
      },
      domContentLoadedTime: {
        name: 'domContentLoadedTime',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.pageTimings.domContentLoadedTime')
      },
      rumSpeedIndex: {
        name: 'RUM Speed Index',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.rumSpeedIndex')
      },
      coachScore: {
        name: 'Coach score',
        metric: get(base.coach, 'pageSummary.advice.performance.score')
      },
      transferSize: {
        name: 'Page transfer size',
        metric: h.size.format(get(base.pagexray, 'pageSummary.transferSize'))
      },
      transferRequests: {
        name: 'Requests',
        metric: get(base.pagexray, 'pageSummary.requests')
      }
    };

    const fields = [];
    for (const key of Object.keys(metrics)) {
      const metric = metrics[key];
      if (metric.metric !== undefined) {
        fields.push({
          title: metric.name,
          value: getMetric(metric.metric),
          short: true
        })
      }
    }

    // add all the errors
    if (results.errors) {
      for (const key of Object.keys(results.errors)) {
        fields.push({
          title: key + ' error',
          value: results.errors[key],
          short: false
        })
      }
    }

    let color = 'danger';

    const limitMetric = metrics[slackOptions.limitMetric];
    if (limitMetric) {
      if (limitMetric.metric > slackOptions.limitError) {
        color = 'good';
      } else if (limitMetric.metric > slackOptions.limitWarning) {
        color = 'warning';
      }
    }

    attachments.push({
      color,
      text: url,
      fields
    });
  }
  return attachments;
};
<commit_msg>Timing metrics should be lower than to get a green light #1508 (#1509)<commit_after>'use strict';

const get = require('lodash.get');
const h = require('../../support/helpers');

function getMetric(metric) {
  if (metric.median) {
    return metric.median + ' ms' + ' (' + metric.max + ')'
  } else {
    return metric;
  }
}

module.exports = function(dataCollection, slackOptions) {
  const attachments = [];

  const resultsPerUrl = dataCollection.urlPages;

  for (let url of Object.keys(resultsPerUrl)) {
    const results = resultsPerUrl[url];
    const base = results.data;
    const metrics = {
      firstPaint: {
        name: 'First paint',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.firstPaint')
      },
      speedIndex: {
        name: 'Speed Index',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.SpeedIndex')
      },
      firstVisualChange: {
        name: 'First Visual Change',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.FirstVisualChange')
      },
      visualComplete85: {
        name: 'Visual Complete 85%',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.VisualComplete85')
      },
      lastVisualChange: {
        name: 'Last Visual Change',
        metric: get(base.browsertime, 'pageSummary.statistics.visualMetrics.LastVisualChange')
      },
      fullyLoaded: {
        name: 'Fully Loaded',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.fullyLoaded')
      },
      domContentLoadedTime: {
        name: 'domContentLoadedTime',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.pageTimings.domContentLoadedTime')
      },
      rumSpeedIndex: {
        name: 'RUM Speed Index',
        metric: get(base.browsertime, 'pageSummary.statistics.timings.rumSpeedIndex')
      },
      coachScore: {
        name: 'Coach score',
        metric: get(base.coach, 'pageSummary.advice.performance.score')
      },
      transferSize: {
        name: 'Page transfer size',
        metric: h.size.format(get(base.pagexray, 'pageSummary.transferSize'))
      },
      transferRequests: {
        name: 'Requests',
        metric: get(base.pagexray, 'pageSummary.requests')
      }
    };

    const fields = [];
    for (const key of Object.keys(metrics)) {
      const metric = metrics[key];
      if (metric.metric !== undefined) {
        fields.push({
          title: metric.name,
          value: getMetric(metric.metric),
          short: true
        })
      }
    }

    // add all the errors
    if (results.errors) {
      for (const key of Object.keys(results.errors)) {
        fields.push({
          title: key + ' error',
          value: results.errors[key],
          short: false
        })
      }
    }

    let color = 'good';

    const limitMetric = metrics[slackOptions.limitMetric];
    if (limitMetric && slackOptions.limitMetric === 'coachScore') {
      if (limitMetric.metric < slackOptions.limitError) {
        color = 'danger';
      } else if (limitMetric.metric < slackOptions.limitWarning) {
        color = 'warning';
      }
    }
    // SpeedIndex/firstVisualChange
    else if (limitMetric) {
      if (limitMetric.metric > slackOptions.limitError) {
        color = 'danger';
      } else if (limitMetric.metric > slackOptions.limitWarning) {
        color = 'warning';
      }
    }

    attachments.push({
      color,
      text: url,
      fields
    });
  }
  return attachments;
};
<|endoftext|>


<commit_before>/**
 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
 *
 * Mail
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

define(function(require) {
	'use strict';

	var Radio = require('radio');
	var _timer = null;
	var _accounts = null;

	Radio.sync.on('start', startBackgroundSync);

	var SYNC_INTERVAL = 30 * 1000; // twice a minute

	function startBackgroundSync(accounts) {
		_accounts = accounts;
		clearTimeout(_timer);
		triggerNextSync();
	}

	function triggerNextSync() {
		_timer = setTimeout(function() {
			var account;
			if (require('state').accounts.length === 0) {
				// Nothing to do right now, just re-trigger the sync event
				triggerNextSync();
			} else if (require('state').accounts.length === 1) {
				account = _accounts.first();
			} else {
				account = _accounts.get(-1);
			}
			sync(account);
		}, SYNC_INTERVAL);
	}

	/**
	 * @param {Account} account
	 * @returns {Promise}
	 */
	function sync(account) {
		return Radio.sync.request('sync:folder', account.folders.first())
			.then(function(newMessages) {
				Radio.ui.trigger('notification:mail:show', newMessages);
			})
			.catch(function(e) {
				console.error(e);
			})
			.then(triggerNextSync);
	}

	return {
		sync: sync
	};
});
<commit_msg>Remove dead sync service<commit_after><|endoftext|>


<commit_before>/*jslint browser: true, devel: true */
/*global SOG, FB, Handlebars */

SOG.player = new SOG.browser.Player({ name: 'Christian ' + Date.now() + 'son' });

function signedIn() {
	'use strict';
	console.log('inloggad');
	FB.api('/me?fields=id,name,first_name,username,picture,link,friends.fields(id,name,picture)', function (d) {
		// Save user data
		SOG.player.updateDataSkit(d);
		// Change on the page
		var facebook_div = document.querySelector('#facebook'),
			template = Handlebars.templates['facebook.hbs'],
			html = template({ name: SOG.player.getFullName(), picture: SOG.player.getPicture() });
		facebook_div.innerHTML = html;
	});
}

function login() {
	'use strict';
	var btn = document.querySelector('#fb-login-button');
	btn.addEventListener('click', function (e) {
		e.preventDefault();
		FB.login(function (res) {
			if (res.authResponse) {
				signedIn();
			}
		});
	});
}

function logout() {
	'use strict';
	FB.logout(function () {
		console.log('bye');
	});
}

window.fbAsyncInit = function () {
	'use strict';
	console.log('fbAsyncInit');
	FB.init({
		appId      : '539892936062898', //App ID
		//appId      : '614840228526917', //App ID
		channelUrl : '//localhost:3000/channel.html', // Channel File
		status     : true, // Check login status
		cookie     : true, // Enable cookies to allow the server to access the session
		xfbml      : true // Parse XFBML
	});
	// Additional init code here
	FB.getLoginStatus(function (response) {
		if (response.status === 'connected') {
			// Inloggad
			console.log('signed in');
			signedIn();
		} else {
			console.log('log in');
			login();
		}
	});
};

(function (d) {
	'use strict';

	var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
	if (d.getElementById(id)) { return; }
	js = d.createElement('script');
	js.id = id;
	js.async = true;
	js.src = "//connect.facebook.net/en_US/all.js";
	ref.parentNode.insertBefore(js, ref);
}(document));

window.onload = function () {
	'use strict';

	var game = SOG.browser.game,
		room = new SOG.browser.Room({ id: 'room', name: 'Lobby', socket: game.getSocket() }),
		gamesList = [].slice.call(document.querySelectorAll('#games .game')),
		wordNode,
		startGame,
		showErrorMessage,
		pageWrapper = document.getElementById('page-wrapper'),
		gameWrapper = document.getElementById('game-wrapper');

	// Init lobby
	SOG.browser.lobby.init(game.getSocket(), document.getElementById('games'));

	// Start game
	startGame = function (data) {
		// Html for game
		gameWrapper.innerHTML = Handlebars.templates['room.hbs']({ picture: SOG.player.getPicture() });

		var gameplan = document.querySelector('#gameplan'),
			chatInputField = document.querySelector('#chat-input input'),
			chatMessages = document.querySelector('#chat-messages'),
			playersList = document.querySelector('#game-user-info .players'),
			timer = document.querySelector('#timer-progress'),
			artboardWrapper = document.getElementById('artboard-wrapper'),
			playerTmpl = Handlebars.templates['player.hbs'],
			chat = SOG.browser.chat,
			artboard = SOG.browser.artboard,
			addPlayerToList,
			drawingPlayer;

		// Add player to list
		addPlayerToList = function (p, data) {
			var you = p.getSocketID() === SOG.player.getSocketID();
			// Show message in chat
			if (!you) { chat.createMessage('', p.getName() + ' har gått med i spelet!'); }
			// Add player to player-list
			playersList.innerHTML += playerTmpl(p.getObject(['name', 'picture'], function () {
				return { points: 214, you: you, id: p.getSocketID() };
			}));
		};

		// Init chat
		chat.init({ input: chatInputField, messages: chatMessages, player: SOG.player });

		// Add current players to list
		data.players.forEach(addPlayerToList);

		// Show messages in chat sent by users
		room.onUserMessage(chat.createMessage);

		// Show messages in chat sent by server
		room.onServerMessage(chat.createMessage);

		// Init artboard
		artboard.init({
			canvas: document.querySelector('#artboard'),
			x: gameplan.offsetLeft + 5,
			y: gameplan.offsetTop + 5,
			room: room
		});

		// New points
		game.onPoints(function (guesser, drawer) {
			// Show a message in the chat
			chat.createMessage('', game.str('ScoreMessage').assign({
				gName: guesser.player.getName(),
				gPoints: guesser.points,
				dName: drawer.player.getName(),
				dPoints: drawer.points
			}));
			// Update scores
			// TODO Make nicer
			document.querySelector('#player-' + guesser.player.getSocketID() + ' .points').innerHTML = guesser.total + ' poäng';
			document.querySelector('#player-' + drawer.player.getSocketID() + ' .points').innerHTML = drawer.total + ' poäng';
		});

		// Correct word
		room.onCorrectWordGuessed(function (data) {
			// Remove old word
			if (typeof wordNode !== 'undefined') {
				gameplan.removeChild(wordNode);
				wordNode = undefined;
				gameWrapper.classList.remove('draw');
				chatInputField.disabled = false;
			}

			if (data.next && data.next.draw) {
				// Disable chat input field
				chatInputField.disabled = true;
				// Show new word
				wordNode = SOG.utils.html('div', {
					id: 'word-wrapper',
					text: '
Din tur att rita' + data.next.word + '',
					to: gameplan
				});
				setTimeout(function () {
					// Minimize word
					wordNode.classList.add('small');

					// Show crayons
					gameWrapper.classList.add('draw');
				}, 2000);
				// Enable drawing
				artboard.enable();
			} else {
				window.setTimeout(function () {
					chatInputField.focus(); // TODO Whats wrong?
				}, 0);
				// Disable drawing
				artboard.disable();
			}

			// Save drawing in chat
			if (data.word !== '' && artboard.getImage() !== '') {
				SOG.player.saveImage(artboard.getImage());

				chat.createMessage({
					img: artboard.getImage(),
					word: data.word,
					name: data.player.getName(),
					picture: data.player.getPicture(),
					fid: data.player.getFacebookID()
				});
			}

			// Clear the artboard
			artboard.clear();

			// Start timer
			timer.className = '';
			setTimeout(function () { timer.classList.add('min-' + (data.next.minutes || 0)); }, 2000);

			// Show who is drawing
			if (drawingPlayer) {
				artboardWrapper.removeChild(drawingPlayer);
				drawingPlayer = undefined;
			}
			if (data.next.player) {
				drawingPlayer = SOG.utils.html('div', {
					id: 'drawing-player',
					text: '' + data.next.player.getName() + ' ritar.',
					to: artboardWrapper
				});
			}

			// When someone leave
			room.onLeaveRoom(function (name, id) {
				playersList.removeChild(document.getElementById('player-' + id));
				chat.createMessage('', name + ' har lämnat spelet.');
			});

			// Game over!
			game.onGameOver(function (data) {
				SOG.utils.html('div', {
					id: 'game-over-wrapper',
					text: Handlebars.templates['gameover.hbs']({ players: data }),
					to: gameplan
				});
			});
		});

		// A player joins the room
		room.onPlayerJoinsRoom(addPlayerToList);

		// Panel for crayon
		SOG.browser.crayons.init({
			sizes: [].slice.call(document.querySelectorAll('#sizes a')),
			colors: [].slice.call(document.querySelectorAll('#colors a'))
		});
	};

	// Show error message
	showErrorMessage = function (message) {
		console.log('An error occured: ' + message);
	};

	// Join room
	SOG.player.join(room);

	SOG.player.onDataChanged(function (p) {
		console.log(p);
	});

	// Change room
	SOG.browser.lobby.onGameSelected(function (gid) {
		room.changeTo(gid, startGame);
		document.body.classList.add('game');
	});

	// Show error messages on error
	game.onError(function (e) {
		var p = SOG.browser.popup(game.str(e.name) || { title: e.name, message: e.message });

		if (e.name === 'DisconnectedException') {
			// Change to first page
			pageWrapper.classList.remove('hide-wrapper');
			gameWrapper.classList.add('hide-wrapper');
			// Add button when connected again
			game.onConnect(function () {
				p.addBtn('Ok, anslut igen!', 'http://skissaochgissa.se');
			});
		}
	});

	window.tt = SOG.player;
};
<commit_msg>Use getElementById<commit_after>/*jslint browser: true, devel: true */
/*global SOG, FB, Handlebars */

SOG.player = new SOG.browser.Player({ name: 'Christian ' + Date.now() + 'son' });

function signedIn() {
	'use strict';
	console.log('inloggad');
	FB.api('/me?fields=id,name,first_name,username,picture,link,friends.fields(id,name,picture)', function (d) {
		// Save user data
		SOG.player.updateDataSkit(d);
		// Change on the page
		var facebook_div = document.querySelector('#facebook'),
			template = Handlebars.templates['facebook.hbs'],
			html = template({ name: SOG.player.getFullName(), picture: SOG.player.getPicture() });
		facebook_div.innerHTML = html;
	});
}

function login() {
	'use strict';
	var btn = document.querySelector('#fb-login-button');
	btn.addEventListener('click', function (e) {
		e.preventDefault();
		FB.login(function (res) {
			if (res.authResponse) {
				signedIn();
			}
		});
	});
}

function logout() {
	'use strict';
	FB.logout(function () {
		console.log('bye');
	});
}

window.fbAsyncInit = function () {
	'use strict';
	console.log('fbAsyncInit');
	FB.init({
		appId      : '539892936062898', //App ID
		//appId      : '614840228526917', //App ID
		channelUrl : '//localhost:3000/channel.html', // Channel File
		status     : true, // Check login status
		cookie     : true, // Enable cookies to allow the server to access the session
		xfbml      : true // Parse XFBML
	});
	// Additional init code here
	FB.getLoginStatus(function (response) {
		if (response.status === 'connected') {
			// Inloggad
			console.log('signed in');
			signedIn();
		} else {
			console.log('log in');
			login();
		}
	});
};

(function (d) {
	'use strict';

	var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
	if (d.getElementById(id)) { return; }
	js = d.createElement('script');
	js.id = id;
	js.async = true;
	js.src = "//connect.facebook.net/en_US/all.js";
	ref.parentNode.insertBefore(js, ref);
}(document));

window.onload = function () {
	'use strict';

	var game = SOG.browser.game,
		room = new SOG.browser.Room({ id: 'room', name: 'Lobby', socket: game.getSocket() }),
		gamesList = [].slice.call(document.querySelectorAll('#games .game')),
		wordNode,
		startGame,
		showErrorMessage,
		pageWrapper = document.getElementById('page-wrapper'),
		gameWrapper = document.getElementById('game-wrapper');

	// Init lobby
	SOG.browser.lobby.init(game.getSocket(), document.getElementById('games'));

	// Start game
	startGame = function (data) {
		// Html for game
		gameWrapper.innerHTML = Handlebars.templates['room.hbs']({ picture: SOG.player.getPicture() });

		var gameplan = document.getElementById('gameplan'),
			chatInputField = document.querySelector('#chat-input input'),
			chatMessages = document.getElementById('chat-messages'),
			playersList = document.querySelector('#game-user-info .players'),
			timer = document.getElementById('timer-progress'),
			artboardWrapper = document.getElementById('artboard-wrapper'),
			playerTmpl = Handlebars.templates['player.hbs'],
			chat = SOG.browser.chat,
			artboard = SOG.browser.artboard,
			addPlayerToList,
			drawingPlayer;

		// Add player to list
		addPlayerToList = function (p, data) {
			var you = p.getSocketID() === SOG.player.getSocketID();
			// Show message in chat
			if (!you) { chat.createMessage('', p.getName() + ' har gått med i spelet!'); }
			// Add player to player-list
			playersList.innerHTML += playerTmpl(p.getObject(['name', 'picture'], function () {
				return { points: 214, you: you, id: p.getSocketID() };
			}));
		};

		// Init chat
		chat.init({ input: chatInputField, messages: chatMessages, player: SOG.player });

		// Add current players to list
		data.players.forEach(addPlayerToList);

		// Show messages in chat sent by users
		room.onUserMessage(chat.createMessage);

		// Show messages in chat sent by server
		room.onServerMessage(chat.createMessage);

		// Init artboard
		artboard.init({
			canvas: document.querySelector('#artboard'),
			x: gameplan.offsetLeft + 5,
			y: gameplan.offsetTop + 5,
			room: room
		});

		// New points
		game.onPoints(function (guesser, drawer) {
			// Show a message in the chat
			chat.createMessage('', game.str('ScoreMessage').assign({
				gName: guesser.player.getName(),
				gPoints: guesser.points,
				dName: drawer.player.getName(),
				dPoints: drawer.points
			}));
			// Update scores
			// TODO Make nicer
			document.querySelector('#player-' + guesser.player.getSocketID() + ' .points').innerHTML = guesser.total + ' poäng';
			document.querySelector('#player-' + drawer.player.getSocketID() + ' .points').innerHTML = drawer.total + ' poäng';
		});

		// Correct word
		room.onCorrectWordGuessed(function (data) {
			// Remove old word
			if (typeof wordNode !== 'undefined') {
				gameplan.removeChild(wordNode);
				wordNode = undefined;
				gameWrapper.classList.remove('draw');
				chatInputField.disabled = false;
			}

			if (data.next && data.next.draw) {
				// Disable chat input field
				chatInputField.disabled = true;
				// Show new word
				wordNode = SOG.utils.html('div', {
					id: 'word-wrapper',
					text: '
Din tur att rita' + data.next.word + '',
					to: gameplan
				});
				setTimeout(function () {
					// Minimize word
					wordNode.classList.add('small');

					// Show crayons
					gameWrapper.classList.add('draw');
				}, 2000);
				// Enable drawing
				artboard.enable();
			} else {
				window.setTimeout(function () {
					chatInputField.focus(); // TODO Whats wrong?
				}, 0);
				// Disable drawing
				artboard.disable();
			}

			// Save drawing in chat
			if (data.word !== '' && artboard.getImage() !== '') {
				SOG.player.saveImage(artboard.getImage());

				chat.createMessage({
					img: artboard.getImage(),
					word: data.word,
					name: data.player.getName(),
					picture: data.player.getPicture(),
					fid: data.player.getFacebookID()
				});
			}

			// Clear the artboard
			artboard.clear();

			// Start timer
			timer.className = '';
			setTimeout(function () { timer.classList.add('min-' + (data.next.minutes || 0)); }, 2000);

			// Show who is drawing
			if (drawingPlayer) {
				artboardWrapper.removeChild(drawingPlayer);
				drawingPlayer = undefined;
			}
			if (data.next.player) {
				drawingPlayer = SOG.utils.html('div', {
					id: 'drawing-player',
					text: '' + data.next.player.getName() + ' ritar.',
					to: artboardWrapper
				});
			}

			// When someone leave
			room.onLeaveRoom(function (name, id) {
				playersList.removeChild(document.getElementById('player-' + id));
				chat.createMessage('', name + ' har lämnat spelet.');
			});

			// Game over!
			game.onGameOver(function (data) {
				SOG.utils.html('div', {
					id: 'game-over-wrapper',
					text: Handlebars.templates['gameover.hbs']({ players: data }),
					to: gameplan
				});
			});
		});

		// A player joins the room
		room.onPlayerJoinsRoom(addPlayerToList);

		// Panel for crayon
		SOG.browser.crayons.init({
			sizes: [].slice.call(document.querySelectorAll('#sizes a')),
			colors: [].slice.call(document.querySelectorAll('#colors a'))
		});
	};

	// Show error message
	showErrorMessage = function (message) {
		console.log('An error occured: ' + message);
	};

	// Join room
	SOG.player.join(room);

	SOG.player.onDataChanged(function (p) {
		console.log(p);
	});

	// Change room
	SOG.browser.lobby.onGameSelected(function (gid) {
		room.changeTo(gid, startGame);
		document.body.classList.add('game');
	});

	// Show error messages on error
	game.onError(function (e) {
		var p = SOG.browser.popup(game.str(e.name) || { title: e.name, message: e.message });

		if (e.name === 'DisconnectedException') {
			// Change to first page
			pageWrapper.classList.remove('hide-wrapper');
			gameWrapper.classList.add('hide-wrapper');
			// Add button when connected again
			game.onConnect(function () {
				p.addBtn('Ok, anslut igen!', 'http://skissaochgissa.se');
			});
		}
	});

	window.tt = SOG.player;
};
<|endoftext|>


<commit_before>import _ from 'lodash';
import {STROKE_WIDTH} from 'src/shared/constants';
import {map, reduce, forEach} from 'fast.js';

/**
 * Accepts an array of raw influxdb responses and returns a format
 * that Dygraph understands.
 */

const DEFAULT_SIZE = 0;
const cells = {
  label: new Array(DEFAULT_SIZE),
  value: new Array(DEFAULT_SIZE),
  time: new Array(DEFAULT_SIZE),
  seriesIndex: new Array(DEFAULT_SIZE),
  responseIndex: new Array(DEFAULT_SIZE),
};

// activeQueryIndex is an optional argument that indicated which query's series we want highlighted.
export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInDataExplorer) {
  // collect results from each influx response
  const results = reduce(raw, (acc, rawResponse, responseIndex) => {
    const responses = _.get(rawResponse, 'response.results', []);
    const indexedResponses = map(responses, (response) => ({...response, responseIndex}));
    return [...acc, ...indexedResponses];
  }, []);

  // collect each series
  const serieses = reduce(results, (acc, {series = [], responseIndex}, index) => {
    return [...acc, ...map(series, (item) => ({...item, responseIndex, index}))];
  }, []);

  const size = reduce(serieses, (acc, {columns, values}) => {
    if (columns.length && values.length) {
      return acc + (columns.length - 1) * values.length;
    }
    return acc;
  }, 0);

  // convert series into cells with rows and columns
  let cellIndex = 0;

  forEach(serieses, ({name, columns, values, index, responseIndex, tags = {}}) => {
    const rows = map(values, (vals) => ({
      name,
      vals,
      index,
    }));

    columns.shift();

    // tagSet is each tag key and value for a series
    const tagSet = map(Object.keys(tags), (tag) => `[${tag}=${tags[tag]}]`).sort().join('');

    forEach(rows, ({vals, name: measurement, index: seriesIndex}) => {
      const [time, ...rowValues] = vals;

      forEach(rowValues, (value, i) => {
        const field = columns[i];
        cells.label[cellIndex] = `${measurement}.${field}${tagSet}`;
        cells.value[cellIndex] = value;
        cells.time[cellIndex] = time;
        cells.seriesIndex[cellIndex] = seriesIndex;
        cells.responseIndex[cellIndex] = responseIndex;
        cellIndex++; // eslint-disable-line no-plusplus
      });
    });
  });

  // labels are a unique combination of measurement, fields, and tags that indicate a specific series on the graph legend
  const labelMemo = {};
  const labels = [];
  for (let i = 0; i < size; i++) {
    const label = cells.label[i];
    const seriesIndex = cells.seriesIndex[i];
    const responseIndex = cells.responseIndex[i];
    const memoKey = `${label}-${seriesIndex}`;
    const existingLabel = labelMemo[memoKey];

    if (!existingLabel) {
      labels.push({
        label,
        seriesIndex,
        responseIndex,
      });
      labelMemo[memoKey] = true;
    }
  }

  const sortedLabels = _.sortBy(labels, 'label');
  const tsMemo = {};

  const timeSeries = [];

  for (let i = 0; i < size; i++) {
    const time = cells.time[i];
    const value = cells.value[i];
    const label = cells.label[i];
    const seriesIndex = cells.seriesIndex[i];

    let existingRowIndex = tsMemo[time];

    if (existingRowIndex === undefined) {
      timeSeries.push({
        time,
        values: Array(sortedLabels.length).fill(null), // TODO clone
      });

      existingRowIndex = timeSeries.length - 1;
      tsMemo[time] = existingRowIndex;
    }

    const values = timeSeries[existingRowIndex].values;
    const labelIndex = sortedLabels.findIndex((find) => find.label === label && find.seriesIndex === seriesIndex);
    values[labelIndex] = value;
    timeSeries[existingRowIndex].values = values;
  }

  const sortedTimeSeries = _.sortBy(timeSeries, 'time');

  const {light, heavy} = STROKE_WIDTH;

  const dygraphSeries = reduce(sortedLabels, (acc, {label, responseIndex}) => {
    acc[label] = {
      strokeWidth: responseIndex === activeQueryIndex ? heavy : light,
    };

    if (!isInDataExplorer) {
      acc[label].axis = responseIndex === 0 ? 'y' : 'y2';
    }

    return acc;
  }, {});

  return {
    labels: ["time", ...map(sortedLabels, ({label}) => label)],
    timeSeries: map(sortedTimeSeries, ({time, values}) => ([new Date(time), ...values])),
    dygraphSeries,
  };
}
<commit_msg>Bug fixes and performance improvements.<commit_after>import _ from 'lodash';
import {STROKE_WIDTH} from 'src/shared/constants';
import {map, reduce, forEach, concat, clone} from 'fast.js';

/**
 * Accepts an array of raw influxdb responses and returns a format
 * that Dygraph understands.
 */

const DEFAULT_SIZE = 0;
const cells = {
  label: new Array(DEFAULT_SIZE),
  value: new Array(DEFAULT_SIZE),
  time: new Array(DEFAULT_SIZE),
  seriesIndex: new Array(DEFAULT_SIZE),
  responseIndex: new Array(DEFAULT_SIZE),
};

// activeQueryIndex is an optional argument that indicated which query's series we want highlighted.
export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInDataExplorer) {
  // collect results from each influx response
  const results = reduce(raw, (acc, rawResponse, responseIndex) => {
    const responses = _.get(rawResponse, 'response.results', []);
    const indexedResponses = map(responses, (response) => ({...response, responseIndex}));
    return [...acc, ...indexedResponses];
  }, []);

  // collect each series
  const serieses = reduce(results, (acc, {series = [], responseIndex}, index) => {
    return [...acc, ...map(series, (item) => ({...item, responseIndex, index}))];
  }, []);

  const size = reduce(serieses, (acc, {columns, values}) => {
    if (columns.length && values.length) {
      return acc + (columns.length - 1) * values.length;
    }
    return acc;
  }, 0);

  // convert series into cells with rows and columns
  let cellIndex = 0;
  let labels = [];

  forEach(serieses, ({name: measurement, columns, values, index: seriesIndex, responseIndex, tags = {}}) => {
    const rows = map(values, (vals) => ({
      vals,
    }));

    columns.shift();

    // tagSet is each tag key and value for a series
    const tagSet = map(Object.keys(tags), (tag) => `[${tag}=${tags[tag]}]`).sort().join('');
    const unsortedLabels = map(columns, (field) => ({
      label: `${measurement}.${field}${tagSet}`,
      responseIndex,
      seriesIndex,
    }));
    labels = concat(labels, unsortedLabels);

    forEach(rows, ({vals}) => {
      const [time, ...rowValues] = vals;

      forEach(rowValues, (value, i) => {
        cells.label[cellIndex] = unsortedLabels[i].label;
        cells.value[cellIndex] = value;
        cells.time[cellIndex] = time;
        cells.seriesIndex[cellIndex] = seriesIndex;
        cells.responseIndex[cellIndex] = responseIndex;
        cellIndex++; // eslint-disable-line no-plusplus
      });
    });
  });

  const sortedLabels = _.sortBy(labels, 'label');
  const tsMemo = {};
  const nullArray = Array(sortedLabels.length).fill(null);

  const labelsToValueIndex = reduce(sortedLabels, (acc, {label, seriesIndex}, i) => {
    acc[label + seriesIndex] = i;
    return acc;
  }, {});

  const timeSeries = [];
  for (let i = 0; i < size; i++) {
    const time = cells.time[i];
    const value = cells.value[i];
    const label = cells.label[i];
    const seriesIndex = cells.seriesIndex[i];

    let existingRowIndex = tsMemo[time];

    if (existingRowIndex === undefined) {
      timeSeries.push({
        time,
        values: clone(nullArray),
      });

      existingRowIndex = timeSeries.length - 1;
      tsMemo[time] = existingRowIndex;
    }

    timeSeries[existingRowIndex].values[labelsToValueIndex[label + seriesIndex]] = value;
  }
  const sortedTimeSeries = _.sortBy(timeSeries, 'time');

  const {light, heavy} = STROKE_WIDTH;

  const dygraphSeries = reduce(sortedLabels, (acc, {label, responseIndex}) => {
    acc[label] = {
      strokeWidth: responseIndex === activeQueryIndex ? heavy : light,
    };

    if (!isInDataExplorer) {
      acc[label].axis = responseIndex === 0 ? 'y' : 'y2';
    }

    return acc;
  }, {});

  return {
    labels: ["time", ...map(sortedLabels, ({label}) => label)],
    timeSeries: map(sortedTimeSeries, ({time, values}) => ([new Date(time), ...values])),
    dygraphSeries,
  };
}
<|endoftext|>


<commit_before>enchant();

window.onload = function() {
  var result = {
    'correctAnswer': 0,

    'distance': 0
  };
  var game_ = new Game(320, 480);
  game_.fps = 24;
  var imagePaths = [
    Config['startImagePath'],
    Config['correctAnswerImagePath'],
    Config['gameOverImagePath'],
    Config['charactorImagePath'],
    Config['backgroundImagePath1'],
    Config['backgroundImagePath2']
  ];
  questionData.forEach(function(data) {
    imagePaths.push('./images/' + data['question']);
  });
  imagePaths.forEach(function(imagePath) {
   game_.preload(imagePath);
  });

  game_.onload = function() {
    var createStartScene = function() {
      var scene = new Scene();
      scene.backgroundColor = '#fcc800';

      var startImage = new Sprite(236, 48);
      startImage.image = game_.assets[Config['startImagePath']];
      startImage.x = 42;
      startImage.y = 50;
      scene.addChild(startImage);

      var description = createLabel('下（した）のステージの名前（なまえ）をタッチするとゲームが始（はじ）まるよ', 180);
      scene.addChild(description);

      // ステージ選択ラベル設定
      var selectStage1 = new Label('ステージA'); // ラベルを作る
      selectStage1.width = 320;
      selectStage1.textAlign = 'center';                 // 文字を中央寄せ
      selectStage1.color = '#ffffff';                    // 文字を白色に
      selectStage1.x = 0;                                // 横位置調整
      selectStage1.y = 222;                              // 縦位置調整
      selectStage1.font = '14px sans-serif';             // 28pxのゴシック体にする
      scene.addChild(selectStage1);                      // シーンに追加

      selectStage1.addEventListener(Event.TOUCH_START, function(e) {
          game_.replaceScene(createGameScene());
      });

      return scene;
    };

    var createLabel = function(text, y) {
      var label = new Label(text);
      label.width = 320;
      label.textAlign = 'center';
      label.color = '#ffffff';
      label.x = 0;
      label.y = y || 284;
      label.font = '14px sans-serif';
      return label;
    };

    var createScoreLabel = function(scene) {
      var label = createLabel('正解数（せいかいすう）: ' + result['correctAnswer'], 200);
      scene.addChild(label);
      label = createLabel('走った距離（はしったきょり）: ' + result['distance'] + '㍍', 220);
      scene.addChild(label);
    };

    var createGameScene = function() {
        var scroll = 0;

        var SCROLL_SPEED = 5;
        var BETWEEN_Q_A = 100;

        var scene = new Scene();
        scene.backgroundColor = '#8cc820';

        var bg1 = new Sprite(1280, 480);
        bg1.image = game_.assets[Config['backgroundImagePath1']];
        bg1.x = 0;
        bg1.y = 0;
        bg1.height = 600;
        scene.addChild(bg1);

        var questionDataIndex = Math.floor(Math.random() * 10 % questionData.length);
        var selectedQuestionData = questionData[questionDataIndex];

        //問題イメージオブジェクト設定
       var question = new Sprite(92, 92);
       question.image = game_.assets['./images/' + selectedQuestionData["question"]];
       question.x = -question.width;
       question.y = 120;
       scene.addChild(question);

        var charactor = new Charactor(game_.assets[Config['charactorImagePath']]);
        scene.addChild(charactor);

        var charactorHit = new Sprite(1, 1);
        charactorHit.x = charactor.x + charactor.width / 2;
        charactorHit.y = charactor.y + charactor.height / 2;
        scene.addChild(charactorHit);

        var scoreLabel = new Label("");            // ラベルをつくる
        scoreLabel.color = '#fff';                 // 白色に設定
        scoreLabel.x = scene.wight / 2;
        scene.addChild(scoreLabel);                // シーンに追加

        var answers = [];
        for(var i = 0; i < 3; i++) {
        var answer = new Label(selectedQuestionData["answer" + (i + 1)]);
          answer.width = 100;
          answer.height = 32;
          answer.backgroundColor = '#000';
          answer.textAlign = 'center';
          answer.color = '#ffffff';
          answer.x = -answer.width;
          answer.y = 50 + (i * 100);
          answer.font = '14px sans-serif';
          if (selectedQuestionData['ans_number'] - 1 == i) {
            answer.correct = true;

          }
          else {
            answer.correct = false;
          }
          answers.push(answer);
          scene.addChild(answer);
        }

        scene.addEventListener(Event.ENTER_FRAME, function(){
            scroll += SCROLL_SPEED;
            result['distance'] += SCROLL_SPEED;
            scoreLabel.text = scroll.toString()+'㍍走破'; // スコア表示を更新

            if (scroll == 350) {
              question.x = 350;
            }
            if (scroll == 840) {
              for(var i = 0; i < 3; i++) {
                answers[i].x = 850;
              }
            }

            question.x -= SCROLL_SPEED;
            for(var i = 0; i < 3; i++) {
              answers[i].x -= SCROLL_SPEED;
              if (answers[i].intersect(charactorHit)) {
                if (answers[i].correct) {
                  result['correctAnswer'] += 1
                  game_.pushScene(createCorrectAnswerScene(scroll));
                }
                else {
                  charactor.frame = 3;
                  game_.pushScene(createGameoverScene(scroll));
                }
              }
            }

            charactor.updateFrame();

            charactorHit.x = charactor.x + charactor.width/2;
            charactorHit.y = charactor.y + charactor.height/2;

            bg1.x -= SCROLL_SPEED;

            if (bg1.x <= -640) {
                bg1.x = 0;
            }
        });

        scene.addEventListener(Event.TOUCH_START, function(e){
          if (charactor.position == 0) {
            charactor.tl.moveTo(80, 50, 12);
          }
          else if (charactor.position == 1) {
            charactor.tl.moveTo(80, 150, 12);
          }
          else if (charactor.position == 2) {
            charactor.tl.moveTo(80, 250, 12);
          }
          charactor.position += 1;
          charactor.position = charactor.position % 3;
        });

        return scene;
    }

    var createCorrectAnswerScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var correctAnswerImage = new Sprite(236, 48);
      correctAnswerImage.image = game_.assets[Config['correctAnswerImagePath']];
      correctAnswerImage.x = 42;
      correctAnswerImage.y = 50;
      scene.addChild(correctAnswerImage);

      createScoreLabel(scene);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createStartScene());
      });

      return scene;
    };

    var createGameoverScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var gameoverImage = new Sprite(189, 97);
      gameoverImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/gameover.png'];
      gameoverImage.x = 66;
      gameoverImage.y = 50;
      scene.addChild(gameoverImage);

      createScoreLabel(scene);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createStartScene());
      });

      return scene;
    };

    game_.replaceScene(createStartScene());
  }

  game_.start();
}
<commit_msg>リトライのクリック時に直ぐゲームが始まるように修正<commit_after>enchant();

window.onload = function() {
  var result = {
    'correctAnswer': 0,

    'distance': 0
  };
  var game_ = new Game(320, 480);
  game_.fps = 24;
  var imagePaths = [
    Config['startImagePath'],
    Config['correctAnswerImagePath'],
    Config['gameOverImagePath'],
    Config['charactorImagePath'],
    Config['backgroundImagePath1'],
    Config['backgroundImagePath2']
  ];
  questionData.forEach(function(data) {
    imagePaths.push('./images/' + data['question']);
  });
  imagePaths.forEach(function(imagePath) {
   game_.preload(imagePath);
  });

  game_.onload = function() {
    var createStartScene = function() {
      var scene = new Scene();
      scene.backgroundColor = '#fcc800';

      var startImage = new Sprite(236, 48);
      startImage.image = game_.assets[Config['startImagePath']];
      startImage.x = 42;
      startImage.y = 50;
      scene.addChild(startImage);

      var description = createLabel('下（した）のステージの名前（なまえ）をタッチするとゲームが始（はじ）まるよ', 180);
      scene.addChild(description);

      // ステージ選択ラベル設定
      var selectStage1 = new Label('ステージA'); // ラベルを作る
      selectStage1.width = 320;
      selectStage1.textAlign = 'center';                 // 文字を中央寄せ
      selectStage1.color = '#ffffff';                    // 文字を白色に
      selectStage1.x = 0;                                // 横位置調整
      selectStage1.y = 222;                              // 縦位置調整
      selectStage1.font = '14px sans-serif';             // 28pxのゴシック体にする
      scene.addChild(selectStage1);                      // シーンに追加

      selectStage1.addEventListener(Event.TOUCH_START, function(e) {
          game_.replaceScene(createGameScene());
      });

      return scene;
    };

    var createLabel = function(text, y) {
      var label = new Label(text);
      label.width = 320;
      label.textAlign = 'center';
      label.color = '#ffffff';
      label.x = 0;
      label.y = y || 284;
      label.font = '14px sans-serif';
      return label;
    };

    var createScoreLabel = function(scene) {
      var label = createLabel('正解数（せいかいすう）: ' + result['correctAnswer'], 200);
      scene.addChild(label);
      label = createLabel('走った距離（はしったきょり）: ' + result['distance'] + '㍍', 220);
      scene.addChild(label);
    };

    var createGameScene = function() {
        var scroll = 0;

        var SCROLL_SPEED = 5;
        var BETWEEN_Q_A = 100;

        var scene = new Scene();
        scene.backgroundColor = '#8cc820';

        var bg1 = new Sprite(1280, 480);
        bg1.image = game_.assets[Config['backgroundImagePath1']];
        bg1.x = 0;
        bg1.y = 0;
        bg1.height = 600;
        scene.addChild(bg1);

        var questionDataIndex = Math.floor(Math.random() * 10 % questionData.length);
        var selectedQuestionData = questionData[questionDataIndex];

        //問題イメージオブジェクト設定
       var question = new Sprite(92, 92);
       question.image = game_.assets['./images/' + selectedQuestionData["question"]];
       question.x = -question.width;
       question.y = 120;
       scene.addChild(question);

        var charactor = new Charactor(game_.assets[Config['charactorImagePath']]);
        scene.addChild(charactor);

        var charactorHit = new Sprite(1, 1);
        charactorHit.x = charactor.x + charactor.width / 2;
        charactorHit.y = charactor.y + charactor.height / 2;
        scene.addChild(charactorHit);

        var scoreLabel = new Label("");            // ラベルをつくる
        scoreLabel.color = '#fff';                 // 白色に設定
        scoreLabel.x = scene.wight / 2;
        scene.addChild(scoreLabel);                // シーンに追加

        var answers = [];
        for(var i = 0; i < 3; i++) {
        var answer = new Label(selectedQuestionData["answer" + (i + 1)]);
          answer.width = 100;
          answer.height = 32;
          answer.backgroundColor = '#000';
          answer.textAlign = 'center';
          answer.color = '#ffffff';
          answer.x = -answer.width;
          answer.y = 50 + (i * 100);
          answer.font = '14px sans-serif';
          if (selectedQuestionData['ans_number'] - 1 == i) {
            answer.correct = true;

          }
          else {
            answer.correct = false;
          }
          answers.push(answer);
          scene.addChild(answer);
        }

        scene.addEventListener(Event.ENTER_FRAME, function(){
            scroll += SCROLL_SPEED;
            result['distance'] += SCROLL_SPEED;
            scoreLabel.text = scroll.toString()+'㍍走破'; // スコア表示を更新

            if (scroll == 350) {
              question.x = 350;
            }
            if (scroll == 840) {
              for(var i = 0; i < 3; i++) {
                answers[i].x = 850;
              }
            }

            question.x -= SCROLL_SPEED;
            for(var i = 0; i < 3; i++) {
              answers[i].x -= SCROLL_SPEED;
              if (answers[i].intersect(charactorHit)) {
                if (answers[i].correct) {
                  result['correctAnswer'] += 1
                  game_.pushScene(createCorrectAnswerScene(scroll));
                }
                else {
                  charactor.frame = 3;
                  game_.pushScene(createGameoverScene(scroll));
                }
              }
            }

            charactor.updateFrame();

            charactorHit.x = charactor.x + charactor.width/2;
            charactorHit.y = charactor.y + charactor.height/2;

            bg1.x -= SCROLL_SPEED;

            if (bg1.x <= -640) {
                bg1.x = 0;
            }
        });

        scene.addEventListener(Event.TOUCH_START, function(e){
          if (charactor.position == 0) {
            charactor.tl.moveTo(80, 50, 12);
          }
          else if (charactor.position == 1) {
            charactor.tl.moveTo(80, 150, 12);
          }
          else if (charactor.position == 2) {
            charactor.tl.moveTo(80, 250, 12);
          }
          charactor.position += 1;
          charactor.position = charactor.position % 3;
        });

        return scene;
    }

    var createCorrectAnswerScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var correctAnswerImage = new Sprite(236, 48);
      correctAnswerImage.image = game_.assets[Config['correctAnswerImagePath']];
      correctAnswerImage.x = 42;
      correctAnswerImage.y = 50;
      scene.addChild(correctAnswerImage);

      createScoreLabel(scene);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createGameScene());
      });

      return scene;
    };

    var createGameoverScene = function(scroll) {
      var scene = new Scene();
      scene.backgroundColor = 'rgba(0, 0, 0, 0.5)';

      var gameoverImage = new Sprite(189, 97);
      gameoverImage.image = game_.assets['./javascripts/enchant_js-0.8.3/images/gameover.png'];
      gameoverImage.x = 66;
      gameoverImage.y = 50;
      scene.addChild(gameoverImage);

      createScoreLabel(scene);

      var retryLabel = new Label('リトライ');
      retryLabel.width = 320;
      retryLabel.textAlign = 'center';
      retryLabel.color = '#ffffff';
      retryLabel.x = 0;
      retryLabel.y = 284;
      retryLabel.font = '24px sans-serif';
      scene.addChild(retryLabel);

      var buttonRetry = new Sprite(320, 32);
      buttonRetry.x = 0;
      buttonRetry.y = 284;
      scene.addChild(buttonRetry);

      buttonRetry.addEventListener(Event.TOUCH_END, function(){
        game_.popScene();
        game_.replaceScene(createGameScene());
      });

      return scene;
    };

    game_.replaceScene(createStartScene());
  }

  game_.start();
}
<|endoftext|>