در قسمت قبل به فرآیند باخت کاربر پرداختیم؛ اگر کاربر بازی را باخت چه اتفاقی بیفتد؟ چه تغییراتی در استایل کار ایجاد شود؟ چه پیامی به او نمایش داده شود؟ ما تمام این جزئیات و مسائل را کدنویسی کردیم و در حال حاضر برنامه ی ما در حالت کاملا عملی و بدون مشکل قرار دارد اما اگر کاربر بازی را ببرد یا ببازد فقط پیام برد و باخت را مشاهده می کند و هیچ اتفاق دیگری نمی افتد. ما نمی خواهیم در این برنامه از انیمیشن های سنگین برای تبریک به کاربر یا چنین عناصری استفاده کنیم اما حداقل باید به کاربر این گزینه را بدهیم که بتواند بازی را دوباره انجام دهد.
برای شروع کار به تابع gameOver بروید. ما باید در این قسمت ابتدا کارکرد دکمه ی submit را تغییر دهیم و کاری کنیم که به جای عبارت submit چیزی شبیه به Play Again (یعنی «دوباره بازی کن») نمایش داده شود:
// Game over function gameOver(won, msg) { let color; won === true ? color = 'green' : color = 'red'; // Disable input guessInput.disabled = true; // Change border color guessInput.style.borderColor = color; // Set text color message.style.color = color; // Set message setMessage(msg); // PLay Again? guessBtn.value = 'Play Again'; guessBtn.className += 'play-again'; }
همانطور که می بینید، ابتدا value دکمه را تغییر داده ام که باعث تغییر متن نمایش داده شده درون دکمه می شود. سپس یک کلاس به نام play-again را برایش ایجاد کرده ام.
سوال: چرا کلاس جدیدی برای این دکمه ساخته ایم؟
پاسخ: نقشه ی ما این است که در صورت اتمام بازی (فارغ از اینکه کاربر بازی را باخته یا برده باشد) دکمه ی submit نیز تغییر کند و کاربر با کلیک روی آن، صفحه ی مرورگر را refresh کند. از آنجایی که از local storage استفاده نمی کنیم، با refresh شدن صفحه اطلاعات قبلی پاک می شود و دوباره کاربر 3 شانس برای حدس زدن عدد صحیح خواهد داشت. البته باید عدد انتخاب شده را نیز به صورت تصادفی انتخاب کنیم، در غیر این صورت کاربر پاسخ صحیح را از دور قبلی یاد می گیرد. قرار است تمام این کارها در قالب یک event-listener انجام شود بنابراین اضافه کردن کلاس باعث می شود بتوانیم این دکمه را با event-listener خود هدف قرار دهیم.
هدف قرار گرفتن این دکمه توسط event-listener آن قدر ها هم آسان نیست! کلاس play-again از ابتدا روی دکمه موجود نیست و بعدا به آن اضافه می شود. بنابراین هنگام لود شدن صفحه و کدهای جاوا اسکریپت، event-listener به ما خطا خواهد داد. چرا؟ به دلیل اینکه به دنبال دکمه ای با کلاس play-again خواهد گشت اما چنین دکمه ای در ابتدا وجود ندارد، بلکه تنها زمانی به وجود می آید که کاربر بازی را برده یا ببازد. راه حل این مشکل استفاده از مفهوم event delegation است. در جلسات قبل در مورد event delegation توضیح داده بودم بنابراین اگر آن را فراموش کرده اید می توانید به قسمت سوم این مجموعه مقاله مراجعه کنید.
من event-listener خود را در ابتدای کدها (بعد از تعریف مقدار min و max) تعریف می کنم:
game.addEventListener('click', function (e) { console.log(1); });
برای نشان دادن یک موضوع مهم به شما، event خودم را از نوع click انتخاب کرده و عدد 1 را در کنسول مرورگر چاپ کرده ام. با این کد به مرورگر بروید و در یک قسمت از div با id=game کلیک کنید (به غیر از خود دکمه). متوجه خواهید شد که با کلیک روی هر قسمتی از div (حتی قسمت های خالی) عدد 1 در کنسول چاپ می شود اما ما می خواهیم این event-listener تنها زمانی اجرا شود که روی دکمه ی Play Again کلیک شود. برای مطمئن شدن از هدف کلیک از target استفاده می کنیم:
game.addEventListener('click', function (e) { if (e.target.className === 'play-again') { window.location.reload(); } });
در کد بالا گفته ایم اگر عنصری که روی آن کلیک کرده ایم دارای کلاس play-again است، صفحه را reload کن! این کد را ذخیره کرده و به مرورگر بروید. متوجه خواهید شد که به محض کلیک روی دکمه ی submit صفحه refresh می شود. توجه داشته باشید که کاربر هنوز بازی را نبرده یا نباخته بنابراین دکمه ی play again را نداریم و در مورد دکمه ی submit صحبت می کنیم. در این حالت کاربر زمان کافی را ندارد تا پیام ما مبنی بر برنده شدن یا باختن خود را بخواند. دلیل این مشکل استفاده از click است. ما باید آن را با mousedown عوض کنیم. در واقع رویداد click خود از دو مرحله ی جداگانه ساخته شده است:
بنابراین اگر از رویداد click استفاده کنیم به محض کلیک کردن روی دکمه (فشار دادن کلیک و رها کردن آن) صفحه refresh می شود یا به عبارتی event-listener ما اجرا می شود اما اگر از رویداد mousedown استفاده کنیم باید ابتدا روی دکمه کلیک کنیم که این کلیک خودش یک mousedown حساب می شود اما فعلا کلاس play-again به آن اضافه نشده است، سپس این کلاس به آن اضافه می شود و event-listener آماده است که mousedown را انجام دهید تا اجرا شود.
بنابراین کد صحیح به شکل زیر است:
// Play again event listener game.addEventListener('mousedown', function (e) { if (e.target.className === 'play-again') { window.location.reload(); } });
حالا تنها کاری که باقی مانده است، تصادفی کردن عدد انتخاب شده می باشد. در حال حاضر ما خودمان این عدد را به صورت دستی روی 2 گذاشته ایم:
// Game values let min = 1, max = 10, winningNum = 2, guessesLeft = 3;
روش بهتر این است که این عدد تصادفی و توسط خود جاوا اسکریپت انتخاب شود. بنابراین کد بالا را به شکل زیر تغییر می دهیم:
// Game values let min = 1, max = 10, winningNum = getRandomNum(min, max), guessesLeft = 3;
یعنی انتخاب عدد را بر عهده ی تابعی به نام getRandomNum (مخفف get random number) قرار داده ایم. این تابع قرار است دو پارامتر بگیرد تا محدوده ی عدد انتخاب شده مشخص شود. به عبارت دیگر اگر قرار است عددی به صورت تصادفی توسط جاوا اسکریپت انتخاب شود باید محدوده ی آن را مشخص کنیم در غیر انی صورت ممکن است عدد تصادفی ما 0.25418754628 باشد! حالا به پایین کدها می رویم و قبل از تعریف تابع setMessage تابع جدید خودمان را تعریف می کنیم. به نظر شما آیا صحیح است که این تابع را به شکل زیر تعریف کنیم؟
// Get Winning Number function getRandomNum(min, max) { console.log(Math.random()); }
اکثر توسعه دهندگان می دانند که تابع random یک عدد تصادفی به ما می دهد اما مشکل اینجاست که این عدد تصادفی همیشه بین صفر و یک خواهد بود به صورتی که صفر را شامل شده اما یک را شامل نمی شود (هیچ وقت برابر 1 نمی شود و همیشه کمتر از آن است). برای آنکه این موضوع را به شما نشان دهم، آن را Console.log کرده ام:
همانطور که می بینید کوچک بودن این عدد تصادفی تنها یک قسمت از مشکل ما است! مشکل دیگر این است که این عدد 16 رقم اعشار دارد که حدس زدن آن را برای کاربر غیرممکن می کند! بنابراین باید برای حل این مشکل فرمولی بسازیم:
// Get Winning Number function getRandomNum(min, max) { console.log(Math.random() * (max - min + 1)); }
در این فرمول عدد اعشاری عجیب و غریب را از random می گیریم و در پرانتز بعدی ضرب می کنیم که می گوید حداکثر منهای حداقل به علاوه ی یک. اگر الان به مرورگر برویم با هر بار refresh صفحه یک مقدار بین 0 تا 10 می گیریم که خود 10 را شامل نمی شود (البته هنوز هم اعشار داریم):
برای آنکه خود عدد 10 را نیز شامل شود می توانیم min را به آن اضافه کنیم:
// Get Winning Number function getRandomNum(min, max) { console.log(Math.random() * (max - min + 1) + min); }
حالا خود عدد 10 نیز جزو نتایج محتمل خواهد بود.
در مرحله ی بعد باید اعشار عدد را حذف کنیم که به راحتی با math.floor انجام می شود:
// Get Winning Number function getRandomNum(min, max) { console.log(Math.floor(Math.random() * (max - min + 1) + min)); }
تابع floor اعداد را به سمت پایین (مقدار کمتر) گرد می کند. حالا که مطمئن شده ایم می توانیم به جای console.log آن را return کنیم:
// Get Winning Number function getRandomNum(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); }
برنامه ی ما تکمیل شده است! حالا یک بازی کاملا جاوا اسکریپتی داریم و به تک تک قسمت های آن آشنا هستیم. پیشنهاد من این است که به جای تمرکز روی زیبایی بازی و... روی مفاهیم ارائه شده در این مقاله تمرکز کنید تا ملکه ی ذهنتان شود. در مقالات بعد روی پروژه های سنگین تری تمرکز خواهیم کرد و هر چه جلوتر می رویم با پروژه های پیشرفته تری روبرو می شویم که جلسات بیشتری را به خود اختصاص خواهند داد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.