반응형
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):
- 카드 배열을 랜덤하게 섞습니다.
반응형
'Javascript 프로젝트 > 기능별 페이지' 카테고리의 다른 글
[기능별 페이지] lotto (0) | 2024.06.11 |
---|---|
[기능별 페이지] memory matching game1 (0) | 2024.06.10 |
[기능별 페이지] omok (0) | 2024.06.10 |
[기능별 페이지] - number guessing game (1) | 2024.06.10 |
[기능별 페이지] 게임 - tictactoe (1) | 2024.06.10 |