---
# System prepended metadata

title: '[程式筆記]太鼓達人-2.前後端連接與紀錄'

---

# [程式筆記]太鼓達人-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()
        }
    });

``` 


