JavaScript >> Javascript 文檔 >  >> JavaScript

使用 JavaScript 創建一個記憶遊戲(超級馬里奧 3)

我收到了一個編碼練習,可以使用任何語言製作一個記憶遊戲。我決定這樣的任務真的不需要像 React 或 jQuery 這樣的庫,所以我用純 JavaScript 完成了它。

由於記憶遊戲讓我想起了《超級馬里奧 3》中的紙牌遊戲,我決定將其作為我應用程序風格的基礎。您可以在此處查看已完成的演示。

先決條件

  • HTML 和 CSS 的基本知識。
  • JavaScript 語法和數據類型的基本知識。

我們還將使用一些 ES6 和一些基本的 DOM 概念,但即使您還不了解它們,也可以學習和跟隨。

目標

遊戲的前提是有一個由 24 張面朝下的卡片組成的網格。牌面由成對的火柴組成。點擊卡片將翻轉它們,顯示價值。當選擇兩個時,如果匹配,則兩張牌都會消失。如果不是,卡片將翻轉回面朝下。每次刷新遊戲,遊戲應該都不一樣。

點擊下面的演示,了解我們將要創建的內容。

  • 查看演示
  • 查看源代碼

規劃

在編寫任何代碼之前,我們需要了解從無到有到成品的實際步驟。一次構建整個遊戲可能看起來有點不知所措,但如果你把它分解成小任務,每個任務在完成之前似乎都是可以完成的。

  • 顯示 12 張卡片。
  • 將卡片複製成 2 組,每組 12 張。
  • 隨機顯示卡片。
  • 為選定的卡片添加選定的樣式。
  • 一次只能選擇兩張卡片。
  • 確定兩張選定的卡片是否匹配並將它們隱藏。
  • 在 2 之後重置猜測計數。
  • 為選擇添加延遲。
  • 最初顯示卡片背面並翻轉選擇
  • 遊戲結束!

現在,可能有一百萬零一種方法來創建這個遊戲。這就是我們要做的方式。

每條指令都是一個版本。版本 0.1、0.2、0.3,直到達到版本 1.0。我將在每個版本的末尾放置一個指向 JavaScript 文件的鏈接,這樣您就不會迷路。所有版本都可以在這裡找到。

設置

首先,讓我們創建設置。我們將創建 index.html ,這將只是一個普通的 HTML 框架,放入 JS 和 CSS。整個應用程序將包含在 game 中 div,所以這個文件根本不會改變。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="urf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />

    <title>Memory Game</title>

    <link rel="stylesheet" href="css/style.css" />
  </head>

  <body>
    <div id="game"></div>

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

我們將添加一些基本樣式,足以讓應用程序有意義。這裡沒有框架或不必要的代碼,甚至沒有任何預處理器。這不是一個 CSS 教程,所以你應該已經知道這裡發生了什麼,但我只是用卡片創建一個彈性網格。每張卡片為 150x150,並具有背景屬性,因為我們將盡快將卡片圖像添加為背景圖像。隨著我們在代碼中添加一些更複雜的功能,比如卡片翻轉,這個 CSS 將被調整,但現在還可以。

style.css
*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 20px 0;
  background: #6589f9;
}

.grid {
  max-width: 960px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;
}

.card {
  margin: 5px;
  background-color: #6589f9;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  height: 150px;
  width: 150px;
}

現在我們的 HTML 和 CSS 已經設置好了,我們將專注於使用 JavaScript 完成這些步驟。

顯示 12 張卡片

第一步是顯示 12 張卡片,每張卡片都有不同的值。為此,我將創建一個對像數組,並將其放入 cardsArray 多變的。每個對像都將包含一個名稱和一個圖像。

script.js
// Card data
const cardsArray = [
  {
    name: 'shell',
    img: 'img/blueshell.png',
  },
  {
    name: 'star',
    img: 'img/star.png',
  },
  {
    name: 'bobomb',
    img: 'img/bobomb.png',
  },
  {
    name: 'mario',
    img: 'img/mario.png',
  },
  {
    name: 'luigi',
    img: 'img/luigi.png',
  },
  {
    name: 'peach',
    img: 'img/peach.png',
  },
  {
    name: '1up',
    img: 'img/1up.png',
  },
  {
    name: 'mushroom',
    img: 'img/mushroom.png',
  },
  {
    name: 'thwomp',
    img: 'img/thwomp.png',
  },
  {
    name: 'bulletbill',
    img: 'img/bulletbill.png',
  },
  {
    name: 'coin',
    img: 'img/coin.png',
  },
  {
    name: 'goomba',
    img: 'img/goomba.png',
  },
]

現在我們有 12 張卡片,但是我們如何顯示它們呢?首先,我們將獲取我所說的將成為整個應用程序根的元素 - id 為 game 的 div .我們將創建一個新的 section 元素,給它 grid 類,並將其附加到 game 內的 DOM 根目錄。

script.js
// Grab the div with an id of root
const game = document.getElementById('game')

// Create a section with a class of grid
const grid = document.createElement('section')
grid.setAttribute('class', 'grid')

// Append the grid section to the game div
game.appendChild(grid)

到目前為止,所做的只是向 DOM 添加一個部分。

現在我們想讓圖像顯示在前端。我們將遍歷 cardsArray 中的每個項目 與 forEach() ,新建一個card 每個對象的 div,並設置 data-name 屬性和 background-image div 的樣式屬性。然後我們將該 div 附加到網格中。這將給我們總共 12 個 div。

script.js
// For each item in the cardsArray array...
cardsArray.forEach((item) => {
  // Create a div
  const card = document.createElement('div')

  // Apply a card class to that div
  card.classList.add('card')

  // Set the data-name attribute of the div to the cardsArray name
  card.dataset.name = item.name

  // Apply the background image of the div to the cardsArray image
  card.style.backgroundImage = `url(${item.img})`

  // Append the div to the grid section
  grid.appendChild(card)
})

好吧,第一步的工作量很大,但現在我們有了!您將有 12 個 div 附加到網格中,每個 div 看起來像這樣。

<div class="card" data-name="shell" style="background-image: url("img/blueshell.png");"></div>
  • 版本 0.1 來源

複製卡片以獲得 2 組 12 個

第二步比第一步簡單得多。現在我們要復制 cardsArray 數組,然後循環遍歷它。首先,在您的數組下方,創建一個 gameGrid 變量,並使用 concat() 複製數組 .

script.js
// Duplicate array to create a match for each card
let gameGrid = cardsArray.concat(cardsArray)

然後替換 cardsArraygameGridforEach() 循環。

script.js
// For each item in the gameGrid array...
gameGrid.forEach(item => {
  // ...

你有它。

  • 版本 0.2 源代碼

隨機顯示卡片

使用 sort() 隨機排列數組 和 Math.random() .不明白它是如何工作的?在這裡。

將此代碼放在 gameGrid 聲明之後 .

script.js
// Randomize game grid on each load
gameGrid.sort(() => 0.5 - Math.random())

一遍又一遍地刷新網格,玩得開心。

  • 版本 0.3 源代碼

為選中的卡片添加選中的樣式

現在我們應該可以選擇卡片了。我只是要添加一個簡單的 CSS 樣式,以便我們可以輕鬆地查看所選項目。

style.css
.selected {
  border: 4px solid blue;
}

我們將為整個網格添加一個事件偵聽器。任何時候點擊一個元素,selected 類將應用於它。將此代碼添加到 script.js 的底部

script.js
// Add event listener to grid
grid.addEventListener('click', function (event) {
  // The event target is our clicked item
  let clicked = event.target

  // Do not allow the grid section itself to be selected; only select divs inside the grid
  if (clicked.nodeName === 'SECTION') {
    return
  }

  // Add selected class
  clicked.classList.add('selected')
})

現在每個選定的 div 都會有一個藍色邊框,由 selected 定義 CSS。

  • 版本 0.4 源代碼

一次只允許選擇兩張卡片

我們一次只需要允許兩個選擇,因為我們正在測試兩張選擇的卡是否匹配。為了做到這一點,我們需要在某處存儲猜測和計數器。首先,我們將只存儲計數。

script.js
let count = 0

現在我們將修改事件監聽器以具有 if 計數為 2 且僅添加 selected 的語句 到兩張牌。我們將把我們的代碼添加到語句中。

script.js
// ...
if (count < 2) {
  count++
  // Add selected class
  clicked.classList.add('selected')
}

  • 版本 0.5 源代碼

判斷兩張選中的卡片是否匹配並隱藏它們

讓我們為匹配創建一些 CSS。我將給它們一個紅色邊框以區分它們,並刪除背景圖像。為什麼我要這樣做而不是僅僅從 DOM 中刪除它們?因為我們需要保留它們曾經的空間——否則,所有元素都會發生變化,這將不再是一個合適的記憶遊戲。

style.css
.match {
  border: 4px solid red;
  background-image: none !important;
}

我們剛剛有一個 count 變量之前,我們將添加一個存儲第一個和第二個猜測的地方。

script.js
let firstGuess = ''
let secondGuess = ''
let count = 0

我將創建一個匹配元素的函數。這將遍歷所有 selected 調用元素時,然後添加 match 類。

script.js
// Add match CSS
const match = () => {
  var selected = document.querySelectorAll('.selected')
  selected.forEach((card) => {
    card.classList.add('match')
  })
}

現在我必須調用 match() 在代碼中的正確時間運行。回到我們的事件監聽器,我將把第一個和第二個猜測分配給它們各自的變量。如果它們都不為空且匹配,則 match() 函數將被調用。

script.js
grid.addEventListener('click', function (event) {
  //...
  if (count < 2) {
    count++
    if (count === 1) {
      // Assign first guess
      firstGuess = clicked.dataset.name
      clicked.classList.add('selected')
    } else {
      // Assign second guess
      secondGuess = clicked.dataset.name
      clicked.classList.add('selected')
    }
    // If both guesses are not empty...
    if (firstGuess !== '' && secondGuess !== '') {
      // and the first guess matches the second match...
      if (firstGuess === secondGuess) {
        // run the match function
        match()
      }
    }
  }
})

現在,猜測不會重置,所以我們一次只能選擇或匹配一件事。但是如果我們選擇兩個我們知道匹配的元素,就會應用正確的 CSS。

現在這裡有一個問題——你能猜出它是什麼嗎?如果我兩次選擇相同的元素,它將認為它是匹配的,因為它們都有相同的 data-name 財產。我不應該能夠兩次選擇同一個元素,所以我們必須在繼續之前解決這個問題。首先,我將添加一個 previousTarget 變量。

script.js
let previousTarget = null

我將點擊的值分配給 prevousTarget 第一次點擊後。

script.js
if (firstGuess !== '' && secondGuess !== '') {
    if (firstGuess === secondGuess) {
      match();
    }
  }
  // Set previous target to clicked
  previousTarget = clicked;
}

最後,我將該檢查添加到我們的 return 櫃檯頂部的聲明。

script.js
if (clicked.nodeName === 'SECTION' || clicked === previousTarget) {
  return
}

現在第二次點擊同一個元素將被忽略。

  • 版本 0.6 源代碼

2 後重置猜測計數

現在,我們只能得到兩個猜測。如果它們是匹配的,則會顯示匹配樣式。如果不是,則將顯示常規選擇的樣式。我們希望允許多種猜測。我們必須通過在兩次猜測後重置猜測計數來做到這一點,無論它們是否匹配。

首先,我將創建一個函數來重置猜測。這會將所有計數和猜測設置回其原始值,並刪除選定的 CSS。

script.js
const resetGuesses = () => {
  firstGuess = ''
  secondGuess = ''
  count = 0

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

然後我將添加 resetGuesses() 匹配檢查器的功能,成功或失敗。

script.js
if (firstGuess === secondGuess) {
  match();
  resetGuesses();
} else {
    resetGuesses();
  }
}

現在您可以進行多個匹配。您會注意到,如果不匹配,選擇樣式會立即消失,但這很好,因為我們沒有設置任何延遲以使其顯示更長時間。

  • 版本 0.7 源代碼

為選擇添加延遲

現在,一切都立即發生。我們希望在我們做出選擇之後有一個延遲,這樣用戶就可以在卡片再次隱藏之前看到他們的選擇是什麼。現在這無關緊要,因為一切都是可見的,但我們可以在對卡片進行最終樣式修改之前處理好它。

我們將使用 setTimeout() 使延誤。首先,我將設置延遲時間,我選擇為 1200 毫秒,即 1.2 秒。

script.js
let delay = 1200

我現在要做的就是將之前的函數放入 setTimeout() , 使用 delay 變量作為超時持續的時間量。這些函數現在變成了回調,它們是用作參數的函數,它們不再需要括號。

script.js
if (firstGuess === secondGuess) {
  setTimeout(match, delay)
  setTimeout(resetGuesses, delay)
} else {
  setTimeout(resetGuesses, delay)
}

現在我們可以在它們消失之前看到選擇和匹配 1.2 秒。

  • 版本 0.8 源代碼

初始顯示卡片背面並在選擇時翻轉

當我第一次做這個的時候,我一直把卡片藏起來,這給測試帶來了不必要的困難。這一次我決定隱藏卡片作為最後一步,一旦所有功能都在那裡。

該遊戲具有我們想要的所有功能,但沒有樣式。我們需要:

  • 最初隱藏卡片
  • 選中時翻轉它們
  • 使匹配消失

所有這些都結合在一起,並且需要對代碼進行一些結構更改。我們必須添加一些更複雜的 CSS 來完成這項工作。

首先,我們的卡片現在都由一個 div 組成。為了實現翻轉,每個 div 需要由三個 div 組成,如下所示:

<div class="card">
  <div class="front"></div>
  <div class="back"></div>
</div>

我們將修改卡片創建循環以添加正面和背面元素。

script.js
gameGrid.forEach((item) => {
  // Create card element with the name dataset
  const card = document.createElement('div')
  card.classList.add('card')
  card.dataset.name = item.name

  // Create front of card
  const front = document.createElement('div')
  front.classList.add('front')

  // Create back of card, which contains
  const back = document.createElement('div')
  back.classList.add('back')
  back.style.backgroundImage = `url(${item.img})`

  // Append card to grid, and front and back to each card
  grid.appendChild(card)
  card.appendChild(front)
  card.appendChild(back)
})

我們有 clicked.dataset.name 的地方 和 clicked.classList.add ,我們必須添加 parentNode 現在,因為我們將點擊一個內部 div (frontback ) 並且數據名稱仍在外部 div (card )。

script.js
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')
}

現在我們將暫時回到 CSS。為了讓翻轉工作,我們將設置每個 card 作為相對的,backfront 作為絕對的。這三個將具有相同的高度和寬度。

style.css
.card {
  position: relative;
  transition: all 0.4s linear;
  transform-style: preserve-3d;
  margin: 5px;
}

.card,
.back,
.front {
  height: 150px;
  width: 150px;
}

.back,
.front {
  position: absolute;
  backface-visibility: hidden;
}

每張卡片的正面(如果您像一副卡片一樣思考,從技術上講是背面,但我稱它為正面是因為它是默認視圖)將是一個問號框。

style.css
.front {
  background: #fab942 url('../img/question.gif') no-repeat center center /
    contain;
}

背面將具有背景圖片的所有屬性到樣式屬性,並為翻轉動畫進行旋轉。

style.css
.back {
  transform: rotateY(180deg);
  background-color: white;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
}

選中的項目會被旋轉,匹配的項目會變成白色,這會覆蓋通過 JavaScript 應用的背景圖片。

樣式.css
.selected {
  transform: rotateY(180deg);
}

.match .front {
  background: white !important;
}

這就是所有的 CSS。

  • 版本 0.9 源代碼

遊戲結束!

你能發現當前遊戲有什麼問題嗎?現在,我看到我可以翻轉已經匹配的項目,所以我將在頂部的 return 語句中禁用它。

script.js
if (
  clicked.nodeName === 'SECTION' ||
  clicked === previousTarget ||
  clicked.parentNode.classList.contains('selected')
) {
  return
}

這是我能找到的最後一個問題!我添加了一個藍色背景以獲得更多馬里奧風格,現在完成了!您可以通過計算和顯示失敗嘗試的次數來進一步擴展這個遊戲,或者在完成時添加一條消息,但我認為這已經足夠了。

結論

在本教程中,我們學到了很多關於在創建應用程序之前對其進行規劃的知識,並將其分解為小步驟以使其易於實現。我們學習瞭如何使用純 JavaScript 在頁面中插入和操作元素,這對只知道如何使用 jQuery 之類的庫的任何人都有幫助。我們做了一些有趣的事情。享受吧!

同樣,您可以在此處查看演示,並在此處查看源代碼。歡迎提出問題、評論和批評。


Tutorial JavaScript 教程
  1. 使用 GSAP 的 Web 動畫指南(1 行 JavaScript 代碼)- 第 1 部分

  2. 使用 React Native 構建警報模式

  3. 活動預訂應用程序 node.js

  4. 使用 Firebase🔥 集成、AntDesign 和 Reach Router 創建我的第一個 React 應用程序

  5. 在javascript中將日期從“Thu Jun 09 2011 00:00:00 GMT+0530(印度標準時間)”轉換為“YYYY-MM-DD”

  6. JavaScript for 循環將對象推送到數組 |示例代碼

  7. 查找字符串中指定字符的所有索引

  1. jQuery 的 .hide() 和設置 CSS 顯示的區別:無

  2. 構建靈活的 Vue.js 組件 - 第 1 部分

  3. 使用 Hooks 模擬 React 生命週期方法

  4. 20 個有趣的 Web 開發發現 - 2013 年 5 月(第 2/2 部分)

  5. Project 35 of 100 - React 電子商務 PWA

  6. 使用 Nuxt.js 忽略您的文件

  7. 分開 -ives 和 +ives 維護它們的順序

  1. 數組 – JavaScript 系列 – 第 20 部分

  2. 使用 React 生成 10 種 QR 碼類型

  3. 按索引解構

  4. 創建了我的第一個 JavaScript 庫