前面不管是 Bubble Sort、Quick Sort 還是 Dijkstra,我都已經把演算法執行過程記錄成一張張「快照」。
但快照做好之後,現在又遇到新的問題:
我要怎麼讓這些快照自己一張一張播放?按下暫停後,又要怎麼真的停下來?
今天就來幫演算法視覺化加上最基本的播放器功能:Play、Pause、Reset。
以 Bubble Sort 為例,目前 steps 裡已經記錄了每個時間點需要的資料:
steps[0] = {
arr: [18, 66, 79, 84, 4, 29, 27, 83],
barStates: ['default', 'default', ...],
phase: 'start',
summary: '共 8 筆資料,準備開始氣泡排序。'
}
steps[1] = {
arr: [18, 66, 79, 84, 4, 29, 27, 83],
barStates: ['active', 'comparing', ...],
phase: 'compare',
summary: '比較 [0] 18 與 [1] 66'
}
所以其實我不是要讓 Bubble Sort 重新執行一次。
真正需要做的事情只是:
steps[0]
↓
steps[1]
↓
steps[2]
↓
steps[3]
↓
...
也就是讓目前顯示的步驟 currentStep 自動增加。
如果只是手動下一步,其實很簡單:
currentStep.value++
畫面原本就是根據:
steps[currentStep.value]
取得目前要顯示的快照,因此 currentStep 從 0 變成 1,畫面自然就會換成 steps[1]。
但我現在想要的是:
不用一直按「下一步」,每隔一段時間自動前進。
我想到可以試試看用 for 迴圈慢慢吐資料……
for 迴圈可以一筆一筆「處理」資料,但不代表瀏覽器會一筆一筆「畫」給你看。
假設這樣寫:
for (let i = 0; i < steps.length; i++) {
currentStep.value = i
}
我原本想像的是:
currentStep = 0 → 畫面顯示第 0 步
currentStep = 1 → 畫面顯示第 1 步
currentStep = 2 → 畫面顯示第 2 步
currentStep = 3 → 畫面顯示第 3 步
但實際上 for 迴圈跑得非常快,而且這些程式會在同一次同步任務裡完成。
所以比較接近:
for 開始
↓
currentStep = 0
currentStep = 1
currentStep = 2
currentStep = 3
...
currentStep = 最後一步
↓
for 結束
↓
Vue / 瀏覽器更新畫面
結果看到的很可能就是:
啪!直接變成最後一張。
所以最後真正需要解決的不是「怎麼一筆一筆取得快照」,而是:
怎麼讓每一張快照之間有等待時間?
這時就可以使用 setTimeout:
function scheduleNext() {
timer = setTimeout(() => {
currentStep.value++
if (!isLastStep.value) {
scheduleNext()
}
}, 700)
}
第一次 setTimeout 時間到了,currentStep 加 1。
如果還沒到最後一步,再執行一次 scheduleNext()。
這樣就會變成:
等待 700ms
→ currentStep + 1
→ 等待 700ms
→ currentStep + 1
→ ...
讓 currentStep 不要一次跑到底,而是每隔一段時間才前進一步,快照也就真的「播放」起來了。
既然播放是靠 Timer 控制,那暫停其實就是:
把還在等待執行的 Timer 清掉。
所以我另外記錄:
const isPlaying = ref(false)
let timer: ReturnType<typeof setTimeout> | null = null
播放時:
function play() {
isPlaying.value = true
scheduleNext()
}
暫停時:
function pause() {
isPlaying.value = false
if (timer) {
clearTimeout(timer)
timer = null
}
}
clearTimeout() 之後,原本排好的下一次 currentStep++ 就不會發生。
而且我沒有修改 currentStep,所以畫面會直接停留在目前這張快照。
再次按下 Play 時,再從現在的 currentStep 繼續往下播放。
Reset 就沒有播放和暫停那麼複雜了。
先停止目前的 Timer,再把:
currentStep.value = 0
設回第 0 步即可:
function reset() {
pause()
currentStep.value = 0
}
因為第 0 張快照本來就是演算法還沒開始執行前的狀態,所以畫面自然也跟著回到最開始。
下一篇,我還想再加入 上一步、下一步以及播放倍速,讓這個播放器不只能自己跑,也可以自己控制演算法執行的節奏。