DenoとWebsocketの二人対戦型システムで、相手の接続状況を取得する



今回使うコード一覧

deno.js

JavaScript
let users = new Map();
let opponent = null;

Deno.serve({
    port: 3000,
    handler: async request => {
        const { socket, response } = Deno.upgradeWebSocket(request);

        const sendToClient = (target, key, value) => {
            const data = {};
            data[key] = value;
            target.send(JSON.stringify(data));
        }

        const setUser = () => {
            const n = users.size;
            if (n % 2 === 0) {
                opponent = socket;
                users.set(opponent, null);
            } else {
                users.set(socket, opponent);
                users.set(opponent, socket);
                sendToClient(socket, 'matched', new Date());
                sendToClient(opponent, 'matched', new Date());
            }
            console.log(n);
        }

        socket.onopen = () => {
            setUser();
        };
        socket.onmessage = e => {

        };
        socket.onclose = () => {
            const opponent = users.get(socket);
            console.log(opponent);
            if (opponent !== undefined && opponent !== null) {
                console.log('ok');
                sendToClient(opponent, 'connection-lost', new Date());
            }
            users.delete(socket); //切断されたユーザを消す
            users.delete(opponent); //切断されたユーザの対戦相手を消す  
        };
        socket.onerror = error => console.error("ERROR:", error);

        return response;
    },
});



index.html

HTML
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>sample</title>
</head>

<body>

    <p id="loading">対戦相手を探しています...</p>

    <script>
        const wsUri = "ws://localhost:3000/";
        const socket = new WebSocket(wsUri);

        const switchData = (data) => {
            data = JSON.parse(data);
            const key = Object.keys(data).toString();
            const value = Object.values(data).toString();
            switch (key) {
                case 'matched':
                    document.getElementById('loading').innerText = `接続完了: ${value}`;
                    break;
                case 'connection-lost':
                    document.getElementById('loading').innerText = '対戦相手の接続が切れました';
            }
        }

        socket.onopen = (e) => {
            console.log("CONNECTED");
        };
        socket.onclose = (e) => {
            console.log("DISCONNECTED");
        };
        socket.onmessage = (e) => {
            console.log(e.data);
            switchData(e.data);
        };
        socket.onerror = (e) => {
            console.log(`ERROR: ${e.data}`);
        };
    </script>
</body>

</html>



Mapで二人対戦のマッチングシステムを作る


usersというmapにユーザの情報を入れていきます。
今回はユーザの情報をsocketと表します。

usersの要素数を2で割ったときの余りが0の時はopponentという変数にsocketの情報を代入します。
opponentは一時的にsocketの情報を保存しておくための変数です。

if文のelseの部分でマッチングしたことを表しています。

mapのkeyに自分のsocketが入り、valueに対戦相手のsocketが入っている状況を作ります。
これで対戦相手の情報をmapに入れることができました。

JavaScript
const n = users.size;
if (n % 2 === 0) {
    opponent = socket;
    users.set(opponent, null);
} else {
    users.set(socket, opponent);
    users.set(opponent, socket);
}




マッチングが完了したという情報を自分と対戦相手に送信します。

JavaScript
sendToClient(socket, 'matched', new Date());
sendToClient(opponent, 'matched', new Date());




対戦相手の接続が切れた時


接続が切れたユーザのペアをmapから消します。

JavaScript
users.delete(socket); //切断されたユーザを消す
users.delete(opponent); //切断されたユーザの対戦相手を消す  


2人対戦型のシステムで、どちらかの接続が切れた時、対戦相手の接続が切れた方のユーザへ通知を送信します。

対戦相手の接続が切れた状況でブラウザのタブをリロードしてしまうと、サーバ側でエラーが出て止まってしまいます。これは、マッチングしていないのに接続が切れるという状況だからです。
これを解決するために、下記のようなif文を使います。

JavaScript
const opponent = users.get(socket);
if (opponent !== undefined && opponent !== null) {
    console.log('ok');
    sendToClient(opponent, 'connection-lost', new Date());
}


まとめ

mapに対戦相手の情報を入れる。

マッチングされていない状況でのリロードに気をつける。