Trong bài viết này, bạn sẽ tạo lại trò chơi đoán từ Wordle. Bài viết này bao gồm logic trò chơi cốt lõi nhưng không triển khai chia sẻ kết quả của bạn. Bài viết cũng không đề cập đến chức năng tạo số liệu thống kê trò chơi.
Hướng dẫn này dành cho các nhà phát triển front-end mới bắt đầu muốn xây dựng một dự án JavaScript tiêu chuẩn, thú vị.
Bạn có thể xem bản demo của dự án đã hoàn thành tại đây.
điều kiện tiên quyết
Hướng dẫn này giả định một sự hiểu biết cơ bản về:
Cách xây dựng bản sao Wordle
Đây là các bước bạn sẽ thực hiện để tạo bản sao Wordle:
- Thiết lập dự án
- Tạo bảng trò chơi
- Tạo bàn phím trên màn hình
- Chấp nhận đầu vào của người dùng
- Thêm thông báo
- Làm cho bàn phím trên màn hình tạo đầu vào
- Thêm hoạt ảnh
Thiết lập dự án
Trước khi xây dựng trò chơi, bạn cần chuẩn bị sẵn một số thành phần. Trước tiên, bạn cần tạo một thư mục cho tất cả mã nguồn của bản sao của chúng tôi. Gọi thư mục này là build.
Sau khi bạn hoàn thành việc đó, hãy thiết lập máy chủ phát triển của mình.
máy chủ trực tiếp
Bạn sẽ sử dụng một máy chủ phát triển được gọi là máy chủ trực tiếp. Bước này là tùy chọn, nhưng giúp bạn không phải tải lại trang sau mỗi lần thay đổi mã nguồn.
Cài đặt máy chủ trực tiếp bằng cách nhập nội dung sau vào thiết bị đầu cuối của bạn:
npm install live-server
Thiết lập HTML
Bên trong bản dựng, tạo một tệp HTML và đặt tên là index.html. Đặt đoạn mã sau vào nó:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wordle</title>
</head>
<body>
<h1> Wordle Clone </h1>
<div id="game-board">
</div>
</body>
</html>
Mã HTML tạo tiêu đề cho trò chơi của chúng ta và tạo vùng chứa cho bảng trò chơi.
Bạn sẽ sử dụng thư viện JavaScript có tên là Toastr cho các thông báo trong trò chơi và thư viện CSS có tên là Animate.css cho hoạt ảnh của bảng.
Để đưa chúng vào dự án của bạn, hãy thêm các liên kết sau vào đầu tệp index.html của bạn.
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet"/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
Các liên kết đó sẽ tìm nạp CSS cho cả Animate.css và Toastr. Đặt đoạn mã sau vào index.html, ngay trước thẻ đóng body:
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
Mã đó sẽ tìm nạp JavaScript cho Toastr và jQuery (vì Toastr phụ thuộc vào nó).
Cài đặt JavaScript
JavaScript của bạn sẽ nằm trong một tệp có tên script.js. Tạo script.js và đặt nó bên trong build.
Đặt mã này ở đầu script.js:
import { WORDS } from "./words.js";
const NUMBER_OF_GUESSES = 6;
let guessesRemaining = NUMBER_OF_GUESSES;
let currentGuess = [];
let nextLetter = 0;
let rightGuessString = WORDS[Math.floor(Math.random() * WORDS.length)]
console.log(rightGuessString)
Đoạn mã này khởi tạo các biến toàn cục mà chúng ta sẽ sử dụng cho trò chơi của mình và chọn một từ ngẫu nhiên từ mảng WORDS
như dự đoán đúng cho vòng này. Chúng tôi cũng ghi kết quả đoán đúng vào bảng điều khiển để gỡ lỗi mã nếu cần.
Danh sách các từ được phép sử dụng sẽ được mã hóa cứng và lưu trữ dưới dạng một mảng trong tệp word.js. Tạo word.js, bên trong bản dựng và sao chép JavaScript từ liên kết này vào đó.
Words.js sẽ trông như thế này:

Thiết lập CSS
Đặt tên cho tệp CSS của bạn là style.css. Style.css cũng nên được đặt trong build.
h1 {
text-align: center;
}
Thiết lập CSS duy nhất mà chúng tôi cần là một chút mã để căn giữa văn bản của tiêu đề
Để tất cả chúng cùng nhau
Cuối cùng, liên kết script.js dưới dạng một mô-đun trong index.html của bạn, sau đó liên kết style.css.
Tại thời điểm này, index.html của bạn sẽ trông như thế này:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wordle</title>
<link rel="stylesheet" href="https://www.freecodecamp.org/news/build-a-wordle-clone-in-javascript/style.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet"/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
</head>
<body>
<h1> Wordle Clone </h1>
<div id="game-board">
</div>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="script.js" type="module"></script>
</body>
</html>
và cấu trúc tệp của bạn sẽ trông như thế này:

Bắt đầu máy chủ trực tiếp bằng cách nhập mã này vào bảng điều khiển của bạn:
live-server build
Đó là nó để thiết lập.
Cách tạo bảng trò chơi
Bạn sẽ tạo bảng trò chơi bằng cách viết một hàm JavaScript. Hãy gọi hàm initBoard
. Thêm mã này vào tệp script.js của bạn:
function initBoard() {
let board = document.getElementById("game-board");
for (let i = 0; i < NUMBER_OF_GUESSES; i++) {
let row = document.createElement("div")
row.className = "letter-row"
for (let j = 0; j < 5; j++) {
let box = document.createElement("div")
box.className = "letter-box"
row.appendChild(box)
}
board.appendChild(row)
}
}
initBoard()
Vậy mã này làm gì? initBoard
tạo một hàng cho mỗi dự đoán mà chúng tôi cung cấp cho người dùng và tạo 5 hộp cho mỗi hàng. Có một hộp cho mỗi chữ cái đoán và chức năng làm cho tất cả chúng trở thành con của hàng.
initBoard
sau đó thêm từng hàng vào vùng chứa bảng. Mỗi hàng được đưa ra lớp letter-row
và mỗi hộp được chỉ định letter-box
.
Tiếp theo, bạn sẽ tạo kiểu cho bảng bằng một số CSS. Đặt đoạn mã sau vào tệp style.css của bạn:
#game-board {
display: flex;
align-items: center;
flex-direction: column;
}
.letter-box {
border: 2px solid gray;
border-radius: 3px;
margin: 2px;
font-size: 2.5rem;
font-weight: 700;
height: 3rem;
width: 3rem;
display: flex;
justify-content: center;
align-items: center;
text-transform: uppercase;
}
.filled-box {
border: 2px solid black;
}
.letter-row {
display: flex;
}
CSS này thực hiện một số điều:
- căn giữa các hàng của bảng theo chiều ngang và chiều dọc
- đặt chiều cao, chiều rộng và đường viền cho mỗi hộp trên bảng
- tạo ra một cái nhìn khác biệt cho một hộp chứa đầy một chữ cái
Tại thời điểm này, khi bạn tải index.html trong trình duyệt của mình, nó sẽ giống như sau:

Cách tạo Bàn phím ảo
Cách đơn giản nhất để tạo bàn phím là với HTML. Thêm mã này vào index.html của bạn, sau div bảng trò chơi:
<div id="keyboard-cont">
<div class="first-row">
<button class="keyboard-button">q</button>
<button class="keyboard-button">w</button>
<button class="keyboard-button">e</button>
<button class="keyboard-button">r</button>
<button class="keyboard-button">t</button>
<button class="keyboard-button">y</button>
<button class="keyboard-button">u</button>
<button class="keyboard-button">i</button>
<button class="keyboard-button">o</button>
<button class="keyboard-button">p</button>
</div>
<div class="second-row">
<button class="keyboard-button">a</button>
<button class="keyboard-button">s</button>
<button class="keyboard-button">d</button>
<button class="keyboard-button">f</button>
<button class="keyboard-button">g</button>
<button class="keyboard-button">h</button>
<button class="keyboard-button">j</button>
<button class="keyboard-button">k</button>
<button class="keyboard-button">l</button>
</div>
<div class="third-row">
<button class="keyboard-button">Del</button>
<button class="keyboard-button">z</button>
<button class="keyboard-button">x</button>
<button class="keyboard-button">c</button>
<button class="keyboard-button">v</button>
<button class="keyboard-button">b</button>
<button class="keyboard-button">n</button>
<button class="keyboard-button">m</button>
<button class="keyboard-button">Enter</button>
</div>
</div>
Bây giờ, tạo kiểu cho đánh dấu bằng cách thêm CSS này vào cuối style.css:
#keyboard-cont {
margin: 1rem 0;
display: flex;
flex-direction: column;
align-items: center;
}
#keyboard-cont div {
display: flex;
}
.second-row {
margin: 0.5rem 0;
}
.keyboard-button {
font-size: 1rem;
font-weight: 700;
padding: 0.5rem;
margin: 0 2px;
cursor: pointer;
text-transform: uppercase;
}
Đây là giao diện của index.html của bạn trong trình duyệt bây giờ:

Cách chấp nhận đầu vào của người dùng
Chiến lược cho đầu vào của người dùng rất đơn giản: khi người chơi nhấn một phím trên bàn phím, chúng tôi muốn đặt phím đó vào đúng vị trí trên bảng. Bạn sẽ thực hiện điều này bằng cách lắng nghe sự kiện keyup.
Khi người chơi nhấn một phím, bạn muốn biết phím đó là gì. Nếu phím là một chữ cái, bạn muốn đặt nó vào đúng vị trí trên bảng.
Bạn tìm ra vị trí đúng trên bảng bằng cách kiểm tra số lần đoán mà người chơi còn lại và số lượng chữ cái mà người chơi đã nhập cho đến nay.
Nếu phím được nhấn là Enter hoặc Backspace, bạn kiểm tra dự đoán hoặc xóa một chữ cái khỏi dự đoán hiện tại. Bất kỳ khóa nào khác chúng tôi bỏ qua.
Thêm mã này vào script.js:
document.addEventListener("keyup", (e) => {
if (guessesRemaining === 0) {
return
}
let pressedKey = String(e.key)
if (pressedKey === "Backspace" && nextLetter !== 0) {
deleteLetter()
return
}
if (pressedKey === "Enter") {
checkGuess()
return
}
let found = pressedKey.match(/[a-z]/gi)
if (!found || found.length > 1) {
return
} else {
insertLetter(pressedKey)
}
})
Đoạn mã này sử dụng một biểu thức chính quy để kiểm tra xem phím chúng ta đã nhấn có phải là một phím chữ cái đại diện cho một chữ cái hay không. Nếu tên của khóa không có bất kỳ chữ cái nào (đó là một số) hoặc có nhiều chữ cái (Shift, Tab), chúng tôi sẽ bỏ qua sự kiện. Nếu không, chúng tôi chèn chữ cái vào bảng.
chènThư
Hãy xác định insertLetter
chức năng. Nó trông như thế này:
function insertLetter (pressedKey) {
if (nextLetter === 5) {
return
}
pressedKey = pressedKey.toLowerCase()
let row = document.getElementsByClassName("letter-row")[6 - guessesRemaining]
let box = row.children[nextLetter]
box.textContent = pressedKey
box.classList.add("filled-box")
currentGuess.push(pressedKey)
nextLetter += 1
}
insertLetter
kiểm tra xem còn chỗ trống trong phần đoán cho chữ cái này không, tìm hàng thích hợp và đặt chữ cái vào hộp.
xóaThư
deleteLetter
trông như thế này:
function deleteLetter () {
let row = document.getElementsByClassName("letter-row")[6 - guessesRemaining]
let box = row.children[nextLetter - 1]
box.textContent = ""
box.classList.remove("filled-box")
currentGuess.pop()
nextLetter -= 1
}
deleteLetter
lấy đúng hàng, tìm hộp cuối cùng và làm trống nó, sau đó đặt lại bộ đếm nextLetter.
kiểm tra đoán
Các checkGuess
chức năng trông như thế này:
function checkGuess () {
let row = document.getElementsByClassName("letter-row")[6 - guessesRemaining]
let guessString = ''
let rightGuess = Array.from(rightGuessString)
for (const val of currentGuess) {
guessString += val
}
if (guessString.length != 5) {
alert("Not enough letters!")
return
}
if (!WORDS.includes(guessString)) {
alert("Word not in list!")
return
}
for (let i = 0; i < 5; i++) {
let letterColor=""
let box = row.children[i]
let letter = currentGuess[i]
let letterPosition = rightGuess.indexOf(currentGuess[i])
// is letter in the correct guess
if (letterPosition === -1) {
letterColor="grey"
} else {
// now, letter is definitely in word
// if letter index and right guess index are the same
// letter is in the right position
if (currentGuess[i] === rightGuess[i]) {
// shade green
letterColor="green"
} else {
// shade box yellow
letterColor="yellow"
}
rightGuess[letterPosition] = "#"
}
let delay = 250 * i
setTimeout(()=> {
//shade box
box.style.backgroundColor = letterColor
shadeKeyBoard(letter, letterColor)
}, delay)
}
if (guessString === rightGuessString) {
alert("You guessed right! Game over!")
guessesRemaining = 0
return
} else {
guessesRemaining -= 1;
currentGuess = [];
nextLetter = 0;
if (guessesRemaining === 0) {
alert("You've run out of guesses! Game over!")
alert(`The right word was: "${rightGuessString}"`)
}
}
}
checkGuess
là khá dài, vì vậy hãy phá vỡ nó. Nó làm một vài điều:
- Đảm bảo đoán là 5 chữ cái
- Đảm bảo dự đoán là một danh sách hợp lệ
- Kiểm tra từng chữ cái của từ và tô màu chúng
- Cho người dùng biết về sự kết thúc của trò chơi
checkGuess
sử dụng một thuật toán đơn giản để quyết định màu nào sẽ tô bóng cho mỗi chữ cái:
- Kiểm tra xem chữ cái có trong từ đúng không
- Nếu chữ cái không có trong từ, chữ cái sẽ có màu xám
- Nếu chữ cái nằm trong từ, hãy kiểm tra xem nó có ở đúng vị trí không
- Nếu chữ cái ở đúng vị trí, màu xanh lá cây
- Khác, màu vàng
checkGuess
sử dụng một chức năng shadeKeyboard
để tô màu các phím của bàn phím ảo, nhưng nó chưa được xác định. Hãy làm điều đó tiếp theo.
bóng bàn phím
function shadeKeyBoard(letter, color) {
for (const elem of document.getElementsByClassName("keyboard-button")) {
if (elem.textContent === letter) {
let oldColor = elem.style.backgroundColor
if (oldColor === 'green') {
return
}
if (oldColor === 'yellow' && color !== 'green') {
return
}
elem.style.backgroundColor = color
break
}
}
}
shadeKeyBoard
nhận chữ cái trên bàn phím ảo mà chúng tôi muốn tô bóng và màu chúng tôi muốn tô bóng. Đây là thuật toán:
- Tìm chìa khóa khớp với chữ cái đã cho
- Nếu phím đã có màu xanh, không làm gì cả
- Nếu phím hiện có màu vàng, chỉ cho phép phím chuyển sang màu xanh lục
- Khác, tô màu phím được truyền cho chức năng
Cách thêm thông báo
Tiếp theo, bạn sẽ thay thế các cảnh báo JavaScript trong checkGuess
với bánh mì nướng, sử dụng Toastr.
Đi qua checkGuess
và thay thế tất cả các cảnh báo thông báo cho người dùng về lỗi bằng các cuộc gọi đến toastr.error()
.
Cảnh báo thông báo cho người dùng đoán đúng nên được thay thế bằng toastr.success()
và cảnh báo cho người dùng biết dự đoán đúng nên được thay thế bằng toastr.info()
.
Đây là giao diện của checkGuess sau khi bạn hoàn thành:
function checkGuess () {
let row = document.getElementsByClassName("letter-row")[6 - guessesRemaining]
let guessString = ''
let rightGuess = Array.from(rightGuessString)
for (const val of currentGuess) {
guessString += val
}
if (guessString.length != 5) {
toastr.error("Not enough letters!")
return
}
if (!WORDS.includes(guessString)) {
toastr.error("Word not in list!")
return
}
for (let i = 0; i < 5; i++) {
let letterColor=""
let box = row.children[i]
let letter = currentGuess[i]
let letterPosition = rightGuess.indexOf(currentGuess[i])
// is letter in the correct guess
if (letterPosition === -1) {
letterColor="grey"
} else {
// now, letter is definitely in word
// if letter index and right guess index are the same
// letter is in the right position
if (currentGuess[i] === rightGuess[i]) {
// shade green
letterColor="green"
} else {
// shade box yellow
letterColor="yellow"
}
rightGuess[letterPosition] = "#"
}
let delay = 250 * i
setTimeout(()=> {
//shade box
box.style.backgroundColor = letterColor
shadeKeyBoard(letter, letterColor)
}, delay)
}
if (guessString === rightGuessString) {
toastr.success("You guessed right! Game over!")
guessesRemaining = 0
return
} else {
guessesRemaining -= 1;
currentGuess = [];
nextLetter = 0;
if (guessesRemaining === 0) {
toastr.error("You've run out of guesses! Game over!")
toastr.info(`The right word was: "${rightGuessString}"`)
}
}
}
Cách để Bàn phím ảo tạo đầu vào
Để bàn phím ảo của bạn hoạt động, tất cả những gì bạn phải làm là gửi một sự kiện nhấn phím bất cứ khi nào bấm vào bất kỳ phím nào trên bàn phím ảo của bạn. Để làm điều đó, hãy thêm mã này vào script.js:
document.getElementById("keyboard-cont").addEventListener("click", (e) => {
const target = e.target
if (!target.classList.contains("keyboard-button")) {
return
}
let key = target.textContent
if (key === "Del") {
key = "Backspace"
}
document.dispatchEvent(new KeyboardEvent("keyup", {'key': key}))
})
Chức năng này lắng nghe một cú nhấp chuột vào vùng chứa bàn phím hoặc bất kỳ phần tử con nào của nó (các nút). Nếu phần tử được nhấp không phải là nút, chúng tôi sẽ thoát khỏi chức năng. Nếu không, chúng tôi gửi một sự kiện mở khóa tương ứng với phím được nhấp.
Cách thêm hoạt ảnh
Chúng ta đã cài đặt animate.css, vì vậy bây giờ hãy viết một hàm JavaScript để sử dụng nó.
const animateCSS = (element, animation, prefix = 'animate__') =>
// We create a Promise and return it
new Promise((resolve, reject) => {
const animationName = `${prefix}${animation}`;
// const node = document.querySelector(element);
const node = element
node.style.setProperty('--animate-duration', '0.3s');
node.classList.add(`${prefix}animated`, animationName);
// When the animation ends, we clean the classes and resolve the Promise
function handleAnimationEnd(event) {
event.stopPropagation();
node.classList.remove(`${prefix}animated`, animationName);
resolve('Animation ended');
}
node.addEventListener('animationend', handleAnimationEnd, {once: true});
});
Chức năng này đến từ trang chủ Animate.css. Nó áp dụng các lớp cho mục tiêu hoạt ảnh để kích hoạt hoạt ảnh và khi hoạt ảnh kết thúc, nó sẽ xóa các lớp mà nó đã thêm vào.
Hàm trả về một lời hứa cho phép bạn thực hiện các hành động chỉ cần chạy sau khi hoạt ảnh kết thúc, nhưng bạn sẽ không cần thực hiện điều đó trong hướng dẫn này.
Bây giờ chúng ta có một chức năng để tạo hoạt ảnh cho bất kỳ phần tử nào, hãy áp dụng nó. quay trở lại của chúng tôi insertLetter
chức năng và thêm dòng sau trước khi chúng tôi thay thế textContent
của box
:
animateCSS(box, "pulse")
đây là cái gì insertLetter
sẽ giống như bây giờ:
function insertLetter (pressedKey) {
if (nextLetter === 5) {
return
}
pressedKey = pressedKey.toLowerCase()
let row = document.getElementsByClassName("letter-row")[6 - guessesRemaining]
let box = row.children[nextLetter]
animateCSS(box, "pulse")
box.textContent = pressedKey
box.classList.add("filled-box")
currentGuess.push(pressedKey)
nextLetter += 1
}
Mã cho biết insertLetter
để nhanh chóng đập từng hộp, ngay trước khi chúng tôi điền vào đó một chữ cái.
Tiếp theo, bạn muốn tạo hiệu ứng động cho từng chữ cái của một lần đoán trong khi bạn đang kiểm tra nó.
Quay lại và sửa đổi checkGuess
như vậy:
let delay = 250 * i
setTimeout(()=> {
//flip box
animateCSS(box, 'flipInX')
//shade box
box.style.backgroundColor = letterColor
shadeKeyBoard(letter, letterColor)
}, delay)
Mã này thêm hoạt ảnh để lật từng hộp theo chiều dọc, ngay trước khi chúng tôi thay đổi màu sắc.
Phần kết luận
Điều đó kết thúc hướng dẫn. Bạn vừa tạo một bản sao Wordle và tôi hy vọng bạn đã có niềm vui trong quá trình này. Bạn có thể tìm thấy mã hoàn chỉnh tại kho lưu trữ GitHub cho dự án này.
Nếu bạn thích bài viết này, bạn có thể tìm thêm bài viết của tôi tại đây hoặc theo dõi tôi trên Twitter.