/**
* BORDERCLOCKS: Manages the ClockInferenceEngine and ClockUtil classes and communicates with the keyboard application. Handles switch-press events and makes selection decisions based on the clock probabilities.
* @module broderclocks
*/
import * as cie from './clock_inference_engine.js';
import * as config from './config.js';
/**
* Manages the ClockInferenceEngine and ClockUtil classes and communicates with the keyboard application. Handles switch-press events and makes selection decisions based on the clock probabilities.
* @param {Keyboard} parent - the parent class, usually an instance of the Keyboard class
* @property {ClockInference} clock_inf - The instance of the `ClockInference` class.
* @property {Array<number>} abs_click_times - An array of the 'absolute' click times from the user leading up to the next selection.
* Absolute click times are UNIX timestamps (ms since Epoch). `abs_click_times` are added as the user clicks.
* @property {Array<number>} rel_click_times - An array of the 'relative' click times from the user leading up to the next selection.
* Relative click times are the time in ms that the user clicked relative to when the clock they selected was at noon.
* `rel_click_times` are added in `ClockInferenceEngine.learn_scores()` following `config.learn_delay` rounds after a selection is made.
* @property {number} latest_time - The last time that the screen was updated as a UNIX timestamps (ms since Epoch). Updated in `ClockUtil.increment()`.
* Used to account for the framerate when the user clicks.
*/
export class BroderClocks {
constructor(parent) {
this.parent = parent;
this.parent.bc_init = true;
this.clock_inf = new cie.ClockInference(this.parent, this, this.parent.prev_data);
this.is_undo = false;
this.is_equalize = false;
this.latest_time = Date.now() / 1000;
this.abs_click_times = [];
this.rel_click_times = [];
this.time_rotate = this.parent.time_rotate;
this.clock_inf.clock_util.change_period(this.time_rotate);
}
/**
* triggered by a switch-press event in the main keyboard class. Updates the clock posteriors and the histogram
* given the information in the new click.
* @param {float} time_in - The epoch-timestamp in ms that the user clicked their switch.
*/
select(time_in) {
this.clock_inf.update_scores(time_in - this.latest_time);
if (config.is_learning) {
this.clock_inf.update_history(time_in - this.latest_time);
}
// store the absolute click time
this.abs_click_times.push(time_in);
if (this.clock_inf.is_winner() && !this.parent.in_tutorial) {
this.clock_inf.win_history[0] = this.clock_inf.sorted_inds[0];
this.clock_inf.entropy.update_bits();
this.parent.make_choice(this.clock_inf.sorted_inds[0]);
} else {
this.init_round(false, false, []);
}
}
/**
* Continues the select process after the word cache promise has loaded
* @param results {Object} - Object with 5 parameters specifying the setup for the new round:
* @param {Array<number>} results.words_on - the clocks turned on given the new word predictions
* @param {Array<number>} results.words_off - the clocks turned off given the new word predictions
* @param {Array<number>} results.word_score_prior - the prior distribution for the clocks_on given the new lm results
* @param {Boolean} results.is_undo - whether the undo clock was selected in the last selection round
* @param {Boolean} results.is_equalize - ? holdover from an obsolete corrective character
* @param {Boolean} results.skip_hist - ? need to figure this out
*/
continue_select(results) {
this.clock_inf.clocks_on = results.words_on;
this.clock_inf.clocks_off = results.words_off;
var clock_score_prior = results.word_score_prior;
this.is_undo = results.is_undo;
this.is_equalize = results.is_equalize;
var skip_hist = results.skip_hist;
if (skip_hist) {
this.init_round(true, true, clock_score_prior);
} else {
if (!this.parent.in_tutorial) {
this.clock_inf.learn_scores(this.is_undo);
}
this.init_round(true, false, clock_score_prior);
}
}
/**
* re-initializes variables at the beginning of a new selection round
*/
init_bits() {
this.bits_per_select = Math.log(this.clock_inf.clocks_on.length) / Math.log(2);
this.num_bits = 0;
}
/**
* re-initializes variables at the beginning of a new selection round.
* @param {Array<number>} clock_score_prior - the prior probabilities from the language model of the clocks currently on.
*/
init_follow_up(clock_score_prior) {
this.init_round(false, false, clock_score_prior);
this.clock_inf.clock_history = [[]];
this.clock_inf.win_history = [-1];
this.init_bits();
}
/**
* Sets up the inference aspects for a new round following a switch event
* @param {Boolean} is_win - Whether a clock is selected after the switch event.
* @param {Boolean} is_start - Whether this is the first round after a switch event.
* @param {Array<number>} clock_score_prior - The prior probabilities over the active clocks before the switch event.
*/
init_round(is_win, is_start, clock_score_prior) {
this.clock_inf.clock_util.init_round(this.clock_inf.clocks_li);
this.clock_inf.clock_util.init_round(this.clock_inf.clocks_on);
var clock;
var clock_ind;
var top_score;
if (is_win || is_start) {
var count = 0;
if (this.is_undo && !this.is_equalize) {
for (clock_ind in this.clock_inf.clocks_on) {
clock = this.clock_inf.clocks_on[clock_ind];
this.clock_inf.cscores[clock] = 0;
count += 1;
}
top_score = 0;
} else {
for (clock_ind in this.clock_inf.clocks_on) {
clock = this.clock_inf.clocks_on[clock_ind];
this.clock_inf.cscores[clock] = clock_score_prior[count];
count += 1;
}
top_score = 0;
}
}
this.clock_inf.update_sorted_inds();
this.clock_inf.clock_util.update_curhours(this.clock_inf.sorted_inds);
this.clock_inf.handicap_cscores(is_win, is_start);
top_score = this.clock_inf.cscores[this.clock_inf.sorted_inds[0]];
var bound_score;
if (this.clock_inf.clock_history[0].length === 0) {
bound_score = top_score - config.max_init_diff;
} else {
bound_score = top_score - this.parent.win_diffs[this.clock_inf.sorted_inds[0]];
}
for (var i in this.clock_inf.clocks_on) {
clock_ind = this.clock_inf.clocks_on[i];
clock = this.parent.clockgrid.clocks[clock_ind];
if (this.clock_inf.cscores[clock_ind] > bound_score) {
clock.highlighted = true;
clock.draw_face();
} else {
clock.highlighted = false;
clock.draw_face();
}
}
}
}