Паттерны и орнаменты

Сетку из одинаковых фигур ты уже строить умеешь — сегодня мы научим её играть: чередовать цвета, менять формы и собираться в настоящий орнамент из цыплят.

Главное правило урока: у цикла всегда есть счётчик-индекс (i, x, y), и если внутри цикла менять цвет или форму в зависимости от этого индекса, повторение перестаёт быть скучным и превращается в узор.

Зачем тебе паттерны

Посмотри на свой телефон. Чехол в горошек, плитка в ванной, шахматная доска в игре, фон чата в мессенджере, узор на свитере, обёртка подарка — всё это паттерны: один и тот же мотив, повторённый по правилу. Глазу это нравится, потому что он сразу видит ритм и порядок, как в припеве любимого трека: одна и та же фраза возвращается, но цепляет.

В прошлом уроке про вложенные циклы и сетки ты научился раскладывать фигуры рядами и столбцами: два цикла, один внутри другого, заполняют весь холст одинаковыми кружками. Получалась ровная сетка — аккуратно, но однообразно, как тетрадь в клетку. Сегодня мы вдохнём в эту сетку жизнь: пусть клетки чередуются цветами, как на шахматной доске, пусть в одних ячейках сидит круг, а в других — квадрат, и пусть весь холст в итоге покроется обоями из маленьких CodeChick.

К концу урока ты соберёшь вот такой кадр: ровные ряды цыплят на нежном фоне, где жёлтые птенцы чередуются с оранжевыми, как клетки доски, а между ними рассыпаны зёрнышки. Издалека — обои для телефона, вблизи — стая аккуратных CodeChick. И всё это рисует цикл всего из нескольких строк: ты задаёшь правило, а компьютер сам повторяет его сотни раз.

Секрет, который мы сегодня раскроем, до смешного простой. Внутри цикла у тебя всегда есть число — номер текущего шага. Если ты научишься читать это число и принимать по нему решения («каждый второй — оранжевый», «каждый третий — квадрат»), ты получишь ключ почти ко всем узорам на свете.

Главная идея: решай по индексу

Что такое индекс цикла

Вспомни обычный цикл из прошлых уроков:

for (let i = 0; i < 5; i++) {
  circle(i * 60 + 40, 200, 40);
}

Результат: пять одинаковых кружков в ряд по горизонтали на одной высоте, на равном расстоянии друг от друга.

Здесь i — это индекс, то есть номер текущего повторения: на первом круге i равен 0, на втором 1, дальше 2, 3, 4. Раньше мы пользовались индексом только чтобы сдвигать фигуру (i * 60 — каждый следующий круг правее). Но индекс — это обычное число, а значит, по нему можно ещё и принимать решения: какой цвет дать фигуре, какую форму нарисовать, какого она будет размера.

Представь учительницу, которая на физкультуре командует: «первый-второй рассчитайсь!» — и каждый запоминает свой номер, а потом «первые номера — шаг вперёд». Так и здесь: цикл присваивает каждой фигуре номер, а ты внутри говоришь «фигуры с чётным номером — жёлтые, с нечётным — оранжевые». Узор рождается не из разных команд, а из одной команды, которая по-разному ведёт себя в зависимости от номера.

Чётный или нечётный: остаток от деления

Как в коде отличить чётный номер от нечётного? Тут на сцену выходит главный герой урока — оператор остатка от деления, он же % (читается «по модулю» или просто «процент»). Он делит одно число на другое и возвращает остаток.

  • 4 % 2 равно 0 — четыре делится на два без остатка;
  • 5 % 2 равно 1 — пятёрку на двойку нацело не поделить, остаётся 1;
  • 6 % 2 равно 0, 7 % 2 равно 1 — и так далее.

Замечаешь ритм? Для чётных чисел i % 2 всегда 0, для нечётных всегда 1. Получается выключатель, который сам щёлкает туда-сюда на каждом шаге цикла: 0, 1, 0, 1, 0, 1… Именно это нам и нужно для чередования.

А ещё остаток умеет считать «по кругу». Посмотри на i % 3 при i = 0, 1, 2, 3, 4, 5: получится 0, 1, 2, 0, 1, 2 — последовательность зацикливается каждые три шага. Это как стрелки часов: дошли до 12 — и снова к единице. С помощью % 3 можно делать узор из трёх повторяющихся мотивов, с % 4 — из четырёх. Запомни этот образ: остаток превращает бесконечно растущий счётчик в короткий повторяющийся ритм.

Разбираем на примерах

Пример 1: полоски через одну

Начнём с самого наглядного — раскрасим ряд кругов через один, как зебру.

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

function draw() {
  background(245);

  for (let i = 0; i < 6; i++) {
    if (i % 2 === 0) {
      fill(255, 209, 64);   // чётный — жёлтый
    } else {
      fill(255, 140, 30);   // нечётный — оранжевый
    }
    circle(i * 60 + 50, 100, 50);
  }
}

Результат: на светлом фоне ряд из шести кругов, которые чередуются по цвету: жёлтый, оранжевый, жёлтый, оранжевый, жёлтый, оранжевый. Ровный ритм, как на леденце или дорожной разметке.

Разберём по шагам. Цикл крутится шесть раз, и на каждом шаге сначала проверяет if (i % 2 === 0) — «остаток от деления номера на 2 равен нулю?». Если да (номер чётный) — ставим жёлтую заливку, иначе — оранжевую. И только потом рисуем круг. Важен порядок: сначала выбрали цвет через fill(), затем нарисовали фигуру — ведь fill() действует на всё, что рисуется после него. Поменяй % 2 на % 3 и добавь третий цвет — получишь узор из трёх повторяющихся оттенков.

Пример 2: шахматная доска на вложенных циклах

Один ряд — это разминка. Настоящий паттерн живёт на сетке, а сетку, как ты помнишь, строят два вложенных цикла. Сделаем шахматную доску.

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

function draw() {
  background(245);

  let size = 50; // размер клетки

  for (let y = 0; y < 8; y++) {
    for (let x = 0; x < 8; x++) {
      if ((x + y) % 2 === 0) {
        fill(255, 209, 64);
      } else {
        fill(120, 200, 120);
      }
      rect(x * size, y * size, size, size);
    }
  }
}

Результат: весь холст превратился в шахматную доску 8 на 8: жёлтые и зелёные квадраты чередуются в обе стороны — и по горизонтали, и по вертикали, как настоящая клетчатая скатерть.

Тут спрятан красивый трюк, и его легко проглядеть. Мы проверяем не x % 2 и не y % 2 по отдельности, а сумму: (x + y) % 2. Почему? Если сдвинуться на одну клетку вправо, x вырастет на 1, и сумма x + y сменит чётность — цвет переключится. Сдвинулись на клетку вниз — y вырос на 1, сумма снова сменила чётность, цвет опять переключился. Так получается, что соседи всегда разного цвета и по горизонтали, и по вертикали — ровно как на шахматной доске. Если бы мы взяли только x % 2, вышли бы вертикальные полоски; только y % 2 — горизонтальные. Сумма даёт именно клетку. Это стоит запомнить как готовый рецепт.

Пример 3: чередуем не цвет, а форму

По индексу можно менять не только цвет, но и саму фигуру. Давай в чётных ячейках рисовать круг, а в нечётных — квадрат.

function setup() {
  createCanvas(400, 400);
  noStroke();
  rectMode(CENTER); // квадрат рисуем от центра, как круг
}

function draw() {
  background(245);
  fill(255, 170, 60);

  let step = 80;

  for (let y = 0; y < 5; y++) {
    for (let x = 0; x < 5; x++) {
      let px = x * step + 40;
      let py = y * step + 40;
      if ((x + y) % 2 === 0) {
        circle(px, py, 50);
      } else {
        rect(px, py, 50, 50);
      }
    }
  }
}

Результат: оранжевая сетка 5 на 5, где круги и квадраты чередуются в шахматном порядке: где сосед-круг, там по диагонали тоже круг, а между ними квадраты. Получается ритмичный орнамент из двух форм одного цвета.

Логика та же, что и с цветом, только в ветках if/else теперь стоят разные фигуры, а не разные fill(). Обрати внимание на две детали. Во-первых, я завёл px и py — позицию ячейки, посчитанную один раз, чтобы и круг, и квадрат рисовались в одной и той же точке. Во-вторых, в setup() появилась строка rectMode(CENTER): по умолчанию rect() рисуется от левого верхнего угла, а circle() — от центра, и без этой настройки квадраты съехали бы относительно кругов. Маленькая, но важная согласованность.

Пример 4: обои из цыплят

Собираем всё вместе — настоящий фон-паттерн из CodeChick. Вынесем рисование одного цыплёнка в отдельную функцию, чтобы цикл оставался чистым.

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

function draw() {
  background(200, 230, 255); // нежно-голубое небо

  let step = 90;

  for (let y = 0; y < 5; y++) {
    for (let x = 0; x < 5; x++) {
      let px = x * step + 45;
      let py = y * step + 45;
      // чётные цыплята жёлтые, нечётные — оранжевые
      if ((x + y) % 2 === 0) {
        drawChick(px, py, color(255, 209, 64));
      } else {
        drawChick(px, py, color(250, 160, 60));
      }
    }
  }
}

function drawChick(x, y, bodyColor) {
  // тело
  fill(bodyColor);
  circle(x, y, 50);
  // клюв
  fill(255, 140, 30);
  triangle(x + 18, y - 4, x + 38, y, x + 18, y + 6);
  // глаз
  fill(40);
  circle(x - 8, y - 8, 7);
}

Результат: весь холст затянут обоями из маленьких CodeChick на голубом небе: ряды аккуратных птенцов, где жёлтые и оранжевые чередуются в шахматном порядке. У каждого свой оранжевый клюв справа и тёмный глаз — целая дружная стая, готовая стать фоном для телефона.

Что здесь нового. Мы спрятали все фигуры одного цыплёнка в функцию drawChick(x, y, bodyColor) — она принимает координаты и цвет тела, а рисует уже знакомого нам героя относительно точки (x, y), как в уроках про мышь. Внутри цикла мы только решаем где и каким цветом, а сам цыплёнок собирается в одном месте. Это огромное удобство: захочешь добавить цыплёнку лапки — поправишь функцию один раз, и обновятся все птенцы сразу. Цвет передаём через color(...) — этот объект хранит готовый цвет, который потом уходит в fill(). Попробуй заменить условие на (x + y) % 3 === 0 — получишь узор, где особый цыплёнок встречается реже, через двух обычных.

Частые ошибки и подводные камни

  • Ставишь fill() после фигуры. Если написать сначала circle(...), а потом fill(...), цвет применится к следующей фигуре, а не к текущей — и узор «съедет» на один шаг. Правило железное: сначала выбираем цвет, потом рисуем.

  • Путаешь % и /. Деление / возвращает частное (5 / 2 это 2.5), а остаток % — то, что осталось (5 % 2 это 1). Для чередования нужен именно остаток. Если узор не чередуется, первым делом проверь, что стоит %, а не /.

  • Сравниваешь через одно = вместо ===. В условии if (i % 2 = 0) один знак равно — это присваивание, и будет ошибка. Сравнение пишется двумя или тремя знаками: i % 2 === 0. Один = «кладёт» значение, два-три — «спрашивают, равны ли».

  • Проверяешь x % 2 вместо (x + y) % 2 на сетке. Тогда вместо шахматной доски получишь вертикальные полоски: цвет меняется только по горизонтали, а сверху вниз остаётся одинаковым. Чтобы клетки чередовались в обе стороны, бери сумму индексов.

  • Забыл про rectMode, когда мешаешь круги и квадраты. circle() рисуется от центра, а rect() по умолчанию — от угла. Без rectMode(CENTER) квадраты окажутся сдвинуты относительно кругов, и ровный орнамент рассыплется. Либо ставь rectMode(CENTER), либо сам учитывай сдвиг при расчёте координат.

Мини-практика: собери свой орнамент

Возьми за основу пример с обоями из цыплят и поэкспериментируй. Вот план:

  1. Поменяй условие чередования с % 2 на % 3 и добавь третий цвет цыплёнка через else if. Посмотри, как меняется ритм узора, когда мотивов становится три, а не два.
  2. Между цыплятами в «пустых» местах нарисуй маленькое зёрнышко (крошечный овал) — например, в ячейках, где (x + y) % 2 === 1, вместо оранжевого цыплёнка клади зерно. Получится узор «цыплёнок — зёрнышко — цыплёнок».
  3. Сделай так, чтобы размер цыплёнка зависел от индекса: попробуй диаметр 40 + (x % 3) * 15. Узор станет «дышать» — птенцы будут расти и уменьшаться по рядам.
  4. Для смелых: добавь к каждому цыплёнку лёгкий поворот через push()/translate()/rotate()/pop(), где угол зависит от (x + y). Орнамент оживёт и станет похож на узор на ткани.

Меняй числа и условия по одному и сразу смотри, что вышло. Именно так, на ощупь, лучше всего чувствуется, как из простого правила рождается сложный на вид узор.

Итоги

Сегодня твоя ровная сетка научилась играть и превратилась в настоящий орнамент. Вот что ты теперь умеешь:

  • Индекс цикла (i, x, y) — это число, по которому можно принимать решения: какой цвет, какую форму, какой размер дать фигуре.
  • Оператор остатка % превращает растущий счётчик в короткий повторяющийся ритм: % 2 чередует через одного, % 3 — через двух.
  • На сетке шахматное чередование даёт сумма индексов: (x + y) % 2 переключает цвет и по горизонтали, и по вертикали.
  • По условию можно менять не только заливку, но и саму фигуру — круг или квадрат.
  • Сложный мотив (целого цыплёнка) удобно вынести в отдельную функцию и вызывать её из цикла, передавая позицию и цвет.

Дальше будет ещё интереснее: до сих пор все наши узоры были строго регулярными, как клетка в тетради. В следующем уроке мы добавим в них случайность — научимся раскидывать перья и зёрна так, чтобы каждый раз получался новый, живой и неповторимый узор. Идеальный порядок встретится с лёгким хаосом. Увидимся на следующей странице!

Проверьте себя
1. Что возвращает оператор остатка % в выражении 7 % 3?
A1 — остаток от деления 7 на 3
B2.33 — результат деления 7 на 3
C21 — произведение 7 и 3
D0 — семь делится на три нацело
2. Почему для шахматного чередования на сетке используют условие (x + y) % 2 === 0, а не x % 2 === 0?
AТак короче писать
BСумма x + y меняет чётность и при шаге вправо, и при шаге вниз, поэтому цвет чередуется в обе стороны
Cx % 2 вообще не работает в p5.js
DТак фигуры рисуются быстрее
3. В каком порядке нужно вызывать fill() и circle(), чтобы круг получил нужный цвет?
AСначала circle(), потом fill()
BСначала fill(), потом circle()
CПорядок не важен
DИх нужно вызывать одновременно
4. Что произойдёт, если внутри цикла менять % 2 на % 3 при выборе цвета?
AУзор сломается и ничего не нарисуется
BЦвета начнут чередоваться группами по три (мотив повторяется каждые три шага)
CВсе фигуры станут одного цвета
DЦикл выполнится только три раза
5. Зачем в примере, где чередуются круги и квадраты, добавили rectMode(CENTER)?
AЧтобы квадраты были крупнее кругов
BЧтобы квадрат рисовался от центра, как круг, и фигуры не съезжали друг относительно друга
CБез него квадраты вообще не рисуются
DЧтобы изменить цвет квадратов
6. Зачем рисование одного цыплёнка выносят в отдельную функцию drawChick(x, y, bodyColor)?
AЭто обязательное требование p5.js
BЧтобы цикл оставался коротким, а изменения мотива применялись сразу ко всем цыплятам через одну правку
CЧтобы цыплята рисовались быстрее
DЧтобы убрать вложенные циклы