javascript

javascript_(UP&DOWN_GAME)_22.05.10(day12)

양빵빵 2022. 5. 10. 16:47

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>UP & DOWN</title>
    <!-- css -->
    <!-- reset -->

    <!-- custom -->
    <!-- <link rel="stylesheet" href="main.css"> -->

    <!-- js -->
    <!-- <script type="text/javascript" src="./1.app.js" defer></script> -->
</head>
<body>
    <div class="wrapper">

        <section class="main">
            <h1 class="main-title">신나는 UP&DOWN 게임</h1>
            <div class="number-wrapper">
                <h2>
                   <em id="begin">1</em>부터 <em id="end">100</em>사이의 숫자를 클릭하세요.
                </h2>
                <div id="numbers">

                </div>
            </div>
        </section>

        <aside class="result">
            <div id="up">UP!!</div>
            <div id="down">DOWN!!</div>
        </aside>

        <div id="finish">Congratulation!!!</div>

    </div>
   
</body>
</html>

 

 

/*reset*/

a {
    color: inherit;
    text-decoration: none;
}

/* common layout */

.wrapper {
    font-size: 18px;
    background: #8c8c8c;
    height: 100vh;
    position: relative;
}

/* section.main */
section.main {
    width: 40%;
    background: #fff;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    border-radius: 10px;
    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.7);
    overflow: hidden;
}

section.main .main-title {
    font-size: 30px;
    font-weight: 700;
    background: #a3f8ff;
    text-align: center;
    padding: 30px 20px;
    border-bottom: 1px solid #d3d3d3;
}

section.main .number-wrapper {
    padding: 50px 20px;
}

section.main .number-wrapper h2 {
    font-size: 22px;
    text-align: center;
    text-decoration: underline;
}

section.main .number-wrapper h2 em {
    font-size: 1.2em;
    font-weight: 700;
    color: #f00;
}

#numbers {
    width: 70%;
    height: 400px;
    border: 1px solid #000;
    margin: 30px auto 0;
    padding: 30px 0 30px 50px;
    overflow: auto;
    display: flex;
    flex-wrap: wrap;
}

#numbers .icon {
    width: 90px;
    height: 90px;
    border-radius: 50%;
    font-size: 32px;
    font-weight: 700;
    color: #fff;
    background: #000;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 5%;
    margin-bottom: 10px;
    cursor: pointer;
}

#numbers .icon:hover {
    transform: scale(1.1);
    opacity: 0.7;
}

#numbers .icon:nth-child(4n + 1) {
    background: orange;
}

#numbers .icon:nth-child(4n + 2) {
    background: skyblue;
}

#numbers .icon:nth-child(4n + 3) {
    background: yellowgreen;
}

#numbers .icon:nth-child(4n) {
    background: orangered;
}

/* aside.result */
.result {
    position: absolute;
    right: 20%;
    top: 50%;
    transform: translateY(-50%);

}

.result div {
    width: 150px;
    height: 150px;
    border-radius: 50%;
    font-size: 30px;
    font-weight: 700;
    color: #fff;
    margin-bottom: 30px;
    display: flex;
    justify-content: center;
    align-items: center;
    background: #000;
}

#up {
    background: #f00;
    cursor: pointer;
}

#down {
    background: #00f;
    cursor: pointer;
}

.result div.selected {

    animation: jump 0.3s infinite linear alternate;
}

@keyframes jump {
    0% {
        transform: translateY(0);
    }

    50% {
        transform: translateY(-10px);
    }

    100% {
        transform: translateY(-20px);
    }
}

/* #finish */
#finish {
    width: 60%;
    height: 200px;
    background: tomato;
    font-size: 80px;
    font-weight: 700;
    border: 2px solid #000;
    border-radius: 20px;
    text-align: center;
    line-height: 200px;

    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: -100;
    opacity: 0;
}

#finish.show {
    animation: drop 1s linear forwards;
}

@keyframes drop {
    0% {
        opacity: 0;
        top: -50%;
    }

    50% {

        opacity: 0.5;
        top: 0;
    }

    100% {
        opacity: 1;
        z-index: 100;
        top: 50%;
    }
}

/* 정답아이콘에 id=move가 붙으면 아이콘이 이동하는 애니메이션 */
.icon#move {
    position: absolute;
    left: 50%;
    top: 10%;
    z-index: 50;
    border: 4px dashed #000;
    animation: move 1s linear forwards;
}
@keyframes move {
    0% {
        top: 0;
        transform: scale(1) rotate(0) translateX(0);
    }
    100% {
        top: 10%;
        transform: scale(2) rotate(720deg) translateX(-50%);
    }
}

 

 

 


//=============== 전역변수, 함수 정의부분 =============//

// 게임진행에 필요한 데이터
// (실제정답, 선택한숫자, 최소값, 최대값)
const gameData = {
    secret: Math.floor(Math.random() * 100) + 1,
    answer: null,
    min: 1,
    max: 100
};


// 숫자 아이콘 생성 함수
function makeIcons() {

    const $numbers = document.getElementById('numbers');

    //가상의 태그
    const $virtual = document.createDocumentFragment();

    for (let i = 1; i <= 100; i++) {
        const $newDiv = document.createElement('div');
        $newDiv.textContent = i;
        $newDiv.classList.add('icon');

        $virtual.appendChild($newDiv);
    }
    $numbers.appendChild($virtual);
}

// UP, DOWN일 경우 해야할 일 정의
// parameter 1: isUp - UP인경우 true, DOWN인 경우 false
// parameter 2: target - 클릭된 요소 노드
function processUpDownCase(isUp, target) {
    const CLASS_NAME = 'selected';
    const [$up, $down] = [...document.querySelector('.result').children]
    if (isUp) {
        $down.classList.remove(CLASS_NAME);
        $up.classList.add(CLASS_NAME);
        gameData.min = gameData.answer + 1;
        document.getElementById('begin').textContent = gameData.min;
    } else {
        $up.classList.remove(CLASS_NAME);
        $down.classList.add(CLASS_NAME);
        gameData.max = gameData.answer - 1;
        document.getElementById('end').textContent = gameData.max;
    }

    // 아이콘 제거 함수 호출
    clearIcons(isUp, target);
}

// 클릭한 아이콘을 기준으로 범위밖의 아이콘을 제거하는 함수
function clearIcons(isUp, target) {
    const $numbers = document.getElementById('numbers');

    let $delTarget = target;
    while($delTarget) {
        let $nextTarget =
            isUp ?
            $delTarget.previousElementSibling
            : $delTarget.nextElementSibling;
        $numbers.removeChild($delTarget);
        $delTarget = $nextTarget;
    }
}


// 정답을 맞출 경우 해야할 일
function processCorrectCase(target) {
    const $finish = document.getElementById('finish');
    $finish.classList.add('show');

    // 정답 div.icon에 id=move를 추가.

    target.setAttribute('id','move');
}

// 사용자가 선택한 숫자를 실제정답과 비교해서 결과를 처리하는 함수
function compareAnswer(target) {
   
    if (gameData.answer === gameData.secret) {
        //정답인 경우
        processCorrectCase(target);
    } else if (gameData.answer < gameData.secret) {
        //UP인 경우
        processUpDownCase(true, target);
    } else {
        //DOWN인 경우
        processUpDownCase(false, target);
    }
}


//=============== 메인 코드 실행부분 ===============//
(function() {

    // 100개의 아이콘 생성하여 배치
    makeIcons();

    // 아이콘 클릭 이벤트 부여
    const $numbers = document.getElementById('numbers');
    $numbers.addEventListener('click', e => {

        //만약에 아이콘을 클릭하지 않았다면 나가!
        if (!e.target.matches('#numbers > .icon')) return;

        // 사용자가 선택한 숫자가 무엇인가???
        // console.log(e.target.textContent);
        gameData.answer = +e.target.textContent;
        // console.log(gameData);

        // 정답 검증하는 함수 호출
        compareAnswer(e.target);
    });

})();



/*

# 시나리오

- 사용자는 100개의 아이콘 중 한 개를 클릭한다.
- 시스템은 정답데이터(1~100사이의 랜덤정수)와
  클릭한 아이콘의 숫자데이터를 비교한다.
- 시스템은 비교결과를 판단하여 UP인경우 DOWN인 경우 그리고 정답인 경우에 따른 효과를 렌더링한다.
- 사용자는 지속적으로 정답을 맞출 때 까지 위의 행위를 반복한다.

===============================

# 세부 시나리오

1. 100개의 아이콘에는 클릭 이벤트가 부여되어야 한다
2-1. UP인 경우
=> up아이콘의 애니메이션을 발동시킨다.
=> 해당 범위에 벗어난 아이콘을 제거한다.
=> h2태그의 최소값을 수정한다.

2-2. down인 경우
=> down아이콘의 애니메이션을 발동시킨다.
=> 해당 범위에 벗어난 아이콘을 제거한다.
=> h2태그의 최대값을 수정한다.

2-3. 정답인 경우
=> finish박스의 애니메이션을 발동시킨다.

===============================

# 필요한데이터

 1-100사의 랜덤정수(고정값),
 사용자가 클릭한 아이콘의 숫자,
 최소값을 저장할 변수, 최대값을 저장할 변수



*/