Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support displaying score lead in analysis graph #828

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,16 @@ export default class Sidebar extends Component {
gameCurrents,
treePosition,

analysisType,
showWinrateGraph,
showGameGraph,
showCommentBox,

graphGridSize,
graphNodeSize,

winrateData
winrateData,
scoreLeadData
},
{winrateGraphHeight, sidebarSplit}
) {
Expand All @@ -139,7 +141,8 @@ export default class Sidebar extends Component {
sideContent: h(WinrateGraph, {
lastPlayer,
width: winrateGraphWidth,
data: winrateData,
data: analysisType === 'winrate' ? winrateData : scoreLeadData,
analysisType,
currentIndex: level,
onCurrentIndexChange: this.handleWinrateGraphChange
}),
Expand Down
93 changes: 78 additions & 15 deletions src/components/sidebars/WinrateGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,26 @@ import {noop} from '../../modules/helper.js'

const t = i18n.context('WinrateGraph')
const setting = remote.require('./setting')
const blunderThreshold = setting.get('view.winrategraph_blunderthreshold')
const blunderThresholdWinrate = setting.get(
'view.winrategraph_blunderthreshold'
)
const blunderThresholdScoreLead = setting.get(
'view.winrategraph_blunderthreshold_scorelead'
)

const formatAnalysisValue = (value, analysisType) => {
if (analysisType === 'winrate') return `${i18n.formatNumber(value)}%`
return `${value >= 0 ? '+' : ''}${i18n.formatNumber(value)}`
}

const transformAnalysisValue = (value, analysisType, dataMax) => {
if (analysisType === 'winrate') return value
return (value / Math.max(20, dataMax)) * 50 + 50
}

class WinrateStrip extends Component {
render() {
let {player, winrate, change} = this.props
let {player, winrate, change, analysisType, blunderThreshold} = this.props

return h(
'section',
Expand All @@ -28,7 +43,7 @@ class WinrateStrip extends Component {
h(
'span',
{class: 'main'},
winrate == null ? '–' : `${i18n.formatNumber(winrate)}%`
winrate == null ? '–' : formatAnalysisValue(winrate, analysisType)
),

h(
Expand Down Expand Up @@ -102,23 +117,38 @@ export default class WinrateGraph extends Component {
}

render() {
let {lastPlayer, width, currentIndex, data} = this.props
let {lastPlayer, width, currentIndex, data, analysisType} = this.props
let {invert} = this.state
let blunderThreshold =
analysisType === 'winrate'
? blunderThresholdWinrate
: blunderThresholdScoreLead
let metricString = analysisType === 'winrate' ? 'Winrate' : 'Score Lead'

let dataMax = Math.max(...data.map(x => (isFinite(x) ? Math.abs(x) : 0)))
let dataDiff = data.map((x, i) =>
i === 0 || x == null || (data[i - 1] == null && data[i - 2] == null)
? null
: x - data[data[i - 1] != null ? i - 1 : i - 2]
)
let dataDiffMax = Math.max(...dataDiff.map(Math.abs), 25)
let dataDiffMax = Math.max(
...dataDiff.map(Math.abs),
analysisType === 'winrate' ? 25 : 10
)

let round2 = x => Math.round(x * 100) / 100
let blackWinrate =
data[currentIndex] == null ? null : round2(data[currentIndex])
let blackWinrateDiff =
dataDiff[currentIndex] == null ? null : round2(dataDiff[currentIndex])
let whiteWinrate =
data[currentIndex] == null ? null : round2(100 - data[currentIndex])
data[currentIndex] == null
? null
: round2(
analysisType === 'winrate'
? 100 - data[currentIndex]
: -data[currentIndex]
)
let whiteWinrateDiff =
dataDiff[currentIndex] == null ? null : -round2(dataDiff[currentIndex])

Expand All @@ -132,8 +162,10 @@ export default class WinrateGraph extends Component {
.map(
([winrate, diff], i) =>
`${
i === 0 ? t('Black Winrate:') : t('White Winrate:')
} ${i18n.formatNumber(winrate)}%${
i === 0
? t(`Black ${metricString}:`)
: t(`White ${metricString}:`)
} ${formatAnalysisValue(winrate, analysisType)}${
diff == null
? ''
: ` (${diff >= 0 ? '+' : '-'}${i18n.formatNumber(
Expand All @@ -156,7 +188,9 @@ export default class WinrateGraph extends Component {
h(WinrateStrip, {
player: lastPlayer,
winrate: lastPlayer > 0 ? blackWinrate : whiteWinrate,
change: lastPlayer > 0 ? blackWinrateDiff : whiteWinrateDiff
change: lastPlayer > 0 ? blackWinrateDiff : whiteWinrateDiff,
analysisType,
blunderThreshold
}),

h(
Expand Down Expand Up @@ -215,7 +249,10 @@ export default class WinrateGraph extends Component {
let instructions = data
.map((x, i) => {
if (x == null) return i === 0 ? [i, 50] : null
return [i, x]
return [
i,
transformAnalysisValue(x, analysisType, dataMax)
]
})
.filter(x => x != null)

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

let command = i === 0 || data[i - 1] == null ? 'M' : 'L'
return `${command} ${i},${x}`
return `${command} ${i},${transformAnalysisValue(
x,
analysisType,
dataMax
)}`
})
.join(' ')
}),
Expand All @@ -326,9 +367,18 @@ export default class WinrateGraph extends Component {
if (i === 0) return 'M 0,50'

if (x == null && data[i - 1] != null)
return `M ${i - 1},${data[i - 1]}`

if (x != null && data[i - 1] == null) return `L ${i},${x}`
return `M ${i - 1},${transformAnalysisValue(
data[i - 1],
analysisType,
dataMax
)}`

if (x != null && data[i - 1] == null)
return `L ${i},${transformAnalysisValue(
x,
analysisType,
dataMax
)}`

return ''
})
Expand All @@ -343,7 +393,20 @@ export default class WinrateGraph extends Component {
class: 'marker',
style: {
left: `${(currentIndex * 100) / width}%`,
top: `${!invert ? data[currentIndex] : 100 - data[currentIndex]}%`
top: `${
!invert
? transformAnalysisValue(
data[currentIndex],
analysisType,
dataMax
)
: 100 -
transformAnalysisValue(
data[currentIndex],
analysisType,
dataMax
)
}%`
}
})
)
Expand Down
79 changes: 38 additions & 41 deletions src/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,39 @@ exports.get = function(props = {}) {
click: () =>
sabaki.setState(({fullScreen}) => ({fullScreen: !fullScreen}))
},
{
label: i18n.t('menu.view', 'Analysis Metric'),
submenu: [
{
label: i18n.t('menu.view', '&Win Rate'),
type: 'checkbox',
checked: analysisType === 'winrate',
accelerator: 'CmdOrCtrl+Shift+H',
click: () => {
setting.set(
'board.analysis_type',
setting.get('board.analysis_type') === 'winrate'
? 'scoreLead'
: 'winrate'
)
}
},
{
label: i18n.t('menu.view', '&Score Lead'),
type: 'checkbox',
checked: analysisType === 'scoreLead',
accelerator: 'CmdOrCtrl+Shift+H',
click: () => {
setting.set(
'board.analysis_type',
setting.get('board.analysis_type') === 'scoreLead'
? 'winrate'
: 'scoreLead'
)
}
}
]
},
{type: 'separator'},
{
label: i18n.t('menu.view', 'Show &Coordinates'),
Expand Down Expand Up @@ -629,50 +662,14 @@ exports.get = function(props = {}) {
},
{
label: i18n.t('menu.view', 'Show &Heatmap'),
submenu: [
{
label: i18n.t('menu.view', '&Don’t Show'),
type: 'checkbox',
checked: !showAnalysis,
accelerator: 'CmdOrCtrl+H',
click: () => toggleSetting('board.show_analysis')
},
{type: 'separator'},
{
label: i18n.t('menu.view', 'Show &Win Rate'),
type: 'checkbox',
checked: !!showAnalysis && analysisType === 'winrate',
accelerator: 'CmdOrCtrl+Shift+H',
click: () => {
setting.set('board.show_analysis', true)
setting.set(
'board.analysis_type',
setting.get('board.analysis_type') === 'winrate'
? 'scoreLead'
: 'winrate'
)
}
},
{
label: i18n.t('menu.view', 'Show &Score Lead'),
type: 'checkbox',
checked: !!showAnalysis && analysisType === 'scoreLead',
accelerator: 'CmdOrCtrl+Shift+H',
click: () => {
setting.set('board.show_analysis', true)
setting.set(
'board.analysis_type',
setting.get('board.analysis_type') === 'scoreLead'
? 'winrate'
: 'scoreLead'
)
}
}
]
type: 'checkbox',
checked: !!showAnalysis,
accelerator: 'CmdOrCtrl+H',
click: () => toggleSetting('board.show_analysis')
},
{type: 'separator'},
{
label: i18n.t('menu.view', 'Show &Winrate Graph'),
label: i18n.t('menu.view', 'Show &Analysis Graph'),
type: 'checkbox',
checked: !!showWinrateGraph,
enabled: !!showGameGraph || !!showCommentBox,
Expand Down
7 changes: 6 additions & 1 deletion src/modules/enginesyncer.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ export default class EngineSyncer extends EventEmitter {
this.analysis = {
sign,
variations,
winrate: Math.max(...variations.map(({winrate}) => winrate))
winrate: Math.max(...variations.map(({winrate}) => winrate)),
scoreLead: Math.max(
...variations.map(({scoreLead}) =>
scoreLead == null ? NaN : scoreLead
)
)
}
} else if (line.startsWith('play ')) {
sign = -sign
Expand Down
33 changes: 25 additions & 8 deletions src/modules/sabaki.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,12 @@ class Sabaki extends EventEmitter {
get winrateData() {
return [
...this.gameTree.listCurrentNodes(state.gameCurrents[state.gameIndex])
].map(x => x.data.SBKV && x.data.SBKV[0])
].map(x => x.data.SBKV && +x.data.SBKV[0])
},
get scoreLeadData() {
return [
...this.gameTree.listCurrentNodes(state.gameCurrents[state.gameIndex])
].map(x => x.data.SBKS && +x.data.SBKS[0])
}
}
}
Expand Down Expand Up @@ -882,14 +887,18 @@ class Sabaki extends EventEmitter {
Math.round(
(sign > 0 ? variation.winrate : 100 - variation.winrate) * 100
) / 100

let startNodeProperties = {
[annotationProp]: [annotationValues[annotationProp]],
SBKV: [winrate.toString()]
}
if (variation.scoreLead != null) {
let scoreLead = Math.round(sign * variation.scoreLead * 100) / 100
startNodeProperties.SBKS = [scoreLead.toString()]
}
this.openVariationMenu(sign, variation.moves, {
x,
y,
startNodeProperties: {
[annotationProp]: [annotationValues[annotationProp]],
SBKV: [winrate.toString()]
}
startNodeProperties
})
}
}
Expand Down Expand Up @@ -1691,13 +1700,21 @@ class Sabaki extends EventEmitter {

if (syncer.analysis != null && syncer.treePosition != null) {
let tree = this.state.gameTrees[this.state.gameIndex]
let {sign, winrate} = syncer.analysis
if (sign < 0) winrate = 100 - winrate
let {sign, winrate, scoreLead} = syncer.analysis
if (sign < 0) {
winrate = 100 - winrate
scoreLead = -scoreLead
}

let newTree = tree.mutate(draft => {
draft.updateProperty(syncer.treePosition, 'SBKV', [
(Math.round(winrate * 100) / 100).toString()
])
if (isFinite(scoreLead)) {
draft.updateProperty(syncer.treePosition, 'SBKS', [
(Math.round(scoreLead * 100) / 100).toString()
])
}
})

this.setCurrentTreePosition(newTree, this.state.treePosition)
Expand Down
1 change: 1 addition & 0 deletions src/setting.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ let defaults = {
'view.sidebar_width': 200,
'view.sidebar_minwidth': 100,
'view.winrategraph_blunderthreshold': 5,
'view.winrategraph_blunderthreshold_scorelead': 2,
'view.winrategraph_height': 90,
'view.winrategraph_minheight': 30,
'view.winrategraph_maxheight': 250,
Expand Down