Skip to content

Commit 6f0751a

Browse files
committed
support displaying score lead in analysis graph
1 parent 8dfdf00 commit 6f0751a

File tree

6 files changed

+153
-67
lines changed

6 files changed

+153
-67
lines changed

src/components/Sidebar.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,16 @@ export default class Sidebar extends Component {
105105
gameCurrents,
106106
treePosition,
107107

108+
analysisType,
108109
showWinrateGraph,
109110
showGameGraph,
110111
showCommentBox,
111112

112113
graphGridSize,
113114
graphNodeSize,
114115

115-
winrateData
116+
winrateData,
117+
scoreLeadData
116118
},
117119
{winrateGraphHeight, sidebarSplit}
118120
) {
@@ -139,7 +141,8 @@ export default class Sidebar extends Component {
139141
sideContent: h(WinrateGraph, {
140142
lastPlayer,
141143
width: winrateGraphWidth,
142-
data: winrateData,
144+
data: analysisType === 'winrate' ? winrateData : scoreLeadData,
145+
analysisType,
143146
currentIndex: level,
144147
onCurrentIndexChange: this.handleWinrateGraphChange
145148
}),

src/components/sidebars/WinrateGraph.js

+78-15
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,26 @@ import {noop} from '../../modules/helper.js'
77

88
const t = i18n.context('WinrateGraph')
99
const setting = remote.require('./setting')
10-
const blunderThreshold = setting.get('view.winrategraph_blunderthreshold')
10+
const blunderThresholdWinrate = setting.get(
11+
'view.winrategraph_blunderthreshold'
12+
)
13+
const blunderThresholdScoreLead = setting.get(
14+
'view.winrategraph_blunderthreshold_scorelead'
15+
)
16+
17+
const formatAnalysisValue = (value, analysisType) => {
18+
if (analysisType === 'winrate') return `${i18n.formatNumber(value)}%`
19+
return `${value >= 0 ? '+' : ''}${i18n.formatNumber(value)}`
20+
}
21+
22+
const transformAnalysisValue = (value, analysisType, dataMax) => {
23+
if (analysisType === 'winrate') return value
24+
return (value / Math.max(20, dataMax)) * 50 + 50
25+
}
1126

1227
class WinrateStrip extends Component {
1328
render() {
14-
let {player, winrate, change} = this.props
29+
let {player, winrate, change, analysisType, blunderThreshold} = this.props
1530

1631
return h(
1732
'section',
@@ -28,7 +43,7 @@ class WinrateStrip extends Component {
2843
h(
2944
'span',
3045
{class: 'main'},
31-
winrate == null ? '–' : `${i18n.formatNumber(winrate)}%`
46+
winrate == null ? '–' : formatAnalysisValue(winrate, analysisType)
3247
),
3348

3449
h(
@@ -102,23 +117,38 @@ export default class WinrateGraph extends Component {
102117
}
103118

104119
render() {
105-
let {lastPlayer, width, currentIndex, data} = this.props
120+
let {lastPlayer, width, currentIndex, data, analysisType} = this.props
106121
let {invert} = this.state
122+
let blunderThreshold =
123+
analysisType === 'winrate'
124+
? blunderThresholdWinrate
125+
: blunderThresholdScoreLead
126+
let metricString = analysisType === 'winrate' ? 'Winrate' : 'Score Lead'
107127

128+
let dataMax = Math.max(...data.map(x => (isFinite(x) ? Math.abs(x) : 0)))
108129
let dataDiff = data.map((x, i) =>
109130
i === 0 || x == null || (data[i - 1] == null && data[i - 2] == null)
110131
? null
111132
: x - data[data[i - 1] != null ? i - 1 : i - 2]
112133
)
113-
let dataDiffMax = Math.max(...dataDiff.map(Math.abs), 25)
134+
let dataDiffMax = Math.max(
135+
...dataDiff.map(Math.abs),
136+
analysisType === 'winrate' ? 25 : 10
137+
)
114138

115139
let round2 = x => Math.round(x * 100) / 100
116140
let blackWinrate =
117141
data[currentIndex] == null ? null : round2(data[currentIndex])
118142
let blackWinrateDiff =
119143
dataDiff[currentIndex] == null ? null : round2(dataDiff[currentIndex])
120144
let whiteWinrate =
121-
data[currentIndex] == null ? null : round2(100 - data[currentIndex])
145+
data[currentIndex] == null
146+
? null
147+
: round2(
148+
analysisType === 'winrate'
149+
? 100 - data[currentIndex]
150+
: -data[currentIndex]
151+
)
122152
let whiteWinrateDiff =
123153
dataDiff[currentIndex] == null ? null : -round2(dataDiff[currentIndex])
124154

@@ -132,8 +162,10 @@ export default class WinrateGraph extends Component {
132162
.map(
133163
([winrate, diff], i) =>
134164
`${
135-
i === 0 ? t('Black Winrate:') : t('White Winrate:')
136-
} ${i18n.formatNumber(winrate)}%${
165+
i === 0
166+
? t(`Black ${metricString}:`)
167+
: t(`White ${metricString}:`)
168+
} ${formatAnalysisValue(winrate, analysisType)}${
137169
diff == null
138170
? ''
139171
: ` (${diff >= 0 ? '+' : '-'}${i18n.formatNumber(
@@ -156,7 +188,9 @@ export default class WinrateGraph extends Component {
156188
h(WinrateStrip, {
157189
player: lastPlayer,
158190
winrate: lastPlayer > 0 ? blackWinrate : whiteWinrate,
159-
change: lastPlayer > 0 ? blackWinrateDiff : whiteWinrateDiff
191+
change: lastPlayer > 0 ? blackWinrateDiff : whiteWinrateDiff,
192+
analysisType,
193+
blunderThreshold
160194
}),
161195

162196
h(
@@ -215,7 +249,10 @@ export default class WinrateGraph extends Component {
215249
let instructions = data
216250
.map((x, i) => {
217251
if (x == null) return i === 0 ? [i, 50] : null
218-
return [i, x]
252+
return [
253+
i,
254+
transformAnalysisValue(x, analysisType, dataMax)
255+
]
219256
})
220257
.filter(x => x != null)
221258

@@ -309,7 +346,11 @@ export default class WinrateGraph extends Component {
309346
if (x == null) return ''
310347

311348
let command = i === 0 || data[i - 1] == null ? 'M' : 'L'
312-
return `${command} ${i},${x}`
349+
return `${command} ${i},${transformAnalysisValue(
350+
x,
351+
analysisType,
352+
dataMax
353+
)}`
313354
})
314355
.join(' ')
315356
}),
@@ -326,9 +367,18 @@ export default class WinrateGraph extends Component {
326367
if (i === 0) return 'M 0,50'
327368

328369
if (x == null && data[i - 1] != null)
329-
return `M ${i - 1},${data[i - 1]}`
330-
331-
if (x != null && data[i - 1] == null) return `L ${i},${x}`
370+
return `M ${i - 1},${transformAnalysisValue(
371+
data[i - 1],
372+
analysisType,
373+
dataMax
374+
)}`
375+
376+
if (x != null && data[i - 1] == null)
377+
return `L ${i},${transformAnalysisValue(
378+
x,
379+
analysisType,
380+
dataMax
381+
)}`
332382

333383
return ''
334384
})
@@ -343,7 +393,20 @@ export default class WinrateGraph extends Component {
343393
class: 'marker',
344394
style: {
345395
left: `${(currentIndex * 100) / width}%`,
346-
top: `${!invert ? data[currentIndex] : 100 - data[currentIndex]}%`
396+
top: `${
397+
!invert
398+
? transformAnalysisValue(
399+
data[currentIndex],
400+
analysisType,
401+
dataMax
402+
)
403+
: 100 -
404+
transformAnalysisValue(
405+
data[currentIndex],
406+
analysisType,
407+
dataMax
408+
)
409+
}%`
347410
}
348411
})
349412
)

src/menu.js

+38-41
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,39 @@ exports.get = function(props = {}) {
562562
click: () =>
563563
sabaki.setState(({fullScreen}) => ({fullScreen: !fullScreen}))
564564
},
565+
{
566+
label: i18n.t('menu.view', 'Analysis Metric'),
567+
submenu: [
568+
{
569+
label: i18n.t('menu.view', '&Win Rate'),
570+
type: 'checkbox',
571+
checked: analysisType === 'winrate',
572+
accelerator: 'CmdOrCtrl+Shift+H',
573+
click: () => {
574+
setting.set(
575+
'board.analysis_type',
576+
setting.get('board.analysis_type') === 'winrate'
577+
? 'scoreLead'
578+
: 'winrate'
579+
)
580+
}
581+
},
582+
{
583+
label: i18n.t('menu.view', '&Score Lead'),
584+
type: 'checkbox',
585+
checked: analysisType === 'scoreLead',
586+
accelerator: 'CmdOrCtrl+Shift+H',
587+
click: () => {
588+
setting.set(
589+
'board.analysis_type',
590+
setting.get('board.analysis_type') === 'scoreLead'
591+
? 'winrate'
592+
: 'scoreLead'
593+
)
594+
}
595+
}
596+
]
597+
},
565598
{type: 'separator'},
566599
{
567600
label: i18n.t('menu.view', 'Show &Coordinates'),
@@ -629,50 +662,14 @@ exports.get = function(props = {}) {
629662
},
630663
{
631664
label: i18n.t('menu.view', 'Show &Heatmap'),
632-
submenu: [
633-
{
634-
label: i18n.t('menu.view', '&Don’t Show'),
635-
type: 'checkbox',
636-
checked: !showAnalysis,
637-
accelerator: 'CmdOrCtrl+H',
638-
click: () => toggleSetting('board.show_analysis')
639-
},
640-
{type: 'separator'},
641-
{
642-
label: i18n.t('menu.view', 'Show &Win Rate'),
643-
type: 'checkbox',
644-
checked: !!showAnalysis && analysisType === 'winrate',
645-
accelerator: 'CmdOrCtrl+Shift+H',
646-
click: () => {
647-
setting.set('board.show_analysis', true)
648-
setting.set(
649-
'board.analysis_type',
650-
setting.get('board.analysis_type') === 'winrate'
651-
? 'scoreLead'
652-
: 'winrate'
653-
)
654-
}
655-
},
656-
{
657-
label: i18n.t('menu.view', 'Show &Score Lead'),
658-
type: 'checkbox',
659-
checked: !!showAnalysis && analysisType === 'scoreLead',
660-
accelerator: 'CmdOrCtrl+Shift+H',
661-
click: () => {
662-
setting.set('board.show_analysis', true)
663-
setting.set(
664-
'board.analysis_type',
665-
setting.get('board.analysis_type') === 'scoreLead'
666-
? 'winrate'
667-
: 'scoreLead'
668-
)
669-
}
670-
}
671-
]
665+
type: 'checkbox',
666+
checked: !!showAnalysis,
667+
accelerator: 'CmdOrCtrl+H',
668+
click: () => toggleSetting('board.show_analysis')
672669
},
673670
{type: 'separator'},
674671
{
675-
label: i18n.t('menu.view', 'Show &Winrate Graph'),
672+
label: i18n.t('menu.view', 'Show &Analysis Graph'),
676673
type: 'checkbox',
677674
checked: !!showWinrateGraph,
678675
enabled: !!showGameGraph || !!showCommentBox,

src/modules/enginesyncer.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ export default class EngineSyncer extends EventEmitter {
145145
this.analysis = {
146146
sign,
147147
variations,
148-
winrate: Math.max(...variations.map(({winrate}) => winrate))
148+
winrate: Math.max(...variations.map(({winrate}) => winrate)),
149+
scoreLead: Math.max(
150+
...variations.map(({scoreLead}) =>
151+
scoreLead == null ? NaN : scoreLead
152+
)
153+
)
149154
}
150155
} else if (line.startsWith('play ')) {
151156
sign = -sign

src/modules/sabaki.js

+25-8
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,12 @@ class Sabaki extends EventEmitter {
196196
get winrateData() {
197197
return [
198198
...this.gameTree.listCurrentNodes(state.gameCurrents[state.gameIndex])
199-
].map(x => x.data.SBKV && x.data.SBKV[0])
199+
].map(x => x.data.SBKV && +x.data.SBKV[0])
200+
},
201+
get scoreLeadData() {
202+
return [
203+
...this.gameTree.listCurrentNodes(state.gameCurrents[state.gameIndex])
204+
].map(x => x.data.SBKS && +x.data.SBKS[0])
200205
}
201206
}
202207
}
@@ -882,14 +887,18 @@ class Sabaki extends EventEmitter {
882887
Math.round(
883888
(sign > 0 ? variation.winrate : 100 - variation.winrate) * 100
884889
) / 100
885-
890+
let startNodeProperties = {
891+
[annotationProp]: [annotationValues[annotationProp]],
892+
SBKV: [winrate.toString()]
893+
}
894+
if (variation.scoreLead != null) {
895+
let scoreLead = Math.round(sign * variation.scoreLead * 100) / 100
896+
startNodeProperties.SBKS = [scoreLead.toString()]
897+
}
886898
this.openVariationMenu(sign, variation.moves, {
887899
x,
888900
y,
889-
startNodeProperties: {
890-
[annotationProp]: [annotationValues[annotationProp]],
891-
SBKV: [winrate.toString()]
892-
}
901+
startNodeProperties
893902
})
894903
}
895904
}
@@ -1691,13 +1700,21 @@ class Sabaki extends EventEmitter {
16911700

16921701
if (syncer.analysis != null && syncer.treePosition != null) {
16931702
let tree = this.state.gameTrees[this.state.gameIndex]
1694-
let {sign, winrate} = syncer.analysis
1695-
if (sign < 0) winrate = 100 - winrate
1703+
let {sign, winrate, scoreLead} = syncer.analysis
1704+
if (sign < 0) {
1705+
winrate = 100 - winrate
1706+
scoreLead = -scoreLead
1707+
}
16961708

16971709
let newTree = tree.mutate(draft => {
16981710
draft.updateProperty(syncer.treePosition, 'SBKV', [
16991711
(Math.round(winrate * 100) / 100).toString()
17001712
])
1713+
if (isFinite(scoreLead)) {
1714+
draft.updateProperty(syncer.treePosition, 'SBKS', [
1715+
(Math.round(scoreLead * 100) / 100).toString()
1716+
])
1717+
}
17011718
})
17021719

17031720
this.setCurrentTreePosition(newTree, this.state.treePosition)

src/setting.js

+1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ let defaults = {
205205
'view.sidebar_width': 200,
206206
'view.sidebar_minwidth': 100,
207207
'view.winrategraph_blunderthreshold': 5,
208+
'view.winrategraph_blunderthreshold_scorelead': 2,
208209
'view.winrategraph_height': 90,
209210
'view.winrategraph_minheight': 30,
210211
'view.winrategraph_maxheight': 250,

0 commit comments

Comments
 (0)