ساخت بازی دوز با جاوا اسکریپت — از صفر تا صد
در این مقاله به صورت عملی با روش ساخت بازی دوز با جاوا اسکریپت خالص آشنا خواهیم شد. برای اجرای مراحل این راهنما شما باید برخی اطلاعاتی مقدماتی در مورد HTML ،CSS و تابعهای سلکتور کوئری داشته باشید. ابتدا یک ساختار ابتدایی برای بازی خود ایجاد میکنیم. بنابراین باید مطمئن شویم که یک رابط کاربری مناسب داریم. برای سهولت کار این بخش را به چند بخش کوچکتر تقسیم میکنیم. به این ترتیب نگهداری کد آسان خواهد بود. این بخشها به شرح زیر هستند:
- عنوان
- شبکه 3 در 3
- نمایش اطلاعات نوبت بازیکن جاری
- نمایش برنده بازی
نمایش نتیجه تساوی - دکمه شروع مجدد برای کل بازی
در ادامه روند بازی را برای هر کلیک روی خانهها مرور میکنیم.
- هر گونه کلیک روی خانههای جدول بازی ردگیری میشود.
- بررسی میشود آیا حرکت مجاز است و آیا قبلاً روی خانه مورد نظر کلیک شده است یا نه.
- حالت بازی بهروزرسانی میشود.
- حالت بازی اعتبارسنجی میشود.
- تغییرهای ایجاد شده در UI بهروزرسانی میشود.
- تا پایان یافتن بازی این مراحل تکرار میشود.
در ادامه به کدنویسی پروژه بازی میپردازیم.
ساختار پوشههای پروژه
کار خود را با ساخت رابط کاربری بازی آغاز میکنیم. چنان که پیشتر اشاره کردیم، این یک بازی ساده است و از این رو رابط کاربری آن نیز ساده خواهد بود.
این ساختار شامل سه فایل اصلی به شرح زیر است:
- index.html – شامل ساختار UI است.
- style.css – ظاهر زیباتری به بازی میبخشد.
- script.js – شامل منطق بازی است.
بخش HTML ساخت بازی دوز
فایل HTML این بازی به صورت زیر است:
1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport"
6 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>Tic Tac Toe</title>
9 <link rel="stylesheet" href="style.css">
10</head>
11<body>
12 <section>
13 <h1 class="game--title">Tic Tac Toe</h1>
14 <div class="game--container">
15 <div data-cell-index="0" class="cell"></div>
16 <div data-cell-index="1" class="cell"></div>
17 <div data-cell-index="2" class="cell"></div>
18 <div data-cell-index="3" class="cell"></div>
19 <div data-cell-index="4" class="cell"></div>
20 <div data-cell-index="5" class="cell"></div>
21 <div data-cell-index="6" class="cell"></div>
22 <div data-cell-index="7" class="cell"></div>
23 <div data-cell-index="8" class="cell"></div>
24 </div>
25 <h2 class="game--status"></h2>
26 <button class="game--restart">Restart Game</button>
27 </section>
28<script src="script.js"></script>
29</body>
30</html>
استایل شیت بازی در تگ <head> قرار دارد تا استایل شیت پیش از HTML بارگذاری شود. همچنین فایل script.js را نیز درست بالای تگ پایانی </body> قرار دادهایم تا فایل جاوا اسکریپت پس از HTML بارگذاری شود.
یک عنصر <h2> نیز داریم که اطلاعات بازی و دکمه ریاستارت را نمایش میدهد. همچنین هر کدام شامل خصوصیات اندیس خانههای دادهای هستند که برای ردگیری کلیکها مورد استفاده قرار میگیرند.
بخش CSS ساخت بازی دوز
فایل CSS بازی دوز ما به صورت زیر است:
1body {
2 font-family: "Arial", sans-serif;
3}
4section {
5 text-align: center;
6}
7.game--container {
8 display: grid;
9 grid-template-columns: repeat(3, auto);
10 width: 306px;
11 margin: 50px auto;
12}
13.cell {
14 font-family: "Permanent Marker", cursive;
15 width: 100px;
16 height: 100px;
17 box-shadow: 0 0 0 1px #333333;
18 border: 1px solid #333333;
19 cursor: pointer;
20line-height: 100px;
21 font-size: 60px;
22}
استایلهایی که در فایل فوق میبینید روی کانتینر game. برای هر خانه گرید CSS ما پیادهسازی میشوند. ما یک گرید 3 در 3 میسازیم تا مطمئن شویم که قالب ستون گرید مناسب است و سپس آن را تکرار میکنیم. این کد خانهها را به 2 ستون تقسیم میکند و به این ترتیب خانه به صورت خودکار عرض خود را مشخص میسازد.
بخش جاوا اسکریپت ساخت بازی دوز
اکنون نوبت به طراحی منطق بازی میرسد.
ما کدهای جاوا اسکریپت را به قطعههای کوچکی تقسیم کردهایم.
1/*
2We store our game status element here to allow us to more easily
3use it later on
4*/
5const statusDisplay = document.querySelector('.game--status');
6let gameActive = true;
7let currentPlayer = "X";
8let gameState = ["", "", "", "", "", "", "", "", ""];
9const winningMessage = () => `Player ${currentPlayer} has won!`;
10const drawMessage = () => `Game ended in a draw!`;
11const currentPlayerTurn = () => `It's ${currentPlayer}'s turn`;
12/*
13We set the inital message to let the players know whose turn it is
14*/
15statusDisplay.innerHTML = currentPlayerTurn();
16function handleCellPlayed() {
17
18}
19function handlePlayerChange() {
20
21}
22function handleResultValidation() {
23
24}
25function handleCellClick() {
26
27}
28function handleRestartGame() {
29
30}
31document.querySelectorAll('.cell').forEach(cell => cell.addEventListener('click', handleCellClick));
32document.querySelector('.game--restart').addEventListener('click', handleRestartGame);
قبلاً منطق بازی را توضیح دادهایم. اینک باید منطق بازی را مدیریت کنیم.
handleCellClick
در تابع handleCellClick باید دو مورد را مدیریت کنیم. ابتدا باید بررسی کنیم آیا قبلاً روی خانه کنونی کلیک شده است یا نه. اگر چنین نباشد، روند بازی ادامه مییابد. روش کار به صورت زیر است:
1function handleCellClick(clickedCellEvent) {
2/*
3We will save the clicked html element in a variable for easier further use
4*/
5 const clickedCell = clickedCellEvent.target;
6/*
7Here we will grab the 'data-cell-index' attribute from the clicked cell to identify where that cell is in our grid.
8Please note that the getAttribute will return a string value. Since we need an actual number we will parse it to an
9integer(number)
10*/
11 const clickedCellIndex = parseInt(
12 clickedCell.getAttribute('data-cell-index')
13 );
14/*
15Next up we need to check whether the call has already been played,
16or if the game is paused. If either of those is true we will simply ignore the click.
17*/
18 if (gameState[clickedCellIndex] !== "" || !gameActive) {
19 return;
20 }
21/*
22If everything if in order we will proceed with the game flow
23*/
24 handleCellPlayed(clickedCell, clickedCellIndex);
25 handleResultValidation();
26}
handleCellPlayed
در این دستگیره، دو مورد را بهروزرسانی میکنیم. ابتدا حالت بازی را بهروزرسانی کرده و سپس رابط کاربری را رفرش میکنیم.
1function handleCellPlayed(clickedCell, clickedCellIndex) {
2/*
3We update our internal game state to reflect the played move,
4as well as update the user interface to reflect the played move
5*/
6 gameState[clickedCellIndex] = currentPlayer;
7 clickedCell.innerHTML = currentPlayer;
8}
در این تابع خانهای که کاربر کلیک کرده و اندیس آن را دریافت میکنیم.
handleResultValidation
اعتبارسنجی نتیجه، بخشی اساسی از بازی دوز را تشکیل میدهد. در این بخش بررسی میکنیم آیا بازی با یک برنده خاتمه یافته یا به تساوی انجامیده است. در ادامه به بررسی این نکته میپردازیم که آیا بازیکن کنونی برنده شده است یا نه.
1const winningConditions = [
2 [0, 1, 2],
3 [3, 4, 5],
4 [6, 7, 8],
5 [0, 3, 6],
6 [1, 4, 7],
7 [2, 5, 8],
8 [0, 4, 8],
9 [2, 4, 6]
10];
11function handleResultValidation() {
12 let roundWon = false;
13 for (let i = 0; i <= 7; i++) {
14 const winCondition = winningConditions[i];
15 let a = gameState[winCondition[0]];
16 let b = gameState[winCondition[1]];
17 let c = gameState[winCondition[2]];
18 if (a === '' || b === '' || c === '') {
19 continue;
20 }
21 if (a === b && b === c) {
22 roundWon = true;
23 break
24 }
25 }
26if (roundWon) {
27 statusDisplay.innerHTML = winningMessage();
28 gameActive = false;
29 return;
30 }
31}
در صورتی که بازی به تساوی رسیده باشد، شرایط دیگر را بررسی میکنیم. در برخی شرایط خاص است که بازی به تساوی میانجامد.
1const winningConditions = [
2 [0, 1, 2],
3 [3, 4, 5],
4 [6, 7, 8],
5 [0, 3, 6],
6 [1, 4, 7],
7 [2, 5, 8],
8 [0, 4, 8],
9 [2, 4, 6]
10];
11function handleResultValidation() {
12 let roundWon = false;
13 for (let i = 0; i <= 7; i++) {
14 const winCondition = winningConditions[i];
15 let a = gameState[winCondition[0]];
16 let b = gameState[winCondition[1]];
17 let c = gameState[winCondition[2]];
18 if (a === '' || b === '' || c === '') {
19 continue;
20 }
21 if (a === b && b === c) {
22 roundWon = true;
23 break
24 }
25 }
26if (roundWon) {
27 statusDisplay.innerHTML = winningMessage();
28 gameActive = false;
29 return;
30 }
31/*
32We will check weather there are any values in our game state array
33that are still not populated with a player sign
34*/
35 let roundDraw = !gameState.includes("");
36 if (roundDraw) {
37 statusDisplay.innerHTML = drawMessage();
38 gameActive = false;
39 return;
40 }
41/*
42If we get to here we know that the no one won the game yet,
43and that there are still moves to be played, so we continue by changing the current player.
44*/
45 handlePlayerChange();
46}
handlePlayerChange
این تابع بازیکن کنونی را عوض کرده و حالت بازی را بهروزرسانی میکند. در این بخش از عملگر سهتایی برای انتساب مقدار به بازیکن جدید استفاده میکنیم.
1function handlePlayerChange() {
2 currentPlayer = currentPlayer === "X" ? "O" : "X";
3 statusDisplay.innerHTML = currentPlayerTurn();
4}
handleRestartGame
این تابع همه ردپاهای بازی را پاک میکند. همه نشانههای روی صفحه بازی حذف میشوند و حالت بازی به وضعیت ابتدایی تغییر مییابد.
1function handleRestartGame() {
2 gameActive = true;
3 currentPlayer = "X";
4 gameState = ["", "", "", "", "", "", "", "", ""];
5 statusDisplay.innerHTML = currentPlayerTurn();
6 document.querySelectorAll('.cell')
7 .forEach(cell => cell.innerHTML = "");
8}
سخن پایانی
این یک راهنمای مقدماتی است و بدیهی است که جای زیادی برای بهبود این بازی وجود دارد. مثلاً میتوان حالت چند بازیکنی را به آن اضافه کرد. امیدواریم از مطالعه این راهنما لذت برده و با کارکرد مقدماتی بازی دوز آشنا شده باشید.