ในบทช่วยสอนนี้ เราจะสร้างเกม Tic-Tac-Toe ที่มีผู้เล่นหลายคนแบบเรียลไทม์ โดยใช้ Node.js , Socket.IO และ แดง . เกมนี้อนุญาตให้ผู้เล่นสองคนเชื่อมต่อจากแท็บเบราว์เซอร์ที่แตกต่างกัน ผลัดกันเล่น และดูการอัปเดตแบบเรียลไทม์ขณะเล่น เราจะใช้ Redis เพื่อจัดการการซิงโครไนซ์สถานะของเกมบนเซิร์ฟเวอร์ WebSocket หลายเซิร์ฟเวอร์ ทำให้แอปพลิเคชันของเราปรับขนาดได้
ในตอนท้าย คุณจะมีเกมที่มีฟังก์ชันการทำงานเต็มรูปแบบพร้อมความสามารถแบบเรียลไทม์และความเข้าใจที่ชัดเจนเกี่ยวกับวิธีการใช้ WebSockets และ Redis เพื่อสร้างแอปพลิเคชันแบบเรียลไทม์ที่ปรับขนาดได้
สิ่งที่คุณจะได้เรียนรู้
-
วิธีใช้ Socket.IO สำหรับการสื่อสารแบบเรียลไทม์
-
วิธีใช้ Redis Pub/Sub เพื่อซิงโครไนซ์สถานะของเกมกับไคลเอนต์หลายตัว
-
วิธีการตั้งค่าสถาปัตยกรรมเซิร์ฟเวอร์ WebSocket ที่ปรับขนาดได้
ข้อกำหนดเบื้องต้น
ก่อนที่เราจะเริ่ม ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้งสิ่งต่อไปนี้แล้ว:
-
Node.js (v16 หรือสูงกว่า)
-
เรดดิส
-
นักเทียบท่า (เป็นทางเลือก สำหรับการเรียกใช้ Redis ในคอนเทนเนอร์)
-
ความรู้พื้นฐานเกี่ยวกับ JavaScript, Node.js และ WebSockets
สารบัญ
-
ภาพรวมโครงการ
-
ขั้นตอนที่ 1:การตั้งค่าสภาพแวดล้อมการพัฒนาของคุณ
-
ขั้นตอนที่ 2:การตั้งค่าโครงการ
-
ขั้นตอนที่ 3:การใช้งาน WebSocket Server ด้วย Redis
-
ขั้นตอนที่ 4:ใช้อินเทอร์เฟซ React Frontend
-
ขั้นตอนที่ 5:เรียกใช้แอปพลิเคชัน
-
ขั้นตอนที่ 6:การดูข้อความ Redis แบบเรียลไทม์
-
สาธิต
-
บทสรุป
ภาพรวมโครงการ
เราจะสร้างเกม Tic-Tac-Toe แบบเรียลไทม์พร้อมคุณสมบัติดังต่อไปนี้:
-
ผู้เล่นสองคน สามารถเชื่อมต่อและเล่นเกมได้
-
บอร์ดเกมจะอัปเดตตามเวลาจริงผ่านแท็บเบราว์เซอร์ต่างๆ
-
เกมจะประกาศผู้ชนะหรือเสมอเมื่อกระดานเต็ม
เราจะใช้:
-
โหนด js ด้วย Socket.IO สำหรับการจัดการการเชื่อมต่อ WebSocket
-
เรดิส Pub/Sub เพื่อจัดการการซิงโครไนซ์สถานะของเกมกับไคลเอนต์
ขั้นตอนที่ 1:การตั้งค่าสภาพแวดล้อมการพัฒนาของคุณ
การติดตั้ง Node.js
ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้ง Node.js บนระบบของคุณ:
node -v
หากคุณยังไม่ได้ติดตั้ง ให้ดาวน์โหลดจาก Node.js
การติดตั้ง Redis
คุณสามารถติดตั้ง Redis ในเครื่องหรือเรียกใช้ในคอนเทนเนอร์ Docker ได้
macOS (ใช้ Homebrew)
ขั้นแรก ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้ง Homebrew บนระบบของคุณก่อนที่จะรันคำสั่งด้านล่าง:
brew install redis
brew services start redis
ตรวจสอบว่าคอนเทนเนอร์ Redis ทำงานด้วยคำสั่งต่อไปนี้:
redis-cli ping
คุณควรเห็น:
PONG
การใช้ Docker เพื่อเรียกใช้ Redis
docker run --name redis-server -p 6379:6379 -d redis
ตรวจสอบว่า Redis ทำงานโดยใช้:
docker exec -it redis-server redis-cli ping
ขั้นตอนที่ 2:การตั้งค่าโครงการ
1. สร้างไดเรกทอรีโครงการ
mkdir tic-tac-toe
cd tic-tac-toe
npm init -y
2. ติดตั้งการอ้างอิง
npm install express socket.io redis dotenv
3. สร้างตัวแปรสภาพแวดล้อม
สร้าง 09 ในรูทโปรเจ็กต์ของคุณโดยมีเนื้อหาดังต่อไปนี้:
PORT=3000
REDIS_HOST=localhost
REDIS_PORT=6379
ขั้นตอนที่ 3:การใช้งาน WebSocket Server ด้วย Redis
ในขั้นตอนนี้ เราจะตั้งค่าเซิร์ฟเวอร์ WebSocket ที่จัดการการโต้ตอบของเกมแบบเรียลไทม์โดยใช้ Node.js , Socket.IO และ แดง . เซิร์ฟเวอร์นี้จะจัดการสถานะของเกม จัดการการเคลื่อนไหวของผู้เล่น และรับรองการซิงโครไนซ์กับไคลเอนต์หลายตัวโดยใช้ Redis Pub/Sub
เราจะแจกแจงโค้ดแต่ละส่วนเพื่อให้คุณเข้าใจว่าทุกอย่างเข้ากันได้อย่างไรคำอธิบายโค้ดเซิร์ฟเวอร์ ป>
สร้างไฟล์ชื่อ 13 และเพิ่มรหัสต่อไปนี้:
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"],
}
});
-
โดเทนวี :โหลดตัวแปรสภาพแวดล้อมจาก
27ไฟล์เพื่อเก็บข้อมูลสำคัญเช่นพอร์ตและคีย์ให้ปลอดภัย -
ด่วน :ตั้งค่าเซิร์ฟเวอร์ Express พื้นฐานเพื่อจัดการคำขอ HTTP
-
http :เราสร้างเซิร์ฟเวอร์ HTTP โดยใช้
32ในตัวของ Node โมดูลซึ่งเราจะใช้กับ Socket.IO สำหรับการสื่อสาร WebSocket -
Socket.IO :ไลบรารีนี้เปิดใช้งานการสื่อสารแบบสองทิศทางแบบเรียลไทม์ระหว่างเซิร์ฟเวอร์และไคลเอนต์
-
การกำหนดค่า CORS :อนุญาตคำขอข้ามต้นทางจากส่วนหน้าของเราที่ทำงานบน
43.
จากนั้น เพื่อสร้างลูกค้าผู้เผยแพร่และสมาชิก Redis เราจะเพิ่มโค้ดต่อไปนี้ใน 53 :
// Initialize Redis clients
const pubClient = createClient();
const subClient = createClient();
await pubClient.connect();
await subClient.connect();
เราใช้ เรดดิส เพื่อจัดการการซิงโครไนซ์ข้อมูลแบบเรียลไทม์ระหว่างไคลเอนต์ที่เชื่อมต่อ
-
pubClient :ใช้เพื่อเผยแพร่ข้อความ (เช่น การอัปเดตสถานะของเกม)
-
ลูกค้าย่อย :สมัครรับข้อความ (ฟังการอัปเดต)
- เชื่อมต่อ() :สร้างการเชื่อมต่อกับเซิร์ฟเวอร์ Redis
ในกระบวนทัศน์นี้ ไคลเอนต์หนึ่งถูกใช้เพื่อเผยแพร่การอัพเดต และอีกอันหนึ่งสมัครรับการอัพเดต ซึ่งจะช่วยหลีกเลี่ยงพฤติกรรมการบล็อก เนื่องจากไคลเอ็นต์ Redis อยู่ใน สมัครสมาชิก โหมดสามารถรับได้เฉพาะข้อความเท่านั้น
หากต้องการสมัครรับข้อมูลช่อง Redis สำหรับการอัปเดตเกม เราจะเพิ่มโค้ดต่อไปนี้ลงใน 65 :
// Subscribe to the Redis channel for game updates
await subClient.subscribe('game-moves', (message) => {
gameState = JSON.parse(message);
io.emit('gameState', gameState);
});
-
subClient.subscribe :ฟังข้อความบน
75ช่อง. -
เมื่อใดก็ตามที่ผู้เล่นทำการเคลื่อนไหวใหม่ สถานะของเกมจะได้รับการอัปเดตใน Redis และไคลเอนต์ที่เชื่อมต่อทั้งหมดจะได้รับแจ้งเกี่ยวกับสถานะใหม่
-
88รหัส> พารามิเตอร์ประกอบด้วยสถานะของเกมเป็นสตริง เราแยกวิเคราะห์ออกเป็นวัตถุ JavaScript และเผยแพร่สถานะที่อัปเดตโดยใช้ Socket.IO .
ต่อไป เพื่อกำหนดสถานะและฟังก์ชันของเกม เราจะเพิ่มโค้ดต่อไปนี้ใน 92 :
// 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,
};
}
-
สถานะเกม :ติดตามสถานะปัจจุบันของบอร์ดและตาของใคร (
103).-
กระดานจะแสดงเป็นอาร์เรย์จำนวน 9 เซลล์ (แต่ละเซลล์สามารถเป็น 'X', 'O' หรือ
114). -
126รหัส> ธงจะกำหนดตาของผู้เล่นคนใด
-
-
รีเซ็ตเกม() :รีเซ็ตบอร์ดและไฟเลี้ยวเป็นสถานะเริ่มต้น เพื่อให้สามารถเริ่มเกมใหม่ได้
ต่อไป ในการจัดการการเชื่อมต่อ WebSocket ให้เพิ่มโค้ดต่อไปนี้ใน 135 :
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);
-
141รหัส> เหตุการณ์จะถูกทริกเกอร์เมื่อไคลเอนต์ใหม่เชื่อมต่อ -
socket.id :ตัวระบุที่ไม่ซ้ำกันสำหรับไคลเอ็นต์ที่เชื่อมต่อแต่ละเครื่อง
-
เราส่ง
154ปัจจุบันทันที ให้กับลูกค้าใหม่เพื่อให้พวกเขาสามารถดูบอร์ดปัจจุบันได้
เพื่อจัดการกับการเคลื่อนไหวของผู้เล่น เราจะเพิ่มโค้ดต่อไปนี้ใน 168 :
// 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);
});
-
เมคมูฟ :เหตุการณ์นี้จะเกิดขึ้นเมื่อผู้เล่นคลิกบนเซลล์
-
การตรวจสอบความถูกต้อง :เราตรวจสอบว่าเซลล์ถูกครอบครองแล้วหรือเกมจบลงแล้วก่อนที่จะทำการย้าย
-
การอัปเดตสถานะของเกม :หากการย้ายถูกต้อง เราจะอัปเดตบอร์ดและสลับผลัดกัน
-
-
สถานะของเกมที่อัปเดตคือ:
-
เผยแพร่ไปยัง Redis :สิ่งนี้ทำให้แน่ใจได้ว่าอินสแตนซ์ทั้งหมดของเซิร์ฟเวอร์ยังคงซิงค์อยู่
-
เผยแพร่ไปยังลูกค้าทุกคน :นี่เป็นการอัปเดตบอร์ดเกมสำหรับผู้เล่นทุกคนทันที
-
เพื่อจัดการกับการรีสตาร์ทเกม เราจะเพิ่มโค้ดต่อไปนี้ใน 172 :
// Handle game restarts
socket.on('restartGame', () => {
resetGame();
io.emit('gameState', gameState);
});
ในการจัดการกับการขาดการเชื่อมต่อของไคลเอ็นต์ เราจะเพิ่มโค้ดต่อไปนี้ใน 185 :
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
สุดท้ายนี้ เพื่อประมวลผลตรรกะของเกม เราจะเพิ่มฟังก์ชันต่อไปนี้ใน 194 :
// 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);
}
-
คำนวณผู้ชนะ() :ตรวจสอบว่ามีชุดค่าผสมที่ชนะบนกระดานหรือไม่
-
isBoardFull() :ตรวจสอบว่าเซลล์ทั้งหมดเต็มหรือไม่ ซึ่งบ่งชี้ว่าเสมอกัน
ขั้นตอนที่ 4:ใช้อินเทอร์เฟซ React Frontend
ในขั้นตอนนี้ เราสร้างส่วนหน้า React ที่เรียบง่ายและโต้ตอบได้สำหรับเกม Tic-Tac-Toe ของเรา ส่วนหน้านี้อนุญาตให้ผู้เล่นเชื่อมต่อกับเซิร์ฟเวอร์ WebSocket ทำการเคลื่อนไหว และดูการอัปเดตบอร์ดเกมแบบเรียลไทม์
ใน 202 ให้เพิ่มโค้ดต่อไปนี้:
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;
ต่อไปนี้เป็นบทสรุปเกี่ยวกับวิธีการแยกย่อยแอป React:
-
การเชื่อมต่อ WebSocket :
- ส่วนหน้าสร้างการเชื่อมต่อกับเซิร์ฟเวอร์โดยใช้
218.
- ส่วนหน้าสร้างการเชื่อมต่อกับเซิร์ฟเวอร์โดยใช้
-
การจัดการของรัฐ :
-
สถานะของเกม (
220) ได้รับการจัดการด้วย230ของ React และรวมถึง:-
กระดาน (9 เซลล์)
-
ธง xIsNext เพื่อระบุตาของผู้เล่นปัจจุบัน
-
ผู้ชนะ สถานะ
-
-
-
การอัปเดตตามเวลาจริง :
-
240รหัส> ตะขอ:-
ฟัง
251อัปเดตจากเซิร์ฟเวอร์ -
อัปเดตสถานะของเกมในเครื่องเมื่อตรวจพบการเปลี่ยนแปลง
-
ทำความสะอาด WebSocket Listener เมื่อส่วนประกอบไม่ได้ต่อเชื่อม
-
-
-
การจัดการการเคลื่อนไหวของผู้เล่น :
-
268รหัส> ฟังก์ชัน:-
ตรวจสอบว่าเซลล์ถูกครอบครองแล้วหรือเกมมีผู้ชนะหรือไม่ก่อนที่จะอนุญาตให้เคลื่อนที่
-
ส่ง
276เหตุการณ์ไปยังเซิร์ฟเวอร์ด้วยดัชนีเซลล์ที่ถูกคลิก
-
-
-
การเรนเดอร์บอร์ดเกม :
-
288รหัส> ฟังก์ชั่นสร้างปุ่มสำหรับแต่ละเซลล์บนกระดาน -
กระดานจะแสดงโดยใช้ตารางขนาด 3x3
-
-
เริ่มเกมใหม่ :
- ปุ่ม "เริ่มเกมใหม่" ส่งเสียง
294กิจกรรมเพื่อรีเซ็ตกระดานเกมสำหรับผู้เล่นทุกคน
- ปุ่ม "เริ่มเกมใหม่" ส่งเสียง
-
อินเทอร์เฟซผู้ใช้ :
- รูปแบบที่เรียบง่ายและโต้ตอบได้ซึ่งช่วยให้ผู้เล่นผลัดกันดูการอัปเดตแบบเรียลไทม์
ขั้นตอนที่ 5:การเรียกใช้แอปพลิเคชัน
การเริ่มต้นแบ็กเอนด์
หากต้องการเริ่มต้นเซิร์ฟเวอร์ส่วนหลัง ให้เปิดหน้าต่างเทอร์มินัลใหม่และรันคำสั่งต่อไปนี้:
cd tic-tac-toe
npm start
การเริ่มต้นส่วนหน้า
หากต้องการเริ่ม React เซิร์ฟเวอร์ส่วนหน้า ให้เปิดหน้าต่างเทอร์มินัลใหม่และรันคำสั่งด้านล่าง (อย่าใช้คำสั่งเดียวกับที่เซิร์ฟเวอร์แบ็คเอนด์ทำงานอยู่ เนื่องจากคุณต้องใช้ทั้งสองอย่างพร้อมกันเพื่อรันเกม)
cd tic-tac-toe-client
npm run dev
การเข้าถึงเกม
เปิดเบราว์เซอร์ของคุณและไปที่:
http://localhost:5173
ขั้นตอนที่ 6:การดูข้อความ Redis แบบเรียลไทม์
ในขณะที่เกมกำลังทำงาน คุณสามารถดูข้อความ Redis เพื่อดูการอัปเดตสถานะของเกมแบบเรียลไทม์ได้
เปิดเทอร์มินัลแล้วรัน:
redis-cli
SUBSCRIBE game-moves
นี่จะแสดงการอัปเดตเกม:
1) "message"
2) "game-moves"
3) "{\"board\":[\"X\",null,\"O\",null,\"X\",null,null,null,null],\"xIsNext\":false}"
ทุกครั้งที่มีการเคลื่อนไหวหรือสถานะของเกมเปลี่ยนแปลง เซิร์ฟเวอร์จะเผยแพร่สถานะเกมที่อัปเดตไปที่ 306 ช่อง. ใช้ 316 คุณสามารถตรวจสอบการอัปเดตเหล่านี้ได้แบบเรียลไทม์ในขณะที่เกมกำลังเล่นอยู่
สาธิต
ในการสาธิตนี้ คุณจะเห็นเกม Tic Tac Toe ทำงานในพื้นที่ ซึ่งสาธิตการอัปเดตแบบเรียลไทม์ในขณะที่ผู้เล่นสลับกัน
รูปแบบการเล่นจะแสดงคุณสมบัติต่างๆ เช่น การสลับเทิร์น การอัพเดตบอร์ด และการประกาศสถานะของเกม (ผู้ชนะหรือเสมอ) สิ่งนี้เน้นย้ำถึงวิธีที่เกมใช้ประโยชน์จากการสื่อสารของ WebSocket เพื่อมอบประสบการณ์การโต้ตอบที่ราบรื่น
ป>
บทสรุป
ขอแสดงความยินดี คุณประสบความสำเร็จในการสร้างเกม Tic-Tac-Toe ผู้เล่นหลายคนแบบเรียลไทม์โดยใช้ Node.js, Socket.IO และ Redis นี่คือสิ่งที่คุณได้เรียนรู้:
-
การสื่อสาร WebSocket แบบเรียลไทม์โดยใช้ Socket.IO .
-
การจัดการสถานะของเกมโดยใช้ Redis Pub/Sub .
-
การสร้างส่วนหน้าแบบตอบสนองด้วย React .
ขั้นตอนถัดไป
-
เพิ่มการตรวจสอบผู้เล่น
-
ใช้คุณลักษณะการแชท
-
ปรับใช้แอปพลิเคชันของคุณกับผู้ให้บริการระบบคลาวด์เพื่อความสามารถในการขยายขนาด
ขอให้สนุกกับการเขียนโค้ด!
เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น