JavaScript 練習 - 牌卡記憶遊戲

Demo

JavaScript 練習 - 牌卡記憶遊戲 - Demo

Introduction 介紹

這是一個蠻常見的 JavaScript 基礎題目練習,這個項目將達到以下目標

  • 把陣列的十二張圖片顯示在畫面
  • 複製圖片為偶數
  • 隨機顯示圖片排列順序
  • 呈現圖片的選擇狀態
  • 一次只能選取兩張
  • 如果圖片配對成功就隱藏
  • 如果猜錯或猜對都可以重新開始選擇
  • 牌卡可以翻面

功能

這個練習的 HTML 原始結構較為簡單,因為希望牌卡順序可以隨機排列,所以先不設定圖片位置。

1
2
3
4
<div id="game">

</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.grid {
max-width: 900px;
height: 500px;
margin: 80px auto;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
}
.card {
margin: 5px;
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
height: 150px;
width: 150px;
}

建立陣列儲存牌卡

首先要建立儲存牌卡的地方,每一張卡都有不同的值,所以建立一個陣列,將它儲存在 cardsArray 變數中,每個物件都包含一個名稱與圖片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const cardsArray = [
{
name: "spade",
img: "img/spade.png"
},
{
name: "two",
img: "img/two.png"
},
{
name: "three",
img: "img/three.png"
},
{
name: "four",
img: "img/four.png"
},
{
name: "five",
img: "img/five.jpg"
},
{
name: "ten",
img: "img/ten.png"
},
{
name: "knight",
img: "img/knight.png"
},
{
name: "queen",
img: "img/queen.png"
},
{
name: "king",
img: "img/king.png"
},
{
name: "joker",
img: "img/joker.png"
},
{
name: "heart",
img: "img/heart3.png"
}
];

使用迴圈獲取陣列,並在畫面顯示牌卡

接著要把牌卡顯示在畫面上,需先獲取放置牌卡的位置,先以 Document.createElement()方法建立 section 元素,再以Element.setAttribute()方法設定元素的 class 屬性名稱,最後將新增元素附加到 DOM 結構中。顯示圖片的重點是使用迴圈獲取陣列裡儲存的資料、為每一張卡片建立一個類別、名稱、屬性,最後再附加到剛建立的放圖片的位置

1
2
3
4
5
6
7
8
9
10
11
12
const game = document.getElementById("game");
const grid = document.createElement("section");
grid.setAttribute("class", "grid");
game.appendChild(grid);

cardsArray.forEach(item => {
const card = document.createElement("div");
card.classList.add("card");
card.dataset.name = item.name;
card.style.backgroundImage = `url(${item.img})`;
grid.appendChild(card);
});

使用 concat() 讓每張圖片成對

接下來要使每種圖片都成偶數,這裡使用 concat() 方法,這個方法可以合併陣列,但不會改變現有的陣列,而是回傳一個新的陣列。
舉例來說:

1
2
3
4
const array1 = ["a", "b", "c"];
const array2 = ["d", "e", "f"];
console.log(array1.concat(array2));
// Array ["a", "b", "c", "d", "e", "f"]

讓圖片隨機排列

因此建立一個新的變數,並且以剛剛儲存陣列的變數為傳入方法的參數,再以sortMath.random()隨機排列陣列。sort() 方法會原地對一個陣列的所有元素進行排序,並回傳此陣列。Math.random 回傳 0~1 之間的隨機數字。最後修改剛剛迴圈要遍歷的對象。

1
2
3
4
lvar gameGrid = cardsArray.concat(cardsArray).sort(function () {
return 0.5 - Math.random();
});
gameGrid.forEach(item => {

設定翻卡的監聽事件

設定監聽事件,如果點擊的確實是卡片,就會翻面

1
2
3
.selected {
transform: rotateY(180deg);
}
1
2
3
4
5
6
7
8
9
grid.addEventListener("click", function(event) {
let clicked = event.target;

if (clicked.nodeName === "SECTION") {
return;
}

clicked.classList.add("selected");
});

限定翻面的卡片數

卡片一次只能點選 2 張,因此設定一個計數器

1
2
3
4
if (count < 2) {
count++;
clicked.classList.add("selected");
}

卡片配對條件

設定卡片配對條件,如果配對成功,就會從畫面隱藏。新增一個函數遍歷所有被點擊的卡片,如果配對成功就為卡片新增 class 屬性 match。條件是第一個點擊的卡片的名稱屬性要等於第二個點擊卡片的名稱屬性,如果相同,就呼叫 match()函數。並且不能同一張卡片的位置點擊 2 次。

1
2
3
.match {
background: none;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let firstGuess = "";
let secondGuess = "";
let previousTarget = null;

const match = () => {
var selected = document.querySelectorAll(".selected");
selected.forEach(card => {
card.classList.add("match");
});
};

grid.addEventListener("click", function(event) {
if (clicked.nodeName === "SECTION" || clicked === previousTarget) {
return;
}
if (count < 2) {
count++;
if (count === 1) {
firstGuess = clicked.dataset.name;
clicked.classList.add("selected");
} else {
secondGuess = clicked.dataset.name;
clicked.classList.add("selected");
}

if (firstGuess !== "" && secondGuess !== "") {
if (firstGuess === secondGuess) {
match();
}
}
}
previousTarget = clicked;
});

設定一個可以重新開始遊戲的函數

點擊卡片 2 次後,猜錯或猜對都可以重新開始選擇。設定一個可以讓我們再次遊戲的函數。也就是以迴圈檢查所有已被選擇的卡片,並且移除他們的目前的選擇樣式。在條件句裡面呼叫這個函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const resetGuesses = () => {
firstGuess = "";
secondGuess = "";
count = 0;

var selected = document.querySelectorAll(".selected");
selected.forEach(card => {
card.classList.remove("selected");
});
};

grid.addEventListener("click", function(event) {
//...
if (firstGuess === secondGuess) {
match();
resetGuesses();
} else {
resetGuesses();
}
}
});

將點擊狀態添加在父節點

目前卡片都還只有一面,我們現在新增了一個 frontback 標籤,但是卡片的名稱屬性還是屬於它們的父層,所以要修改一下使點擊時的選擇狀態仍是添加在被點擊者的父層。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
gameGrid.forEach(item => {
const card = document.createElement("div");
card.classList.add("card");
card.dataset.name = item.name;

// 卡片正面
const front = document.createElement("div");
front.classList.add("front");

// 卡片背面
const back = document.createElement("div");
back.classList.add("back");
back.style.backgroundImage = `url(${item.img})`;

grid.appendChild(card);
card.appendChild(front);
card.appendChild(back);
});

grid.addEventListener("click", function(event) {
if (count === 1) {
firstGuess = clicked.parentNode.dataset.name;
console.log(firstGuess);
clicked.parentNode.classList.add("selected");
} else {
secondGuess = clicked.parentNode.dataset.name;
console.log(secondGuess);
clicked.parentNode.classList.add("selected");
}
//...
});

設定隱藏背面

最後設定翻面的時候,另一面不可見

1
2
3
4
5
.back,
.front {
position: absolute;
backface-visibility: hidden;
}

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2020 CYC'S BLOG All Rights Reserved.

UV : | PV :