kaorahi / lizgoban

Leela Zero & KataGo visualizer
GNU General Public License v3.0
169 stars 28 forks source link

Search progress visualization (evolving from 'PV board capture results' discussion) #100

Open qcgm1978 opened 9 months ago

qcgm1978 commented 9 months ago

The PV change diagram does not show the result of capturing, as shown in the figure.

I want to display the change diagram dynamically. It would be strange if capturing did not have any effect.

截屏2023-11-28 11 38 25
kaorahi commented 9 months ago

That is by design. The current behavior is natural to me as it is the common style in most Go books. But if you like... (on c9a637737)

--- a/src/draw_goban.js
+++ b/src/draw_goban.js
@@ -141,6 +141,9 @@ function draw_goban_with_variation(canvas, suggest, opts) {
           suggest === (R.suggest[0] || {})
     const displayed_stones = copy_stones_for_display(opts.stones)
     const bturn = opts.stones ? opts.bturn : R.bturn
+    const stone2move = (s, i, j) => s.stone && {move: idx2move(i, j), is_black: s.black}
+    const fake_history = aa_map(displayed_stones, stone2move).flat().filter(identity)
+    const vgame = require('./game.js').create_game(fake_history)
     variation.forEach((move, k) => {
         const b = xor(bturn, k % 2 === 1), w = !b
         const {pvVisits, pvEdgeVisits, uptodate_len} = suggest
@@ -156,7 +159,10 @@ function draw_goban_with_variation(canvas, suggest, opts) {
             ...(supplementary_info || {}),
         }
         merge_stone_at(move, displayed_stones, pv_stone)
+        vgame.push({move, is_black: b})
     })
+    const vstone = vgame.current_stones()
+    aa_each(displayed_stones, (s, i, j) => {s.stone = vstone[i][j].stone})
     const new_pv_move = suggested_variation_p &&
           (suggest.new_pv || [])[suggest.uptodate_len]
     new_pv_move && merge_stone_at(new_pv_move, displayed_stones, {new_pv_p: true})

To comment in advance, I will NEVER implement the animation of the principal variation. In my opinion, it is useless because long variations are quite unreliable. Instead, if you want to watch the variation move by move, actually play it by pressing the Enter key repeatedly to get a more reliable sequence. You will notice that the result is often different from the original principal variation.

qcgm1978 commented 9 months ago

You're right, but I need to directly modify the variation inside the draw_goban_with_variation function to implement the animation effect. I tried it and it worked as the following code snippet and video. In my opinion, this will allow me to see the AI's thought process and how it improves its moves.:

function draw_goban_with_variation(canvas, suggest, opts) {
    ...
    const stone2move = (s, i, j) => s.stone && { move: idx2move(i, j), is_black: s.black }
    const fake_history = aa_map(displayed_stones, stone2move).flat().filter(identity)
    const vgame = require('./game.js').create_game(fake_history)
    // different move_count has different pv
    if (!R[R.move_count]) {
        R[R.move_count] = {
            variation,
            pv_len: 0
        }
    } else {
        const current_variation = R[R.move_count].variation
        let i = 0
        // avoid drawing the same pv part
        for (i; i < current_variation.length; i++) {
            if (current_variation[i] !== variation[i]) {
                break;
            }
        }
        R[R.move_count] = {
            variation,
            pv_len: R[R.move_count].pv_len < i ? R[R.move_count].pv_len : i
        }
    }
    if (!opts.var_end) {
        if (R.pv_timeout) {
            opts.var_end = R[R.move_count].pv_len
        } else {
            R[R.move_count].pv_len++
            opts.var_end = R[R.move_count].pv_len
            R.pv_timeout = true
            const timeout = 500
            setTimeout(() => {
                R.pv_timeout = false
            }, timeout)
        }
    }
    variation = variation.slice(0, opts.var_end ||
        variation.length
    )
    variation.forEach((move, k) => {
        ...
        vgame.push({ move, is_black: b })
    })
    const vstone = vgame.current_stones()
    aa_each(displayed_stones, (s, i, j) => { s.stone = vstone[i][j].stone })
    ...

https://github.com/kaorahi/lizgoban/assets/3024299/9afaae84-430b-4a4a-a528-3f29049d77c7

kaorahi commented 9 months ago

Wow! I've never seen such a fun visualization of the search progress. It looks as if the AI is pondering various options and feeling undecided, just like a human. I'd like to keep this issue open, as other users might also find your movie interesting.

kaorahi commented 9 months ago

I've been dreaming of displaying the killer move in my GUI for a long time --- the move whose discovery prompts the AI to change its previous opinion in the search process. But it seems it might require some tweaks to the engine itself and hasn't been realized yet...

qcgm1978 commented 6 months ago

It is worth considering displaying multiple PV charts simultaneously. This has the following advantages:

I have completed the first step, which is to display multiple PVs simultaneously. After clicking on a PV, it can be displayed in original canvas. The next step is to consider adjusting the order and size based on some criteria, such as win rate.

If specific data can be recorded, it will be possible to see which step of a change affected the win rate of a PV. This is what you mentioned as the "killer move." The demo video and core code as the following:

https://github.com/kaorahi/lizgoban/assets/3024299/63b2d3e1-e9f2-473b-b580-9a76d211e3f6

const draw_pv = with_opts((...args) => {
    ...
    for (let i = 0; i < pv_num; i++) {
        const c = document.querySelector(`#sub_goban_container_${i}`)
        const arg = { ...args[1], ind: i }
        draw(c, arg)
    }
}, subgoban_mouse)