본문 바로가기
Javascript 프로젝트/기능별 페이지

[기능별 페이지] memory matching game2

by cogito21_js 2024. 6. 10.
반응형

HTML (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Memory Matching Game</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <header>
        <h1>Memory Matching Game</h1>
    </header>
    <main>
        <div class="game-container">
            <label for="boardRows">Rows:</label>
            <input type="number" id="boardRows" value="4" min="2" max="10">
            <label for="boardCols">Columns:</label>
            <input type="number" id="boardCols" value="4" min="2" max="10">
            <label for="cardType">Card Type:</label>
            <select id="cardType">
                <option value="letters">Letters</option>
                <option value="numbers">Numbers</option>
                <option value="colors">Colors</option>
            </select>
            <div id="numberRange" class="number-range">
                <label for="numberMin">Min:</label>
                <input type="number" id="numberMin" value="1">
                <label for="numberMax">Max:</label>
                <input type="number" id="numberMax" value="20">
                <button id="applyNumberRange">Apply</button>
            </div>
            <div class="board" id="board"></div>
            <button id="resetButton">Reset Game</button>
        </div>
    </main>
    <script src="script.js"></script>
</body>
</html>

설명

  • <header> 태그: 게임 제목을 포함합니다.
  • <main> 태그: 게임 설정과 게임 보드를 포함하는 메인 컨텐츠 영역입니다.
  • <div class="game-container">: 게임 설정 요소와 게임 보드를 포함하는 컨테이너입니다.
    • <label><input> 요소: 사용자가 보드의 행과 열 수를 설정할 수 있습니다.
    • <label><select> 요소: 사용자가 카드 타입을 선택할 수 있습니다 (letters, numbers, colors).
    • <div id="numberRange" class="number-range">: 사용자가 숫자 카드 타입을 선택한 경우 숫자 범위를 설정할 수 있는 입력 필드와 적용 버튼을 포함합니다.
    • <div class="board" id="board">: 게임 보드가 표시될 영역입니다.
    • <button id="resetButton">: 게임을 재설정하는 버튼입니다.

CSS (styles.css)

/* 기본 스타일 설정 */
body {
    font-family: 'Arial', sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 0;
    background-color: #f4f4f9;
}

header {
    margin-top: 20px;
}

h1 {
    font-size: 2em;
}

main {
    display: flex;
    justify-content: center;
    align-items: center;
    flex: 1;
    padding: 20px;
}

.game-container {
    background: #fff;
    border-radius: 10px;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    padding: 20px;
    text-align: center;
}

.board {
    display: grid;
    grid-gap: 10px;
    margin-top: 20px;
}

.card {
    width: 100px;
    height: 150px;
    perspective: 1000px;
}

.card-inner {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform 0.6s;
}

.card.flipped .card-inner {
    transform: rotateY(180deg);
}

.card-front, .card-back {
    width: 100%;
    height: 100%;
    position: absolute;
    backface-visibility: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 5px;
    font-size: 2em;
}

.card-front {
    background-color: #fff;
    color: black;
    transform: rotateY(180deg);
}

.card-back {
    background-color: #333;
    color: white;
}

button {
    margin-top: 20px;
    padding: 10px 20px;
    font-size: 16px;
    cursor: pointer;
    border: none;
    border-radius: 5px;
    background-color: #6200ea;
    color: white;
    transition: background-color 0.3s ease;
}

button:hover {
    background-color: #3700b3;
}

label, input, select {
    margin-top: 10px;
    margin-bottom: 10px;
    font-size: 1em;
}

.number-range {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    margin-top: 10px;
}

#numberMin, #numberMax {
    width: 50px;
    padding: 2px;
    font-size: 14px;
    height: 20px;
}

#applyNumberRange {
    padding: 5px 10px;
    font-size: 14px;
    height: 24px;
    line-height: 14px;
    background-color: #6200ea;
    color: white;
    border: none;
    border-radius: 5px;
    transition: background-color 0.3s ease;
    margin-left: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
}

#applyNumberRange:hover {
    background-color: #3700b3;
}

설명

  • 기본 스타일:
    • body, header, main 등 기본적인 페이지 레이아웃과 스타일을 설정합니다.
  • 게임 컨테이너:
    • .game-container: 게임 설정 요소와 보드를 포함하는 컨테이너의 스타일을 설정합니다.
  • 카드 스타일:
    • .card, .card-inner, .card-front, .card-back: 카드의 앞면과 뒷면을 포함한 카드 요소의 스타일을 설정합니다.
    • .flipped: 카드가 뒤집혔을 때의 애니메이션 효과를 설정합니다.
  • 숫자 범위 설정:
    • .number-range: 숫자 범위 설정을 위한 입력 필드와 버튼을 포함하는 컨테이너의 스타일을 설정합니다.
    • #numberMin, #numberMax: 숫자 범위 입력 필드의 크기와 스타일을 설정합니다.
    • #applyNumberRange: 숫자 범위 적용 버튼의 크기와 스타일을 설정합니다.

JavaScript (script.js)

document.addEventListener('DOMContentLoaded', () => {
    const board = document.getElementById('board');
    const resetButton = document.getElementById('resetButton');
    const boardRowsInput = document.getElementById('boardRows');
    const boardColsInput = document.getElementById('boardCols');
    const cardTypeSelect = document.getElementById('cardType');
    const numberRange = document.getElementById('numberRange');
    const numberMinInput = document.getElementById('numberMin');
    const numberMaxInput = document.getElementById('numberMax');
    const applyNumberRangeButton = document.getElementById('applyNumberRange');

    let firstCard = null;
    let secondCard = null;
    let lockBoard = false;
    let cards = [];

    cardTypeSelect.addEventListener('change', () => {
        if (cardTypeSelect.value === 'numbers') {
            numberRange.style.display = 'flex';
        } else {
            numberRange.style.display = 'none';
        }
    });

    applyNumberRangeButton.addEventListener('click', initializeGame);

    function initializeGame() {
        board.innerHTML = '';
        const rows = parseInt(boardRowsInput.value);
        const cols = parseInt(boardColsInput.value);
        const totalCards = rows * cols;

        if (totalCards % 2 !== 0) {
            alert('The total number of cards must be even.');
            return;
        }

        cards = generateCards(totalCards);
        shuffle(cards);
        createBoard(rows, cols);
    }

    function generateCards(totalCards) {
        const cardValues = [];
        const pairCount = totalCards / 2;
        const cardType = cardTypeSelect.value;

        if (cardType === 'letters') {
            for (let i = 0; i < pairCount; i++) {
                const value = String.fromCharCode(65 + i); // A, B, C, ...
                cardValues.push(value, value);
            }
        } else if (cardType === 'numbers') {
            const min = parseInt(numberMinInput.value);
            const max = parseInt(numberMaxInput.value);
            for (let i = 0; i < pairCount; i++) {
                const value = Math.floor(Math.random() * (max - min + 1)) + min;
                cardValues.push(value, value);
            }


 } else if (cardType === 'colors') {
            const colors = ['#FF5733', '#33FF57', '#3357FF', '#F333FF', '#FF33A1', '#A1FF33', '#33FFF6', '#F633FF'];
            for (let i = 0; i < pairCount; i++) {
                const color = colors[i % colors.length];
                cardValues.push(color, color);
            }
        }

        return cardValues;
    }

    function createBoard(rows, cols) {
        board.style.gridTemplateColumns = `repeat(${cols}, 100px)`;
        board.style.gridTemplateRows = `repeat(${rows}, 150px)`;
        cards.forEach(createCard);
    }

    function createCard(cardValue) {
        const cardElement = document.createElement('div');
        cardElement.classList.add('card');
        const cardInner = document.createElement('div');
        cardInner.classList.add('card-inner');

        const cardFront = document.createElement('div');
        cardFront.classList.add('card-front');
        if (typeof cardValue === 'string' && cardValue.startsWith('#')) {
            cardFront.style.backgroundColor = cardValue;
        } else {
            cardFront.textContent = cardValue;
        }

        const cardBack = document.createElement('div');
        cardBack.classList.add('card-back');
        cardBack.textContent = '?';

        cardInner.appendChild(cardFront);
        cardInner.appendChild(cardBack);
        cardElement.appendChild(cardInner);

        cardElement.dataset.value = cardValue;
        cardElement.addEventListener('click', handleCardClick);
        board.appendChild(cardElement);
    }

    function handleCardClick(event) {
        if (lockBoard) return;
        const clickedCard = event.currentTarget;

        if (clickedCard === firstCard) return;

        clickedCard.classList.add('flipped');

        if (!firstCard) {
            firstCard = clickedCard;
        } else {
            secondCard = clickedCard;
            checkForMatch();
        }
    }

    function checkForMatch() {
        const isMatch = firstCard.dataset.value === secondCard.dataset.value;
        isMatch ? disableCards() : unflipCards();
    }

    function disableCards() {
        firstCard.removeEventListener('click', handleCardClick);
        secondCard.removeEventListener('click', handleCardClick);
        resetBoard();
    }

    function unflipCards() {
        lockBoard = true;
        setTimeout(() => {
            firstCard.classList.remove('flipped');
            secondCard.classList.remove('flipped');
            resetBoard();
        }, 1500);
    }

    function resetBoard() {
        [firstCard, secondCard, lockBoard] = [null, null, false];
    }

    function shuffle(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [array[i], array[j]] = [array[j], array[i]];
        }
    }

    resetButton.addEventListener('click', initializeGame);
    boardRowsInput.addEventListener('change', initializeGame);
    boardColsInput.addEventListener('change', initializeGame);
    cardTypeSelect.addEventListener('change', initializeGame);

    initializeGame();
});

설명

  • DOM 요소 가져오기: 게임 설정과 관련된 HTML 요소를 가져옵니다.
  • 이벤트 리스너 설정: 각 입력 요소의 변경 이벤트와 리셋 버튼 클릭 이벤트에 대한 리스너를 설정합니다.
    • cardTypeSelect.addEventListener('change', () => { ... }): 카드 타입을 변경할 때 숫자 범위 설정을 표시하거나 숨깁니다.
    • applyNumberRangeButton.addEventListener('click', initializeGame): "Apply" 버튼을 클릭했을 때 게임을 초기화합니다.
  • 게임 초기화 함수 (initializeGame):
    • board.innerHTML = '': 보드를 초기화합니다.
    • 보드 크기와 카드 타입에 따라 게임을 초기화합니다.
    • 카드 배열을 셔플한 후, 보드를 생성합니다.
  • 카드 생성 함수 (generateCards):
    • 선택된 카드 타입에 따라 카드 값을 생성합니다.
    • 각 카드 타입별로 짝을 이룰 수 있는 카드 값을 생성합니다.
  • 보드 생성 함수 (createBoard):
    • 보드의 그리드 레이아웃을 설정합니다.
    • 각 카드를 보드에 추가합니다.
  • 카드 클릭 처리 함수 (handleCardClick):
    • 카드를 클릭했을 때 처리 작업을 수행합니다.
    • 첫 번째 클릭 시 첫 번째 카드를 설정하고, 두 번째 클릭 시 두 번째 카드를 설정한 후 매칭을 확인합니다.
  • 매칭 확인 함수 (checkForMatch):
    • 두 카드의 매칭 여부를 확인합니다.
    • 매칭된 카드는 비활성화하고, 매칭되지 않은 카드는 다시 뒤집습니다.
  • 카드 비활성화 함수 (disableCards):
    • 매칭된 카드를 비활성화합니다.
  • 카드 뒤집기 함수 (unflipCards):
    • 매칭되지 않은 카드를 다시 뒤집습니다.
  • 보드 리셋 함수 (resetBoard):
    • 보드 상태를 초기화합니다.
  • 카드 셔플 함수 (shuffle):
    • 카드 배열을 랜덤하게 섞습니다.
반응형