เกมของคุณจะสามารถรองรับจำนวนไอเทมที่มากขึ้น (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>