Skip to content
От: Божидар Маринов Дата: Последна промяна:

Урок 1, Съхранение на повече стойности с масиви

Допълнителни линкове към този урок:

Основни точки от този урок

Продължавайки дискусията за типове стойности, масивът е стойност, която е списък от други стойности. Масивите ни позволяват да съхраним повече от едно "нещо" в дадена променлива. Обикновено те вървят ръка за ръка с циклите, и ни помагат да автоматизираме дори повече неща.

За да създадем стойност от тип масив ("array"), трябва само да напишем две квадратни скоби, [ и ], и между тях да изредим стойностите, които са част от масива.

Така например, този код създава масив и го записва в променливата c:

let c = [4, 10, 12]

По този начин, в c съм записал няколко неща (в случая, три числа), без да трябва да използвам няколко различни променливи.

За да взема някоя стойност от масива, отново използвам квадратни скоби. Този път ги слагам точно след масива (например масив[...]), а в квадратните скоби пиша номера (започващ от нула) на стойността, която искам да получа.

Така например, ако искам да прочета първото число в c, мога да напиша следното:

// ...
let c = [100, 200, 300]
function draw() {
  // ...
  rect(c[0], 10, 10, 10)
}

Този код би нарисувал правоъгълник с разстояние от левия край равно на първото число в масива c, т.е. 100.

Ако сменя кода да използва c[2] вместо c[0], то ще получа правоъгълник на мястото определено от третото число в масива, тоест 300.

Ако искам да променя някоя от стойностите в един масив, мога да действам подобно на начина, по който сменяме стойността на една променлива; отново си взимам конкретния елемент от масива с квадратни скоби след масива, а след това с = оказвам новата стойност (масив[...] = ...). Например с кода отгоре, може да пробвате следното в конзолата:

c
// [100, 200, 300] // началната стойност на c
c[0] = 150
// 150             // променяме c[0]
c
// [150, 200, 300] // новата стойност на c

Между другото, може да създаваме и нови елементи по този начин, ако дадем номер на стойност, след края на масива. Продължавайки същия пример:

c[3] = 0
// 0                  // променяме c[3] (четвърта стойност)
c
// [150, 200, 300, 0] // c вече има четири стойности

Когато използваме квадратни скоби за да прочетем или променим някой елемент от един масив, в скобите може да сложим израз, който да произведе число. Това ни позволява да използваме променливи, за да изберем правилния елемент от масива, което е много полезно когато правим цикли, както ще видим в примера по-надолу.

c[1] // втори елемент
c[1 + 1] // = c[2], трети елемент
let а = 0
c[а] // = c[0], първи елемент
a = 1
c[а] // = c[1], втори елемент (без да променяме тази линия, а само като сменяме стойността на променливата `a`)

И последно, в JavaScript може да вземем броя елементи записани в един масив използвайки .length след масива. ("length" означава дължина; думичката е малко трудна за запомняне и писане, но ако я разделите по звуци, "le" (ле), "ng" (н), "th" (меко т), може би ще ви е по-лесно да я запомните.)
Например, изпълнявайки отново код в конзолата:

c
// [100, 200, 300]  // стойността на c
c.length
// 3                // броя елементи в c
c[3] = 0
// 0                // добавяме четвърта стойност
c.length
// 4                // вече са четири

И за сега това са операциите, които ще правим с масиви:

Част 2: пример

Допълнителни линкове към този пример:

Примера като текст

За пример за това как могат да се използват масиви, нека да разгледаме нещо което бяхме правили по-рано в урока за анимации. Тогава, бяхме направили една фигурка (тогава правоъгълник, днес кръгче), която се движи от левия край на екрана до десния, използвайки променливи.

Набързо пресъздавайки примера от тогава, получаваме следния код:

function setup() {
  createCanvas(400, 400);
}

let x = 0            // Променлива, в която съхраняваме мястото на кръгчето

function draw() {
  background(220);
  
  circle(x, 100, 30) // Всеки кадър рисуваме кръгче
  x += 3             // И го отместваме леко надясно за следващия кадър
  if (x > 415) {     // Но ако излезе от края на екрана (415 защото 15 е радиуса на кръгчето)
    x = -15          // Го връщаме обратно в началото, така че анимацията да се върти
  }
}

Този код е супер, ако искаме да анимираме едно кръгче, но ако имаме няколко кръгчета със същата анимация / логика, се налага да повторим целия код (тъй като нито функции нито цикли ще могат да ни помогнат да повторим променливата няколко пъти).

Примерно за две кръгчета, ще стане нещо като това:

function setup() {
  createCanvas(400, 400);
}

let x = 0
let x2 = 100          // Повтаряме променливата  (от 100, за да е интересна анимацията)

function draw() {
  background(220);
  
  circle(x, 100, 30)
  x += 3
  if (x > 415) {
    x = -15
  }
  circle(x2, 130, 30) // И повтаряме и кода.. :/  (като преместваме кръгчето по-надолу)
  x2 += 3
  if (x2 > 415) {
    x2 = -15
  }
}

А представете си колко код бихме имали ако бяха не две, а сто кръгчета!

За да може да напишем този код по-късо, ще ни трябват масиви.

В случая, може да запишем x координатата на всяко кръгче като отделен елемент на един масив.
Тоест, вместо x = 0 и x2 = 100, ще имаме само x = [0, 100]. А по-долу в кода, за да достигнем отново тези числа, ще използваме x[0] (първия елемент от масива, т.е. 0) и x[1] (втория елемент от масива, т.е. 100), вместо предишните x и x2.

Правейки тези промени, получавам следния код:

function setup() {
  createCanvas(400, 400);
}

let x = [0, 100]        // Използваме масив за да съхраним всички стойности с само една променлива.
/// Също може да го запишем това като: 
// let x = []
// x[0] = 0    // вместо старото let x = 0
// x[1] = 100  // вместо старото let x2 = 1000

function draw() {
  background(220);
  
  circle(x[0], 100, 30) // Заместваме x с x[0]
  x[0] += 3
  if (x[0] > 415) {
    x[0] = -15
  }
  circle(x[1], 130, 30) // ..и x2 с x[1]
  x[1] += 3
  if (x[1] > 415) {
    x[1] = -15
  }
}

Така е малко по-добре; няма да ми се налага да повтарям let ... линията, тъй като всички числа са в само една променлива. Но кода за всяко кръгче все още ми се повтаря. За да не се повтаря, ще направя нещо подобно на това което правих в урока за цикъла "докато": ще използвам променлива за да може по-голямата част от кода да остане същата, а аз само да сменям стойността на променливата между повторенията.

В случая именувам променливата i, от английската дума "index", на български, индекс. Тази дума използваме за да наречем номера на един елемент в масив / списък. Тази променлива се използва много често в програмирането, когато пишем цикли (а ако i е заета, използваме j, k, l, и т.н.).

Променяйки кода да използва променлива, получавам това:

function setup() {
  createCanvas(400, 400);
}

let x = [0, 100]

function draw() {
  background(220);
  
  let i = 0             // създавам променлива за номера на кръгчето (за първото кръгче, номер 0)
  circle(x[i], 100, 30) // x[i] e x[0] тук
  x[i] += 3
  if (x[i] > 415) {
    x[i] = -15
  }
  
  i = 1                 // преминавам към второто кръгче (с номер 1)
  circle(x[i], 130, 30) // x[i] e x[1] тук
  x[i] += 3
  if (x[i] > 415) {
    x[i] = -15
  }
}

От тук насетне, остава само да го преправя да използва for цикъл. Това е подобно на начина по който писахме цикли в предните уроци, така че тук няма да го описвам в детайл; само ще отбележа, че тъй като искаме да минем през стойностите 0 и 1 (на i), то ще започнем от 0, и ще проверяваме дали променливата е по-малка от 2.

Една важна бележка тук е, че трябва също така да изчисля y координатата на кръгчето, 100 за първото и 130 за второто, използвайки i. В случая използвам 100 + 30 * i, но е възможно тук също да използваме масив.

function setup() {
  createCanvas(400, 400);
}

let x = [0, 100]

function draw() {
  background(220);
  
  for(let i = 0; i < 2; i ++) {    // Това се изпълнява два пъти, за i = 0 и i = 1
    circle(x[i], 100 + i * 30, 30) // 100 + i * 30 e 100 при i = 0 и 130 при i = 1
    x[i] += 3
    if (x[i] > 415) {
      x[i] = -15
    }
  }
}

Сега, ако искам да добавя трето кръгче, е достатъчно да добавя още едно число в масива, и долу в цикъла да сменя i < 2 с i < 3. Даже, за да не трябва да го сменям всеки път, ще го направя от сега да е i < x.length.

И по този начин получавам код, който рисува и анимира много кръгчета на веднъж! 🎉

Сега, ако искам да направя 100 кръгчета, вместо да ги описвам едно по едно в масива x (защото това все пак биха били 100 числа в моя кода), аз мога да използвам друг цикъл за да задам стойност на x[0], x[1], x[2], ..., x[98], и x[99]. Това ще направя в setup за да имам достъп до random (подобно на примера със сладоледа)

let x = [0, 100]

function setup() {
  createCanvas(400, 400);
  for (let i = 0; i < 100; i ++) { // i = 0, 1, 2, .. 98, 99
    x[i] = random(-15, 415)        // x[i] е x[0], x[1], ... до x[99]
                                   // random(-15, 415) дава случайно число от -15 до 415
  }
}

function draw() {
  background(220);
  
  for(let i = 0; i < x.length; i ++) { // x.length за да изброи всички кръгчета в масива
    circle(x[i], i * 4, 30)     // i * 4 за да се съберат на екрана
    x[i] += 3
    if (x[i] > 415) {
      x[i] = -15
    }
  }
}

И така получаваме крайния резултат: 100 кръгчета на екрана, всяко със собствена анимация.

Ако искате, може да пробвате да промените линията x[i] += 3, така че всяко кръгче да се движи с различна скорост. Например може да я направите на x[i] += 3 + i * 0.1 – така по-долните кръгчета ще бъдат по-бързи от горните кръгчета.

Задача

Използвайки масив и цикъл, направете капки дъжд, които падат. (Може например да ги нарисувате като къси линии с line Или пък като малки елипси с ellipse.)

След това добавете някакъв вид "чадър", който се движи с мишката (mouseX/mouseY), и който спира капките дъжд. За най-лесно, може да направите чадъра да бъде правоъгълник; за да проверите дали една капка е застанала върху чадъра, може да използвате условие подобно на следното:

// Координати и размер на чадъра; може и да не са отделни променливи, а просто изрази
let umbrellaX, umbrellaY, umbrellaW, umbrellaH
rect(umbrellaX, umbrellaY, umbrellaW, umbrellaH)
// Координати на капката
let dropX
let dropY
// Проверка дали капката е ударила чадъра:
if (dropX > umbrellaX && dropX < umbrellaX + umbrellaW &&
    dropY > umbrellaY && dropY < umbrellaY + umbrellaH ) {
  // ...
}

Когато чадъра спре някоя капка, то тази капка трябва да се върне обратно отгоре; все едно рестартираме анимацията от по-рано.

Ако не може да си го представите съвсем, може да погледнете този пример - но се опитайте да не копирате код от там; все пак, идеята на задачата е да опитате и сами. 😃