가위바위보 플러그인 예제


개요

  • 유저메뉴에 가위바위보 신청을 등록합니다.
  • 상태 패턴을 이용하여 작성되었습니다.
  • 해시를 이용하여 상대의 가위바위보를 검증합니다.
  • 타이머를 이용하여 지정시간이후에는 다시 원상태로 돌아오게 되어있습니다.

 

사용된 API

 

소스

<script> // 가위바위보 플러그인 
U.chat('*').on('after.create#RPS', function(room, data) {
    /*
        * after.create 때 작동하는 콜백입니다.
        * 이 콜백의 아이디는 #RPS 입니다.
     */
    var channel = room.plugin.add('RPS'); // RPS 이름의 채널를 가져옵니다.
    var randomKey, myChoice, target, targetHash, status, wait_timer, response_timer, popup_id; // 기초정보를 정의해줍니다.
    var states = { // 상태 패턴을 이용하여 작성하였습니다.
        'normal' : {
            'would' : function( room, data) { // 누군가가 나에게 가위바위보를 요청했을때 수신하게되는 함수입니다.
                target = data.user.nick; // 가위바위보의 상대방을 정의하는 부분입니다.
                popup_id = room.skin.popup.confirm('가위바위보신청', data.user.nick+' 님이 가위바위보를 신청하였습니다.', function(result) { // room.skin.popup 를 이용하여 가위바위보를 물어보는 함수입니다.
                    if(result) { // 승낙한 경우입니다.
                        channel.sendTo(data.user.nick, ['apply']); // 상대에게 승낙을 알힙니다.
                        status = states.wait_choice; // 상태를 '상대의 가위바위보 결정을 대기' 로 바꿉니다.
                        selectRPS(); // 가위바위보 선택창을 띄웁니다.
                    } else {
                        channel.sendTo(data.user.nick, ['reject']);
                        init(); // 거절하거나, 무시하면 모든 변수를 초기화합니다.
                    }
                });
                response_timer_start();    
            }
        },
        'wait_apply' : {
            'apply' : function(room, data) { // 내가 보낸 요청을, 상대방이 수락했을때 입니다.
                if(data.user.nick != target) return false; // 내가 요청한 인물이였는지를 확인합니다.
                status = states.wait_choice; // '상대방의 가위바위보 결정을 대기'하는 상태로 바꿉니다.
                selectRPS(); // 가위바위보 선택창을 띄웁니다.
            },
            'reject' : function(room, data){ // 거절되면 select 팝업레이어를 지웁니다.
                if(data.user.nick != target) return false; // 상대방이 나에게 요청을 수락했는지  그리고  내가 요청한 인물이였는지를 확인합니다.
                room.print('상대방이 거절했거나, 반응이 없습니다.');
                room.skin.popup.close(popup_id);
                init(); // 초기화시킵니다.
            }

        },
        'wait_choice' : { 
            'hash' : function(room, data) { // 상대방이 값을 결정하여, 인증해시를 보내준 경우입니다.
                if(data.user.nick != target) return false; // 내가 요청한 인물이였는지를 확인합니다.
                targetHash = data.data[1]; // 상대의 인증해시를 저장합니다.

                if ( myChoice != -1) { // 내가 결정을 안한 상태라면, 결과를 만들수 없고, 보낼 값도 없으므로 넘어갑니다.
                    channel.sendTo(target, ['myChoice'].concat(randomKey, myChoice)); // 나의 결정을 보냅니다.
                    status = states.wait_result; // 결과를 기다리는 상태로 변경합니다.
                }

            },
            'reject' : function(room, data){ // 거절되면 confirm 팝업레이어를 지웁니다.
                if(data.user.nick != target) return false; // 상대방이 나에게 요청을 수락했는지  그리고  내가 요청한 인물이였는지를 확인합니다.
                room.print('상대방이 거절했거나, 반응이 없습니다.');
                room.skin.popup.close(popup_id);
                init(); // 초기화시킵니다.
            }

        },
        'wait_result' : {
            'myChoice' : function(room, data) { // 상대방이 자신의 손을 보여줍니다.
                if(data.user.nick != target) return false; // 내가 요청한 인물이였는지를 확인합니다.
                if(makeHash([data.data[1], data.data[2], data.data[3], data.data[4]], data.data[5]) == targetHash) { // 'hash' 부분에서 왔던 인증해시 와 들어온 데이터로 만든 인증해시를 비교합니다.
                    room.print('나 : '+n2s(myChoice)+', 상대방 : '+n2s(data.data[5])); // 값을 표시합니다.
                    room.skin.popup.alert('가위바위보 결과', jud(myChoice, data.data[5])); // 결과를 room.skin.popup 을 이용하여 사용자에게 팝업레이어를 띄웁니다.
                } else { 
                    alert('해쉬 오류'); // 어떠한 이유에서인지 상대방이 보낸 인증해시와 그후에 보낸 값이 일치하지 않습니다.
                    // 이경우에는 상대방이 값을 조작했을 경우입니다.
                }
                init(); // 결과가 끝났으므로 재정의해줍니다.
            },
            'reject' : function(room, data){ // 거절되면 confirm 팝업레이어를 지웁니다.
                if(data.user.nick != target) return false; // 상대방이 나에게 요청을 수락했는지  그리고  내가 요청한 인물이였는지를 확인합니다.
                room.print('상대방이 거절했거나, 반응이 없습니다.'); // 안내 메세지를 띄웁니다
                room.skin.popup.close(popup_id);
                init(); // 초기화시킵니다.
            }

        },
    }
    init(); // 모든 변수를 초기화해줍니다.

    room.skin.userMenu.add({ // room.skin.userMenu 를 이용하여 유저를 눌렀을때 가위바위보가 뜨게 합니다.
        id:'RPS' // 아이디입니다.
            , text:'가위바위보신청' // 유저메뉴에 글자입니다.
            , onClick: function(room, data) { // 콜백입니다.
                if(status !== states.normal) {
                    room.print('이미 게임이 진행중입니다.');
                    return false;
                }
                status = states.wait_apply; // 신청하는 순간, 상태를 wait_apply 로 변경합니다.
                target = data.target; // 타겟을 설정하고
                channel.sendTo(data.target, ['would']); // 채널에 전송합니다.
                wait_timer_start();

            }
    });

    channel.onReceived(function(room, data) { // 채널리스너 입니다. RPS 채널로 오는 모든 데이터를 듣습니다.
        if(data.type != room.plugin.ONLY_ME) return false; // 나에게만 보낸 메세지가 아니므로 무시합니다.
        status[data.data[0]] && status[data.data[0]](room, data); // 상태 패턴을 이용하기떄문에 현재 상태함수에 값을 넘겨줍니다.
    });
    room.on('after.join#RPS', function(room, data) { // 유저가 채팅방에 접속하면 플러그인이 작동한다는 사실을 알려줍니다.
        room.print('가위바위보 플러그인 작동');
    });


    function response_timer_start() { // 나의 반응에 제한을 겁니다.
        stop_timer(); // 모든 타이머는 하나만 돌아가고 있으면 됩니다 !
        response_timer = setTimeout(function() { // 타이머를 정의합니다.
            if(target) { // 상대가 정해져있다면 상대에게 거절의사를 보냅니다.
                channel.sendTo(target, ['reject']);
            }
            init(); // 게임을 초기화합니다.
        }, 15000);
    }

    function wait_timer_start() {  // 요청, 가위바위보결정 등에 제한시간을 걸어 오랜시간 결정을 안하는지 판단합니다.
        stop_timer(); // 타이머를 초기화합니다.
        wait_timer = setTimeout(function() { // 타이머를 정의합니다.
            room.print('상대가 거절했거나, 반응이 없습니다.'); // 안내 메세지를 띄웁니다 .
            init(); // 게임을 초기화합니다.
        }, 15000);
    }

    function stop_timer() { // 게임타이머을 초기화합니다.
        if(wait_timer)
            clearTimeout(wait_timer);
        if(response_timer)
            clearTimeout(response_timer);
        wait_timer = response_timer = undefined;
    }

    function init() { // 변수를 초기화해주는 함수입니다.
        randomKey = []; // 인증을 위한 랜덤값을 저장하는 배열입니다.
        myChoice = -1; // 내가 결정한 값을 저장해둡니다.
        target = undefined; // 상대방을 저장해둡니다. ( 닉네임 기반 )
        targetHash = ''; // 상대방이 보낸 인증해시를 저장하는 변수입니다.
        status = states.normal; // 상태를 보통상태(아무것도없는상태)로 돌립니다.
        room.skin.popup.close(popup_id); // popup 을 끕니다.
        popup_id = 0; // 팝업 아이디를 초기화합니다. 
        stop_timer();
        
    }

    function selectRPS() { // 가위 바위 보 를 결정하는 팝업레이어를 띄우는 기능을하는 함수입니다.
        if( status != states.wait_choice ) return false;
        popup_id = room.skin.popup.select('가위바위보', '선택하기', {0:'가위', 1:'바위', 2:'보'}, function(result) { // room.skin.popup 모듈을 이용하여 select 이 담긴 팝업레이어를 띄웁니다.
            if(result == 0 || result == 1 || result == 2) { // 선택한 사항이 있는경우만 해당
                randomKey = [randomString(4), randomString(4), randomString(4), randomString(4)]; // 내가 결정한 값을 인증하기위한 인증해시의 salt, 랜덤키를 만듭니다.
                channel.sendTo(target, ['hash', makeHash(randomKey, result)]); // 대상자에게 인증해시만를 보냅니다.
                wait_timer_start();
                if(targetHash) { // 이미 상대방의 인증해시가 들어온경우 상대방에게 내가 결정한 값을 알려줍니다.
                    channel.sendTo(target, ['myChoice'].concat(randomKey, result)); // 랜덤값 4개와 값을 보냅니다.
                    status = states.wait_result; // 결과를 기다리는 상태로 변경합니다.
                }
                myChoice = result; // 내 결과를 변수에 저장합니다.
            } else {
                selectRPS();
            }
        });    
        response_timer_start();
    }

    function n2s(choice) { // 숫자로 되어있는 값을 한국어로 변경해주는 함수입니다.
        if(choice == 0)
            return '가위';
        if(choice == 1)
            return '바위';
        if(choice == 2)
            return '보';
    }

    function jud(a, b) { // 가위바위보 승패를 판단하는 부분입니다.
        if(a == b) 
            return 'draw';
        else
            return (a+1)%3 == b? 'lose': 'win';

    }

    function hashCode( str ) { // 글자를 특유의 hash 로 변경해주는 함수입니다.
        var hash = 0;
        if (str.length == 0) return hash;
        for (i = 0; i < str.length; i++) {
            char = str.charCodeAt(i);
            hash = ((hash<<5)-hash)+char;
            hash = hash & hash; // Convert to 32bit integer
        }
        return hash;
    }

    function randomString(string_length) { // 랜덤 string 을 return 해주는 함수입니다.
        var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
        var randomstring = '';
        for (var i=0; i<string_length; i++) {
            var rnum = Math.floor(Math.random() * chars.length);
            randomstring += chars.substring(rnum,rnum+1);
        }
        return randomstring;
    }

    function makeHash(rn, choice) { // 플러그인개발자가 정한, 임의의 해시 제조법 입니다.
        return hashCode(rn.join(choice))+''; // hashCode( [랜덤값 4개 ].join(가위바위보) ) 입니다.
    }
});
</script>