حلقه Loop

آخرین به روزرسانی در 12 اردیبهشت 1402
نوشته شده توسط علی خادم
حلقه

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

ما که داریم برنامه‌نویسی رو با 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 یه حلقه ساختیم
  • (دوم) با کلیدواژه var یه متغیر تعریف کردیم و مقدارشو 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 نوشته بشه توی هر فریم یک بار تکرار می‌شه و برای همینه که ما می‌تونیم چیزهای مختلفو روی مانیتور به حرکت دربیاریم.

می‌تونیم به جای کشیدن این خطوط، از تابع 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()
     }
}

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

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() {
     
}

مرحله بعدی اینه که یه چیزی بکشیم. می‌خوام همه صفحه رو با مربع پُر کنم. از تابع‌های push و translate و 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) {
               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()
               
          }
     }
  
}