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-17 08:59:50 -06:00
|
|
|
|
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;
|
2017-07-17 08:59:50 -06:00
|
|
|
activeColor?: Color;
|
2017-07-08 09:04:16 -06:00
|
|
|
lastUpdate: Millis;
|
|
|
|
}
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
type ColorMap<T> = { [C in Color]: T };
|
2017-07-08 09:04:16 -06:00
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
export interface ClockElements {
|
|
|
|
time?: HTMLElement;
|
2017-08-18 12:19:40 -06:00
|
|
|
clock?: HTMLElement;
|
2017-07-17 08:59:50 -06:00
|
|
|
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 = {
|
2020-09-29 10:45:00 -06:00
|
|
|
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
|
|
|
};
|
|
|
|
|
2017-07-18 19:43:34 -06:00
|
|
|
showTenths: (millis: Millis) => boolean;
|
2017-07-17 08:59:50 -06:00
|
|
|
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;
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
elements = {
|
2017-07-08 09:04:16 -06:00
|
|
|
white: {},
|
2021-02-06 06:26:05 -07:00
|
|
|
black: {},
|
2017-07-17 08:59:50 -06:00
|
|
|
} as ColorMap<ClockElements>;
|
2017-07-08 09:04:16 -06:00
|
|
|
|
2017-07-18 19:43:34 -06:00
|
|
|
private tickCallback?: number;
|
|
|
|
|
2019-01-25 01:06:31 -07:00
|
|
|
constructor(d: RoundData, readonly opts: ClockOpts) {
|
2017-07-17 08:59:50 -06:00
|
|
|
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;
|
2017-07-18 19:43:34 -06:00
|
|
|
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;
|
2017-07-18 19:43:34 -06:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2017-07-17 08:59:50 -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);
|
2017-07-17 08:59:50 -06:00
|
|
|
|
|
|
|
setClock = (d: RoundData, white: Seconds, black: Seconds, delay: Centis = 0) => {
|
2018-01-17 05:44:23 -07:00
|
|
|
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,
|
2017-07-17 08:59:50 -06:00
|
|
|
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
|
|
|
};
|
2017-07-18 19:43:34 -06:00
|
|
|
|
|
|
|
if (isClockRunning) this.scheduleTick(this.times[d.game.player], delayMs);
|
2017-04-24 06:57:06 -06:00
|
|
|
};
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
addTime = (color: Color, time: Centis): void => {
|
2021-02-06 06:26:05 -07:00
|
|
|
this.times[color] += time * 10;
|
|
|
|
};
|
2017-07-17 08:59:50 -06:00
|
|
|
|
2021-02-06 06:26:05 -07:00
|
|
|
stopClock = (): Millis | void => {
|
2017-07-17 08:59:50 -06:00
|
|
|
const color = this.times.activeColor;
|
|
|
|
if (color) {
|
2017-07-23 15:46:17 -06:00
|
|
|
const curElapse = this.elapsed();
|
|
|
|
this.times[color] = Math.max(0, this.times[color] - curElapse);
|
2017-07-17 08:59:50 -06:00
|
|
|
this.times.activeColor = undefined;
|
2017-07-23 15:46:17 -06:00
|
|
|
return curElapse;
|
2017-06-16 19:56:18 -06:00
|
|
|
}
|
2021-02-06 06:26:05 -07:00
|
|
|
};
|
2017-07-17 08:59:50 -06:00
|
|
|
|
2021-02-06 06:26:05 -07:00
|
|
|
hardStopClock = (): void => (this.times.activeColor = undefined);
|
2017-12-02 21:43:00 -07:00
|
|
|
|
2019-01-19 21:39:54 -07:00
|
|
|
private scheduleTick = (time: Millis, extraDelay: Millis) => {
|
2017-07-18 19:43:34 -06:00
|
|
|
if (this.tickCallback !== undefined) clearTimeout(this.tickCallback);
|
2019-11-22 09:53:32 -07:00
|
|
|
this.tickCallback = setTimeout(
|
2017-07-18 19:43:34 -06:00
|
|
|
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
|
|
|
|
);
|
|
|
|
};
|
2017-07-18 19:43:34 -06:00
|
|
|
|
2019-01-18 21:33:06 -07:00
|
|
|
// Should only be invoked by scheduleTick.
|
2017-07-18 19:43:34 -06:00
|
|
|
private tick = (): void => {
|
|
|
|
this.tickCallback = undefined;
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
const color = this.times.activeColor;
|
2017-07-18 19:43:34 -06:00
|
|
|
if (color === undefined) return;
|
2017-07-17 08:59:50 -06:00
|
|
|
|
2019-07-09 14:17:37 -06:00
|
|
|
const now = performance.now();
|
2019-06-22 12:07:46 -06:00
|
|
|
const millis = Math.max(0, this.times[color] - this.elapsed(now));
|
2017-04-24 06:57:06 -06:00
|
|
|
|
2019-06-22 12:07:46 -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
|
|
|
|
2017-07-17 08:59:50 -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
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-07-09 14:17:37 -06:00
|
|
|
elapsed = (now = performance.now()) => Math.max(0, now - this.times.lastUpdate);
|
2017-07-23 15:46:17 -06:00
|
|
|
|
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];
|
2017-09-09 14:48:58 -06:00
|
|
|
|
|
|
|
isRunning = () => this.times.activeColor !== undefined;
|
2017-04-24 06:57:06 -06:00
|
|
|
}
|