Memory Match Game (เกมจับคู่ความจํา)

ผู้เขียนบทความ : นาย ภูวรินทร์ อิ่มสุวรรณ รหัส 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. การดำเนินงาน

การดำเนินงานสำหรับเกมนี้สามารถแบ่งได้เป็นขั้นตอนดังนี้:

  1. วางแผนโครงสร้างและออกแบบเกมเบื้องต้น
  2. เขียนโค้ด HTML สำหรับโครงสร้างหน้าเว็บ
  3. ใช้ CSS ในการออกแบบการ์ดและเอฟเฟกต์ภาพเคลื่อนไหว
  4. เขียน JavaScript เพื่อจัดการตรรกะของเกม เช่น การสุ่มการ์ดและการจับคู่
  5. ทดสอบการทำงานของเกมในหลากหลายเบราว์เซอร์และอุปกรณ์

7. วิธีใช้งานโปรแกรม

  1. เปิดไฟล์ HTML ในเว็บเบราว์เซอร์
  2. หน้าต่างเกมจะแสดงขึ้นพร้อมการ์ดที่ถูกคว่ำหน้า
  3. คลิกการ์ดสองใบเพื่อพยายามหาคู่ที่ตรงกัน
  4. หากการ์ดทั้งสองใบตรงกัน การ์ดจะหงายอยู่ หากไม่ตรงกัน การ์ดจะคว่ำกลับไป
  5. จับคู่การ์ดทั้งหมดภายในเวลาที่กำหนด
  6. สามารถเริ่มเกมใหม่ได้โดยการคลิกปุ่ม “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>

อธิบาย:

  • การเพิ่มเสียงเอฟเฟกต์เมื่อผู้เล่นจับคู่การ์ดสำเร็จ

วิดีโออธิบายการเล่นเกม และอธิบายโค้ด

You may also like...

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *