Skip to content

Commit

Permalink
Refactor game charts some more
Browse files Browse the repository at this point in the history
And fix study acpl chart cursor again by moving
the pubsub.on('ply', ...) call into the chart initializer.
  • Loading branch information
benediktwerner committed Oct 6, 2022
1 parent 4557bd6 commit d9503c9
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 324 deletions.
11 changes: 0 additions & 11 deletions ui/@types/lichess/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ interface Lichess {
update(data: any, mainline: any[]): void;
(data: any, mainline: any[], trans: Trans, el: HTMLElement): void;
};
movetimeChart: any;
playMusic(): any;
quietMode?: boolean;
analysis?: any; // expose the analysis ctrl
Expand Down Expand Up @@ -260,16 +259,6 @@ interface Window {
readonly LichessAnalyseNvui?: (ctrl: any) => {
render(): any;
};
readonly LichessChartGame: {
acpl: {
(data: any, mainline: Tree.Node[], trans: Trans, el: HTMLElement, hunter: boolean): Promise<void>;
update?(data: any, mainline: Tree.Node[]): void;
};
movetime: {
(data: any, trans: Trans, hunter: boolean): Promise<void>;
render?(): void;
};
};
readonly LichessChartRatingHistory?: any;
readonly LichessKeyboardMove?: any;
readonly stripeHandler: any;
Expand Down
6 changes: 4 additions & 2 deletions ui/analyse/src/ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ export default class AnalyseCtrl {
if (this.music && set !== 'music') this.music = null;
});

lichess.pubsub.on('analysis.change.trigger', this.onChange);
lichess.pubsub.on('ply.trigger', () =>
lichess.pubsub.emit('ply', this.node.ply, this.tree.lastMainlineNode(this.path).ply === this.node.ply)
);
lichess.pubsub.on('analysis.chart.click', index => {
this.jumpToIndex(index);
this.redraw();
Expand Down Expand Up @@ -389,7 +391,7 @@ export default class AnalyseCtrl {
if (this.study) this.study.onJump();
}
if (this.music) this.music.jump(this.node);
lichess.pubsub.emit('ply', this.node.ply, this.tree.lastMainlineNode(this.path).ply);
lichess.pubsub.emit('ply', this.node.ply, this.tree.lastMainlineNode(this.path).ply === this.node.ply);
this.showGround();
}

Expand Down
42 changes: 17 additions & 25 deletions ui/analyse/src/serverSideUnderboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { PlyChartHTMLElement, PlyChart } from 'chart/dist/interface';
import type { AcplChart } from 'chart/dist/interface';

import AnalyseCtrl from './ctrl';
import { baseUrl } from './view/util';
Expand All @@ -18,8 +18,8 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) {
gameGifLink = document.querySelector('.game-gif') as HTMLAnchorElement,
positionGifLink = document.querySelector('.position-gif') as HTMLAnchorElement;
let lastInputHash: string;
let advChart: PlyChart;
let timeChart: PlyChart;
let advChart: AcplChart;
let timeChartLoaded = false;

const updateGifLinks = (fen: Fen) => {
const ds = document.body.dataset;
Expand All @@ -44,7 +44,7 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) {
() => (v ? $menu.find('[data-panel="computer-analysis"]') : $menu.find('span:eq(1)')).trigger('mousedown'),
50
);
if (v) $('#acpl-chart').each((_, e) => (e as PlyChartHTMLElement).highcharts.reflow());
if (v) advChart?.reflow();
});
lichess.pubsub.on('analysis.change', (fen: Fen, _) => {
const nextInputHash = `${fen}${ctrl.bottomColor()}`;
Expand All @@ -54,13 +54,9 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) {
lastInputHash = nextInputHash;
}
});
lichess.pubsub.on('ply', (ply: number, mainlinePly: number) => {
advChart?.selectPly(mainlinePly, ply == mainlinePly);
timeChart?.selectPly(mainlinePly, ply == mainlinePly);
});
lichess.pubsub.on('analysis.server.progress', (d: AnalyseData) => {
if (!window.LichessChartGame) startAdvantageChart();
else if (window.LichessChartGame.acpl.update) window.LichessChartGame.acpl.update(d, ctrl.mainline);
else advChart?.updateData(d, ctrl.mainline);
if (d.analysis && !d.analysis.partial) $('#acpl-chart-loader').remove();
});
}
Expand All @@ -69,19 +65,17 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) {
`<div id="acpl-chart-loader"><span>Stockfish 15<br>server analysis</span>${lichess.spinnerHtml}</div>`;

function startAdvantageChart() {
if (window.LichessChartGame?.acpl.update || window.LichessAnalyseNvui) return;
if (advChart || window.LichessAnalyseNvui) return;
const loading = !ctrl.tree.root.eval || !Object.keys(ctrl.tree.root.eval).length;
const $panel = $panels.filter('.computer-analysis');
if (!$('#acpl-chart').length) $panel.html('<div id="acpl-chart"></div>' + (loading ? chartLoader() : ''));
else if (loading && !$('#acpl-chart-loader').length) $panel.append(chartLoader());
lichess.loadModule('chart.game').then(() => {
window
.LichessChartGame!.acpl(data, ctrl.mainline, ctrl.trans, $('#acpl-chart')[0] as HTMLElement, ctrl.opts.hunter)
.then(() => {
advChart = ($('#acpl-chart')[0] as PlyChartHTMLElement).highcharts;
advChart?.selectPly(ctrl.node.ply, ctrl.tree.lastMainlineNode(ctrl.path).ply == ctrl.node.ply);
});
});
lichess
.loadModule('chart.game')
.then(() => window.LichessChartGame.acpl($('#acpl-chart')[0] as HTMLElement, data, ctrl.mainline, ctrl.trans))
.then(chart => {
advChart = chart;
});
}

const storage = lichess.storage.make('analysis.panel');
Expand All @@ -92,13 +86,11 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) {
.removeClass('active')
.filter('.' + panel)
.addClass('active');
if ((panel == 'move-times' || ctrl.opts.hunter) && !window.LichessChartGame?.movetime.render)
lichess.loadModule('chart.game').then(() =>
window.LichessChartGame!.movetime(data, ctrl.trans, ctrl.opts.hunter).then(() => {
timeChart = ($('#movetimes-chart')[0] as PlyChartHTMLElement).highcharts;
timeChart?.selectPly(ctrl.node.ply, ctrl.tree.lastMainlineNode(ctrl.path).ply == ctrl.node.ply);
})
);
if ((panel == 'move-times' || ctrl.opts.hunter) && !timeChartLoaded)
lichess.loadModule('chart.game').then(() => {
timeChartLoaded = true;
window.LichessChartGame.movetime($('#movetimes-chart')[0] as HTMLElement, data, ctrl.trans, ctrl.opts.hunter);
});
if ((panel == 'computer-analysis' || ctrl.opts.hunter) && $('#acpl-chart').length)
setTimeout(startAdvantageChart, 200);
};
Expand Down
18 changes: 8 additions & 10 deletions ui/analyse/src/study/serverEval.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
import { prop } from 'common';
import { AcplChart } from 'chart/dist/interface';
import { bind, onInsert } from 'common/snabbdom';
import { spinnerVdom } from 'common/spinner';
import type { PlyChartHTMLElement } from 'chart/dist/interface';
import { h, VNode } from 'snabbdom';
import AnalyseCtrl from '../ctrl';

export default class ServerEval {
requested = prop(false);
chartEl = prop<PlyChartHTMLElement | null>(null);
requested = false;
chart?: AcplChart;

constructor(readonly root: AnalyseCtrl, readonly chapterId: () => string) {}

reset = () => {
this.requested(false);
this.requested = false;
};

onMergeAnalysisData = () => window.LichessChartGame?.acpl.update?.(this.root.data, this.root.mainline);
onMergeAnalysisData = () => this.chart?.updateData(this.root.data, this.root.mainline);

request = () => {
this.root.socket.send('requestAnalysis', this.chapterId());
this.requested(true);
this.requested = true;
};
}

export function view(ctrl: ServerEval): VNode {
const analysis = ctrl.root.data.analysis;

if (!ctrl.root.showComputer()) return disabled();
if (!analysis) return ctrl.requested() ? requested() : requestButton(ctrl);
if (!analysis) return ctrl.requested ? requested() : requestButton(ctrl);

return h(
'div.study__server-eval.ready.' + analysis.id,
{
hook: onInsert(el => {
lichess.requestIdleCallback(async () => {
await lichess.loadModule('chart.game');
window.LichessChartGame!.acpl(ctrl.root.data, ctrl.root.mainline, ctrl.root.trans, el, false);
ctrl.chartEl(el as PlyChartHTMLElement);
ctrl.chart = await window.LichessChartGame!.acpl(el, ctrl.root.data, ctrl.root.mainline, ctrl.root.trans);
}, 800);
}),
},
Expand Down
32 changes: 18 additions & 14 deletions ui/chart/src/acpl.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { loadHighcharts, MovePoint, selectPly } from './common';
import divisionLines from './division';
import { PlyChartHTMLElement } from './interface';
import { AcplChart, AnalyseData, Player } from './interface';

const acpl: Window['LichessChartGame']['acpl'] = async (
data: any,
mainline: any[],
trans: Trans,
el: PlyChartHTMLElement
el: HTMLElement,
data: AnalyseData,
mainline: Tree.Node[],
trans: Trans
) => {
await loadHighcharts('highchart');
acpl.update = (d: any, mainline: any[]) =>
el.highcharts && el.highcharts.series[0].setData(makeSerieData(d, mainline) as any);

const area = window.Highcharts.theme.lichess.area;
const line = window.Highcharts.theme.lichess.line;

const blurs = [toBlurArray(data.player), toBlurArray(data.opponent)];
if (data.player.color === 'white') blurs.reverse();

const makeSerieData = (d: any, mainline: any[]) => {
const makeSerieData = (d: AnalyseData, mainline: Tree.Node[]) => {
const partial = !d.analysis || d.analysis.partial;
return mainline.slice(1).map(node => {
const isWhite = (node.ply & 1) == 1;

let cp;
if (node.eval && node.eval.mate) {
cp = node.eval.mate > 0 ? Infinity : -Infinity;
} else if (node.san.includes('#')) {
} else if (node.san?.includes('#')) {
cp = isWhite ? Infinity : -Infinity;
if (d.game.variant.key === 'antichess') cp = -cp;
} else if (node.eval && typeof node.eval.cp !== 'undefined') {
Expand Down Expand Up @@ -62,7 +60,7 @@ const acpl: Window['LichessChartGame']['acpl'] = async (
const disabled = { enabled: false };
const noText = { text: null };
const serieData = makeSerieData(data, mainline);
el.highcharts = window.Highcharts.chart(el, {
const chart: AcplChart = window.Highcharts.chart(el, {
credits: disabled,
legend: disabled,
series: [
Expand Down Expand Up @@ -154,9 +152,15 @@ const acpl: Window['LichessChartGame']['acpl'] = async (
],
},
});
el.highcharts.firstPly = data.treeParts[0].ply;
el.highcharts.selectPly = selectPly;
lichess.pubsub.emit('analysis.change.trigger');
chart.firstPly = data.treeParts[0].ply;
chart.selectPly = selectPly.bind(chart);
chart.updateData = (d: AnalyseData, mainline: Tree.Node[]) =>
chart.series[0].setData(makeSerieData(d, mainline) as any);

lichess.pubsub.on('ply', chart.selectPly);
lichess.pubsub.emit('ply.trigger');

return chart;
};

// the color prefixes below are mirrored in analyse/src/roundTraining.ts
Expand All @@ -168,6 +172,6 @@ const glyphProperties = (node: Tree.Node) => {
else return [undefined, undefined];
};

const toBlurArray = (player: any) => (player.blurs && player.blurs.bits ? player.blurs.bits.split('') : []);
const toBlurArray = (player: Player) => (player.blurs && player.blurs.bits ? player.blurs.bits.split('') : []);

export default acpl;
11 changes: 4 additions & 7 deletions ui/chart/src/division.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
interface Division {
middle?: number;
end?: number;
}
import { Division } from './interface';

export default function (div: Division, trans: Trans) {
export default function (div: Division | undefined, trans: Trans) {
const lines = [];
lines.push({
color: window.Highcharts.theme.lichess.line.accent,
Expand All @@ -12,7 +9,7 @@ export default function (div: Division, trans: Trans) {
zIndex: 6,
});
const textWeak = window.Highcharts.theme.lichess.text.weak;
if (div.middle) {
if (div?.middle) {
lines.push({
label: {
text: trans('opening'),
Expand Down Expand Up @@ -44,7 +41,7 @@ export default function (div: Division, trans: Trans) {
zIndex: 5,
});
}
if (div.end)
if (div?.end)
lines.push({
label: {
text: trans('endgame'),
Expand Down
53 changes: 47 additions & 6 deletions ui/chart/src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
import type Highcharts from 'highcharts';

export interface HighchartsHTMLElement extends HTMLElement {
highcharts: Highcharts.ChartObject;
}

export interface PlyChart extends Highcharts.ChartObject {
firstPly: number;
selectPly(ply: number, isMainline: boolean): void;
}

export interface PlyChartHTMLElement extends HTMLElement {
highcharts: PlyChart;
export interface AcplChart extends PlyChart {
updateData(d: AnalyseData, mainline: Tree.Node[]): void;
}

export interface Division {
middle?: number;
end?: number;
}

export interface Player {
color: 'white' | 'black';
blurs?: {
bits?: string;
};
}

export interface AnalyseData {
player: Player;
opponent: Player;
treeParts: Tree.Node[];
game: {
division?: Division;
variant: {
key: string;
};
moveCentis?: number[];
status: {
name: string;
};
};
analysis?: {
partial: boolean;
};
clock?: {
running: boolean;
initial: number;
increment: number;
};
}

declare global {
interface Window {
readonly LichessChartGame: {
acpl(el: HTMLElement, data: AnalyseData, mainline: Tree.Node[], trans: Trans): Promise<AcplChart>;
movetime(el: HTMLElement, data: AnalyseData, trans: Trans, hunter: boolean): Promise<PlyChart>;
};
}
}
Loading

0 comments on commit d9503c9

Please sign in to comment.