文系出身1年目SEがagora.io(WebRTC SDK)を利用してじゃんけんゲームをつくってみたお話

#はじめに
[agora.io](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=2ade1a3bfffc66e53af3)とは、アメリカのagora社が提供しているライブ配信・ビデオ通話向けのSDKです。

このSDK(今回はWebRTC)を利用して、同じ部署の若手3名でじゃんけんゲームをつくってみました。
[こちら](https://github.com/fu-jimoto/AgoraIO-OnlineJanken)で公開しています。

#開発環境
Windows10 pro 64bit
Google Chrome
Visual Studio Code
GitHub
Agora Video SDK for Web 2.8.0
Agora RTM SDK for Web 0.9.1

#開発工程と工数(人日)
- 要件定義：1
- 画面設計：0.5
- 内部設計：1.25
- 実装：8.25
- テスト・修正：6.5

#じゃんけんゲームの概要
ビデオ通話しながら、じゃんけんをするゲームです。

ゲーム参加者は、2~3名です。
2名の場合、参加者どちらかが「はじめる」ボタンを押してゲームを開始します。
3名の場合、3名入室した時点で自動的にゲームが開始されます。

ゲームのおおよその流れは以下の通りです。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386074/975607f4-f136-00eb-53bd-e6cd75e29afd.png)


- 入室画面

入室ボタンを押して、入室します。
ゲーム参加者がすでに3名に達している場合は、エラーメッセージ表示され、入室できません。

![join.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386074/d5a65b09-47cd-74ee-71c9-1b51add32982.png)


- ゲーム画面 (ゲーム開始中)

入室するとビデオ通話が開始されます。
ゲームを開始すると、グー、チョキ、パーの役ボタンを選択できるようになります。
![game1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386074/0a701954-5b84-a1f2-f30d-2e5664047c10.png)


- ゲーム画面 (ゲーム終了後)

ゲーム参加者全員が役を選択し終えると、結果が表示されます。
![game2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386074/32331a1e-eb55-b425-bfb7-97cbcefce945.png)


#SDKの利用用途
- [Agora Video SDK](https://docs.agora.io/en/Video/product_video?platform=All%20Platforms) for Web : 映像音声の送受信、映像音声のミュート・ミュート解除

- [Agora RTM (Real-time Messaging) SDK](https://docs.agora.io/en/Real-time-Messaging/product_rtm?platform=All%20Platforms) for Web : ゲーム参加者数の取得、ゲーム開始情報の送受信、役 (グー、チョキ、パー) の送受信

※RTM SDKは、それまでメッセージ送受信用に提供されていたSignaling SDKの後継サービスで、2019年8月に正式リリースされました。Signaling SDKと比べて、通信の安定性や1秒あたりに送信できるメッセージ数が向上しています。


#実装


- ゲーム開始情報・役の送信

RTM SDKの[sendMessage](https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_web/classes/rtmchannel.html#sendmessage)というAPIを利用して、ゲーム開始情報や役を対戦相手に送信します。
このAPIの引数に送信するメッセージを入れています。
以下のコードでは、自分が選択した役 (localRole) を送信しています。

```AgoraRTM-JankenGame.js
agoraRTMchannel.sendMessage({text:localRole}).then(() => {
    console.log(agoraRTMaccountName + " succeed in sending role: " + localRole);
    local_client_status.src = "icons/fight!.png";
    btnRoleDisable();
    checkMlist();
}).catch(error => {
    console.log(agoraRTMaccountName + " failed to sending role" + error);
    alert("もう一度選択して下さい。");
});
```

- ゲーム開始情報・役の受信

一方受信側は、[on](https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_web/interfaces/rtmevents.rtmchannelevents.html#channelmessage)というAPIの第1引数を'ChannelMessage'にして、メッセージ受信イベントを取得しています。
受信するメッセージは、ゲーム開始情報と役の情報2種類あるので、if文を使ってメッセージ内容によって、受信後の処理を変えています。

```AgoraRTM-JankenGame.js
//メッセージ受信
function getChannelMessages(){
    agoraRTMchannel.on('ChannelMessage', function(message, memberId){
        console.log(agoraRTMaccountName + " got message: " + message.text + " from " + memberId);
        console.log("gameStatus is " + gameStatus);
        remoteMessage = message.text;
        if(remoteMessage == "startGame()" && gameStatus == false){
            startGame();
        }else if(remoteMessage == "1" || remoteMessage == "2" || remoteMessage == "3"){
            remoteMessage = Number(remoteMessage);
            console.log("typeof remoteMessage is " + typeof remoteMessage);
            console.log("remoteMessage is " + remoteMessage);
            getRoles(remoteMessage, memberId);
        }
    });
}
```

- 勝ち負けの判定

グー：1、チョキ：2、パー：3として、それぞれの数値の引き算の値で、じゃんけんの勝ち負けの判定をおこないました。
2人でゲームをした場合のコードは、それなりにすっきり書けたのですが、3人の場合はとても冗長になってしまいました。
(もっと短いコードで書ける方法があれば、コメントいただけると嬉しいです。)

```AgoraRTM-JankenGame.js
//判定：2人で遊ぶ場合
function judgeResult2(localRole, remoteRole1){
    main.src = "icons/result.png";
    console.log("localRole is " + localRole);
    console.log("remoteRole1 is " + remoteRole1);
    var diff = localRole - remoteRole1;
    console.log("localRole - remoteRole1 = " + diff);

    if(diff == 0){
        result = "あいこ";
        remote_client1_status.src = picDraw;
        local_client_status.src = picDraw;
    }else if(diff == -1 || diff == 2){
        result = "勝ち";
        remote_client1_status.src = picLoser;
        local_client_status.src = picWinner;
    }else{
        result = "負け"
        remote_client1_status.src = picWinner;
        local_client_status.src = picLoser;
    }
    console.log("Result is " + result);

    dispRemoteRole1();
    dispLocalRole();
    gameStatus = false;
    console.log("gameStatus is " + gameStatus);

    setTimeout("moveSelect()", 5000);
}

//判定：3人で遊ぶ場合
function judgeResult3(localRole, remoteRole1, remoteRole2){
    main.src = picResult;
    console.log("localRole is " + localRole);
    console.log("remoteRole1 is " + remoteRole1);
    console.log("remoteRole2 is " + remoteRole2);
    var diff1 = localRole - remoteRole1;
    var diff2 = localRole -remoteRole2; 
    var diff3 = remoteRole1 - remoteRole2;
    console.log("localRole - remoteRole1 = " + diff1);
    console.log("localRole - remoteRole2 = " + diff2);
    console.log("remoteRole1 - remoteRole2 = " + diff3);
    if(((diff1 == 0)&&(diff2 == 0)&&(diff3 == 0))||((localRole != remoteRole1)&&(localRole != remoteRole2)&&(remoteRole1 != remoteRole2))){
        result = "あいこ";
        remote_client1_status.src = picDraw;
        remote_client2_status.src = picDraw;
        local_client_status.src = picDraw;
    }else if(((diff1 == -1)||(diff1 == 2))&&((diff2 == -1)||(diff2 == 2))&&(diff3 == 0)){
        result = "勝ち";
        remote_client1_status.src = picLoser;
        remote_client2_status.src = picLoser;
        local_client_status.src = picWinner;
    }else if((diff1 == 0)&&((diff2 == -1)||(diff2 == 2))&&((diff3 == -1)||(diff3 == 2))){
        result = "勝ち";
        remote_client1_status.src = picWinner;
        remote_client2_status.src = picLoser;
        local_client_status.src = picWinner;
    }else if(((diff1 == -1)||(diff1 == 2))&&(diff2 == 0)){
        result = "勝ち";
        remote_client1_status.src = picLoser;
        remote_client2_status.src = picWinner;
        local_client_status.src = picWinner;
    }else if(diff3 == 0){
        result = "負け";
        remote_client1_status.src = picWinner;
        remote_client2_status.src = picWinner;
        local_client_status.src = picLoser;
    }else if(diff2 == 0){
        result = "負け";
        remote_client1_status.src = picWinner;
        remote_client2_status.src = picLoser;
        local_client_status.src = picLoser;
    }else{
        result = "負け";
        remote_client1_status.src = picLoser;
        remote_client2_status.src = picWinner;
        local_client_status.src = picLoser;
    }
    console.log("Result is " + result);

    dispRemoteRole1();
    dispRemoteRole2();
    dispLocalRole();
    gameStatus = false;
    console.log("gameStatus is " + gameStatus);

    setTimeout("moveSelect()", 5000);    
}
```

#感想
- ミュート機能等はAPIをひとつコールするだけで実装できたので、思っていたよりも簡単でした。
- 開発をしていくうちに変数名をたくさん使うようになり、変数名のつけ方に悩みました。
- 3人で共同開発したので、それぞれコードの書き方に違いがあったため、エラーが出たり、GitHubにコミットした際にコンフリクトが発生したりしたので、大変でした。
- テストをする度に新しいバグが見つかり、完成まで想像以上に時間がかかってしまいました。
- Video SDKには、BGMを流せるAPIもあったので、今後はそれを使ってもっと楽しいゲームにしたいです。

#参照
[Agora.io Developer Center : Agora Web SDK API Reference](https://docs.agora.io/en/Video/API%20Reference/web/index.html)
[Agora.io Developer Center : RTM API Reference](https://docs.agora.io/en/Signaling/signal_web?platform=Web)
[Bootstrap](https://getbootstrap.com/)

#最後に
[**agora.ioに関するお問い合わせはこちらから**](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=2ade1a3bfffc66e53af3)
[<img width="912" alt="スクリーンショット 0001-08-15 13.41.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300377/5d815376-9b89-08fa-9434-6fa7e8e67bcf.png">](https://jp.vcube.com/lp/agora?utm_campaign=Agora&utm_source=Qiita&arc_id=2ade1a3bfffc66e53af3)