3 روش نوشتن حلقه در جاوااسکریپت Loop

آخرین به روزرسانی در 31 خرداد 1402
نوشته شده توسط علی خادم
3 روش نوشتن حلقه در جاوااسکریپت

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

ما که داریم برنامه‌نویسی رو با p5.js یاد می‌گیریم، از قبل با مفهوم حلقه آشنا هستیم: تابع draw یه لوپه که داره یه تیکه کُد رو همین طوری پشت سر هم تکرار می‌کنه تا وقتی که برنامه بسته بشه؛ اما خودمون چطوری می‌تونیم چنین لوپی بنویسیم؟

روش اول: ساختن حلقه فور

توی جاوااسکریپت برای به ساختن یه لوپ چند تا راه وجود داره. من معمولاً از for استفاده می‌کنم. برنامه ما با استفاده از for می‌تونه یک عملیاتو به دفعاتی که خودمون مشخص می‌کنیم، تکرار کنه. یه لوپِ for از پنج عنصر تشکیل می‎شه:

  • کلیدواژه for ـ دربرگیرنده کلِ حلقه
  • متغیر اولیه ـ یه جور شمارنده که معمولاً از 0 شروع می‌شه
  • شرط ـ شرطی که تا وقتی true باشه لوپ ادامه پیدا می‌کنه
  • به‌روزرسانی متغیر اولیه ـ آخر هر تکرار، متغیر اولیه به‌روز می‌شه
  • عملیات ـ اتفاق‌هایی که توی هر بار تکرار حلقه می‌افته
function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {
     background(200)
     fill(220, 30, 90)
     noStroke()

     // a for loop
     for(let i = 0; i < 10; i++) {
          ellipse(i*50, 0, 50, 50)
     }

}
کشیدن دایره با استفاده از حلقه

توی مثالِ ساده‌ی بالا می‌تونید پنج مرحله ساختن یه حلقه رو ببینید:

  • (اول) با کلیدواژه for یه حلقه ساختیم
  • (دوم) با کلیدواژه let یه متغیر تعریف کردیم و مقدارشو 0 قرار دادیم
  • (سوم) شرط ادامه حلقه رو کوچک‌‌تر از 10 بودن متغیر اولیه مشخص کردیم
  • (چهارم) تعیین کردیم که بعد از هر بار تکرار حلقه، یه واحد به متغیر اولیه اضافه بشه
  • (پنجم) عملیاتی که حلقه قراره انجام بده رو مشخص کردیم که کشیدن یه دایره‌س

وقتی برنامه رو اجرا کنید می‌بینید که ده تا دایره روی صفحه کشیده می‌شه؛ چون یازدهمین بار که حلقه اجرا می‌شه، متغیر اولیه‌ی ما 11 شده و در این صورت شرط حلقه، یعنی i < 10 دیگه true نیست، حلقه بسته می‌شه و تمام.

روش دوم: ساختن حلقه‌ با استفاده از آرایه

آرایه‌ها رو که یادتون هست؟ یه راه دیگه برای ساختن حلقه‌ی for استفاده کردن از یه آرایه‌س؛ توی این حالت، نوشتن حلقه یه خرده ساده‌تره و دیگه نیازی به تعریف کردن یه متغیر و این جور کارا ندارید؛ ببینید چطور نوشته می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight);

     // a for loop
     const planets = ['mercury', 'venus', 'earth']
     for(planet of planets) {
          console.log(planet)
     }

}
function draw() {
  
}
ساختن حلقه با استفاده از آزایه

روش سوم: ساختن حلقه وایل

یه جور حلقه دیگه هم هست به نام while که تا زمانی که یه شرط true باشه تکرار می‌شه. توی مثال پایین این قدر حلقه تکرار می‌شه که متغیر myValue به عدد صد برسه؛ توی هر بار اجرا شدن حلقه هم عدد 10 به myValue اضافه می‌شه:

function setup() {
     createCanvas(windowWidth, windowHeight);

     // a while loop
     let myValue = 0
     while(myValue < 100) {
          myValue += 10
          console.log('The value of myValue is ' + myValue)
     }

}
function draw() {
  
}
ساختن حلقه وایل

در عمل حلقه‌ها به چه دردی می‌خورن؟

حالا که با حلقه‌ها آشنا شدیم می‌تونیم ببینیم توی یه پروژه واقعی چطوری می‌شه از حلقه‌ها استفاده کرد. از یه مثال ساده شروع می‌کنم: توی این اسکیس می‌خوام یه مربع بکشم و توشو هاشور بزنم.

const rectWidth = 100

function setup() {
     createCanvas(windowWidth, windowHeight)
     rectMode(CENTER)
     background(200)
     fill(235)
     noLoop()
}
function draw() {
  
     // square
     rect(windowWidth/2, windowHeight/2, rectWidth)

     // hatch
     translate(windowWidth/2 - rectWidth/2, windowHeight/2 - rectWidth/2)
     for(let i = 0; i < rectWidth; i += 10) {
          line(i, 0, 0, i)
     }
     for(let i = 0; i < rectWidth; i += 10) {
          line(rectWidth, i, i, rectWidth)
     }

}
یک مربع در وسط تصویر

ببینیم توی مثال بالا دقیقاً چه اتفاقی افتاده:

اول وسط بوم یه مربع کشیدم؛ برای این کار از متغیری به نام rectWidth استفاده کردم که در واقع اندازه مربعی که قراره کشیده بشه رو مشخص می‌کنه. می‌تونید تغییرش بدید و نتیجه رو ببینید. همچنین از پارامتر CENTER توی تابع rectMode استفاده کردم که باعث می‌شه مربع از مرکز خودش جانمایی بشه.

بعد با استفاده از تابع translate نقطه صفر و صفر بوم رو به گوشه مربع منتقل می‌کنم.

حالا باید از حلقه for استفاده کنم تا با فاصله ده پیکسل ده پیکسل و با استفاده از تابع line نقاط روی اضلاع مربع رو به هم وصل کنم. همون طور که می‌بینید برای کشیدن خطوط نیمه بالای مربع از یه حلقه استفاده کردم و برای کشیدن خطوط نیمه پایین مربع، از یه حلقه دیگه.

توی اسکیس بعدی می‌خوام از یه حلقه for استفاده کنم و یه ستاره بکشم:

function setup() {
     createCanvas(windowWidth, windowHeight)
}
function draw() {
     background(255)
     strokeWeight(10)

     // move canvas origin to center
     translate(width / 2, height / 2)

     // number of lines
     const circleResolution = int(map(mouseY, 0, height, 1, 15))

     // line length
     const radius = mouseX - width

     // spread lines in a circular shape 
     const angle = TWO_PI / circleResolution

     // loop
     for (let i=0; i<=circleResolution; i++) {
          const x = cos(angle * i) * (radius/5);
          const y = sin(angle * i) * (radius/5);
          line(0, 0, x, y);
     }
  
}
یک ستاره که با استفاده از یک حلقه به وجود آمده است

اگه این کد رو اجرا کنید می‌بینید که اندازه و تعداد پرهای ستاره با حرکت دادن ماوس روی صفحه تغییر می‌کنه؛ قبلاً یاد گرفتیم که هر کدی که توی تابع draw نوشته بشه توی هر فریم یک بار تکرار می‌شه و برای همینه که ما می‌تونیم چیزهای مختلفو روی مانیتور به حرکت دربیاریم.

توی اسکیس بالا هم از تابع translate استفاده کردم تا نقطه صفر و صفرِ بوم رو به وسط صفحه منتقل کنم. بعد از تابع map استفاده کردم و تعداد پَرهای ستاره رو نسبت به موقعیت ماوس توی متغیر circleResolution ذخیره کردم. بعد بازم بر اساس موقعیت ماوس طول پَرهای ستاره رو توی متغیر radius ذخیره کردم. بعد بر اساس تعداد پَرهای ستاره، زاویه بین هر پَر ستاره رو مشخص کردم و در نهایت یه لوپ نوشتم تا پَرهای ستاره رو بر اساس متغیرهایی که تعریف کردم بکشه.

می‌تونیم به جای کشیدن این خطوط، از تابع vertex که قبلاً یادگرفتیم استفاده کنیم و شکل‌های مختلف تولید کنیم:

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     background(255)
     strokeWeight(3)
     stroke(0, 25)
  }
  
function draw() {
     if (mouseIsPressed && mouseButton == LEFT) {
          push()
          translate(windowWidth / 2, windowHeight / 2)
          const circleResolution = int(map(mouseY + 100, 0, windowHeight, 3, 10))
          const radius = mouseX - width / 2
          const angle = TWO_PI / circleResolution

          // shape
          beginShape()
          for (let i = 0; i <= circleResolution; i++) {
               const x = cos(angle * i) * radius
               const y = sin(angle * i) * radius
               vertex(x, y)
          }
          endShape()

          pop()
     }
}

حلقه های تو در تو

توی حلقه‌ها می‌شه دوباره حلقه نوشت؛ به این الگو می‌گن حلقه‌های تودرتو؛ این الگو خیلی رایجه و معمولاً برای درست کردن یه شبکه استفاده می‌شه؛ مثلاً به این اسکیس نگاه کنید؛ توی این اسکیس با شکل دایره یه شبکه روی بوم می‌سازیم که تعداد خونه‌های این شبکه توسط متغیر tileCount مشخص می‌شه و شعاع هر دایره بر اساس موقعیت مکانی ماوس محاسبه می‌شه:

const tileCount = 15
const circleAlpha = 130

function setup() {
     createCanvas(windowWidth, windowHeight);
}
function draw() {
     background(255)
     randomSeed(0)
     strokeWeight(5)
     translate(windowWidth / tileCount / 2, windowHeight / tileCount / 2)
  
     for (let gridY = 0; gridY < tileCount; gridY++) {
          for (let gridX = 0; gridX < tileCount; gridX++) {

               const posX = windowWidth / tileCount * gridX
               const posY = windowHeight / tileCount * gridY
               ellipse(posX, posY, mouseY / tileCount)
    
          }
     }
}

بی‌نظمی مکعبی

این یکی یه اسکیسِ معروفه به نام «بی‌نظمی مکعبی» که با الگوی حلقه‌های تودرتو نوشته می‌شه؛ این مورد رو با همدیگه به صورت قدم به قدم می‌نویسیم: با یه اسکیس ساده شروع می‌کنیم که شامل setup و draw باشه و از noLoop استفاده می‌کنیم چون یه اسکیس استاتیک یا ایستا داریم درست می‌کنیم که نیازی به انیمیشن نداره؛ یه متغیر هم نیاز داریم برای ذخیره اندازه مربع‌ها:

const squareSize = 50

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     noLoop()
     background(220)
}
function draw() {
     
}

مرحله بعدی اینه که یه چیزی بکشیم. می‌خوام همه صفحه رو با مربع پُر کنم. سایز مربع‌ها بر اساس متغیر squareSize مشخص می‌شه. از تابع‌های push و translate و pop داخل حلقه‌های تودرتو استفاده می‌کنم تا ردیف‌ها و ستون‌های شبکه رو تعریف کنم و با تابع rect مربع‌ها رو می‌کشم:

const squareSize = 50

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     noLoop()
     strokeWeight(3)
     background(220)
}
function draw() {
     for(let x = squareSize*2; x <= width - squareSize*2; x += squareSize) {
          for(let y = squareSize*2; y <= height - squareSize*2; y += squareSize) {
               push()
	             translate(x, y)
               rect(-squareSize/2, -squareSize/2, squareSize)
               pop()
          }
     }
}

حالا می‌خوایم نظمشونو به هم بزنیم، طوری که هر چی به سمت پایین بوم بریم، بی‌نظمی بیشتری رو ببینیم:

const squareSize = 50

function setup() {
     createCanvas(windowWidth, windowHeight)
     noFill()
     noLoop()
     strokeWeight(3)
     background(220)
}
function draw() {
     for(let x = squareSize*2; x <= width - squareSize*2; x += squareSize) {
          for(let y = squareSize*2; y <= height - squareSize*2; y += squareSize) {

               // random rotation
               const rotation = y/height * QUARTER_PI/45 * random() * 25;

               // create each square
               push()
               translate(x, y);
               rotate(rotation);
               rect(-squareSize/2, -squareSize/2, squareSize)
               pop()
               
          }
     }
  
}

ارسال نظر

ایمیل شما منتشر نخواهد شد. بخش‌های ضروری با * مشخص شده‌اند.