bemusic/bemuse

View on GitHub
bemuse/src/app/analytics.js

Summary

Maintainability
B
6 hrs
Test Coverage
import ObjectID from 'bson-objectid'
import mean from 'mean'
import median from 'median'
import variance from 'variance'
import { MISSED } from 'bemuse/game/judgments'
import { stringify } from 'qs'

import * as Options from './entities/Options'
import getLR2Score from './interactors/getLR2Score'
import getNonMissedDeltas from './interactors/getNonMissedDeltas'

const ga = window.ga || function () {}
const startTime = Date.now()
const sid = ObjectID.generate()

function getLabel(chart) {
  return `${chart.md5}`
}

function getSongTitle(song) {
  if (song.custom) return '(custom song)'
  return song.title
}

export function send(category, action, label, value, extra) {
  console.log('[Analytics]', category, action, label, value, extra)
  ga('send', 'event', category, action, label, value)
  try {
    if (window.ga) {
      const sessionLength = Date.now() - startTime
      const data = {
        info: { sid, category, action, label, value, extra, t: sessionLength },
      }
      window.navigator.sendBeacon(
        'https://analytics.bemuse.ninja/collect.php?' + stringify(data)
      )
    }
  } catch (e) {
    console.warn('[Analytics]', 'Cannot send beacon:', e)
  }
  try {
    if (window.amplitude) {
      window.amplitude.getInstance().logEvent(`${category} / ${action}`, {
        label,
        value,
        ...extra,
      })
    }
  } catch (e) {
    console.warn('[Analytics]', 'Cannot send to Amplitude:', e)
  }
}

export function gameStart(song, chart, gameMode, options) {
  send('song', 'play', getSongTitle(song))
  send('game', 'start', getLabel(chart), chart.info.level, {
    gameMode,
    bga: Options.isBackgroundAnimationsEnabled(options) ? 'y' : 'n',
    autoVelocity: Options.isAutoVelocityEnabled(options) ? 'y' : 'n',
    latency: +options['system.offset.audio-input'],
  })
  send('game', 'mode', gameMode)
}

export function gameFinish(song, chart, gameState, gameMode) {
  const player = gameState.game.players[0]
  const notechart = player.notechart
  const state = gameState.player(player)
  const stats = state.stats
  send('song', 'finish', getSongTitle(song))
  send('game', 'finish', getLabel(chart), stats.score, {
    gameMode: gameMode,
    level: chart.info.level,
    bpm: chart.bpm.median,
    speed: state.speed,
    score: stats.score,
    maxCombo: stats.maxCombo,
    totalCombo: stats.totalCombo,
    totalNotes: stats.totalNotes,
    accuracy: stats.accuracy,
    stats: getDeltaStats(stats.deltas),
    lr2Score: getLR2Score(stats.deltas, notechart.expertJudgmentWindow),
    laneCover: player.options.laneCover,
    placement: player.options.placement,
    scratch: player.options.scratch,
    counts: {
      w1: stats.counts['1'],
      w2: stats.counts['2'],
      w3: stats.counts['3'],
      w4: stats.counts['4'],
      w5: stats.counts[MISSED],
    },
  })
}

export function recordGameLoadTime(gameLoadTimeMillis) {
  console.log(`[Analytics] Game load time: ${gameLoadTimeMillis} ms`)
  ga('send', 'timing', 'Game', 'load', gameLoadTimeMillis)
}

export function getDeltaStats(deltas) {
  const nonMissDeltas = getNonMissedDeltas(deltas)
  return {
    sd: Math.sqrt(variance(nonMissDeltas)),
    mean: mean(nonMissDeltas),
    median: median(nonMissDeltas),
  }
}

export function gameEscape(song, chart, gameState) {
  const state = gameState.player(gameState.game.players[0])
  send('song', 'escape', getSongTitle(song))
  send('game', 'escape', getLabel(chart), state.stats.score)
}

export function gameQuit(song, chart, gameState) {
  const state = gameState.player(gameState.game.players[0])
  send('song', 'quit', getSongTitle(song))
  send('game', 'quit', getLabel(chart), state.stats.score)
}

export function action(label) {
  send('action', 'trigger', label)
}