ผู้เขียนบทความ : นาย ภูวรินทร์ อิ่มสุวรรณ รหัส 167404130069
คณะวิศวกรรมศาสตร์ : สาขาวิศวกรรมไฟฟ้า
วิชา : 04000104 การโปรแกรมคอมพิวเตอร์ 1/2567
1. ความเป็นมา
เกม “Memory Match” เป็นเกมที่ได้รับความนิยมทั่วโลก โดยมีจุดประสงค์หลักในการพัฒนาทักษะการจำของผู้เล่น เกมประเภทนี้ถูกใช้ทั้งในแวดวงการศึกษาและเพื่อความบันเทิงสำหรับทุกเพศทุกวัย เกมที่เราสร้างในโปรเจกต์นี้ใช้เทคโนโลยี HTML, CSS และ JavaScript ซึ่งเป็นเทคโนโลยีเว็บพื้นฐานที่ช่วยในการพัฒนาเกมที่ทำงานผ่านเว็บเบราว์เซอร์ได้โดยไม่ต้องติดตั้งซอฟต์แวร์เพิ่มเติม
2. วัตถุประสงค์
วัตถุประสงค์หลักของการพัฒนาเกมนี้ คือ:
- สร้างเกมฝึกสมองที่ช่วยพัฒนาทักษะการจำและการสังเกตของผู้เล่น
- ฝึกทักษะการเขียนโปรแกรม HTML, CSS, และ JavaScript
- สร้างเกมที่สามารถใช้งานได้ง่ายและสนุกสนาน
3. ขอบเขต
- เกมจะประกอบด้วยการ์ดจำนวน 16 ใบ (8 คู่) ที่ถูกวางในรูปแบบตาราง 4×4
- เมื่อเริ่มเกม การ์ดทั้งหมดจะถูกคว่ำหน้า ผู้เล่นจะต้องคลิกการ์ดเพื่อเปิดเผยสัญลักษณ์ที่อยู่ด้านหลัง
- เมื่อเปิดการ์ดสองใบที่ตรงกัน การ์ดเหล่านั้นจะยังคงหงายอยู่
- ผู้เล่นมีเวลาจำกัดในการจับคู่ให้ครบ
4. ประโยชน์ที่คาดว่าจะได้รับ
- พัฒนาทักษะการเขียนโปรแกรมสำหรับผู้พัฒนา
- สร้างความเพลิดเพลินและฝึกการจดจำของผู้เล่น
- สร้างโอกาสในการเรียนรู้การจัดการเวลาและการแก้ไขปัญหาที่เกิดขึ้นระหว่างการพัฒนา
5. ความรู้ที่เกี่ยวข้อง
- HTML: สำหรับโครงสร้างของหน้าเว็บและการจัดวางองค์ประกอบต่างๆ
- CSS: สำหรับการออกแบบและจัดแต่งหน้าเว็บให้ดูน่าสนใจ รวมถึงการทำภาพเคลื่อนไหว
- JavaScript: สำหรับจัดการตรรกะของเกม เช่น การจับคู่การ์ด, การนับเวลา และการควบคุมการแสดงผล
- DOM Manipulation: การใช้งาน Document Object Model (DOM) เพื่อเปลี่ยนแปลงองค์ประกอบ HTML ในระหว่างการเล่นเกม
6. การดำเนินงาน
การดำเนินงานสำหรับเกมนี้สามารถแบ่งได้เป็นขั้นตอนดังนี้:
- วางแผนโครงสร้างและออกแบบเกมเบื้องต้น
- เขียนโค้ด HTML สำหรับโครงสร้างหน้าเว็บ
- ใช้ CSS ในการออกแบบการ์ดและเอฟเฟกต์ภาพเคลื่อนไหว
- เขียน JavaScript เพื่อจัดการตรรกะของเกม เช่น การสุ่มการ์ดและการจับคู่
- ทดสอบการทำงานของเกมในหลากหลายเบราว์เซอร์และอุปกรณ์
7. วิธีใช้งานโปรแกรม
- เปิดไฟล์ HTML ในเว็บเบราว์เซอร์
- หน้าต่างเกมจะแสดงขึ้นพร้อมการ์ดที่ถูกคว่ำหน้า
- คลิกการ์ดสองใบเพื่อพยายามหาคู่ที่ตรงกัน
- หากการ์ดทั้งสองใบตรงกัน การ์ดจะหงายอยู่ หากไม่ตรงกัน การ์ดจะคว่ำกลับไป
- จับคู่การ์ดทั้งหมดภายในเวลาที่กำหนด
- สามารถเริ่มเกมใหม่ได้โดยการคลิกปุ่ม “Reset Game” หรือ “Play Again” หลังจากจบเกม
8. การทดลอง
- ทดสอบการทำงานของเกมในหลากหลายอุปกรณ์ เช่น คอมพิวเตอร์ตั้งโต๊ะ, แท็บเล็ต, และสมาร์ทโฟน เพื่อให้แน่ใจว่าเกมทำงานได้ดีในทุกขนาดหน้าจอ
- ตรวจสอบความเข้ากันได้กับเบราว์เซอร์หลักๆ เช่น Chrome, Firefox, Safari, และ Edge
- ทดสอบความเร็วและการตอบสนองของเกม โดยเฉพาะเมื่อการ์ดหลายใบถูกคลิกอย่างรวดเร็ว
9. เทคนิคการประยุกต์เพื่อเพิ่มประสิทธิภาพ
- การใช้ LocalStorage: เพื่อเก็บคะแนนหรือระดับของผู้เล่นทำให้สามารถเล่นเกมต่อจากที่ค้างไว้ได้
- ปรับแต่ง Animation: ใช้ภาพเคลื่อนไหวที่ราบรื่นยิ่งขึ้น หรือใช้การ์ดที่มีธีมต่างๆ เพื่อให้เกมน่าสนใจมากขึ้น
- เพิ่มระดับความยาก: ขยายขนาดบอร์ดหรือเพิ่มจำนวนคู่ของการ์ดเพื่อท้าทายผู้เล่นมากขึ้น
- เพิ่มฟีเจอร์ออนไลน์: อาจพัฒนาให้ผู้เล่นสามารถแข่งขันกับผู้เล่นอื่นแบบออนไลน์ได้
10. สรุปผลและข้อเสนอแนะ
เกมนี้เป็นเกมที่เหมาะสมสำหรับการฝึกสมองและความจำสำหรับผู้เล่นทุกเพศทุกวัย ด้วยการใช้เทคโนโลยีเว็บเบื้องต้นทำให้เกมสามารถเล่นได้ในเบราว์เซอร์ทุกชนิด การทดสอบในเบราว์เซอร์ต่างๆ ยืนยันว่าเกมทำงานได้อย่างราบรื่น แต่เพื่อเพิ่มความน่าสนใจอาจเพิ่มฟีเจอร์ใหม่ เช่น เพิ่มระดับความยาก หรือเพิ่มระบบจัดเก็บคะแนน
11. ข้อมูลอ้างอิง
- Mozilla Developer Network (MDN) สำหรับเอกสารประกอบ HTML, CSS, และ JavaScript
- Stack Overflow สำหรับคำถามและแนวทางการแก้ปัญหาการพัฒนา
12. โค้ดเกมส์
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Match Game</title>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@300;600&display=swap" rel="stylesheet">
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #282c34;
height: 100vh;
margin: 0;
font-family: 'Raleway', sans-serif;
}
h1 {
font-size: 3rem;
color: #ffcc00;
margin-bottom: 30px;
text-shadow: 2px 2px 8px rgba(255, 255, 0, 0.7);
}
.game-container {
display: flex;
flex-direction: column;
align-items: center;
}
.game-board {
display: grid;
grid-template-columns: repeat(4, 120px);
grid-gap: 15px;
margin-bottom: 20px;
}
.card {
width: 120px;
height: 120px;
background-color: #555;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease, border-color 0.3s;
perspective: 1000px;
border: 2px solid transparent;
}
.card:hover {
border-color: #ffcc00;
transform: scale(1.05);
}
.inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.6s ease-in-out;
}
.card.flipped .inner {
transform: rotateY(180deg);
}
.front, .back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
font-size: 48px;
border-radius: 10px;
font-weight: 600;
}
.front {
background-color: #444;
color: #fff;
}
.back {
color: white;
transform: rotateY(180deg);
}
.card.color1 .back { background-color: #4CAF50; }
.card.color2 .back { background-color: #f39c12; }
.card.color3 .back { background-color: #3498db; }
.card.color4 .back { background-color: #e74c3c; }
.card.color5 .back { background-color: #9b59b6; }
.card.color6 .back { background-color: #1abc9c; }
.card.matched { animation: fadeOut 0.5s ease forwards; }
.controls { margin-top: 20px; }
#timer { font-size: 2rem; margin-bottom: 20px; color: #fff; }
button { padding: 10px 20px; background-color: #4CAF50; color: #fff; border: none; border-radius: 5px; cursor: pointer; font-weight: 600; }
button:hover { background-color: #45a049; }
.game-over-message {
font-size: 2rem;
color: #333;
background-color: rgba(255, 215, 0, 0.9);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
display: none;
white-space: nowrap;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; transform: scale(0); }
}
.confetti {
position: fixed;
width: 10px;
height: 10px;
background-color: #f39c12;
z-index: 100;
animation: confetti-fall 3s linear infinite;
}
.confetti:nth-child(2) { background-color: #e74c3c; animation-delay: 0.2s; }
.confetti:nth-child(3) { background-color: #9b59b6; animation-delay: 0.4s; }
.confetti:nth-child(4) { background-color: #1abc9c; animation-delay: 0.6s; }
.confetti:nth-child(5) { background-color: #3498db; animation-delay: 0.8s; }
@keyframes confetti-fall {
0% { top: -10px; left: calc(100% * var(--x)); }
100% { top: 100vh; left: calc(100% * var(--x) + 50px); transform: translateY(10px) rotate(30deg); }
}
@media (max-width: 600px) {
.game-board {
grid-template-columns: repeat(2, 100px);
}
}
</style>
</head>
<body>
<h1>Memory Match Game</h1>
<div class="game-container">
<div class="game-over-message" id="gameOverMessage">You matched all the cards! 🎉</div>
<div class="game-board" id="gameBoard"></div>
<div class="controls">
<div id="timer">Time: 03:00</div>
<button id="resetButton" onclick="resetGame()">Reset Game</button>
<button id="playAgainButton" style="display:none;" onclick="resetGame()">Play Again</button>
</div>
</div>
<!-- Confetti -->
<div class="confetti" style="--x: 0;"></div>
<div class="confetti" style="--x: 0.2;"></div>
<div class="confetti" style="--x: 0.4;"></div>
<div class="confetti" style="--x: 0.6;"></div>
<div class="confetti" style="--x: 0.8;"></div>
<audio id="matchSound" src="path/to/match-sound.mp3"></audio>
<script>
let cards = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'].flatMap(v => [v, v]), firstCard, secondCard, lock = false, matches = 0, time = 180, timer, hasStarted = false;
const board = document.getElementById('gameBoard');
const gameOverMessage = document.getElementById('gameOverMessage');
const resetButton = document.getElementById('resetButton');
const playAgainButton = document.getElementById('playAgainButton');
function setupGame() {
board.innerHTML = '';
cards = [...cards].sort(() => 0.5 - Math.random());
const colors = ['color1', 'color2', 'color3', 'color4', 'color5', 'color6'];
const cardColors = {};
cards.forEach((value, index) => {
if (!cardColors[value]) {
cardColors[value] = colors[Math.floor(index / 2) % colors.length];
}
});
cards.forEach((v, index) => {
let card = document.createElement('div');
card.classList.add('card');
card.dataset.value = v;
card.classList.add(cardColors[v]);
let inner = document.createElement('div');
inner.classList.add('inner');
let front = document.createElement('div');
front.classList.add('front');
front.innerHTML = '?';
let back = document.createElement('div');
back.classList.add('back');
back.innerHTML = v;
inner.appendChild(front);
inner.appendChild(back);
card.appendChild(inner);
card.onclick = flipCard;
board.appendChild(card);
});
matches = 0;
time = 180;
hasStarted = false;
updateTimer();
clearInterval(timer);
gameOverMessage.style.display = 'none';
playAgainButton.style.display = 'none'; // ซ่อนปุ่มเริ่มใหม่
}
function flipCard() {
if (lock || this === firstCard || this.classList.contains('flipped')) return;
if (!hasStarted) {
hasStarted = true;
timer = setInterval(updateTime, 1000);
}
this.classList.add('flipped');
if (!firstCard) {
firstCard = this;
} else {
secondCard = this;
checkForMatch();
}
}
function checkForMatch() {
if (firstCard.dataset.value === secondCard.dataset.value) {
document.getElementById('matchSound').play(); // เล่นเสียงเมื่อจับคู่
firstCard.classList.add('matched');
secondCard.classList.add('matched');
matches += 1;
if (matches === cards.length / 2) {
clearInterval(timer);
gameOverMessage.style.display = 'block';
playAgainButton.style.display = 'inline-block'; // แสดงปุ่มเริ่มใหม่
triggerConfetti();
}
resetTurn();
} else {
lock = true;
setTimeout(() => {
firstCard.classList.remove('flipped');
secondCard.classList.remove('flipped');
resetTurn();
}, 1000);
}
}
function resetTurn() {
[firstCard, secondCard, lock] = [null, null, false];
}
function updateTime() {
time--;
if (time === 0) {
clearInterval(timer);
gameOverMessage.style.display = 'block';
gameOverMessage.textContent = 'Time\'s up! Game over.';
playAgainButton.style.display = 'inline-block'; // แสดงปุ่มเริ่มใหม่
}
updateTimer();
}
function updateTimer() {
document.getElementById('timer').textContent = `Time: ${String(Math.floor(time / 60)).padStart(2, '0')}:${String(time % 60).padStart(2, '0')}`;
}
function resetGame() {
setupGame();
}
function triggerConfetti() {
const confettiCount = 100;
for (let i = 0; i < confettiCount; i++) {
const confetti = document.createElement('div');
confetti.classList.add('confetti');
confetti.style.left = `${Math.random() * 100}vw`;
confetti.style.animationDuration = `${Math.random() * 2 + 2}s`;
document.body.appendChild(confetti);
setTimeout(() => {
confetti.remove();
}, 4000);
}
}
setupGame();
</script>
</body>
</html>
การอธิบายโค้ดแต่ละส่วนของโปรเจกต์ Memory Match Game
1. ส่วนของ HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Match Game</title>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@300;600&display=swap" rel="stylesheet">
<style>
<!-- CSS สำหรับกำหนดรูปแบบการแสดงผลต่างๆ -->
</style>
</head>
<body>
<h1>Memory Match Game</h1>
<div class="game-container">
<!-- เกมบอร์ดสำหรับแสดงการ์ด -->
<div class="game-board" id="gameBoard"></div>
<!-- การแสดงผลของเวลาและปุ่มสำหรับเริ่มใหม่ -->
<div class="controls">
<div id="timer">Time: 03:00</div>
<button id="resetButton" onclick="resetGame()">Reset Game</button>
</div>
</div>
<!-- ใส่เสียงและอนิเมชันเมื่อจับคู่ถูกต้อง -->
<audio id="matchSound" src="path/to/match-sound.mp3"></audio>
<script>
<!-- JavaScript สำหรับควบคุมเกม -->
</script>
</body>
</html>
อธิบาย:
- โครงสร้าง HTML ประกอบด้วยส่วนหัว (
<head>
) ที่กำหนดเมตาข้อมูลและลิงก์ฟอนต์ ส่วนเนื้อหาหลัก (<body>
) ประกอบด้วยหัวข้อ (<h1>
) คำอธิบายเกม กระดานเกม และปุ่มควบคุมการรีเซ็ตเกม นอกจากนี้ยังมีเสียงและแอนิเมชันที่แสดงเมื่อจับคู่การ์ดได้ถูกต้อง
2.ส่วนของ CSS
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #282c34;
height: 100vh;
margin: 0;
font-family: 'Raleway', sans-serif;
}
h1 {
font-size: 3rem;
color: #ffcc00;
text-shadow: 2px 2px 8px rgba(255, 255, 0, 0.7);
}
/* กำหนดรูปแบบการแสดงผลของการ์ดและเกมบอร์ด */
.card {
width: 120px;
height: 120px;
background-color: #555;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
}
อธิบาย:
body
: กำหนดลักษณะการจัดวางองค์ประกอบในหน้าเว็บ โดยให้เนื้อหาทั้งหมดอยู่กึ่งกลางหน้าจอ และใช้ฟอนต์ ‘Raleway’h1
: กำหนดขนาดตัวอักษรและสีของหัวข้อ รวมถึงการสร้างแสงเงาให้ดูมีมิติ.card
: รูปแบบของการ์ด กำหนดขนาด ความสูง ความกว้าง สีพื้นหลัง การจัดวางตัวอักษรให้กลาง และเพิ่มเอฟเฟกต์การเปลี่ยนแปลงเมื่อผู้ใช้วางเมาส์
3.ส่วนของ JavaScript
let cards = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'].flatMap(v => [v, v]), firstCard, secondCard, lock = false, matches = 0, time = 180, timer, hasStarted = false;
const board = document.getElementById('gameBoard');
const gameOverMessage = document.getElementById('gameOverMessage');
function setupGame() {
board.innerHTML = '';
cards = [...cards].sort(() => 0.5 - Math.random());
const colors = ['color1', 'color2', 'color3', 'color4', 'color5', 'color6'];
// สร้างการ์ดแต่ละใบ และจัดการการคลิกเพื่อพลิกการ์ด
cards.forEach((v, index) => {
let card = document.createElement('div');
card.classList.add('card');
card.dataset.value = v;
card.classList.add(cardColors[v]);
let inner = document.createElement('div');
inner.classList.add('inner');
let front = document.createElement('div');
front.classList.add('front');
front.innerHTML = '?';
let back = document.createElement('div');
back.classList.add('back');
back.innerHTML = v;
inner.appendChild(front);
inner.appendChild(back);
card.appendChild(inner);
card.onclick = flipCard;
board.appendChild(card);
});
}
อธิบาย:
- กำหนดตัวแปรเริ่มต้นสำหรับการ์ด การจับคู่ เวลานับถอยหลัง และสถานะของเกม
setupGame()
: เป็นฟังก์ชันที่ใช้สำหรับตั้งค่าเกมใหม่ทุกครั้งที่ผู้เล่นเริ่มหรือกดรีเซ็ต โดยจะสุ่มการเรียงลำดับของการ์ดและสร้างการ์ดใน HTML พร้อมกับการตั้งค่าเหตุการณ์เมื่อผู้เล่นคลิกที่การ์ด
function flipCard() {
if (lock || this === firstCard || this.classList.contains('flipped')) return;
if (!hasStarted) {
hasStarted = true;
timer = setInterval(updateTime, 1000);
}
this.classList.add('flipped');
if (!firstCard) {
firstCard = this;
} else {
secondCard = this;
checkForMatch();
}
}
function checkForMatch() {
if (firstCard.dataset.value === secondCard.dataset.value) {
document.getElementById('matchSound').play();
firstCard.classList.add('matched');
secondCard.classList.add('matched');
matches += 1;
if (matches === cards.length / 2) {
clearInterval(timer);
gameOverMessage.style.display = 'block';
}
resetTurn();
} else {
lock = true;
setTimeout(() => {
firstCard.classList.remove('flipped');
secondCard.classList.remove('flipped');
resetTurn();
}, 1000);
}
}
อธิบาย:
flipCard()
: ฟังก์ชันนี้จะทำงานเมื่อการ์ดถูกคลิก โดยการ์ดจะถูกพลิกเพื่อแสดงค่า หากเป็นการ์ดใบแรก จะเก็บการ์ดไว้ และหากเป็นการ์ดใบที่สองจะเรียกฟังก์ชันcheckForMatch()
checkForMatch()
: ตรวจสอบว่าการ์ดสองใบที่ถูกคลิกมีค่าเดียวกันหรือไม่ หากตรงกันจะนับเป็นคู่ที่จับได้สำเร็จ และถ้าเก็บได้ครบทุกคู่จะจบเกม
function resetGame() {
setupGame();
}
function updateTime() {
time--;
if (time === 0) {
clearInterval(timer);
gameOverMessage.textContent = 'Time\'s up! Game over.';
}
document.getElementById('timer').textContent = `Time: ${String(Math.floor(time / 60)).padStart(2, '0')}:${String(time % 60).padStart(2, '0')}`;
}
อธิบาย:
resetGame()
: ฟังก์ชันที่เรียกเพื่อเริ่มเกมใหม่หรือรีเซ็ตเกมupdateTime()
: ทำหน้าที่ลดเวลาลงทีละวินาที และแสดงเวลาที่เหลืออยู่บนหน้าจอ หากเวลาหมด เกมจะจบลงและแสดงข้อความ “Time’s up! Game over.”
4.ส่วนแสดงผลเสียงและแอนิเมชัน
<audio id="matchSound" src="path/to/match-sound.mp3"></audio>
อธิบาย:
- การเพิ่มเสียงเอฟเฟกต์เมื่อผู้เล่นจับคู่การ์ดสำเร็จ
วิดีโออธิบายการเล่นเกม และอธิบายโค้ด