# [程式筆記]太鼓達人-2.前後端連接與紀錄
接續前篇已經做好的極簡風太鼓達人遊戲,本篇要用websocket連結前後端,將後端的 python server當作AI玩家,前端就像播報員不斷將遊戲狀態報給玩家,而server不斷給出動作指令,當然目前AI玩家還不會學習,只會隨機的給出動作指令,並記下遊戲每個瞬間的變化,累積資料以供之後學習。
### 往返通訊
前端與後端之間的通訊就像在打桌球,從頭到尾都是一顆球打來打去,只是前端永遠都是發球方,後端只能先最好準備等球打來在回擊。
先假設server端已經準備好了,當前端建立起websocket的連線之後,觸發onopen事件裡面的send函數就會做第一次發球,直接把剛建好的s0、a、r、s1等初始內容發給玩家,(為了方便初始值是隨便給的,之後訓練時略過第一筆數據就好)。
等AI玩家打新的action指令回來後,前端就被觸發了onmessage事件,計算好這個動作帶來的得分後,例如此處動作"o"就代表打鼓觸發beat函數,接著就將當下的遊戲狀態作為新的s1,原本的s1丟進s0,重新把s0、a、r、s1再打給server,如此建立起不斷往返的通訊迴路。
另外,設定了pause變數作為前端是否繼續播報當下狀況判斷,當遊戲有特殊狀況發生時就可以中斷通訊。
``` javascript=70
var ws = new WebSocket("ws://localhost:9999/echo");
var t=0,s0=0,a=0,r=0,s1=0
var pause=1
ws.onopen = function(){
pause=0
s1=red.x()-100
ws.send([t,s0,a,r,s1].join())
};
ws.onmessage = function (evt){
a = evt.data;
r=0
if( a=='o' ){
beat()
}
s0=s1
s1=red.x()-100
if(!pause){
ws.send([t,s0.toFixed(2),a,r.toFixed(2),s1.toFixed(2)].join())
}
};
</script>
```
### 隨機玩家
即便是強化學習AI的在累積足夠經驗前,基本上就是先以隨機的方式在挑選動作,但每個動作的機率也不能隨便設,在此我先設定每個瞬間只有0.05的機率回做出敲鼓的動作,盡量讓每種遊戲狀況都能發生到,也避免一直亂敲學到了一堆扣分的動作。
在下一步的動作把回傳給前端後,就順便將剛才收到的s0,a,r,s1等訊息記在本本(txt檔)裡,記滿10000筆資料後就收工。
```python=1
import asyncio
import websockets
import random
n=0
f = open("tempoRecord.txt", "w")
async def echo(websocket, path):
async for msg in websocket:
global n
n+=1
f.write(msg+"\n")
a=random.random()
if a>0.95 : action='o'
else : action='x'
await asyncio.sleep(.02)
await websocket.send(action)
if n%100==0 :
print("record "+str(n)+" data to tempoRecord.txt")
if n>10000:
f.close()
asyncio.get_event_loop().stop()
asyncio.get_event_loop().close()
asyncio.get_event_loop().run_until_complete(
websockets.serve(echo,'localhost', 9999))
asyncio.get_event_loop().run_forever()
```
這裡值得特別注意的是,為甚麼在隨機決定好動作後到send給前端前,要故意sleep耽擱個0.02秒,
簡而言之是要模擬控制延遲,例如人機對戰為求公平,會將這個延遲秒數近似於人類的反射弧,往更深一點講這關乎於回合遊戲與即時遊戲的差異性。
### 特殊狀況
```javascript=38
function run(){
t+=1
red.x(red.x()-1)
if( red.x()<100 ){
pause=1
r=-10
red.x(150+Math.random()*150)
s0=s1
s1=red.x()-100
ws.send([t,s0,a,r,s1].join())
pause=0
}
}
setInterval('run()',10)
function beat(){
end.strokeWidth(5)
end.stroke('yellow')
setTimeout(() => {
end.strokeWidth(2)
end.stroke('black')
}, 100);
if( red.x()<150 ){
r=10-(red.x()-100)/5
red.x(150+Math.random()*150)
}else{
r=-1
}
// console.log(r)
}
document.addEventListener("keydown", (event) => {
if(event.key=='a'){
// beat()
}
});
```