เกมโยงเส้นความสัมพันธ์

 


เกมของคุณจะสามารถรองรับจำนวนไอเทมที่มากขึ้น (20 ไอเทม) และสุ่มเลือกใช้ครั้งละ 10 ไอเทมในแต่ละครั้งที่เริ่มเกม ทำให้เกมมีความท้าทายและหลากหลายมากยิ่งขึ้น คุณสามารถเพิ่มไอเทมเพิ่มเติมได้ง่ายๆ โดยการเพิ่มวัตถุในอาร์เรย์ items และเกมจะจัดการให้โดยอัตโนมัติ

วิธีใช้งานเกม

เริ่มเกม

- เมื่อโหลดหน้าเว็บ ไอเทมในแต่ละคอลัมน์จะถูกสุ่มเลือก 10 ไอเทมจากทั้งหมด 20 ไอเทม และสุ่มตำแหน่งในคอลัมน์ซ้ายและขวา
- ผู้เล่นสามารถคลิกที่ไอเทมจากคอลัมน์ซ้ายและขวาเพื่อเชื่อมโยงคู่ที่ต้องการ

การเชื่อมโยง

- เมื่อผู้เล่นเลือกไอเทมจากทั้งสองฝั่ง ระบบจะตรวจสอบว่ามีการเชื่อมโยงซ้ำหรือไม่ ถ้ามีจะลบการเชื่อมโยงเดิมก่อนเพิ่มใหม่
- เส้นโค้งเชื่อมโยงจะถูกวาดขึ้นพร้อมแอนิเมชัน

การส่งคำตอบ

- เมื่อผู้เล่นกดปุ่ม "ส่งคำตอบ", ระบบจะตรวจสอบการจับคู่ทุกคู่
- เส้นเชื่อมโยงที่ถูกต้องจะเปลี่ยนเป็นสีเขียว และเส้นที่ผิดจะเปลี่ยนเป็นสีแดง
- ป๊อบอัพจะแสดงคะแนนและรายละเอียดของการจับคู่ที่ผิด

รีเซ็ตเกม

- ผู้เล่นสามารถกดปุ่ม "รีเซ็ตเกม" เพื่อเริ่มเกมใหม่ โดยจะล้างการเชื่อมโยงทั้งหมด สุ่มตำแหน่งไอเทมใหม่ และปิดป๊อบอัพถ้ามี

1. styles.css


    /* styles.css */
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 20px;
      background-color: #eef2f3;
      min-height: 100vh;
      margin: 0;
    }
   
    h1 {
      margin-bottom: 20px;
      color: #333;
    }
   
    .container {
      display: flex;
      justify-content: space-around;
      width: 90%;
      position: relative;
      margin-bottom: 20px;
    }
   
    .column {
      display: flex;
      flex-direction: column;
      gap: 20px;
      width: 40%;
    }
   
    .item {
      background-color: #fff;
      padding: 10px;
      border: 2px solid #ddd;
      cursor: pointer;
      text-align: center;
      border-radius: 10px;
      position: relative;
      user-select: none;
      transition: transform 0.2s, box-shadow 0.2s;
    }
   
    .item img {
      width: 100px;
      height: 100px;
      object-fit: contain;
    }
   
    .item:hover {
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    }
   
    .item.selected {
      border-color: #4a90e2;
      box-shadow: 0 0 10px rgba(74, 144, 226, 0.7);
    }
   
    .svg-container {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
    }
   
    .buttons {
      display: flex;
      gap: 10px;
    }
   
    button {
      padding: 10px 20px;
      font-size: 16px;
      cursor: pointer;
      border: none;
      border-radius: 5px;
      transition: background-color 0.3s, transform 0.2s;
    }
   
    #submit-btn {
      background-color: #4CAF50;
      color: white;
    }
   
    #submit-btn:hover {
      background-color: #45a049;
      transform: scale(1.05);
    }
   
    #reset-btn {
      background-color: #f44336;
      color: white;
    }
   
    #reset-btn:hover {
      background-color: #da190b;
      transform: scale(1.05);
    }
   
    /* Styles for popup */
    .popup {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.5); /* semi-transparent background */
      display: none; /* hidden by default */
      justify-content: center;
      align-items: center;
      z-index: 1000;
    }
   
    .popup-container {
      background-color: #fff;
      padding: 20px 30px;
      border-radius: 8px;
      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
      text-align: center;
      max-width: 80%;
      max-height: 80%;
      overflow-y: auto;
    }
   
    .popup-container h3 {
      color: #e74c3c;
      margin-bottom: 15px;
    }
   
    .popup-container p {
      margin: 10px 0;
    }
   
    .popup-container button {
      margin-top: 15px;
      padding: 8px 16px;
      background-color: #3498db;
      color: #fff;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
   
    .popup-container button:hover {
      background-color: #2980b9;
    }
   
    .popup-container .incorrect-match {
      text-align: left;
      margin-top: 10px;
    }
   
    /* New styles for score message */
    .score-message {
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 15px;
      color: #333;
    }
   
 


2. script.js

/* script.js */
    // ข้อมูลไอเทม (20 ไอเทม)
    const items = [
      { id: 1, image: 'images/apple.png', imageTh: 'images/apple_th.png', description: 'Apple is a common fruit.' },
      { id: 2, image: 'images/banana.png', imageTh: 'images/banana_th.png', description: 'Banana is rich in potassium.' },
      { id: 3, image: 'images/orange.png', imageTh: 'images/orange_th.png', description: 'Orange is a citrus fruit.' },
      { id: 4, image: 'images/grape.png', imageTh: 'images/grape_th.png', description: 'Grape can be used to make wine.' },
      { id: 5, image: 'images/mango.png', imageTh: 'images/mango_th.png', description: 'Mango is sweet and juicy.' },
      { id: 6, image: 'images/pineapple.png', imageTh: 'images/pineapple_th.png', description: 'Pineapple has a spiky skin.' },
      { id: 7, image: 'images/strawberry.png', imageTh: 'images/strawberry_th.png', description: 'Strawberries are red and sweet.' },
      { id: 8, image: 'images/watermelon.png', imageTh: 'images/watermelon_th.png', description: 'Watermelon is refreshing in summer.' },
      { id: 9, image: 'images/kiwi.png', imageTh: 'images/kiwi_th.png', description: 'Kiwi has a fuzzy skin.' },
      { id: 10, image: 'images/pear.png', imageTh: 'images/pear_th.png', description: 'Pear is a juicy fruit.' },
      { id: 11, image: 'images/cherry.png', imageTh: 'images/cherry_th.png', description: 'Cherries are small and red.' },
      { id: 12, image: 'images/peach.png', imageTh: 'images/peach_th.png', description: 'Peaches have soft skin and sweet flesh.' },
      { id: 13, image: 'images/blueberry.png', imageTh: 'images/blueberry_th.png', description: 'Blueberries are blue and rich in antioxidants.' },
      { id: 14, image: 'images/coconut.png', imageTh: 'images/coconut_th.png', description: 'Coconuts have a hard shell and white flesh.' },
      { id: 15, image: 'images/lemon.png', imageTh: 'images/lemon_th.png', description: 'Lemons are sour citrus fruits.' },
      { id: 16, image: 'images/lime.png', imageTh: 'images/lime_th.png', description: 'Limes are small green citrus fruits.' },
      { id: 17, image: 'images/raspberry.png', imageTh: 'images/raspberry_th.png', description: 'Raspberries are red and tart.' },
      { id: 18, image: 'images/blackberry.png', imageTh: 'images/blackberry_th.png', description: 'Blackberries are dark and juicy.' },
      { id: 19, image: 'images/cantaloupe.png', imageTh: 'images/cantaloupe_th.png', description: 'Cantaloupes are orange melon fruits.' },
      { id: 20, image: 'images/nectarine.png', imageTh: 'images/nectarine_th.png', description: 'Nectarines are smooth-skinned peaches.' }
      // คุณสามารถเพิ่มไอเทมเพิ่มเติมได้ที่นี่
    ];

    // ฟังก์ชันสำหรับสุ่มตำแหน่งของไอเทม
    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]];
      }
      return array;
    }

    // ฟังก์ชันสำหรับรีเซ็ตเกม
    function resetGame() {
      // ล้างการเชื่อมโยงทั้งหมด
      connections.forEach(conn => {
        svgContainer.removeChild(conn.path);
      });
      connections = [];
     
      // ล้าง selection
      selectedLeft = null;
      selectedRight = null;
     
      // ลบคลาส selected จากไอเทมทั้งหมด
      document.querySelectorAll('.item').forEach(item => {
        item.classList.remove('selected');
      });
     
      // สุ่มตำแหน่งใหม่
      shuffleItems();
     
      // รีเซ็ตสีเส้นถ้ามีการตรวจคำตอบก่อนหน้านี้
      svgContainer.innerHTML = '';
     
      // ปิด popup ถ้ามี
      closePopup();
    }

    // สุ่มตำแหน่งของไอเทมในแต่ละคอลัมน์
    function shuffleItems() {
      const leftColumn = document.getElementById('left-column');
      const rightColumn = document.getElementById('right-column');

      // สุ่มเลือก 10 ไอเทมจากทั้งหมด 20 ไอเทม
      const shuffledItems = shuffle([...items]);
      const selectedItems = shuffledItems.slice(0, 10);

      // สุ่มเรียงลำดับสำหรับคอลัมน์ซ้ายและขวา
      const leftItems = shuffle([...selectedItems]);
      const rightItems = shuffle([...selectedItems]);

      // ล้างคอลัมน์ก่อนและเพิ่มไอเทมที่สุ่มแล้ว
      leftColumn.innerHTML = '';
      rightColumn.innerHTML = '';

      leftItems.forEach(item => {
        const itemDiv = document.createElement('div');
        itemDiv.classList.add('item');
        itemDiv.setAttribute('data-match', item.id);
        itemDiv.innerHTML = `<img src="${item.image}" alt="${getImageName(item.image)}">`;
        leftColumn.appendChild(itemDiv);
      });

      rightItems.forEach(item => {
        const itemDiv = document.createElement('div');
        itemDiv.classList.add('item');
        itemDiv.setAttribute('data-match', item.id);
        itemDiv.innerHTML = `<img src="${item.imageTh}" alt="${getImageName(item.imageTh)}">`;
        rightColumn.appendChild(itemDiv);
      });
    }

    // ฟังก์ชันสำหรับดึงชื่อไฟล์จาก URL รูปภาพ
    function getImageName(imagePath) {
      const parts = imagePath.split('/');
      const fileName = parts[parts.length - 1];
      return fileName.split('.')[0];
    }

    // การตั้งค่า SVG
    const svgContainer = document.querySelector('.svg-container');

    // การจัดเก็บการเชื่อมโยง
    let connections = []; // {left: Element, right: Element, path: SVGPathElement, correct: boolean}

    // การเลือกไอเทม
    let selectedLeft = null;
    let selectedRight = null;

    // สุ่มตำแหน่งของไอเทมเมื่อโหลดหน้าเว็บ
    document.addEventListener('DOMContentLoaded', () => {
      shuffleItems();
      addEventListeners();
    });

    // เพิ่ม event listeners ให้กับไอเทม
    function addEventListeners() {
      document.querySelectorAll('.left .item').forEach(item => {
        item.addEventListener('click', () => {
          if (selectedLeft === item) {
            item.classList.remove('selected');
            selectedLeft = null;
          } else {
            if (selectedLeft) selectedLeft.classList.remove('selected');
            selectedLeft = item;
            item.classList.add('selected');
          }
          attemptConnection();
        });
      });

      document.querySelectorAll('.right .item').forEach(item => {
        item.addEventListener('click', () => {
          if (selectedRight === item) {
            item.classList.remove('selected');
            selectedRight = null;
          } else {
            if (selectedRight) selectedRight.classList.remove('selected');
            selectedRight = item;
            item.classList.add('selected');
          }
          attemptConnection();
        });
      });
    }

    // ฟังก์ชันสำหรับพยายามเชื่อมโยงเมื่อมีการเลือกทั้งสองฝั่ง
    function attemptConnection() {
      if (selectedLeft && selectedRight) {
        // ตรวจสอบว่ามีการเชื่อมโยงซ้ำหรือไม่
        const existing = connections.find(conn => conn.left === selectedLeft || conn.right === selectedRight);
        if (existing) {
          // ถ้ามีการเชื่อมโยงอยู่แล้ว ให้ลบการเชื่อมโยงเดิม
          svgContainer.removeChild(existing.path);
          connections = connections.filter(conn => conn !== existing);
        }

        // วาดเส้นเชื่อมโยงใหม่
        const path = drawCurve(selectedLeft, selectedRight);
        connections.push({ left: selectedLeft, right: selectedRight, path, correct: null });

        // เพิ่มแอนิเมชันให้กับเส้น
        path.style.strokeDasharray = path.getTotalLength();
        path.style.strokeDashoffset = path.getTotalLength();
        path.getBoundingClientRect(); // Trigger reflow
        path.style.transition = 'stroke-dashoffset 0.5s ease';
        path.style.strokeDashoffset = '0';

        // ยกเลิกการเลือก
        selectedLeft.classList.remove('selected');
        selectedRight.classList.remove('selected');
        selectedLeft = null;
        selectedRight = null;
      }
    }

    // ฟังก์ชันสำหรับวาดเส้นโค้งเชื่อมโยงระหว่างไอเทมสองอัน
    function drawCurve(leftItem, rightItem, color = 'black') {
      const leftRect = leftItem.getBoundingClientRect();
      const rightRect = rightItem.getBoundingClientRect();
      const svgRect = svgContainer.getBoundingClientRect();

      // คำนวณตำแหน่งเริ่มต้นและปลายของเส้น
      const startX = leftRect.right - svgRect.left;
      const startY = leftRect.top + leftRect.height / 2 - svgRect.top;
      const endX = rightRect.left - svgRect.left;
      const endY = rightRect.top + rightRect.height / 2 - svgRect.top;

      // คำนวณจุดควบคุมสำหรับเส้นโค้ง
      const controlX1 = startX + (endX - startX) / 2;
      const controlY1 = startY;
      const controlX2 = startX + (endX - startX) / 2;
      const controlY2 = endY;

      // สร้างเส้น Path ด้วย Cubic Bezier Curve
      const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      const d = `M ${startX} ${startY} C ${controlX1} ${controlY1}, ${controlX2} ${controlY2}, ${endX} ${endY}`;
      path.setAttribute('d', d);
      path.setAttribute('stroke', color);
      path.setAttribute('stroke-width', 3);
      path.setAttribute('fill', 'none');
      svgContainer.appendChild(path);

      return path;
    }

    // ฟังก์ชันป๊อบอัพ
    function showWrongMatchPopup(score, wrongMatches) {
      const popup = document.getElementById('wrongPopup');
      const scoreMessage = document.getElementById('scoreMessage');
      const messageList = document.getElementById('wrongMatchList');
     
      // ตั้งค่า message สำหรับคะแนน
      scoreMessage.innerText = `คุณทำได้ถูกต้อง ${score} จาก ${connections.length} คู่`;

      // ล้างข้อความเก่าจากการจับคู่ผิด
      messageList.innerHTML = '';

      // เพิ่มแต่ละการจับคู่ผิดลงใน popup
      wrongMatches.forEach(match => {
        // ดึงข้อมูลไอเทมที่ถูกต้องจากอาร์เรย์ items โดยใช้ data-match จากพาเรนท์ของ img
        const correctMatchId = match.correctMatch.parentElement.dataset.match;
        const correctItem = items.find(item => item.id == correctMatchId);
        const p = document.createElement('p');
        p.classList.add('incorrect-match');
        p.innerHTML = `คุณจับคู่ผิด <strong>${match.left.alt}</strong> กับ <strong style="color:red">${match.right.alt}</strong>
<br>คำตอบที่ถูกต้องคือ <strong style="color:green">${match.correctMatch.alt}</strong>
<br>คำอธิบาย: ${correctItem.description}`;
        messageList.appendChild(p);
      });

      popup.style.display = 'flex'; // แสดง popup
    }

    function closePopup() {
      const popup = document.getElementById('wrongPopup');
      popup.style.display = 'none';
    }

    // ปุ่มส่งคำตอบ
    const submitBtn = document.getElementById('submit-btn');
    submitBtn.addEventListener('click', () => {
      const totalPairs = document.querySelectorAll('.left .item').length;
     
      if (connections.length !== totalPairs) {
        alert(`กรุณาเชื่อมโยงให้ครบทุกคู่ (เชื่อม ${connections.length} จาก ${totalPairs} คู่)`);
        return;
      }

      // ตรวจสอบคำตอบและเก็บผิดไว้
      let wrongMatches = [];
      connections.forEach(conn => {
        if (conn.left.dataset.match === conn.right.dataset.match) {
          conn.correct = true;
          conn.path.setAttribute('stroke', 'green');
        } else {
          conn.correct = false;
          conn.path.setAttribute('stroke', 'red');
          // ค้นหาคำที่ถูกต้องสำหรับการจับคู่ที่ผิด
          const correctRight = document.querySelector(`.right .item[data-match="${conn.left.dataset.match}"]`);
          wrongMatches.push({
            left: conn.left.querySelector('img'),
            right: conn.right.querySelector('img'),
            correctMatch: correctRight.querySelector('img')
          });
        }
      });

      // คำนวณคะแนน
      const correctCount = connections.filter(conn => conn.correct).length;

      // แสดง popup สำหรับคำตอบที่ผิด พร้อมคะแนน
      showWrongMatchPopup(correctCount, wrongMatches);
     
      // ปิดการเลือกไอเทม
      selectedLeft = null;
      selectedRight = null;
    });

    // ปุ่มรีเซ็ตเกม
    const resetBtn = document.getElementById('reset-btn');
    resetBtn.addEventListener('click', () => {
      resetGame();
    });

    // ฟังก์ชันสำหรับปรับขนาด SVG เมื่อหน้าต่างเปลี่ยนขนาด
    window.addEventListener('resize', () => {
      svgContainer.innerHTML = ''; // ล้างเส้นทั้งหมด
      connections.forEach(conn => {
        conn.path = drawCurve(conn.left, conn.right, conn.correct === true ? 'green' : (conn.correct === false ? 'red' : 'black'));
        if (conn.correct !== null) {
          // เพิ่มแอนิเมชันให้กับเส้นหลังจากรีวาด
          conn.path.style.strokeDasharray = conn.path.getTotalLength();
          conn.path.style.strokeDashoffset = conn.path.getTotalLength();
          conn.path.getBoundingClientRect(); // Trigger reflow
          conn.path.style.transition = 'stroke-dashoffset 0.5s ease';
          conn.path.style.strokeDashoffset = '0';
        }
      });
    });
 


3. index.html

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>เกมโยงเส้นความสัมพันธ์</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <h1>เกมโยงเส้นความสัมพันธ์</h1>
  <div class="container">
    <div class="column left" id="left-column">
      <!-- ไอเทมจะถูกสร้างโดย JavaScript -->
    </div>
    <div class="column right" id="right-column">
      <!-- ไอเทมจะถูกสร้างโดย JavaScript -->
    </div>
  </div>
  <svg class="svg-container"></svg>
  <div class="buttons">
    <button id="submit-btn">ส่งคำตอบ</button>
    <button id="reset-btn">รีเซ็ตเกม</button>
  </div>
 
  <!-- Popup for wrong matches -->
  <div class="popup" id="wrongPopup">
    <div class="popup-container">
      <h3>ผลการจับคู่</h3>
      <p class="score-message" id="scoreMessage"></p>
      <div id="wrongMatchList">
        <!-- รายการจับคู่ผิดจะแสดงที่นี่ -->
      </div>
      <button onclick="closePopup()">ตกลง</button>
    </div>
  </div>

  <script src="script.js"></script>
</body>
</html>




แสดงความคิดเห็น (0)
ใหม่กว่า เก่ากว่า