مقدمه

بازی حدس عدد یک مسئله‌ی کلاسیک مقدماتی در برنامه‌نویسی است که ما در اینجا قصد داریم آن را با استفاده از زبان برنامه‌نویسی جاوااسکریپت در محیط Node.js پیاده‌سازی کنیم. کارکرد این بازی به این صورت است که ابتدا برنامه یک عدد صحیح تصادفی بین 1 تا 100 تولید می‌کند و سپس از بازیکن خواسته می‌شود که این عدد را حدس بزند. اگر حدس او صحیح باشد، یک پیغام تبریک برای وی نمایش داده خواهد شد و در غیر این صورت، پیغامی نمایش داده خواهد شد که مشخص می‌کند عددی که حدس زده از عدد مورد نظر کوچکتر است یا بزرگتر تا بازیکن حدس بعدی را بر اساس آن تعیین کند.


ایجاد و تنظیم یک پروژه جدید

جاوااسکریپت یک زبان تفسیری یا Interpreted است که تفسیر و اجرای کدها را در یک مرحله انجام می‌دهد. کدهای نوشته‌شده به این زبان برای اجرا به یک مفسر و به بیان دقیق‌تر به یک محیط میزبان نیاز دارند. محیط‌های میزبان مانند مرورگرهای وب و Node.js علاوه بر مفسر، مجموعه‌ای شامل چندین کتابخانه و API نیز دارند که قابلیت‌هایی مانند I/O و Networking را فراهم می‌کنند. ما در اینجا برای نوشتن بازی حدس عدد از Node.js به عنوان محیط میزبان کدهای جاوااسکریپت خود استفاده می‌کنیم. بنابراین، قبل از هر چیز از نصب بودن Node.js روی سیستم خود مطمئن شوید.

برای ایجاد یک پروژه‌ی Node ابتدا یک ترمینال باز کنید و کامندهای زیر را اجرا کنید:

$ mkdir guess-the-number
$ cd guess-the-number
$ npm init -y
وجود کاراکتر $ در ابندای هر کامند بالا به این معناست که این کامندها در ترمینال لینوکس نوشته شده و نتیجتاً توسط شِل‌هایی مانند bash اجرا شده یا برای اجرا به کرنل واگذار می‌شوند. اگر از ویندوز استفاده می‌کنید، می‌توانید این کامندها را در PowerShell به همین صورت اجرا کنید. دقت کنید که PowerShell با Windows PowerShell فرق دارد. Windows PowerShell که به طور پیش‌فرض روی ویندوز نصب است، مختص همین سیستم‌عامل است اما PowerShell یک نسخه‌ی Cross-platform از این شل است که به نصب دستی نیاز دارد. در ضمن، از Git Bash هم می‌توانید روی هر سیستم‌عاملی استفاده کنید. ابزاری مثل WSL هم می‌تواند یک انتخاب مناسب دیگر باشد که امکان استفاده از توزیع‌های لینوکسی را در دل ویندوز فراهم می‌کند.

کامند اول یک دایرکتوری با نام guess-the-number ایجاد می‌کند که دایرکتوری اصلی یا ریشه‌ی (root) پروژه‌ی ما خواهد بود. سپس با دستور cd به این دایرکتوری منتقل شده و با کامند npm init یک پروژه‌ی Node را راه‌اندازی کرده‌ایم. در واقع، اجرای این کامند باعث می‌شود که فایلی با نام package.json ایجاد شود که محل نگهداری اطلاعات مربوط به پروژه، وابستگی‌ها (dependencies) و گزینه‌های تنظیماتی پروژه است.

بعد از ایجاد و راه‌اندازی پروژه، دایرکتوری guess-the-number را در یک IDE مانند VSCode باز کنید. اگر به محتویات فایل package.json نگاه کنید، عبارت main: index.js را خواهید دید که مشخص می‌کند فایل اصلی پروژه‌ی ما که در نهایت اجرا خواهد شد، index.js نام دارد. می‌توانید آن را تغییر داده و نام دیگری مثل app.js یا main.js یا حتی نام خود پروژه را به جای index.js وارد کنید. سپس، فایلی با همین نام ایجاد کنید. ما این فایل را به app.js تغییر می‌دهیم و فایل app.js را ایجاد می‌کنیم. حالا کد پروژه را مطابق گام‌هایی که در ادامه شرح داده خواهند شد، در فایل app.js وارد می‌کنیم.

بازی حدس عدد یک پروژه‌ی کوچک و کم‌حجم است و بنابراین، کل کد مورد نیاز این پروزه را در فایل app.js که در واقع، تنها ماژول برنامه‌ی ماست، می‌نویسیم. در مورد برنامه‌های بزرگتر، کدها در چند ماژول تقسیم شده و ماژول‌ها به هم دسترسی دارند. محتویات فایل app.js برای پروژه‌ی بازی حدس عدد در نهایت به صورت زیر خواهد بود.

GitHub Icon Copy Icon JAVASCRIPT
const readline = require('readline');

// Function to generate a random number between min and max (inclusive)
function generateRandomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
            
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});
            
console.log("Guess the number!");
            
const secretNumber = generateRandomNumber(1, 100);
            
function askGuess() {
    rl.question('Please input your guess: ', (input) => {
        const guess = parseInt(input.trim());
            
        if (isNaN(guess)) {
            console.log("Invalid input, please enter a number.");
            askGuess();
            return;
        }
            
        console.log(`You guessed: ${guess}`);
            
        if (guess < secretNumber) {
            console.log("Too small!");
            askGuess();
        } else if (guess > secretNumber) {
            console.log("Too big!");
            askGuess();
        } else {
            console.log("You win!");
            rl.close();
        }
    });
}
            
askGuess();

اگر همه‌چیز در مورد کدهای بالا برایتان روشن است که هیچ، اما در غیر این صورت در ادامه می‌توانید توضیحات مربوط به بخش‌های مختلف کدها را ببینید.

گام ۱: ماژول readline را به پروژه وارد کنید.

Copy Icon JAVASCRIPT
const readline = require('readline');     

همانطور که گفتیم، یک محیط میزبان جاوااسکریپت مانند Node علاوه بر مفسر، تعدادی API نیز دارد که قابلیت‌هایی مانند I/O را فراهم می‌کنند. این چیزی نیست که منحصر به جاوااسکریپت باشد و اساساً در هر زبانی چنین قابلیت‌هایی توسط محیط میزبان یا پلتفرم آن زبان ارائه می‌شود. ماژول readline بخشی از Node است که امکان خواندن دیتا از ورودی را فراهم می‌کند. با استفاده از تابع require این ماژول را به پروژه وارد (import) می‌کنیم و آن را در متغیری با همین نام ذخیره می‌کنیم. حالا از طریق متغیر readline به امکانات این ماژول دسترسی داریم.

گام ۲: ایجاد تابع تولید اعداد تصادفی

Copy Icon JAVASCRIPT
// Function to generate a random number between min and max (inclusive)
function generateRandomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

در اینجا تابعی با نام generateRandomNumber تعریف کرده‌ایم که دو پارامتر ورودی با نام‌های min و max ذزیافت کرده و یک عدد صحیح تصادفی در بازه‌ی [min, max] تولید می‌کند.

دقت کنید که ما در اینجا از نمادگذاری مرسوم در ریاضیات برای نمایش بازه‌ها استفاده کرده و می‌کنیم. برای کسانی که با این نمادگذاری آشنا نیستند، عرض می‌کنم که بازه‌ی [a, b] متشکل از اعداد بین a و b و خود این دو عدد است. چنین بازه‌ای اصطلاحاً بسته از هر دو طرف نامیده می‌شود. اما بازه‌ی (a, b) که از دو طرف باز گفته می‌شود، شامل خود a و b نیست. به همین ترتیب، بازه‌ی [a, b) که از چپ بسته و از راست باز است، شامل a هست اما شامل b خیر.

تابع Math.random() یک تابع built-in است که یک عدد تصادفی در بازه‌ی [0, 1) تولید می‌کند. وقتی این عدد در مقدار max – min + 1 ضرب شود، به عددی در بازه‌ی [0, max – min + 1) تبدیل می‌شود و وقتی این عدد به عنوان آرگومان به تابع Math.floor() پاس شود، رو به پایین گِرد می‌شود و به یک عدد صحیح در بازه‌ی [0, max – min] تبدیل می‌شود. و بالاخره با افزودن مقدار min به عدد تولید شده، عدد مورد نظر از بازه‌ی [min, max] خواهد بود.

پس، ما حالا تابعی داریم که دو عدد را دریافت کرده و عدد صحیحی بین آن دو را به طور تصادفی تولید کرده و برمی‌گرداند.

گام ۳: ایجاد اینترفیس readline

Copy Icon JAVASCRIPT
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

در اولین خط، ماژول readline را به پروژه وارد کردیم و الان قصد داریم از امکانات این ماژول استفاده کنیم. ماژول readline دارای متدی است به نام createInterface که همانطور که نامش نشان می‌دهد، یک اینترفیس برای خواندن دیتا از استریم‌های قابل خواندن (مثل process.stdin) و نوشتن دیتا در استریم‌های قابل نوشتن (مثل process.stdout) تعریف می‌کند. در اینجا ما یک اینترفیس readline را ایجاد کرده‌ایم و stdin را به عنوان input و stdout را به عنوان output تعیین کرده‌ایم. با این کار، کنسول را هم به عنوان استریم خواندن داده‌ها و هم به عنوان استریم نوشتن داده‌ها مشخص کرده‌ایم. در گام ۶ از این اینترفیس برای تعامل با کاربر استفاده می‌کنیم.

گام ۴: نمایش پیغام ابتدایی

Copy Icon JAVASCRIPT
console.log("Guess the number!");

گام ۵: تولید عدد تصادفی

Copy Icon JAVASCRIPT
const secretNumber = generateRandomNumber(1, 100);

تابعی را که در بالا برای تولید اعداد تصادفی تعریف کردیم، فراخوانی کرده و اعداد 1 و 100 را به عنوان پارمترهای این مند فراهم می‌کنیم. در نتیجه، یک عدد صحیح بین 1 تا 100 تولید شده و در متغیری به نام secretNumber ذخیره می‌شود.

گام ۶: تعریف یک تابع برای مدیریت ورودی کاربر

Copy Icon JAVASCRIPT
function askGuess() {
  rl.question('Please input your guess: ', (input) => {
      const guess = parseInt(input.trim());
                      
      if (isNaN(guess)) {
          console.log("Invalid input, please enter a number.");
          askGuess();
          return;
      }
                      
      console.log(`You guessed: ${guess}`);
                      
      if (guess < secretNumber) {
          console.log("Too small!");
          askGuess();
      } else if (guess > secretNumber) {
          console.log("Too big!");
          askGuess();
      } else {
          console.log("You win!");
          rl.close();
      }
  });
}

حالا باید از کاربر بخواهیم حدس خود را وارد کند و پردازش لازم را روی آن انجام دهیم. در گام ۳ ما یک اینترفیس readline ایجاد کردیم و آن را در متغیری به نام rl ذخیره کردیم. بنابراین، rl یک شیء اینترفیس است و به متدهایی مثل question دسترسی دارد. این متد دارای فرم کلی question(query, callback) است که در آن query یک رشته (string) است که در کنسول نمایش داده می‌شود و بعد از پاسخ کاربر، تابع callback اجرا می‌شود. از آنجایی که ورودی از نوع string در نظر گرفته می‌شود، ابتدا با استفاده از متد trim فاصله‌های سفیدی را که ممکن است در ابتدا و انتهای آن باشد، حذف کرده و سپس با استفاده از متد parseInt آن را به یک عدد صحیح تبدیل می‌کنیم و نتیجه را در متغیر guess ذخیره می‌کنیم.

با توجه به اینکه هر رشته‌ای را نمی‌توان به یک عدد صحیح تبدیل کرد، متد isNaN بررسی می‌کند که آیا این تبدیل انجام‌شدنی است یا خیر. اگر تبدیل شدنی نباشد، یک پیغام خطا نمایش داده شده و مجدداً تابع اجرا می‌شود. در نهایت هم حدس کاربر با عدد تصادفی مقایسه می‌شود و اگر درست باشد، اینترفیس با متد rl.close() بسته می‌شود. در صورت نادرست بودن حدس کاربر هم، پیغام مناسب بر حسب اینکه حدس کاربر از عدد مورد نظر برنامه کوچکتر یا بزرگتر است، نمایش داده می‌شود و مجدداً تابع اجرا می‌شود تا کاربر حدس بعدی را وارد کند.

گام ۷: اجرای تابع askGuess()

Copy Icon JAVASCRIPT
askGuess();

ما منطق برنامه را در تابعی به نام askGuess نوشته‌ایم و برای اینکه این تابع برای بار اول اجرا شود، این تابع را فراخوانی می‌کنیم.

حالا می‌توانید یک بار دیگر وضعیت نهایی فایل app.js را ببینید.

GitHub Icon Copy Icon JAVASCRIPT
const readline = require('readline');

// Function to generate a random number between min and max (inclusive)
function generateRandomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
            
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});
            
console.log("Guess the number!");
            
const secretNumber = generateRandomNumber(1, 100);
            
function askGuess() {
    rl.question('Please input your guess: ', (input) => {
        const guess = parseInt(input.trim());
            
        if (isNaN(guess)) {
            console.log("Invalid input, please enter a number.");
            askGuess();
            return;
        }
            
        console.log(`You guessed: ${guess}`);
            
        if (guess < secretNumber) {
            console.log("Too small!");
            askGuess();
        } else if (guess > secretNumber) {
            console.log("Too big!");
            askGuess();
        } else {
            console.log("You win!");
            rl.close();
        }
    });
}
            
askGuess();