lila/ui/round/src/clock/clockCtrl.ts

172 lines
4.5 KiB
TypeScript
Raw Permalink Normal View History

2017-07-08 09:04:16 -06:00
import { updateElements } from './clockView';
2021-02-06 06:26:05 -07:00
import { RoundData } from '../interfaces';
2019-01-25 08:30:41 -07:00
import * as game from 'game';
2017-07-08 09:04:16 -06:00
export type Seconds = number;
export type Centis = number;
export type Millis = number;
interface ClockOpts {
2021-02-06 06:26:05 -07:00
onFlag(): void;
soundColor?: Color;
nvui: boolean;
2017-07-08 09:04:16 -06:00
}
export interface ClockData {
running: boolean;
initial: Seconds;
increment: Seconds;
white: Seconds;
black: Seconds;
emerg: Seconds;
2021-04-20 12:37:16 -06:00
showTenths: Prefs.ShowClockTenths;
2017-07-08 09:04:16 -06:00
showBar: boolean;
2017-07-08 15:09:41 -06:00
moretime: number;
2017-07-08 09:04:16 -06:00
}
interface Times {
white: Millis;
black: Millis;
activeColor?: Color;
2017-07-08 09:04:16 -06:00
lastUpdate: Millis;
}
type ColorMap<T> = { [C in Color]: T };
2017-07-08 09:04:16 -06:00
export interface ClockElements {
time?: HTMLElement;
clock?: HTMLElement;
bar?: HTMLElement;
2019-06-14 17:45:17 -06:00
barAnim?: Animation;
2017-07-08 09:04:16 -06:00
}
interface EmergSound {
play(): void;
next?: number;
2021-02-06 06:26:05 -07:00
delay: Millis;
2017-07-08 09:04:16 -06:00
playable: {
white: boolean;
black: boolean;
};
2017-07-08 06:49:10 -06:00
}
2017-07-08 09:04:16 -06:00
export class ClockController {
emergSound: EmergSound = {
play: () => lichess.sound.play('lowTime'),
2017-04-24 06:57:06 -06:00
delay: 20000,
playable: {
white: true,
2021-02-06 06:26:05 -07:00
black: true,
},
2017-04-24 06:57:06 -06:00
};
showTenths: (millis: Millis) => boolean;
showBar: boolean;
2017-07-08 09:04:16 -06:00
times: Times;
2017-04-24 06:57:06 -06:00
2021-02-06 06:26:05 -07:00
barTime: number;
timeRatioDivisor: number;
2017-07-08 09:04:16 -06:00
emergMs: Millis;
elements = {
2017-07-08 09:04:16 -06:00
white: {},
2021-02-06 06:26:05 -07:00
black: {},
} as ColorMap<ClockElements>;
2017-07-08 09:04:16 -06:00
private tickCallback?: number;
2019-01-25 01:06:31 -07:00
constructor(d: RoundData, readonly opts: ClockOpts) {
const cdata = d.clock!;
2017-07-08 09:04:16 -06:00
2021-04-20 12:37:16 -06:00
if (cdata.showTenths === Prefs.ShowClockTenths.Never) this.showTenths = () => false;
else {
2021-04-20 12:37:16 -06:00
const cutoff = cdata.showTenths === Prefs.ShowClockTenths.Below10Secs ? 10000 : 3600000;
2021-02-06 06:26:05 -07:00
this.showTenths = time => time < cutoff;
}
2019-01-25 01:06:31 -07:00
this.showBar = cdata.showBar && !this.opts.nvui;
2019-06-14 17:45:17 -06:00
this.barTime = 1000 * (Math.max(cdata.initial, 2) + 5 * cdata.increment);
this.timeRatioDivisor = 1 / this.barTime;
2017-07-08 09:04:16 -06:00
2021-02-06 06:26:05 -07:00
this.emergMs = 1000 * Math.min(60, Math.max(10, cdata.initial * 0.125));
2017-04-24 06:57:06 -06:00
this.setClock(d, cdata.white, cdata.black);
2017-07-08 09:04:16 -06:00
}
2017-04-24 06:57:06 -06:00
2021-02-06 06:26:05 -07:00
timeRatio = (millis: number): number => Math.min(1, millis * this.timeRatioDivisor);
setClock = (d: RoundData, white: Seconds, black: Seconds, delay: Centis = 0) => {
const isClockRunning = game.playable(d) && (game.playedTurns(d) > 1 || d.clock!.running),
2021-02-06 06:26:05 -07:00
delayMs = delay * 10;
2017-04-24 06:57:06 -06:00
2017-07-08 09:04:16 -06:00
this.times = {
2017-04-24 06:57:06 -06:00
white: white * 1000,
black: black * 1000,
activeColor: isClockRunning ? d.game.player : undefined,
2021-02-06 06:26:05 -07:00
lastUpdate: performance.now() + delayMs,
2017-04-24 06:57:06 -06:00
};
if (isClockRunning) this.scheduleTick(this.times[d.game.player], delayMs);
2017-04-24 06:57:06 -06:00
};
addTime = (color: Color, time: Centis): void => {
2021-02-06 06:26:05 -07:00
this.times[color] += time * 10;
};
2021-02-06 06:26:05 -07:00
stopClock = (): Millis | void => {
const color = this.times.activeColor;
if (color) {
const curElapse = this.elapsed();
this.times[color] = Math.max(0, this.times[color] - curElapse);
this.times.activeColor = undefined;
return curElapse;
2017-06-16 19:56:18 -06:00
}
2021-02-06 06:26:05 -07:00
};
2021-02-06 06:26:05 -07:00
hardStopClock = (): void => (this.times.activeColor = undefined);
2019-01-19 21:39:54 -07:00
private scheduleTick = (time: Millis, extraDelay: Millis) => {
if (this.tickCallback !== undefined) clearTimeout(this.tickCallback);
this.tickCallback = setTimeout(
this.tick,
2019-06-09 05:34:36 -06:00
// changing the value of active node confuses the chromevox screen reader
2019-01-19 21:39:54 -07:00
// so update the clock less often
2021-02-06 06:26:05 -07:00
this.opts.nvui ? 1000 : (time % (this.showTenths(time) ? 100 : 500)) + 1 + extraDelay
);
};
2019-01-18 21:33:06 -07:00
// Should only be invoked by scheduleTick.
private tick = (): void => {
this.tickCallback = undefined;
const color = this.times.activeColor;
if (color === undefined) return;
const now = performance.now();
const millis = Math.max(0, this.times[color] - this.elapsed(now));
2017-04-24 06:57:06 -06:00
this.scheduleTick(millis, 0);
if (millis === 0) this.opts.onFlag();
else updateElements(this, this.elements[color], millis);
2017-04-24 06:57:06 -06:00
if (this.opts.soundColor === color) {
2017-07-08 09:04:16 -06:00
if (this.emergSound.playable[color]) {
if (millis < this.emergMs && !(now < this.emergSound.next!)) {
this.emergSound.play();
this.emergSound.next = now + this.emergSound.delay;
this.emergSound.playable[color] = false;
2017-04-24 06:57:06 -06:00
}
2017-07-08 09:04:16 -06:00
} else if (millis > 1.5 * this.emergMs) {
this.emergSound.playable[color] = true;
2017-04-24 06:57:06 -06:00
}
}
};
elapsed = (now = performance.now()) => Math.max(0, now - this.times.lastUpdate);
2021-02-06 06:26:05 -07:00
millisOf = (color: Color): Millis =>
this.times.activeColor === color ? Math.max(0, this.times[color] - this.elapsed()) : this.times[color];
isRunning = () => this.times.activeColor !== undefined;
2017-04-24 06:57:06 -06:00
}