یکی از کارایی که کامپیوترها خوب انجام میدن، تکرار کردنه. فرض کنید قراره هزار تا شکل با ویژگیهای مختلف توی برنامهتون بسازید؛ برای چنین مواردی که قراره یه کاری به دفعات زیادی تکرار کنیم، توی برنامهنویسی از ساختاری به نام حلقه یا لوپ استفاده میکنیم. حلقهها از مهمترین مفاهیم برنامهنویسی هستن. با استفاده از یه حلقه، میتونیم یه تیکه کُد رو بارها و بارها تکرار کنیم.
ما که داریم برنامهنویسی رو با 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()
}
}
}