Trong hướng dẫn này, chúng tôi sẽ xây dựng trò chơi Tic-Tac-Toe nhiều người chơi trong thời gian thực sử dụng Node.js , Socket.IO và Redis . Trò chơi này cho phép hai người chơi kết nối từ các tab trình duyệt khác nhau, thay phiên nhau chơi và xem các cập nhật theo thời gian thực khi họ chơi. Chúng tôi sẽ sử dụng Redis để quản lý đồng bộ hóa trạng thái trò chơi trên nhiều máy chủ WebSocket, giúp ứng dụng của chúng tôi có thể mở rộng quy mô.
Cuối cùng, bạn sẽ có một trò chơi đầy đủ chức năng với khả năng thời gian thực và hiểu biết vững chắc về cách sử dụng WebSockets và Redis để xây dựng các ứng dụng thời gian thực có thể mở rộng.
Bạn sẽ học được gì
-
Cách sử dụng Socket.IO để liên lạc theo thời gian thực.
-
Cách sử dụng Redis Pub/Sub để đồng bộ hóa trạng thái trò chơi trên nhiều máy khách.
-
Cách thiết lập kiến trúc máy chủ WebSocket có thể mở rộng.
Điều kiện tiên quyết
Trước khi chúng ta bắt đầu, hãy đảm bảo bạn đã cài đặt các ứng dụng sau:
-
Node.js (v16 trở lên)
-
Làm lại
-
Docker (tùy chọn, để chạy Redis trong vùng chứa)
-
Kiến thức cơ bản về JavaScript, Node.js và WebSockets.
Mục lục
-
Tổng quan dự án
-
Bước 1:Thiết lập môi trường phát triển của bạn
-
Bước 2:Thiết lập dự án
-
Bước 3:Triển khai Máy chủ WebSocket với Redis
-
Bước 4:Triển khai giao diện React Frontend
-
Bước 5:Chạy ứng dụng
-
Bước 6:Xem tin nhắn Redis trong thời gian thực
-
Bản trình diễn
-
Kết luận
Tổng quan về dự án
Chúng tôi sẽ xây dựng trò chơi Tic-Tac-Toe thời gian thực với các tính năng sau:
-
Hai người chơi có thể kết nối và chơi trò chơi.
-
Bảng trò chơi cập nhật theo thời gian thực trên các tab trình duyệt khác nhau.
-
Trò chơi công bố người chiến thắng hoặc tuyên bố hòa khi bàn cờ đầy.
Chúng tôi sẽ sử dụng:
-
Node.js với Socket.IO để xử lý các kết nối WebSocket.
-
Redis Pub/Sub để quản lý đồng bộ hóa trạng thái trò chơi giữa các máy khách.
Bước 1:Thiết lập môi trường phát triển của bạn
Cài đặt Node.js
Đảm bảo bạn đã cài đặt Node.js trên hệ thống của mình:
node -v
Nếu bạn chưa cài đặt nó, hãy tải xuống từ Node.js.
Cài đặt Redis
Bạn có thể cài đặt Redis cục bộ hoặc chạy nó trong vùng chứa Docker.
macOS (Sử dụng Homebrew)
Trước tiên, hãy đảm bảo rằng bạn đã cài đặt Homebrew trên hệ thống của mình trước khi chạy các lệnh bên dưới:
brew install redis
brew services start redis
Xác minh rằng vùng chứa Redis đang chạy bằng lệnh sau:
redis-cli ping
Bạn sẽ thấy:
PONG
Sử dụng Docker để chạy Redis
docker run --name redis-server -p 6379:6379 -d redis
Kiểm tra xem Redis có đang chạy hay không bằng cách sử dụng:
docker exec -it redis-server redis-cli ping
Bước 2:Thiết lập dự án
1. Tạo thư mục dự án
mkdir tic-tac-toe
cd tic-tac-toe
npm init -y
2. Cài đặt phụ thuộc
npm install express socket.io redis dotenv
3. Tạo biến môi trường
Tạo một .env tập tin trong thư mục gốc dự án của bạn với nội dung sau:
PORT=3000
REDIS_HOST=localhost
REDIS_PORT=6379
Bước 3:Triển khai Máy chủ WebSocket bằng Redis
Trong bước này, chúng ta sẽ thiết lập máy chủ WebSocket xử lý các tương tác trò chơi trong thời gian thực bằng cách sử dụng Node.js , Socket.IO và Redis . Máy chủ này sẽ quản lý trạng thái trò chơi, xử lý các bước di chuyển của người chơi và đảm bảo đồng bộ hóa trên nhiều máy khách bằng Redis Pub/Sub.
Chúng tôi sẽ chia nhỏ từng phần của mã để bạn hiểu chính xác mọi thứ khớp với nhau như thế nào.Giải thích mã máy chủ
Tạo một tệp có tên server.js và thêm đoạn mã sau:
import dotenv from 'dotenv';
import express from 'express';
import http from 'http';
import { Server } from 'socket.io';
import { createClient } from 'redis';
dotenv.config(); // Load environment variables from .env file
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: "http://localhost:5173",
methods: ["GET", "POST"],
}
});
-
dotenv :Tải các biến môi trường từ một
.envđể giữ an toàn cho những thông tin nhạy cảm như cổng và khóa. -
chuyển phát nhanh :Thiết lập máy chủ Express cơ bản để xử lý các yêu cầu HTTP.
-
http :Chúng tôi tạo một máy chủ HTTP bằng cách sử dụng
httptích hợp của Node mô-đun mà chúng tôi sẽ sử dụng với Socket.IO để liên lạc với WebSocket. -
Socket.IO :Thư viện này cho phép giao tiếp hai chiều theo thời gian thực giữa máy chủ và máy khách.
-
Cấu hình CORS :Cho phép các yêu cầu có nguồn gốc chéo từ giao diện người dùng của chúng tôi chạy trên
localhost:5173.
Sau đó, để tạo nhà xuất bản Redis và khách hàng đăng ký, chúng tôi sẽ thêm mã sau vào server.js :
// Initialize Redis clients
const pubClient = createClient();
const subClient = createClient();
await pubClient.connect();
await subClient.connect();
Chúng tôi sử dụng Redis để xử lý việc đồng bộ hóa dữ liệu theo thời gian thực giữa các máy khách được kết nối.
-
pubClient :Được sử dụng để đăng thông báo (như cập nhật trạng thái trò chơi).
-
Khách hàng phụ :Đăng ký tin nhắn (nghe cập nhật).
- kết nối() :Thiết lập kết nối tới máy chủ Redis.
Trong mô hình này, một khách hàng được sử dụng để xuất bản các bản cập nhật và khách hàng còn lại đăng ký các bản cập nhật. Điều này giúp tránh hành vi chặn vì ứng dụng khách Redis trong đăng ký chế độ chỉ có thể nhận tin nhắn.
Để đăng ký các kênh Redis để cập nhật trò chơi, chúng tôi sẽ thêm mã sau vào server.js :
// Subscribe to the Redis channel for game updates
await subClient.subscribe('game-moves', (message) => {
gameState = JSON.parse(message);
io.emit('gameState', gameState);
});
-
subClient.subscribe :Nghe tin nhắn trên
game-moveskênh. -
Bất cứ khi nào người chơi thực hiện một nước đi mới, trạng thái trò chơi sẽ được cập nhật trong Redis và tất cả máy khách được kết nối sẽ được thông báo về trạng thái mới.
-
messagetham số chứa trạng thái trò chơi dưới dạng một chuỗi. Chúng tôi phân tích nó thành một đối tượng JavaScript và phát trạng thái cập nhật bằng cách sử dụng Socket.IO .
Tiếp theo, để xác định trạng thái và chức năng của trò chơi, chúng ta sẽ thêm đoạn mã sau vào server.js :
// Define initial game state
let gameState = {
board: Array(9).fill(null),
xIsNext: true,
};
// Function to reset the game
function resetGame() {
gameState = {
board: Array(9).fill(null),
xIsNext: true,
};
}
-
gameState :Theo dõi trạng thái hiện tại của bảng và lượt của ai (
xIsNext).-
Bảng được thể hiện dưới dạng một mảng gồm 9 ô (mỗi ô có thể là 'X', 'O' hoặc
null). -
xIsNextcờ xác định lượt của người chơi nào.
-
-
resetGame() :Đặt lại bàn cờ và chỉ báo chuyển về trạng thái ban đầu, cho phép trò chơi mới bắt đầu.
Tiếp theo, để xử lý các kết nối WebSocket, hãy thêm đoạn mã sau vào server.js :
io.on('connection', (socket) => {
console.log('New client connected:', socket.id);
// Send the current game state to the newly connected client
socket.emit('gameState', gameState);
-
io.on('connection')sự kiện được kích hoạt khi một máy khách mới kết nối. -
socket.id :Mã định danh duy nhất cho mỗi máy khách được kết nối.
-
Chúng tôi gửi ngay
gameStatehiện tại cho khách hàng mới để họ có thể xem bảng hiện tại.
Để xử lý các bước di chuyển của người chơi, chúng tôi sẽ thêm đoạn mã sau vào server.js :
// Handle player moves
socket.on('makeMove', (index) => {
// Prevent making a move if cell is already taken or game is over
if (gameState.board[index] || calculateWinner(gameState.board)) return;
// Update the board and switch turns
gameState.board[index] = gameState.xIsNext ? 'X' : 'O';
gameState.xIsNext = !gameState.xIsNext;
// Publish the updated game state to Redis
pubClient.publish('game-moves', JSON.stringify(gameState));
io.emit('gameState', gameState);
});
-
makeMove :Sự kiện này được kích hoạt khi người chơi nhấp vào một ô.
-
Xác thực :Chúng tôi kiểm tra xem ô đã bị chiếm chưa hoặc trò chơi đã kết thúc chưa trước khi thực hiện nước đi.
-
Cập nhật trạng thái trò chơi :Nếu nước đi hợp lệ, chúng tôi cập nhật bảng và đổi lượt.
-
-
Trạng thái trò chơi được cập nhật khi đó là:
-
Đã xuất bản lên Redis :Điều này đảm bảo rằng tất cả các phiên bản của máy chủ luôn được đồng bộ hóa.
-
Được phát tới tất cả khách hàng :Điều này ngay lập tức cập nhật bảng trò chơi cho tất cả người chơi.
-
Để xử lý việc khởi động lại trò chơi, chúng tôi sẽ thêm mã sau vào server.js :
// Handle game restarts
socket.on('restartGame', () => {
resetGame();
io.emit('gameState', gameState);
});
Để xử lý việc ngắt kết nối máy khách, chúng tôi sẽ thêm đoạn mã sau vào server.js :
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
Cuối cùng, để xử lý logic của trò chơi, chúng ta sẽ thêm các hàm sau vào server.js :
// Function to check if there's a winner
function calculateWinner(board) {
const lines = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
for (let [a, b, c] of lines) {
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
return board[a];
}
}
return null;
}
function isBoardFull(board) {
return board.every((cell) => cell !== null);
}
-
CalculateWinner() :Kiểm tra xem có sự kết hợp chiến thắng nào trên bàn cờ hay không.
-
isBoardFull() :Kiểm tra xem tất cả các ô đã được điền chưa, biểu thị kết quả hòa.
Bước 4:Triển khai giao diện React Frontend
Trong bước này, chúng tôi xây dựng giao diện React đơn giản và tương tác cho trò chơi Tic-Tac-Toe của mình. Giao diện người dùng này cho phép người chơi kết nối với máy chủ WebSocket, thực hiện các nước đi và xem cập nhật bảng trò chơi trong thời gian thực.
Trong App.jsx , thêm đoạn mã sau:
import React, { useEffect, useState } from 'react';
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
function App() {
const [gameState, setGameState] = useState({
board: Array(9).fill(null),
xIsNext: true,
winner: null
});
useEffect(() => {
socket.on('gameState', (state) => {
setGameState(state);
});
return () => socket.off('gameState');
}, []);
const handleClick = (index) => {
if (gameState.board[index] || gameState.winner) return;
socket.emit('makeMove', index);
};
const renderCell = (index) => (
<button onClick={() => handleClick(index)}>{gameState.board[index]}</button>
);
return (
<div>
<h1>Multiplayer Tic-Tac-Toe</h1>
<div className="board">
{[...Array(9)].map((_, i) => renderCell(i))}
</div>
<button onClick={() => socket.emit('restartGame')}>Restart Game</button>
</div>
);
}
export default App;
Dưới đây là tóm tắt về cách chia nhỏ ứng dụng React:
-
Kết nối WebSocket :
- Giao diện người dùng thiết lập kết nối đến máy chủ bằng
socket.io-client.
- Giao diện người dùng thiết lập kết nối đến máy chủ bằng
-
Quản lý nhà nước :
-
Trạng thái trò chơi (
gameState) được quản lý bằnguseStatecủa React và bao gồm:-
Bảng (9 ô).
-
Cờ xIsNext để cho biết lượt của người chơi hiện tại.
-
Người chiến thắng trạng thái.
-
-
-
Cập nhật theo thời gian thực :
-
useEffectmóc:-
Đã nghe
gameStatecập nhật từ máy chủ. -
Cập nhật trạng thái trò chơi cục bộ khi phát hiện thấy thay đổi.
-
Dọn dẹp trình nghe WebSocket khi thành phần này chưa được kết nối.
-
-
-
Xử lý các bước di chuyển của người chơi :
-
handleClickchức năng:-
Kiểm tra xem ô đã bị chiếm hay chưa hoặc trò chơi có người chiến thắng hay không trước khi cho phép di chuyển.
-
Gửi
makeMovesự kiện đến máy chủ với chỉ mục ô được nhấp.
-
-
-
Hiển thị bảng trò chơi :
-
renderCellHàm tạo một nút cho mỗi ô trên bảng. -
Bảng được hiển thị bằng lưới 3x3.
-
-
Khởi động lại trò chơi :
- Nút "Khởi động lại trò chơi" phát ra
restartGamesự kiện thiết lập lại bảng trò chơi cho tất cả người chơi.
- Nút "Khởi động lại trò chơi" phát ra
-
Giao diện người dùng :
- Bố cục tương tác và đơn giản cho phép người chơi thay phiên nhau xem thông tin cập nhật theo thời gian thực.
Bước 5:Chạy ứng dụng
Bắt đầu phần phụ trợ
Để khởi động máy chủ phụ trợ, hãy mở một cửa sổ terminal mới và chạy các lệnh sau:
cd tic-tac-toe
npm start
Bắt đầu giao diện người dùng
Để khởi động máy chủ giao diện người dùng React, hãy mở một cửa sổ terminal mới và chạy các lệnh bên dưới (không sử dụng cùng một lệnh mà máy chủ phụ trợ đang chạy vì bạn cần cả hai lệnh chạy đồng thời để chạy trò chơi).
cd tic-tac-toe-client
npm run dev
Truy cập trò chơi
Mở trình duyệt của bạn và điều hướng đến:
http://localhost:5173
Bước 6:Xem tin nhắn Redis trong thời gian thực
Trong khi trò chơi đang chạy, bạn có thể xem tin nhắn Redis để biết thông tin cập nhật về trạng thái trò chơi theo thời gian thực.
Mở terminal và chạy:
redis-cli
SUBSCRIBE game-moves
Điều này sẽ hiển thị các bản cập nhật trò chơi:
1) "message"
2) "game-moves"
3) "{\"board\":[\"X\",null,\"O\",null,\"X\",null,null,null,null],\"xIsNext\":false}"
Mỗi khi thực hiện một nước đi hoặc trạng thái trò chơi thay đổi, máy chủ sẽ xuất bản trạng thái trò chơi đã cập nhật lên game-moves kênh. Sử dụng redis-cli , bạn có thể theo dõi những cập nhật này theo thời gian thực khi trò chơi đang được chơi.
Bản trình diễn
Trong bản demo này, bạn sẽ thấy trò chơi Tic Tac Toe chạy cục bộ, thể hiện các cập nhật theo thời gian thực khi người chơi thay phiên nhau.
Trò chơi giới thiệu các tính năng như chuyển lượt, cập nhật bảng và thông báo trạng thái trò chơi (người chiến thắng hoặc hòa). Điều này nêu bật cách trò chơi tận dụng giao tiếp WebSocket để mang lại trải nghiệm tương tác mượt mà.
Kết luận
Xin chúc mừng, bạn đã xây dựng thành công trò chơi Tic-Tac-Toe nhiều người chơi trong thời gian thực bằng Node.js, Socket.IO và Redis. Đây là những gì bạn đã học được:
-
Giao tiếp WebSocket thời gian thực bằng Socket.IO .
-
Quản lý trạng thái trò chơi bằng Redis Pub/Sub .
-
Xây dựng giao diện người dùng đáp ứng với React .
Các bước tiếp theo
-
Thêm xác thực người chơi.
-
Triển khai tính năng trò chuyện.
-
Triển khai ứng dụng của bạn cho nhà cung cấp đám mây để có khả năng mở rộng.
Chúc bạn viết mã vui vẻ!
Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu