2017-07-08 09:04:16 -06:00
|
|
|
import { updateElements } from './clockView';
|
2017-07-17 08:59:50 -06:00
|
|
|
import { RoundData } from '../interfaces'
|
|
|
|
import { game } from 'game';
|
|
|
|
|
2017-04-24 06:57:06 -06:00
|
|
|
|
2017-07-08 09:04:16 -06:00
|
|
|
export type Seconds = number;
|
|
|
|
export type Centis = number;
|
|
|
|
export type Millis = number;
|
|
|
|
|
|
|
|
interface ClockOpts {
|
|
|
|
onFlag(): void;
|
|
|
|
soundColor?: Color
|
|
|
|
}
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
export type TenthsPref = 0 | 1 | 2;
|
|
|
|
|
2017-07-08 09:04:16 -06:00
|
|
|
export interface ClockData {
|
|
|
|
running: boolean;
|
|
|
|
initial: Seconds;
|
|
|
|
increment: Seconds;
|
|
|
|
white: Seconds;
|
|
|
|
black: Seconds;
|
|
|
|
emerg: Seconds;
|
2017-07-17 08:59:50 -06:00
|
|
|
showTenths: TenthsPref;
|
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;
|
2017-07-08 09:04:16 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
interface EmergSound {
|
|
|
|
play(): void;
|
|
|
|
next?: number;
|
|
|
|
delay: Millis,
|
|
|
|
playable: {
|
|
|
|
white: boolean;
|
|
|
|
black: boolean;
|
|
|
|
};
|
2017-07-08 06:49:10 -06:00
|
|
|
}
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
const nowFun = window.performance && performance.now() > 0 ?
|
|
|
|
performance.now.bind(performance) : Date.now;
|
|
|
|
|
2017-07-08 09:04:16 -06:00
|
|
|
export class ClockController {
|
|
|
|
|
|
|
|
emergSound: EmergSound = {
|
|
|
|
play: window.lichess.sound.lowtime,
|
2017-04-24 06:57:06 -06:00
|
|
|
delay: 20000,
|
|
|
|
playable: {
|
|
|
|
white: true,
|
|
|
|
black: true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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
|
|
|
|
2017-07-08 09:04:16 -06:00
|
|
|
timePercentDivisor: number
|
|
|
|
emergMs: Millis;
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
elements = {
|
2017-07-08 09:04:16 -06:00
|
|
|
white: {},
|
|
|
|
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;
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
constructor(d: RoundData, public opts: ClockOpts) {
|
|
|
|
const cdata = d.clock!;
|
2017-07-08 09:04:16 -06:00
|
|
|
|
2017-07-18 19:43:34 -06:00
|
|
|
if (cdata.showTenths === 0) this.showTenths = () => false;
|
|
|
|
else {
|
|
|
|
const cutoff = cdata.showTenths === 1 ? 10000 : 3600000;
|
|
|
|
this.showTenths = (time) => time < cutoff;
|
|
|
|
}
|
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
this.showBar = cdata.showBar;
|
|
|
|
this.timePercentDivisor = .1 / (Math.max(cdata.initial, 2) + 5 * cdata.increment);
|
2017-07-08 09:04:16 -06:00
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
this.emergMs = 1000 * Math.min(60, Math.max(10, cdata.initial * .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
|
|
|
|
2017-07-17 08:59:50 -06:00
|
|
|
timePercent = (millis: number): number =>
|
|
|
|
Math.max(0, Math.min(100, millis * this.timePercentDivisor));
|
|
|
|
|
|
|
|
setClock = (d: RoundData, white: Seconds, black: Seconds, delay: Centis = 0) => {
|
|
|
|
const isClockRunning = game.playable(d) &&
|
2017-07-18 19:43:34 -06:00
|
|
|
((d.game.turns - d.game.startedAtTurn) > 1 || d.clock!.running),
|
|
|
|
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,
|
2017-07-18 19:43:34 -06:00
|
|
|
lastUpdate: nowFun() + 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 => {
|
|
|
|
this.times[color] += time * 10
|
|
|
|
}
|
|
|
|
|
|
|
|
stopClock = (): Millis|void => {
|
|
|
|
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
|
|
|
}
|
2017-07-17 08:59:50 -06:00
|
|
|
}
|
|
|
|
|
2017-07-18 19:43:34 -06:00
|
|
|
scheduleTick = (time: Millis, extraDelay: Millis) => {
|
|
|
|
if (this.tickCallback !== undefined) clearTimeout(this.tickCallback);
|
|
|
|
this.tickCallback = setTimeout(
|
|
|
|
this.tick,
|
|
|
|
time % (this.showTenths(time) ? 100 : 500) + 1 + extraDelay);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should only be involked by scheduleTick.
|
|
|
|
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
|
|
|
|
|
|
|
const now = nowFun();
|
2017-08-01 14:31:47 -06:00
|
|
|
const millis = this.times[color] - this.elapsed(now);
|
2017-04-24 06:57:06 -06:00
|
|
|
|
2017-07-08 09:04:16 -06:00
|
|
|
if (millis <= 0) this.opts.onFlag();
|
2017-07-17 08:59:50 -06:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2017-07-18 19:43:34 -06:00
|
|
|
|
|
|
|
this.scheduleTick(millis, 0);
|
2017-04-24 06:57:06 -06:00
|
|
|
};
|
|
|
|
|
2017-08-01 14:31:47 -06:00
|
|
|
elapsed = (now = nowFun()) => Math.max(0, now - this.times.lastUpdate);
|
2017-07-23 15:46:17 -06:00
|
|
|
|
|
|
|
millisOf = (color: Color): Millis => (this.times.activeColor === color ?
|
|
|
|
Math.max(0, this.times[color] - this.elapsed()) :
|
2017-07-17 08:59:50 -06:00
|
|
|
this.times[color]
|
|
|
|
);
|
2017-09-09 14:48:58 -06:00
|
|
|
|
|
|
|
isRunning = () => this.times.activeColor !== undefined;
|
2017-04-24 06:57:06 -06:00
|
|
|
}
|