<commit_before>import * as xhr from './xhr';
import config from './config';
import sign from 'puz/sign';
import { Api as CgApi } from 'chessground/api';
import { getNow, puzzlePov, sound } from 'puz/util';
import { makeCgOpts } from 'puz/run';
import { parseUci } from 'chessops/util';
import { prop, Prop } from 'common';
import { Role } from 'chessground/types';
import { StormOpts, StormData, StormVm, StormRecap, StormPrefs } from './interfaces';
import { Run } from 'puz/interfaces';
import { Combo } from 'puz/combo';
import CurrentPuzzle from 'puz/current';
import { Clock } from 'puz/clock';
import { PromotionCtrl } from 'chess/promotion';

export default class StormCtrl {
  private data: StormData;
  private redraw: () => void;
  pref: StormPrefs;
  run: Run;
  vm: StormVm;
  trans: Trans;
  promotion: PromotionCtrl;
  ground = prop<CgApi | false>(false) as Prop<CgApi | false>;
  flipped = false;

  constructor(opts: StormOpts, redraw: (data: StormData) => void) {
    this.data = opts.data;
    this.pref = opts.pref;
    this.redraw = () => redraw(this.data);
    this.trans = lichess.trans(opts.i18n);
    this.run = {
      pov: puzzlePov(this.data.puzzles[0]),
      moves: 0,
      errors: 0,
      current: new CurrentPuzzle(0, this.data.puzzles[0]),
      clock: new Clock(config),
      history: [],
      combo: new Combo(config),
      modifier: {
        moveAt: 0,
      },
    };
    this.vm = {
      signed: prop(undefined),
      lateStart: false,
      filterFailed: false,
      filterSlow: false,
    };
    this.promotion = new PromotionCtrl(this.withGround, this.setGround, this.redraw);
    setTimeout(() => {
      this.run.current.moveIndex = 0;
      this.setGround();
    }, 100);
    this.checkDupTab();
    setTimeout(this.hotkeys, 1000);
    if (this.data.key) setTimeout(() => sign(this.data.key!).then(this.vm.signed), 1000 * 40);
    setTimeout(() => {
      if (!this.run.clock.startAt) {
        this.vm.lateStart = true;
        this.redraw();
      }
    }, config.timeToStart + 1000);
    lichess.pubsub.on('zen', () => {
      if (!this.run.endAt) {
        const zen = $('body').toggleClass('zen').hasClass('zen');
        window.dispatchEvent(new Event('resize'));
        xhr.setZen(zen);
      }
    });
    $('#zentog').on('click', this.toggleZen);
  }

  end = (): void => {
    this.run.history.reverse();
    this.run.endAt = getNow();
    this.ground(false);
    this.redraw();
    sound.end();
    xhr.record(this.runStats(), this.data.notAnExploit).then(res => {
      this.vm.response = res;
      this.redraw();
    });<commit_msg>only finish storm run once<commit_after>import * as xhr from './xhr';
import config from './config';
import sign from 'puz/sign';
import { Api as CgApi } from 'chessground/api';
import { getNow, puzzlePov, sound } from 'puz/util';
import { makeCgOpts } from 'puz/run';
import { parseUci } from 'chessops/util';
import { prop, Prop } from 'common';
import { Role } from 'chessground/types';
import { StormOpts, StormData, StormVm, StormRecap, StormPrefs } from './interfaces';
import { Run } from 'puz/interfaces';
import { Combo } from 'puz/combo';
import CurrentPuzzle from 'puz/current';
import { Clock } from 'puz/clock';
import { PromotionCtrl } from 'chess/promotion';

export default class StormCtrl {
  private data: StormData;
  private redraw: () => void;
  pref: StormPrefs;
  run: Run;
  vm: StormVm;
  trans: Trans;
  promotion: PromotionCtrl;
  ground = prop<CgApi | false>(false) as Prop<CgApi | false>;
  flipped = false;

  constructor(opts: StormOpts, redraw: (data: StormData) => void) {
    this.data = opts.data;
    this.pref = opts.pref;
    this.redraw = () => redraw(this.data);
    this.trans = lichess.trans(opts.i18n);
    this.run = {
      pov: puzzlePov(this.data.puzzles[0]),
      moves: 0,
      errors: 0,
      current: new CurrentPuzzle(0, this.data.puzzles[0]),
      clock: new Clock(config),
      history: [],
      combo: new Combo(config),
      modifier: {
        moveAt: 0,
      },
    };
    this.vm = {
      signed: prop(undefined),
      lateStart: false,
      filterFailed: false,
      filterSlow: false,
    };
    this.promotion = new PromotionCtrl(this.withGround, this.setGround, this.redraw);
    setTimeout(() => {
      this.run.current.moveIndex = 0;
      this.setGround();
    }, 100);
    this.checkDupTab();
    setTimeout(this.hotkeys, 1000);
    if (this.data.key) setTimeout(() => sign(this.data.key!).then(this.vm.signed), 1000 * 40);
    setTimeout(() => {
      if (!this.run.clock.startAt) {
        this.vm.lateStart = true;
        this.redraw();
      }
    }, config.timeToStart + 1000);
    lichess.pubsub.on('zen', () => {
      if (!this.run.endAt) {
        const zen = $('body').toggleClass('zen').hasClass('zen');
        window.dispatchEvent(new Event('resize'));
        xhr.setZen(zen);
      }
    });
    $('#zentog').on('click', this.toggleZen);
  }

  end = (): void => {
    if (this.run.endAt) return;
    this.run.history.reverse();
    this.run.endAt = getNow();
    this.ground(false);
    this.redraw();
    sound.end();
    xhr.record(this.runStats(), this.data.notAnExploit).then(res => {
      this.vm.response = res;
      this.redraw();
    });
{'fraction_non_alphanumeric': 0.16536334159832158, 'fraction_numerical': 0.009917985885943162, 'mean_word_length': 2.9487951807228914, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 25, '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': '3178456', 'n_tokens_mistral': 1852, 'n_tokens_neox': 1805, 'n_words': 393}