이로

짝 맞추기 게임 만들기 본문

컴퓨터/과제

짝 맞추기 게임 만들기

利路 2019. 6. 16. 19:12
반응형

카드 8장으로  4x4 짝 맞추기 게임 만들기 입니다. 외적인 요소는 신경쓰지 않고 코드만 작성하였습니다.

 

초보 코더입니다. 수정할 사항이나 더 나은 방법이 있으면 조언 부탁드리겠습니다. 

 

 

아이디어

코딩 전 계획 세우기

 

1. 테이블에 이미지 배치하고 script에서 th와 img의 정보를 읽어오기

 1.1 th의 id는 맞 춘카드, 못 맞춘카드 구분하는 용도이며 img의 id는 같은카드인지 아닌지 확인하는 용도로 설정

 

2. 각 버튼 구현하기

 주의할점

 2.1 정답보기 버튼 : 어떠한 상황에서도 정답보기 버튼 클릭시 정답 공개

 2.2 뒷면 보이기 버튼 : 게임 도중에 뒷면보이기 버튼 클릭 불가능하게 하기

 2.3 섞기 버튼 : 게임 도중에 섞지 못하게 하기

 2.4 재시작 버튼 : 언제든 재시작 할 수 있어야 한다. 게임 중 재시작 버튼 누를 수 있다.

      1) 모든 카드 뒤집기

      2) 섞기

      3) 맞췄을 때 th의 id=1된거 초기화해주기

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        /* 카드 이미지 사이즈 조절 */
        img {
            width: 200px;
            height: 270px;
            opacity: 0.7;
        }
    </style>
</head>

<body>

    <!-- 1. 버튼 4개(정답보기, 뒷면 보이기, 섞기, 재시작) 만들기 -->
    <button id='back1'>정답보기</button> <!-- 앞면 보이기-->
    <button id='back2'>뒷면 보이기</button><!-- 뒷면 보이기-->
    <button id='shuffle'>섞기</button><!-- 섞기-->
    <button id='restart'>재시작</button><!-- 재시작-->

    <!-- 2. 테이블 만들고, 초기 카드 배치하자 -->
    <!-- 2.1 카드 초기 배치시 52장 모두 넣지말고, 8장 골라서 2번씩 배치해 16장으로 게임 진행하게 하자. -->
    <!-- 2.2 th의 id=0 - 못맞춘 카드, id=1 맞춘 카드 -->
    <!-- 2.3 img의 id- 같은 카드 구별하는 숫자 -->
    <table style="width: 800px; height: auto;">
        <tr>
            <th id=0>
                <img src="s1.png" alt="0" id="1">
            </th>
            <th id=0>
                <img src="s1.png" alt="0" id="1">
            </th>
            <th id=0>
                <img src="h1.png" alt="0" id="2">
            </th>
            <th id=0>
                <img src="h1.png" alt="0" id="2">
            </th>
        </tr>
        <tr>
            <th id=0>
                <img src="d1.png" alt="0" id="3">
            </th>
            <th id=0>
                <img src="d1.png" alt="0" id="3">
            </th>
            <th id=0>
                <img src="c1.png" alt="0" id="4">
            </th>
            <th id=0>
                <img src="c1.png" alt="0" id="4">
            </th>
        </tr>
        <tr>
            <th id=0>
                <img src="s2.png" alt="0" id="5">
            </th>
            <th id=0>
                <img src="s2.png" alt="0" id="5">
            </th>
            <th id=0>
                <img src="h2.png" alt="0" id="6">
            </th>
            <th id=0>
                <img src="h2.png" alt="0" id="6">
            </th>
        </tr>
        <tr>
            <th id=0>
                <img src="d2.png" alt="0" id="7">
            </th>
            <th id=0>
                <img src="d2.png" alt="0" id="7">
            </th>
            <th id=0>
                <img src="c2.png" alt="0" id="8">
            </th>
            <th id=0>
                <img src="c2.png" alt="0" id="8">
            </th>
        </tr>
    </table>
    <!-- 
* 클릭시 테이블 내용 바뀌게하려면? -> script에서 th를 불러와서 th끼리 순서 바꿔보자

* html 에서 같은 테이블에 이미지 A,B 두 장 저장해놓고 A는 보이기 B는 안보이기 를  설정할 수 있나?
백그라운드에 앞면, 얹는 이미지에 뒷면 해볼까? -->

    <script>
        window.onload = function () {
            var th = document.getElementsByTagName('th');
            var img = document.getElementsByTagName('img'); // 이미지
            var bk1 = document.getElementById('back1'); // 앞면 보이기
            var bk2 = document.getElementById('back2'); // 뒷면 보이기
            var sf = document.getElementById('shuffle'); // 섞기
            var restart = document.getElementById('restart'); // 섞기
            var sfDeck = {}; // 섞은 덱 저장할 곳
            var frflag;// 카드 앞면시 true
            var bkflag;// 카드 뒷면시 true
            var playGame;// 게임진행중 뒷면 클릭 못하게하기 게임진행은 카드 클릭 시작한 순간부터 시작.
            var matchTest = new Array(); // 카드 매칭 테스트시 사용할 배열
            var clickAble; // 카드 2장 클릭시 틀렸을 때, 다른카드들 클릭 못하게 한다.
            saveDeck(th); // 처음 로딩시 기본덱 저장하기. (안하면 첫 버튼을 앞면 보이기 클릭시 오류)
            gameStart();

            // ==========   /게임 재시작   ==========
            restart.onclick = function () { // 카드 뒤집고, 카드덱 데이터를 셔플하기
                playGame = false;
                resetId();
                flipCard();
                shuffle2();
            }

            // ==========   게임시작 함수   ==========
            function gameStart() {
                shuffle(th); // 앞면인 상태에서 셔플
                saveDeck(th); // 셔플한 덱 저장
                flipCard(); // 카드 뒤집기
                resetId();
            }

            // ==========   /카드 이미지 클릭   ==========
            // 카드 클릭시 게임 시작 및 카드 뒤집기
            for (let i = 0; i < th.length; i++) {
                th[i].onclick = function () {
                    if (th[i].id == "1") { return; } 
                    // alt값이 1인카드는 맞춘카드이므로 아래로 진행하지 못하게 한다.
                    if (matchTest[0] != th[i] && (bkflag) && clickAble) { 
                    // 같은카드 두번 클릭한 것 아니고 게임이 시작되어 뒷면보이기 클릭한 상태일 때
                    // 진행된다.
                        playGame = true;
                        matchflag = false;
                        th[i].innerHTML = sfDeck[i]; // th하나 클릭 시 뒤집게하는데, 
                        //i번째의 sfDeck를 보이게한다.
                        matchTest.push(th[i]);
                        matchT();
                        gameEndTest();
                    }
                }
            }
            // ---- 오류
            // 같은 카드 찾아서 처음 클릭시 앞면 유지, 그러나 한번 더 클릭하면 다시 뒷면으로 바뀐다.
            // --> 같은카드 클릭시 아무 작업 못하게 하자
            // 앞면에서 클릭시 뒷면으로 바뀐다. -> 뒷면인 상태에서 진행하게 하자.

            // ==========   -모두 매칭시 게임종료 안내문구-   ==========
            function gameEndTest() {
                let cnt = 0;
                for (let i = 0; i < th.length; i++) {
                    if (th[i].id == "1") {
                        cnt++;
                    }
                    if (th.length == cnt) {
                        setTimeout(function () { alert('게임이 종료되었습니다.'); }, 1000)
                    }
                }
            }

            // ==========   -카드 이미지 클릭-   ==========
            function resetId() { // 앞면보이기 클릭시 th의 id들 0으로 초기화해주기
                for (let i = 0; i < th.length; i++) {
                    th[i].id = "0";
                }
            }

            // ==========   /카드 매칭테스트   ==========
            function matchT() {
                if (matchTest.length == 2) {
                    if ((matchTest[0].childNodes[1].id) == (matchTest[1].childNodes[1].id)) {
                    //img의 id로 맞춘 숫자카드인지 검사
                        matchTest[0].id = "1"; // th의 id 0 - 아직 못 맞춘 카드, 1- 맞춘 카드
                        matchTest[1].id = "1";
                        matchTest = new Array();
                        return;
                    } else {//다른숫자카드}
            // 하나 클릭 시 뒤집으면 뒤집는동안 아무 작업 못하게 해야한다. 
                        clickAble = false;
            // 일정 시간 지난 뒤 다시 back 화면이 표시되게 한다.
                        setTimeout(function () {
                            matchTest[0].innerHTML = '<img src="back.jpg" alt="0" id="0">';
                            matchTest[1].innerHTML = '<img src="back.jpg" alt="0" id="0">';
                            matchTest = new Array();
                            clickAble = true;
                        }, 1000)
                    }
                }
            }
            // ---- 오류
            // 하나 뒤집고 시간 텀 내서 뒤집으면 앞면이 계속 표시된다. 
            //--> 하나 클릭 시 다 뒤집힐 때 까지 아무 작업 못하게 하자 
            
            // ==========   /정답보기 버튼   ==========
            bk1.onclick = function () {
                if (bkflag && clickAble) {// 게임 시작전에는 섞기만 가능하며, 뒷면인 상태여야만
                //앞면을 볼 수 있다. 클릭 모션이 종료된 후에만 답을 볼 수 있게 한다.
                    for (let i = 0; i < th.length; i++) { // '정답 보기' 클릭시 카드 앞면 보이게하기
                        th[i].innerHTML = sfDeck[i];
                    }
                    resetId()
                    bkflag = false;
                    frflag = true;
                    playGame = false; // 정답을 봤으므로 게임포기로 간주한다.
                }
            }
            // ---- 오류
            // 뒷면 보이기 상태에서 카드 2장을 시간 조금 두고 클릭 한 뒤, 한장이 뒤집어진 상태에서
            //정답보기 클릭시 앞면이 뒷면으로 변한다.


            // ==========   /뒷면 보이기 버튼   ==========
            bk2.onclick = function () {
                if (!playGame) { flipCard(); } // 게임 진행중에 뒷면으로 못바꾸게하기. 게임 포기하려면
                //앞면보기 누르게하기.
            }

            // ==========   /카드 뒤집기   ==========
            function flipCard() {
                for (let i = 0; i < th.length; i++) {
                    th[i].innerHTML = '<img src="back.jpg" alt="" id="0">';
                }
                bkflag = true;
                frflag = false;
                clickAble = true;
            }

            // ==========   /섞기 버튼   ==========
            sf.onclick = function () {
                if (!playGame) { // 게임 진행중에는 섞지못하게하기
                    if (bkflag) {
                        shuffle2(); // 뒤집은 상태에서 셔플
                    } else {
                        shuffle(th); // 앞면인 상태에서 셔플
                        saveDeck(th); // 셔플한 덱 저장
                    }
                }
            }
            // ---- 오류
            // 현재 뒷면 보이기 클릭시 섞기 안된다. 섞기는 모두 뒷면일때, 또는 모두 앞면일때 섞게 하자.
            //--> play게임일 때 섞기 버튼 가능하게 하자.

            // ==========   /뒤집은 상태에서 섞기   ==========
            function shuffle2() {
                var c = 0;
                for (let i = 0; i < th.length; i++) {
                    c = Math.floor(Math.random() * (th.length));
                    let tmp = sfDeck[0];
                    sfDeck[0] = sfDeck[c];
                    sfDeck[c] = tmp;
                }
            }
            // ---- 오류
            // 뒷면에서 섞을 경우 문제다. 뒷면에서 섞기 버튼 누르면 뒷면 유지한 상태에서 
            // 임시저장덱만 섞자.

            // ==========   /앞면 상태에서 섞기   ==========
            function shuffle(ac) {
                var c = 0;
                for (let i = 0; i < ac.length; i++) {
                    let tmp = ac[0].innerHTML
                    c = Math.floor(Math.random() * (ac.length))
                    ac[0].innerHTML = ac[c].innerHTML
                    ac[c].innerHTML = tmp;
                }
            }

            // ==========   /섞은 덱 저장하기   ==========
            function saveDeck(ac) {
                for (let i = 0; i < ac.length; i++) {
                    sfDeck[i] = (ac[i].innerHTML)
                }
                playGame = false;
                frflag = true;
                bkflag = false;
            }
        }
    </script>

</body>

</html>

 

 

190619 덧)

1. id는 유일하지않아도 프로그램은 잘 돌아가지만, 관례상 유일해야 한다고합니다. 

2. 테이블로 만드는것도 좋은 방법이지만 div로 만들어서 img를 추가하는 방법을 고민해보라고 하셨습니다.

3. JS로 만든 뒤, jQuery로 바꿔 보라고 하셨습니다.

 

그리하여 다시 코딩해보았습니다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        /* 카드 이미지 사이즈 조절 */
        img {
            width: 200px;
            height: 270px;
            opacity: 0.7;
        }

        .card {
            display: inline-block;
            /* width: 200px;
        height: auto;
        /* margin: 10px; */
        }
    </style>
    <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
</head>

<body>

    <!-- 1. 버튼 4개(정답보기, 뒷면 보이기, 섞기, 재시작) 만들기 -->
    <button id='back1'>정답보기</button> <!-- 앞면 보이기-->
    <button id='back2'>뒷면 보이기</button><!-- 뒷면 보이기-->
    <button id='shuffle'>섞기</button><!-- 섞기-->
    <button id='restart'>재시작</button><!-- 재시작-->
    <!-- <button id='hint' onclick="hint()">Hint</button>Hint -->

    <div class=; id="board" style="width: 900px; height: auto">

    </div>

    <script>
        // ==================== board 구성하기 ====================
        var cardArr = [];
        var backCard = '<img src="back.jpg" alt="" id="0">';
        var tmp = '';
        // 배열 만들어서 카드 먼저 배치해놓고, 배열만 섞어서 제공하게하자.
        // cardArr에 이미지 소스들 집어넣기
        for (let i = 1; i <= 8; i++) {
            // board에 넣을 img src를 배열에 넣기
            cardArr.push('<div data-state="0" class="card"><img src="' + i + '.png" alt="" data-num="' + i + '" ></div>');
            cardArr.push('<div data-state="0" class="card"><img src="' + i + '.png" alt="" data-num="' + i + '" ></div>');
            // 화면에 넣을 이미지 주소 복사하기
            tmp += '<div data-state="0" class="card"><img src="' + i + '.png" alt="" data-num="' + i + '" ></div>'
            tmp += '<div data-state="0" class="card"><img src="' + i + '.png" alt="" data-num="' + i + '" `></div>'
        }
        $('#board').html(tmp);
        var bkflag;// 카드 뒷면시 true
        var playGame;// 게임진행중에 모두 뒷면 클릭 못하게하기 게임진행은 카드 클릭 시작한 순간부터 시작.
        var matchTest = new Array(); // 카드 매칭 테스트시 사용할 배열
        var clickAble; // 카드 2장 클릭시 틀렸을 때, 다른카드들 클릭 못하게 한다.

        // ====================================   start   ====================================
        $(document).ready(function () { gameStart() })

        // ====================================   /게임 재시작   ====================================
        $('#restart').click(function () {// 카드 뒤집고, 카드덱 데이터를 셔플하기
            gameStart()
        })

        // ====================================   /정답보기 버튼   ====================================
        $('#back1').click(function () {
            if (bkflag && clickAble) {// 게임 시작전에는 섞기만 가능하며, 뒷면인 상태여야만 앞면을 볼 수 있다. 클릭 모션이 종료된 후에만 답을 볼 수 있게 한다.
                frontCard();
                resetId()
            }
        })
        // ---- 오류
        // 뒷면 보이기 상태에서 카드 2장을 시간 조금 두고 클릭 한 뒤, 한장이 뒤집어진 상태에서 정답보기 클릭시 앞면이 뒷면으로 변한다.

        // ====================================   /앞면 보이기 버튼   ====================================
        function frontCard() {
            // for (let i = 0; i < $('#board>div').length; i++) {
            //     $('#board>div').eq(i).html(cardArr[i])
            // }

            $.each($('#board>div'), function (index) { $('#board>div').eq(index).html(cardArr[i]) })
            bkflag = false;
            playGame = false; // 정답을 봤으므로 게임포기로 간주한다.
            clickAble = false;
        }

        // ====================================   /뒷면 보이기 버튼   ====================================
        $('#back2').click(function () {
            if (!playGame) { flipCard(); } // 게임 진행중에 뒷면으로 못바꾸게하기. 게임 포기하려면 앞면보기 누르게하기.
        })

        // ====================================   /섞기 버튼   ====================================
        $('#shuffle').click(function () {
            //if문으로 모두 뒷면 or 모두 앞면인 상태일 때 앞or뒷 화면 띄워주게한다.
            if (!playGame) { // 게임 진행중에는 섞지못하게하기
                shuffle(); // 섞기
                if (bkflag) {
                    flipCard();
                } else {
                    frontCard();
                }
            }
        })

        // // ====================================   게임시작 함수   ====================================
        function gameStart() {
            playGame = false;
            shuffle(); // 셔플
            flipCard(); // 카드 뒤집기
            resetId();
            playGame = true;
        }

        // // ====================================   -모두 매칭시 게임종료 안내문구-   ==================================== // 지금 작업하고있는 곳
        function gameEndTest() {
            let cnt = 0;
            for (let i = 0; i < $('#board>div').length; i++) {
                if ($('#board>div').eq(i).attr("data-state") == "1") {
                    cnt++;
                }
                if ($('#board>div').length == cnt) {
                    setTimeout(function () { alert('게임이 종료되었습니다.'); }, 1000)
                }
            }
        }

        // // ====================================   div data-num 리셋(카드 앞,뒷면 체크)   ====================================
        function resetId() { $('#board>div').attr("data-state", "0") }// 앞면보이기 클릭시 th의 id들 0으로 초기화해주기

        // // ====================================   /카드 매칭테스트   ====================================
        function matchT() {
            if (matchTest.length == 2) { //img의 data-num으로 비교 matchTest[0]=$('#board>div').eq(i)인 상태이다.
                if (matchTest[0].children().children().attr("data-num") == matchTest[1].children().children().attr("data-num")) { //img의 id로 맞춘 숫자카드인지 검사
                    matchTest[0].attr("data-state", "1"); // th의 id 0 - 아직 못 맞춘 카드, 1- 맞춘 카드
                    matchTest[1].attr("data-state", "1");
                    matchTest = new Array();
                    return;
                } else {//다른숫자카드}
                    // 하나 클릭 시 뒤집으면 뒤집는동안 아무 작업 못하게 해야한다. 
                    clickAble = false;
                    // 일정 시간 지난 뒤 다시 back 화면이 표시되게 한다.
                    setTimeout(function () {
                        matchTest[0].html(backCard);
                        matchTest[1].html(backCard);
                        matchTest = new Array();
                        clickAble = true;
                    }, 1000)
                }
            }
        }

        // ====================================   /카드 뒤집기   ====================================
        function flipCard() {
            for (let i = 0; i < $('#board>div').length; i++) {
                $('#board>div').eq(i).html(backCard)
            }
            bkflag = true;
            clickAble = true;
        }

        // // ====================================   섞기   ====================================
        function shuffle() {
            cardArr.sort(function (a, b) { return Math.random() - 0.5 });
        }

        // ====================================   /카드 이미지 클릭   ====================================
        // 카드 클릭시 게임 시작 및 카드 뒤집기
        for (let i = 0; i < $('#board>div').length; i++) {
            $('#board>div').eq(i).click(function () {
                if ($('#board>div').eq(i).attr('data-state') == "1") { return; }// alt값이 1인카드는 맞춘카드이므로 아래로 진행하지 못하게 한다.
                playGame = true;
                matchflag = false;
                if (matchTest[0] != $('#board>div').eq(i) && (bkflag) && clickAble) { // 같은카드 두번 클릭한 것 아니고, 게임이 시작되어 뒷면보이기 클릭한 상태 일 때 진행된다.
                    $('#board>div').eq(i).html(cardArr[i])
                    matchTest.push($('#board>div').eq(i));
                    matchT();
                    gameEndTest();
                }
            });
        }
    </script>

</body>

</html>

수정사항

1. jQuery문 사용하지 않은 코드들을 가급적 jQuery문으로 바꿔 사용했습니다.

(for 문도 $.each()로 사용할 수 있다고 하셨는데, 연구해보겠습니다.)

2. 기존 코드는 테이블로 구성했으나, 지금은 div로 구성했습니다.

 

jQuery문으로 작성하는것이 익숙해지면 편할것 같다는 느낌이 들었습니다.

반응형
Comments