Natura kodu. Symulowanie systemów naturalnych przy użyciu JavaScript - Daniel Shiffman

Kup ebooka

119.70 zł
101.75 zł (101,75 zł najniższa cena z 30 dni)

-
Proszę czekać

xvii

Podziękowania

Świat wokół nas porusza się na wiele skomplikowanych i pięknych sposobów. Początkowe części naszego życia spędziliśmy, ucząc się naszego środowiska za pomocą percepcji i interakcji. Oczekujemy, że fizyczny świat wokół nas będzie za-chowywał się zgodnie z naszą pamięcią percepcyjną, np. jeśli upuścimy kamień, to spadnie on z powodu grawitacji, jeśli wieją porywy wiatru, lżejsze obiekty będą przez niego przenoszone. Ta lekcja skupia się na zrozumieniu, symulacji i dołą-czaniu elementów naszego świata opartych na ruchu do tworzonych przez nas światów cyfrowych. Mamy nadzieję, że utworzymy intuicyjne, bogate i satys-fakcjonujące doświadczenia, korzystając z percepcyjnych wspomnień naszych użytkowników.

- James Tu, opis kursu Dynamic Bodies, wiosna 2003, ITP

W 2003 roku, jako dyplomant programu ITP (Interactive Telecommunications Program) w Tisch School of the Arts na New York University, zapisałem się na kurs o nazwie Dynamic Bodies. Kurs ten był prowadzony przez adiunkta w ITP, projek-tanta interakcji Jamesa Tu. Moja praca skupiała się wtedy na serii eksperymentów programistycznych, które generowały nierealistyczne obrazy w czasie rzeczywistym. Aplikacje obejmowały przechwytywanie obrazów z żywego źródła i "malowanie" barw za pomocą elementów, które poruszały się po ekranie zgodnie z różnymi regu-łami. Kurs prowadzony przez Tu - obejmujący wektory, siły, oscylacje, układy czą-stek, rekurencję, kierowanie i sprężyny - idealnie pasował do mojej pracy.

Wykorzystywałem te pojęcia nieformalnie w swoich własnych projektach, ale ni-gdy nie poświęciłem czasu, aby dokładnie przeanalizować naukę związaną z tymi algorytmami lub nauczyć się technik obiektowych, aby sformalizować ich implemen-tację. W tym samym semestrze zapisałem się także na kurs prowadzony przez Philipa Galantera w Foundations of Generative Art Systems, który skupiał się na teorii i prak-tyce sztuki generacyjnej i obejmował takie tematy, jak chaos, automaty komórkowe, algorytmy generyczne, sieci neuronowe i fraktale. Zarówno kurs Tu, jak i Galantera otworzyły mi oczy na świat algorytmów symulacyjnych - technik, które prowadziły mnie w ciągu kilku kolejnych lat pracy i nauczania, służąc za podstawę i inspirację dla tej książki.

Jednak w tej historii brakuje kawałka układanki.

xxvi Wprowadzenie

opracowaną w ITP przez Rune Madsena i Steve'a Klise'a. Wszystko zostało zaprojek-towane i wystylizowane przy użyciu CSS - bez ręcznego składu i makiety.

Rzeczywistość, w której powstała ta książka, nie jest tak czysta, a historia tego, jak do tego doszło, jest długa. Jeśli chcecie dowiedzieć się więcej, koniecznie prze-czytajcie zawarte tu podziękowania, a następnie zatrudnijcie osoby, którym podzię-kowałem, aby pomogli wydać waszą książkę! Więcej szczegółów zamieszczam też w powiązanym repozytorium GitHub (https://github.com/nature-of-code).

Najważniejszy jest fakt, że materiał jest taki sam bez względu na format, w jakim go czytasz. Jedyną różnicą będzie sposób korzystania z przykładów kodu - więcej na ten temat w punkcie "Jak czytać kod" na stronie xxxiii.

Połączenie z Coding Train

Osobiście nadal uwielbiam połączenie masy celulozowej, starannie złączonej odpor-nym grzbietem, na którym zostały artystycznie rozmieszczone barwne składniki, aby przekazać słowa i idee. Jednak od 2012 roku, kiedy pod wpływem impulsu nagrałem w moim biurze w ITP swoją pierwszą lekcję wideo na temat programowania, odkry-łem ogromną wartość i radość z przekazywania pomysłów i lekcji za pomocą rucho-mych obrazków.

Krótko mówiąc, mam kanał YouTube o nazwie The Coding Train (https://www.youtube.com/thecodingtrain). Wspomniałem o nim wcześniej, omawiając możliwości zapoznania się ze wstępnymi materiałami do tej książki, a jeśli będziecie dalej czytać, zobaczycie, że w całej książce odwołuję się do związanych z tym filmów. Mogę na-wiązać do jednego z moich filmów na temat powiązanego algorytmu lub alternatyw-nej techniki dla konkretnego przykładu kodowania, lub zasugerować serię na temat pokrewnego pojęcia, co może wprowadzić dodatkowy kontekst dla analizowanego przeze mnie tematu.

Jeśli lubicie uczyć się za pomocą wideo, pracuję również nad dodatkowym zesta-wem samouczków wideo, które zawierają dokładnie ten sam materiał, co ta książka. Dziesięć lat temu utworzyłem ich całe mnóstwo przy użyciu Processing, a ostatnio zacząłem publikować serię aktualizacji przy użyciu p5.js. W chwili pisania tego tekstu jestem mniej więcej w połowie rozdziału 5.

Dodatkowe materiały

Istnieje również mnóstwo wyjątkowych materiałów edukacyjnych uczących symu-lacji i algorytmów generatywnych, których nie zapisałem ani nie nagrałem. Zawsze zalecam, aby podczas nauki nowych tematów zapoznawać się z różnymi perspekty-wami i głosami. Możliwe, że to, co napisałem, nie trafi do was, podobnie jak może nie pomóc słuchanie, gdy powtarzam te same informacje w formie wideo, niezależnie od

Wprowadzenie xxvii

tego jak bardzo będę się starał przed kamerą. Czasami lepiej jest, gdy ktoś inny pisze, mówi lub pokazuje te same koncepcje innymi słowami i w innym stylu. W tym celu na stronie internetowej książki zamieszczam część "Dodatkowe materiały". Jeśli tworzy-cie własne materiały lub macie jakieś zalecenia dotyczące ich włączenia, skontaktujcie się ze mną!

Dwie szybkie rekomendacje, które mam pod ręką, to The Computational Beauty of Nature autorstwa Gary'ego Williama Flake'a (MIT Press, 1998) - to stamtąd pier-wotnie zaczerpnąłem wiele pomysłów na tę książkę - oraz doskonale zorganizo-wane źródło online That Creative Code Page autorstwa Taru Muhonena i Raphaela de Courville'a (https://thatcreativecode.page).

Opowieść o tej książce

Patrząc na spis treści książki, można zobaczyć 12 rozdziałów (0-11!), z których każdy obejmuje inny temat. I w pewnym sensie ta książka jest właśnie tym - przeglądem kilkunastu koncepcji i powiązanych z nimi przykładów kodu. Niemniej jednak, ukła-dając materiał, zawsze wyobrażałem sobie coś w rodzaju liniowej narracji. Zanim za-czniecie lekturę, chciałbym oprowadzić was po tej opowieści.

Część 1: Przedmioty nieożywione

Piłka do footballu leży na trawie. Kopnięcie wyrzuca ją w powietrze. Grawitacja ściąga ją z powrotem na dół. Silny podmuch wiatru utrzymuje ją w powietrzu jeszcze przez chwilę, aż spadnie i odbije się od głowy skaczącego zawodnika.

Piłka nie jest żywa - nie dokonuje żadnych wyborów co do tego, jak będzie się poruszać po świecie. Jest raczej nieożywionym obiektem czekającym na popychanie i ciągnięcie przez siły otoczenia.

Jak zamodelować piłkę nożną poruszającą się na cyfrowym płótnie? Jeśli kiedy-kolwiek programowaliście okrąg poruszający się po ekranie, to zapewne napisaliście następujący wiersz kodu:

x = x + 1;

Rysujemy kształt w położeniu x. Z każdą kolejną ramką animacji zwiększamy wartość x, przerysowujemy kształt i gotowe - iluzja ruchu! Może też pójść o krok lub dwa dalej i dołączyć położenie y, a także zmienne dla prędkości wzdłuż osi x i y:

x = x + xspeed;

y = y + yspeed;

xxviii Wprowadzenie

Część 1 tej opowieści rozwinie ten pomysł jeszcze bardziej. Po zbadaniu sposobu używania różnych rodzajów losowości do napędzania ruchu obiektu (rozdział 0), za-mierzam wziąć te zmienne xspeed i yspeed i pokazać, jak razem tworzą one wektor (rozdział 1). Nie powstaną z tego żadne nowe funkcjonalności, ale zostaną zbudo-wane solidne podstawy programowania ruchu na potrzeby pozostałej części książki.

Gdy dowiecie się co nieco o wektorach, szybko zdacie sobie sprawę, że siła (rozdział 2) jest wektorem. Kopnięcie futbolówki to przyłożenie siły. Co siła robi z obiektem? Według Sir Isaaca Newtona siła jest równa masie pomnożonej przez przyspieszenie, więc siła powoduje przyspieszenie obiektu. Modelowanie sił pozwala tworzyć systemy z dynamicznym ruchem, w których obiekty poruszają się zgodnie z różnorodnymi zasadami.

Piłka, do której przyłożono siłę, może się również obracać. Obiekt poruszający się zgodnie ze swoim przyspieszeniem liniowym, może obracać się ze swoim przyspie-szeniem kątowym (rozdział 3). Zrozumienie podstaw kątów i trygonometrii pozwala modelować obracające się obiekty, a także zrozumieć zasady ruchu oscylacyjnego, jak kołysanie się wahadła lub odchylanie się sprężyny.

Gdy już uporacie się z podstawami ruchu i sił dla pojedynczego obiektu nieoży-wionego, pokażę wam, jak utworzyć tysiące takich obiektów i zarządzać nimi jak po-jedynczą jednostką zwaną układem cząstek (rozdział 4). Układy cząstek są również dobrym pretekstem do przyjrzenia się dodatkowym cechom programowania obiek-towego - dziedziczeniu i polimorfizmowi.

Część 2: To żyje!

Co to znaczy modelować życie? Nie jest łatwo odpowiedzieć na to pytanie, ale za-cznę od budowania obiektów, które mają zdolność postrzegania swojego otoczenia. Zastanówmy się nad tym przez chwilę. Klocek, który spada ze stołu, porusza się zgod-nie z siłami, podobnie jak delfin pływający w wodzie. Jest jednak kluczowa różnica: klocek nie może zdecydować się na skok ze stołu, podczas gdy delfin może zdecy-dować się na wyskoczenie z wody. Delfin ma marzenia i pragnienia. Odczuwa głód i strach, a te odczucia wpływają na jego ruchy. Analizując techniki modelowania au-tonomicznych agentów (rozdział 5), nauczycie się, jak tchnąć życie w nieożywione obiekty, pozwalając im podejmować decyzje dotyczące ich ruchów zgodnie z tym, jak rozumieją one otoczenie.

Wszystkie przykłady z rozdziałów od 1 do 5 zostały napisane "od zera", co ozna-cza, że kod algorytmów kierowania ruchem obiektów został utworzony bezpośred-nio w p5.js. Z pewnością nie jestem jednak pierwszym programistą, który rozważał pomysł symulowania fizyki i życia w animacji, więc w następnej kolejności badam, w jaki sposób można wykorzystać biblioteki fizyki (rozdział 6) do modelowania

Wprowadzenie xxix

bardziej wyrafinowanego zachowania. Omawiam funkcje dwóch bibliotek: Matter.js i Toxiclibs.js.

Pod koniec rozdziału 5 analizuję zachowania grupowe, które wykazują cechy sy-stemu złożonego. Taki system jest zwykle definiowany jako system, który jest czymś więcej niż tylko sumą jego części. Podczas gdy poszczególne elementy systemu mogą być niezwykle proste i łatwe do zrozumienia, zachowanie systemu jako całości może być bardzo złożone, inteligentne i trudne do przewidzenia. Pogoń za złożonością pro-wadzi od myślenia wyłącznie o modelowaniu ruchu do sfery systemów opartych na regułach. Co można modelować za pomocą automatów komórkowych (rozdział 7), systemów komórek żyjących na siatce? Jakie rodzaje wzorów można wygenerować za pomocą fraktali (rozdział 8), geometrii natury?

Część 3: Inteligencja

Sprawiliśmy, że rzeczy się poruszyły. Następnie daliśmy tym rzeczom nadzieje, ma-rzenia i obawy, wraz z zasadami, według których mają żyć. Ostatnim krokiem w tej książce będzie wprowadzenie do naszych dzieł inteligentnego podejmowania decy-zji. Czy do systemów obliczeniowych można zastosować biologiczny proces ewolucji (rozdział 9) w celu ewolucji zachowania autonomicznych agentów? Czy można za-programować sztuczną sieć neuronową (rozdział 10), czerpiąc inspirację z ludzkiego mózgu? W jaki sposób agenty mogą podejmować decyzje, uczyć się na błędach i do-stosowywać się do otoczenia (rozdział 11)?

Korzystanie z tej książki jako sylabusa

Mimo że treść tej książki z pewnością wymaga intensywnego i mocno skompreso-wanego semestru, zaprojektowałem ją tak, aby pasowała do 14-tygodniowego kursu. Zauważyłem, że niektóre rozdziały sprawdzają się, gdy są rozłożone na dwa lub więcej tygodni, podczas gdy inne można połączyć i analizować razem w jednym tygodniu. Oto jeden z możliwych programów nauczania:

Tydzień 1

Losowość i wektory (rozdziały 0-1)

Tydzień 2

Siły (rozdział 2)

Tydzień 3

Oscylacja (rozdział 3)

Tydzień 4

Układy cząstek (rozdział 4)

Tydzień 5

Autonomiczne agenty (rozdział 5)

Tydzień 6

Biblioteki fizyki (rozdział 6)

Tydzień 7

Projekt w połowie semestru dotyczący ruchu

Tydzień 8

Systemy złożone: dwuwymiarowe komórkowe i fraktale (rozdziały 7-8)

xxx Wprowadzenie

Tydzień 9

Algorytmy genetyczne (rozdział 9)

Tydzień 10

Sieci neuronowe i neuroewolucja (rozdziały 10-11)

Tydzień 11

Omówienie tematu projektu końcowego

Tygodnie 12-13

Warsztaty dotyczące projektu końcowego

Tydzień 14

Prezentacja projektu końcowego

Jeśli rozważacie wykorzystanie tego tekstu na kursie lub podczas warsztatów, skon-taktujcie się ze mną. Mam nadzieję, że w końcu uda mi się ukończyć towarzyszący mu zestaw filmów, a także dołączyć pomocne slajdy jako dodatkowe materiały eduka-cyjne. Jeśli stworzycie własne, chętnie o tym usłyszę!

Jak czytać kod

Kod jest w tej książce głównym środkiem przekazu i przeplata się przez całą narrację, gdy jest rozkładany na części i analizowany. Czasami pojawia się jako pełne, samo-dzielne przykłady, innym razem jako pojedynczy wiersz lub dwa, a często rozciąga się na całe podrozdziały w wielu krótkich fragmentach, z objaśnieniami umieszczo-nymi pomiędzy nimi. Niezależnie od formy, kod zawsze będzie wyświetlany czcionką stałopozycyjną. Oto krótki przewodnik po tym, jak poruszać się po typach kodu roz-sianych po całej książce.

Pełne przykłady

Każdy rozdział zawiera w pełni funkcjonalne przykłady kodu napisane przy użyciu biblioteki p5.js. Jak one wyglądają, można zobaczyć na sąsiedniej stronie.

W każdym rozdziale przykłady są kolejno ponumerowane, aby ułatwić znalezie-nie odpowiedniego kodu online. W drukowanej wersji książki zrzut ekranu znajduje się tuż pod tytułem przykładu. W wersji online szkic jest osadzony bezpośrednio na stronie. W przypadku animowanych przykładów (a prawie wszystkie są takie), zrzuty ekranu często pokazują "ścieżkę" ruchu. Efekt ten został osiągnięty poprzez dodanie przezroczystości do funkcji background(255, 10), choć załączony kod nie zawiera tego ulepszenia.

Poniżej przykładu znajduje się kod, ale nie zawsze jest to pełny kod. Ponieważ wiele przykładów jest dość długich i obejmuje wiele plików, dokładam wszelkich starań, aby dołączyć fragment, który podkreśla główne aspekty przykładu lub jakiekolwiek nowe elementy, które zostały w danej części wprowadzone, a nie zostały jeszcze omó-wione wcześniej.

Wprowadzenie xxxi

Przykład #.# Tytuł przykładu

function setup() {

createCanvas(640, 240); Ten rozmiar płótna* jest używany w celu dostosowania układu książki, ale nie jest istotny dla przykładów.

background(255);

}

function draw() {

fill(0, 25);

stroke(0, 50);

circle(random(width), random(height), 16); Za każdym razem narysuj losowy okrąg za pomocą funkcji draw().

}

Pełną wersję kodu można znaleźć na stronie internetowej książki. Można tam wcho-dzić w interakcje, modyfikować i eksperymentować z kodem w edytorze p5.js Web Editor (https://editor.p5js.org). Ponadto wszystko jest zawarte w repozytorium GitHub książki. Poniżej znajdują się linki do wszystkich materiałów:

- Strona internetowa książki (https://natureofcode.com) zawiera pełny tekst książki (w języku angielskim), dodatkowe lektury i materiały oraz wszystkie przykłady kodu.

* Czytelnika może początkowo dziwić słowo "płótno" używane w kontekście obrazów komputero-wych. Wystarczy jednak pomyśleć o naszych algorytmach i programach jak o malarzach. Powierzch-nia, na której powstaje obraz, zwyczajowo nazywa się płótnem, bo ten właśnie materiał - odpo-wiednio przygotowany ("zagruntowany") i naciągnięty na drewnianą ramę - od wieków służy jako podłoże dla obrazów olejnych (choć oczywiście nie tylko ten). Niekiedy mianem "płótna" określa się także także gotowe arcydzieło. Zatem "płótno" (ang. canvas) to po prostu ta przestrzeń ekranu, w któ-rej będą pojawiać się obrazy generowane przez nasz kod (wszystkie przypisy pochodzą od tłumacza lub redaktora wydania polskiego).

xxxii Wprowadzenie

- Repozytoria GitHub (https://github.com/nature-of-code) zawierają surowy kod źródłowy strony internetowej książki, proces kompilacji książki i wszystkie przy-kłady kodu.

- Oprócz strony internetowej i repozytoriów GitHub, można również uzyskać do-stęp do kodu, przeglądając listę szkiców w edytorze internetowym p5.js (https://editor.p5js.org/natureofcode/sketches).

Warto zauważyć, że w przykładzie użyłem komentarzy, aby odnieść się do tego, co dzieje się w kodzie. Komentarze te znajdują się obok kodu (ich wygląd może się róż-nić w zależności od sposobu czytania książki). Cieniowanie tła grupuje komentarze z odpowiadającymi im wierszami kodu.

Pełne fragmenty

Chociaż rzadko, "pełne" części kodu są czasami mieszane z tekstem głównym. Czasami, tak jak w przypadku przykładu #.# w poprzednim punkcie, mogę zaprezen-tować cały kod powiązany z pełnym szkicem p5.js. W większości przypadków jednak za "pełny" fragment kodu uznaję kod całej funkcji lub klasy - w pełni ukończony blok kodu, wraz z otwierającymi i zamykającymi nawiasami klamrowymi i wszystkim, co znajduje się między nimi. Coś w tym rodzaju:

function draw() { Cała funkcja draw() dla przykładu

background(255);

for (let x = 0; x < width; x += spacing) {

fill(255);

circle(x, height / 2, spacing);

}

}

Ten fragment pokazuje całą funkcję draw(), ale nadal nie jest to kompletny szkic. Zakłada on istnienie zmiennej globalnej o nazwie spacing, a także funkcji setup(), która wywołuje funkcję createCanvas().

Kod bez kontekstu

Od czasu do czasu na stronie można znaleźć wiersze kodu bez otaczającej je funkcji lub kontekstu. Te fragmenty mają na celu zilustrowanie danej kwestii, ale niekoniecz-nie mają być w danej postaci uruchamiane. Mogą one przedstawiać pojęcie, niewielki fragment algorytmu lub technikę kodowania.

Wprowadzenie xxxiii

fill(240, 99, 164); Wartości RGB, aby okręgi były różowe

Warto zauważyć, że ten bezkontekstowy fragment pasuje do wcięcia wiersza fill(255) w poprzednim "pełnym" fragmencie. Robię to, gdy kod stanowi część czegoś poka-zanego wcześniej. Co prawda, nie zawsze będzie to działać tak czysto lub idealnie, ale staram się jak mogę!

Wycięty kod

Uważaj na nożyczki! Ten element projektu wskazuje, że fragment kodu jest kontynu-acją poprzedniego fragmentu lub będzie kontynuowany po pewnym tekście wyjaś-niającym. Czasami nie jest on naprawdę kontynuowany, ale jest po prostu odcinany, ponieważ cały kod nie jest istotny dla danej dyskusji. Nożyczki są po to, by powie-dzieć: "Hej, może być więcej tego kodu powyżej lub poniżej, ale w każdym razie jest to część czegoś większego!". Oto, jak może to wyglądać z otaczającym tekstem.

Pierwszym krokiem do stworzenia szkicu p5.js jest utworzenie płótna:

function setup() {

createCanvas(640, 240);

Następnie nadszedł czas na narysowanie tła:

background(255);

Lubię też umieszczać okrąg na środku płótna:

circle(width / 2, height / 2, 200);

}

W funkcji draw() mogę zacząć umieszczać kwadraty w losowych miejscach tła i w usta-lonym okręgu. Reszta kodu może być dowolna!

function draw() {

rectMode(CENTER);

square(random(width), random(height), 20);

Warto zauważyć, że zachowuję spójne wcięcia, aby pomóc w ustaleniu kontekstu i znów używam ikony nożyczek, aby wskazać, gdzie kod jest kontynuowany lub ucięty.

xxxiv Wprowadzenie

Szczególnym efektem ubocznym korzystania z fragmentów kodu jest fakt, że czę-sto można zauważyć otwierające nawiasy klamrowe w jednym fragmencie, które mają odpowiedni nawias zamykający dopiero kilka fragmentów kodu później (jeśli w ogóle). Jeśli jesteście przyzwyczajeni do czytania kodu w JavaScripcie, może to po-czątkowo wywołać lekką panikę, ale miejmy nadzieję, że się do tego przyzwyczaicie.

Ćwiczenia

Każdy rozdział zawiera ponumerowane ćwiczenia, które służą jako przestrzeń do za-bawy, eksperymentów i wykraczania poza pojęcia i kod przedstawiony w rozdziałach. Oto jak może wyglądać ćwiczenie:

Ćwiczenie #.#

Spróbuj zmodyfikować przykład #.# tak, aby każdy okrąg miał losowy rozmiar:

function draw() {

fill(0, 25);

stroke(0, 50);

circle(random(width), random(height), _________________);

}

Abyście byli czujni, ćwiczenia są dostępne w różnych formatach. Niektóre z nich sta-nowią wyzwania techniczne, wymagając napisania odmiany konkretnego algorytmu lub rozwiązania ściśle określonego problemu. Inne mają charakter otwarty, zachęca-jąc do zabawy i eksperymentowania i podążania za własnymi pomysłami. Niektóre zawierają fragmenty kodu z pustymi miejscami, które zachęcają do ich bezpośred-niego wypełnienia. Nie wahajcie się pisać lub bazgrać w tej książce, podczas ich przerabiania!

Rozwiązania

Rozwiązania ćwiczeń znajdują się na stronie internetowej książki. Powinienem raczej powiedzieć, że dążę do umieszczenia rozwiązań wszystkich ćwiczeń na stronie inter-netowej książki. W tej chwili dostępnych jest tylko kilka z nich, ale mam nadzieję, że do czasu, gdy to przeczytasz, będzie ich znacznie więcej. Jeśli chcielibyście dodać rozwiązanie do któregoś z ćwiczeń, z przyjemnością zrobię to za pośrednictwem re-pozytorium GitHub książki!

Wprowadzenie xxxv

Projekt ekosystemu

Choć chciałbym udawać, że wszystkiego można się nauczyć, wylegując się w wy-godnym fotelu i czytając prozę, to aby nauczyć się programowania, trzeba naprawdę trochę programować. Ćwiczenia rozrzucone po każdym rozdziale są dobrym począt-kiem, ale pomocne może okazać się również pamiętanie o bardziej istotnym pomyśle na projekt (lub dwa), który można rozwijać, przechodząc od rozdziału do rozdziału. W istocie, prowadząc w ITP mój kurs Nature of Code, często odkrywałem, że stu-denci lubią w tracie semestru tworzyć pojedynczy projekt, krok po kroku, tydzień po tygodniu.

Na końcu każdego rozdziału znajduje się seria podpowiedzi dla jednego takiego projektu - ćwiczenia, które opierają się na sobie nawzajem, po jednym temacie. Ten projekt jest oparty na następującym scenariuszu. Zostaliście poproszeni przez mu-zeum nauki o opracowanie oprogramowania dla nowej wystawy, Cyfrowy ekosystem, świata animowanych, proceduralnych stworzeń, które żyją w komputerowej symu-lacji, aby cieszyć swym działaniem odwiedzających muzeum. Nie mam zamiaru su-gerować, że jest to szczególnie innowacyjna lub kreatywna koncepcja. Wykorzystuję raczej ten przykładowy projekt ekosystemu jako dosłowną reprezentację treści za-wartych w książce, pokazując, w jaki sposób elementy mogą pasować do siebie w jed-nym programie. Zachęcam do opracowania własnego pomysłu, być może bardziej abstrakcyjnego i nietradycyjnego.

Uzyskiwanie pomocy i przesyłanie opinii

Kodowanie może być trudne i frustrujące, a pomysły zawarte w tej książce nie zawsze są proste. Nie musicie robić tego sami. Prawdopodobnie czyta to właśnie teraz ktoś, kto chciałby współorganizować grupę badawczą lub klub książki, gdzie można się spotkać, porozmawiać i podzielić się swoimi zmaganiami i sukcesami. Jeśli nie znaj-dziecie lokalnej społeczności, z którą można wspólnie odbyć tę podróż, to może zor-ganizować społeczność internetową? Dwa miejsca, które sugeruję, to oficjalne fora Processing (https://discourse.processing.org) i serwer Discord Coding Train (https://thecodingtrain.com/discord).

Uważam, że wersja online tej książki jest żywym dokumentem i czekam na waszą opinię. Wszystkie informacje związane z książką można znaleźć na stronie Nature of Code (https://natureofcode.com). Surowy tekst źródłowy książki i wszystkie ilustracje znajdują się w GitHubie (https://github.com/nature-of-code). Prosimy o pozostawie-nie opinii i przesłanie poprawek za pomocą zgłoszeń GitHuba (https://github.com/nature-of-code/noc-book-2/issues).

xviii Podziękowania

Kurs Galantera był oparty przede wszystkim na teorii, zaś Tu uczył za pomocą Macromedia Director oraz języka programowania Lingo. W tamtym semestrze na-uczyłem się wielu algorytmów, tłumacząc je na język C++ (język, którego w tam-tym okresie używałem w sposób niezgrabny, a było to, zanim pojawiły się kreatywne środowiska kodowania, takie jak openFrameworks i Cinder). Jednak pod koniec semestru odkryłem coś, co nazywało się Processing (https://www.processing.org). Oprogramowanie to było wtedy w fazie alfa (wersja 0055), a ja miałem trochę do-świadczenia z Javą i byłem zaintrygowany na tyle, aby zadać pytanie: Czy ten przyja-zny artystom język programowania i środowisko open source mogą być właściwym miejscem do tworzenia zestawu podręczników i przykładów dotyczących programo-wania i symulacji? Mając wsparcie społeczności ITP i Processing, rozpocząłem coś, co dziś jest niemal dwudziestoletnią podróżą poprzez nauczanie kodowania.

Na początek chciałbym podziękować Redowi Burnsowi, który kierował ITP przez pierwszych 30 lat i zmarł w 2013 roku. Red wspierał i zachęcał mnie do pracy przez ponad 10 lat. Dan O'Sullivan, prodziekan w Emerging Media na uczelni Tisch School of the Arts, był przede wszystkim moim mentorem i pierwszą osobą, która zasugero-wała, abym zaczął opracowywać podręczniki do programowania. Shawn Van Every, obecny dziekan wydziału, był moim współpracownikiem podczas mojego pierw-szego roku nauczania na pełny etat i stanowił w tym okresie bogate źródło pomocy i inspiracji. Jestem wdzięczny za wsparcie i zachęty ze strony profesor Luisy Pereiry z ITP. Jej praca nad przygotowywaną książką Code of Music była wielce inspirująca. Jej innowacyjne podejście do interakcyjnych materiałów edukacyjnych pomogło mi przemyśleć i przedefiniować mój własny proces pisania i publikowania.

Wibrujące i ożywcze środowisko ITP ukształtowało wiele niezwykłych indywi-dualności. Niezależnie od tego, czy byli to koledzy z pierwszych lat koncepcji tej książki, czy też nowe twarze wnoszące fale inspiracji, jestem wdzięczny pracowni-kom wydziału ITP/IMA. Na podziękowania zasługują: Ali Santana, Allison Parrish, Blair Simmons, Clay Shirky, Craig Protzel, Danny Rozin, David Rios, Gabe Barcia-Colombo, Katherine Dillon, Marianne Petit, Marina Zurkow, Matt Romein, Mimi Yin, Nancy Hechinger, Pedro Galvao Cesar de Oliveira, Sarah Rothberg, Sharon De La Cruz, Tom Igoe oraz Yeseul Song.

Pełen zaangażowania i niestrudzony personel ITP i IMA (Interactive Media Arts) odegrał ogromną rolę w utrzymaniu ekosystemu w rozkwicie i tworzeniu wszyst-kiego. Dziękuję wielu osobom, z którymi pracowałem przez wiele lat. Są to: Adrian Mandeville, Brian Kim, Daniel Tsadok, Dante Delgiacco, Edward Gordon, Emma Asumeng, George Agudow, John Duane, Lenin Compres, Luke Bunn, Marlon Evans, Matt Berger, Megan Demarest, Midori Yasuda, Phil Caridi, Rob Ryan, Scott Broussard i Shirley Lin.

xxxvi Wprowadzenie

Co ważniejsze, chcę zobaczyć, co tworzycie! Możecie dzielić się swoimi pomy-słami, przesyłając je do kolekcji pasażerów pociągu (Passenger Showcase) na stronie Coding Train (https://thecodingtrain.com/showcase) lub w kanałach na wspomnianym Discordzie. Komentarze na YouTube są zawsze mile widziane (choć szczerze mówiąc, często lepiej ich nie czytać) i nie krępujcie się oznaczać mnie na dowolnej platformie, jaką mają do zaoferowania w przyszłości media społecznościowe - którakolwiek jest najbardziej przyjazna i najmniej toksyczna! Chcę cieszyć się wszystkimi bloopami, które pływają w waszych ekosystemach. Niezależnie od tego, czy triumfalnie przeska-kują przez falę kreatywności, czy też robią mały plusk w stawie nauki, wygrzewajmy się w falach, które wysyłają poprzez naturę kodowania!

Podziękowania xix

Specjalne podziękowanie kieruję do pomocniczych adiunktów wydziału, Ellen Nickles i Nuntinee Tansrisakul, które w 2021 roku nauczały ze mną asynchronicznej wersji online kursu Nature of Code, pomimo szczytu światowej pandemii. Ich wkład i idee z tego semestru bardzo wzbogaciły materiały kursu.

Studenci ITP i IMA, a jest ich zbyt wielu, żeby ich wymieniać, stanowili niezwykłe źródło informacji zwrotnych w całym procesie pisania. Wiele materiałów z tej książki pochodzi z mojego kursu pod tym samym tytułem, który prowadziłem 17 razy. Mam stosy roboczych wydruków książki z uwagami pisanymi na marginesach, a także roz-ległe archiwum e-maili od studentów z poprawkami, komentarzami i hojnymi sło-wami wsparcia.

Chciałbym wyróżnić kilkoro studentów, którzy pracowali jako dyplomanci nad materiałami do książki. Podczas pracy w projekcie ITP/IMA Equitable Syllabus, Briana Jones i Chaski No zapewnili niezwykłe wsparcie badawcze, które rozwinęło pojęcia i odwołania w tej książce. Jako asystentka w licencjackiej wersji klasy Nature of Code, Gracy Whelihan udzieliła nieocenionego wsparcia i uwag oraz zawsze przy-pominała mi o pięknie liczb losowych.

Jason Gao i Stuti Mohgaonkar pracowali nad budową systemów tworzenia materia-łów książki, wprowadzając nowe przepływy pracy w pisaniu i edycji. Elias Jarzombek także zasługuje na wymienienie za jego rady i wsparcie techniczne, wynikające z pro-jektu książki Code of Music.

Po zrobieniu dyplomu Jason Gao kontynuował projektowanie i rozwijanie systemu kompilacji książki i jej witryny internetowej (https://natureofcode.com). Jeśli skieru-jecie się tam teraz, zobaczycie owoce jego wielu talentów: pełną wersję książki, która gładko integruje się z edytorem sieciowy p5.js. Jego realizacja znacznie wykracza poza moją początkową wizję.

Wnętrze tej książki i witryna internetowa zostały starannie zaprojektowane przez Tuan Huang. Zaczęła ona rozwijać pomysły związane z układem książki wiosną 2003 roku podczas zajęć z Nature of Code. Po dyplomie udoskonaliła projekt, pracując nad stworzeniem spójnego języka wizualnego łączącego różne elementy książki. Jej minimalistyczna i elegancka estetyka znacznie rozwinęła wizualną atrakcyjność i do-stępność książki. Specjalne podziękowania należą się OpenMoji (https://openmoji.org) - projektowi open source emotikonów i ikon (Licencja Creative Commons CC BY-SA 4.0) - za zapewnienie zachwycającego i szerokiego zbioru emotikonów uży-wanych w różnych miejscach książki.

Mam także dług wdzięczności dla energetycznej i wspierającej społeczności ko-dowania oraz Processing Foundation. Nie pisałbym tej książki bez Casey Reas i Bena Frya, którzy w 2001 roku stworzyli Processing i współtworzyli Processing Foundation. Poświęcili ponad 20 lat na zbudowanie i utrzymanie oprogramowania i społeczno-ści. Połowę swoje wiedzy zawdzięczam czytaniu kodu źródłowego i dokumentacji

xx Podziękowania

Processing. Elegancka prostota języka Processing, witryny i IDE stanowi oryginalne źródło inspiracji dla całej moje pracy i nauczania.

Lauren Lee McCarthy, twórczyni p5.js zasiała ziarno, które sprawiło, że udało się przekształcić tę książkę na kod JavaScript. Jest niestrudzoną orędowniczką integracji i dostępu do open source, a jej podejście do budowania społeczności było dla mnie ogromnie inspirujące. Cassie Tarakajian stworzyła edytor sieciowy p5.js, heroiczne przedsięwzięcie, które umożliwiło zebranie i uporządkowanie wszystkich przykła-dów kodu z tej książki.

Moje szczere podziękowania kieruję do obecnych i poprzednich członków zarządu Processing (wraz z Casey, Benem i Laureen): Dorothy Santos, Johanna Hedva, Kate Hollenbach i Xin Xin. Specjalne podziękowanie należy się kierującym projektem, personelu i absolwentów fundacji, którzy odegrali kluczową rolę w kształtowaniu i napędzaniu społeczności i jej projektów. Są to Andres Colubri, Charles Reinhardt, Evelyn Masso, Jesse C. Thompson, Jonathan Feinberg, Moira Turner, Qianqian Ye, Rachel Lim, Raphael de Courville, Saber Khan, Suhyun (Sonia) Choi, Toni Pizza, Tsige Tafesse oraz Xiaowei R. Wang.

W rozdziale 10 wprowadzam projekt m15.js, bibliotekę towarzyszącą p5.js, której celem jest danie kreatywnym programistom możliwości uczenia się maszyn w sposób przyjazny i dostępny. Dziękuję wielu uczonym i studentom z ITP/IMA, którzy do-łożyli się do rozwoju. Są to: Apoorva Avadhana, Ashley Lewis, Bomani McClendon, Christina Dacanay, Cristobal Valenzuela, Lydia Jessup, Miaoye Que, Micaelle Lages, Michael Weinberg, Orpheas Kofinakos, Ozi Chukwukeme, Sam Krystal, Yining Shi i Ziyuan (Peter) Lin. Dziękuję profesorowi J.H. Moonowi, profesorowi Gottfriedowi Haiderowi oraz Quinn Fangqing He z NYU Shanghai, który dodatkowo wspierał roz-wój biblioteki i był uprzejmy przeczytać pierwsze wersje rozdziałów dotyczących sieci neuronowych. Linda Paiste zasługuje na wzmiankę o jej wysiłkach w celu po-prawy bazy kodu. Wreszcie przekazuję specjalne podziękowania Joey K. Lee, który zapewnił cenne wsparcie i uwagi zarówno dla samej książki, jak i dla rozwoju m14.js.

Chciałbym też podziękować badaczowi AI, Davidowi Ha, którego badania doty-czące neuroewolucji (patrz "Additional Resources" na witrynie książki) zainspiro-wały mnie do stworzenia przykładów implementacji tej techniki za pomocą m15.js i dodania nowego rozdziału do tej książki.

Przez ostatnich 10 lat większość czasu spędziłem na tworzeniu podręczników wi-deo na moim kanale YouTube, The Coding Train. Jestem niezwykle wdzięczny za ogromne wsparcie i współpracę ze strony bardzo wielu osób, utrzymujących Coding Train w ruchu i na właściwych torach (podczas gdy ja ciężko pracuję nad zmianami kierunku). Są to między innymi: Chloe Desaulles, Cy X, David Snyder, Dusk Virkus, Elizabeth Perez, Jason Heglund, Katie Chan, Kline Gareth, Kobe Liesenborgs oraz Mathieu Blanchette. Specjalne podziękowania kieruję do Melissy Rodriguez, która

Podziękowania xxi

pomogła znaleźć i uzyskać pozwolenia na użycie zdjęć rozpoczynających każdy rozdział.

Podziękowania kieruję też do platformy streamingowej Nebula i jej prezesa Dave'a Wiskusa za niezłomne wsparcie, i do twórcy Nebuli, Grady'ego Hillhouse'a, który poradził mi współpracę z wydawnictwem No Strach Press, które wydrukowało ten cholerny tekst. Nie byłbym w stanie dotrzeć do tak szerokiej rzeszy odbiorców bez platformy YouTube, więc specjalne podziękowania kieruję do znakomitego me-nedżera partnerów YouTube Deana Kowalskiego, a także do Doreen Tran, która po-maga w kierowaniu YouTube Skilling w Ameryce Północnej.

Mam też myślących, mądrych, hojnych i życzliwych widzów. Chciałbym zwłasz-cza podziękować następującym osobom: Dipam Sen, Francis Turmel, Kathy McGuinessoraz Simon Tiger, oferujących rady, uwagi, poprawki, wsparcie tech-niczne i nie tylko to. Dzięki nim książka jest znacznie lepsza.

Chcę też podziękować wielu osobom, które współpracowały ze mną ponad 10 lat temu nad wydaniem z roku 2012. Są to: David Wilson (okładka i projekt książki), Rune Madsen i Steve Klise (budowa systemu i witryny), Shannon Fry (edycja), Evan Emolo, Miguel Bermudez oraz wszyscy wspierający Kickstarter, którzy pomogli sfi-nansować tę pracę.

Specjalne wyróżnienie należy się Zannah Marsh, która bez wytchnienia pracowała nad stworzeniem ponad 100 ilustracji do wersji książki z 2012 . Jakimś cudem zgo-dziła się, aby ponownie wykonać tę pracę do nowego wydania. Chcę jej szczególnie podziękować za cierpliwość i chęć podążania za mną, gdy zbyt wiele razy zmieniałem zdanie na temat poszczególnych ilustracji. No i te koty! Uśmiecham się za każdym razem, gdy widzę, jak piszą na klawiaturze.

A teraz prawdziwy powód, dla którego się tu spotykamy. Jestem prawie pewien, że gdyby nie No Starch Press, nie czytalibyście tych słow. Oczywiście moglibyście zna-leźć zaktualizowane podręczniki na witrynie internetowej, ale współpraca, wsparcie oraz przemyślane i uprzejme ustalanie terminów przez zespoły naprawdę przepchnęły mnie przez wyboje. Chcę wyrazić ogromną wdzięczność wydawcy Nathanowi Heidelbergerowi, który jest odpowiedzialny za to, że książka ta ma sens, nie mówiąc o uzasadnionych śmiesznych żartach. (Wina za te wszystkie złe leży wyłącznie po mo-jej stronie). Dziękuję Jasperowi Palfree, redaktorowi technicznemu, który cierpliwie tłumaczył mi tak wiele razy, aż zrozumiałem, jaka jest różnica między ruchem linio-wym a obrotowym (i wyjaśnił inne niezliczone pojęcia z zakresu nauki i kodu). Chcę też przekazać specjalne podziękowania redaktorce Sharon Wilkey, której skrupulatna dbałość o szczegóły wygładziły każde zdanie i dodały idealne wykończenie. Ponadto dziękuję Audrey Doyle za bystre oko przy korekcie tekstu. Dziękuję też założycielowi No Starch, Billowi Pollockowi, który nauczył mnie wszystkiego, czego potrzebowa-łem o zakupach w Trader Joe, redaktor prowadzącej Jill Franklin za jej życzliwość

xxii Podziękowania

i cierpliwe wsparcie oraz zespołowi produkcji kierowanemu przez starszą redaktor produkcji Jennifer Kepler i menedżerkę produkcji Sabrinę Plomitallo-Gonzalez, któ-rzy dostosowali się do mojego nietypowego sposobu pracy Notion ? GitHub ? PDF.

Wreszcie z serca dziękuję mojej żonie Aliki Caloyeras, która ma zawsze rację. Poważnie, ma jakieś super moce. Kocham cię. A także naszym dzieciom - Eliasowi, który łaskawie pozwala mi zachować godność, nie niszcząc mnie całkowicie w ko-szykówce i grach wideo, oraz Olympii, która przypomina mi, że "czuję się na 22", gdy gramy w tryktraka i w karty i razem się śmiejemy. Chcę też podziękować mojemu ojcu, Bernardowi Shiffmanowi, który szczodrze dzielił się ze mną swoją wiedzą mate-matyczną i cały czas kierował do mnie uwagi, oraz moje matce, Doris Yaffe Shiffman, i bratu Jonathanowi Shiffmanowi, którzy zawsze bardzo mnie wspierali, zadając pyta-nie "Jak tam ci idzie praca nad książką?".

xxiii

Wprowadzenie

Ponad dziesięć lat temu samodzielnie opublikowałem The Nature of Code, zasób in-ternetowy i drukowaną książkę dotyczącą badań nieprzewidywalnych ewolucyjnych i pojawiających się właściwości natury za pomocą oprogramowania i kreatywnej struktury kodowania Processing. Stwierdzenie, że od tego czasu wiele się zmieniło w świecie technologii i kreatywnych mediów, to zbyt mało powiedziane, gdyż jest to temat stulecia. Dlatego jestem tutaj ponownie, z nową i zrestartowaną wersją tej książki opartą na JavaScripcie i bibliotece p5.js. Tym razem książka zawiera kilka no-wych sztuczek kodowania, ale to ta sama stara natura - ptaki wciąż machają skrzyd-łami, a jabłka wciąż spadają nam na głowy.

Czym jest ta książka?

Od 2004 roku prowadzę w ITP/IMA (Tisch School of the Arts, New York University) kurs zatytułowany Introduction to Computational Media. Początki tych zajęć sięgają 1987 roku i pracy Mike'a Millsa i Johna Henry'ego Thompsona (wynalazcy języka pro-gramowania Lingo). W ramach tego kursu studenci uczą się podstaw programowania (zmienne, instrukcje warunkowe, pętle, obiekty, tablice), a także pojęć związanych z tworzeniem interaktywnych projektów medialnych (piksele, dane, dźwięk, sieci, 3D i inne). W 2008 roku połączyłem moje materiały tego kursu w książce wprowadza-jącej, Learning Processing, a w 2015 roku stworzyłem serię samouczków wideo, które podążają tą samą ścieżką, lecz w JavaScripcie z biblioteką p5.js.

Gdy uczeń pozna już podstawy i zapozna się z szeregiem aplikacji, jego następ-nym krokiem może być zagłębienie się w konkretny obszar. Może skupić się na wi-zji komputerowej, wizualizacji danych lub poezji generatywnej. Kolejnym krokiem może też być mój kurs Nature of Code (również prowadzony w ITP/IMA od 2008 roku). Rozpoczyna się on dokładnie tam, gdzie kończy się mój materiał wprowadza-jący i pokazuje świat technik programowania, które skupiają się na algorytmach i sy-mulacjach. Książka, którą czytacie, wyewoluowała z tego kursu.

Mój cel dla tej książki jest prosty: chcę przyjrzeć się zjawiskom, które naturalnie występują w świecie fizycznym i dowiedzieć się, jak napisać kod w celu ich symulacji.

Czym więc dokładnie jest ta książka? Czy jest to książka o naukach przyrodni-czych? Odpowiedź brzmi - zdecydowanie nie. To prawda, mogę analizować tematy, które pochodzą z fizyki lub biologii, ale nie będę ich badał, stosując się do poważ-nego rygoru akademickiego. Zamiast tego książka jest "inspirowana rzeczywistymi

xxiv Wprowadzenie

wydarzeniami". Wybieram fragmenty nauk przyrodniczych i matematyki potrzebne do zbudowania programowej interpretacji natury i zbaczam z kursu lub pomijam szczegóły według własnego uznania.

Czy jest to książka o sztuce lub projektowaniu? Również powiedziałbym, że nie. Niezależnie od tego, jak nieformalne może być moje podejście, wciąż skupiam się na algorytmach i powiązanych z nimi technikach programowania. Oczywiście otrzy-mane pokazy są wizualne (przejawiają się jako animowane szkice p5.js), ale są to dosłowne wizualizacje samych algorytmów i technik programowania, narysowane wyłącznie za pomocą podstawowych kształtów i kolorów w skali szarości. Mam jed-nak nadzieję, że wy, drodzy czytelnicy, wykorzystacie swoją kreatywność i pomysły wizualne, aby stworzyć na podstawie tych przykładów nowe, wciągające prace. (Nie będę narzekał, jeśli zmienicie każdy szkic w tęczę).

W końcu, jeśli ta książka jest jeszcze czymś, to jest staromodnym podręcznikiem programowania. Podczas gdy temat z zakresu nauk przyrodniczych (fizyka newto-nowska, wzrost komórek, ewolucja) może stanowić zalążek rozdziału, a wyniki mogą inspirować projekty artystyczne, sama treść zawsze sprowadza się do implementacji kodu, ze szczególnym naciskiem na programowanie obiektowe.

Słowo o p5.js

Biblioteka p5.js jest przeformułowaniem kreatywnego środowiska kodowania Processing na potrzeby nowoczesnej sieci. Używam jej w tej książce z kilku powodów. Przede wszystkim jest to środowisko, które jest mi bardzo dobrze znane. Wprawdzie oryginalny Processing zbudowany na Javie jest moją pierwszą miłością i nadal do niego się zwracam, gdy wypróbowuję nowy pomysł, lecz to p5.js używam do naucza-nia na wielu moich zajęcia z programowania. Jest darmowy, jest open source i dobrze nadaje się dla początkujących, a ponieważ jest to JavaScript, wszystko działa bezpo-średnio w przeglądarce internetowej, więc nie wymaga instalacji.

Jednak dla mnie Processing i p5.js to przede wszystkim społeczność ludzi, a nie bi-blioteki czy ramy postepowania. Ci ludzie poświęcili niezliczone godziny na tworze-nie i udostępnianie oprogramowania. Napisałem tę książkę dla tej społeczności i dla każdego, kto uwielbia zaspakajać swoją dociekliwość i bawić się kodem.

Podsumowując, nic nie wiąże treści tej książki ściśle z p5.js ani z Processing. Ta książka mogłaby zostać napisana przy użyciu zwykłego JavaScriptu lub Javy albo do-wolnej liczby innych kreatywnych środowisk kodowania typu open source, takich jak openFrameworks, Cinder i tak dalej. Mam nadzieję, że po ukończeniu tej książki będę w stanie wydać wersje przykładów działające w innych środowiskach. Jeśli ktoś jest za-interesowany pomocą w przeniesieniu tych przykładów, proszę o kontakt pod adresem daniel@natureofcode.com. Śmiało, jeśli wiesz, że chcesz przenieść Naturę kodu do PHP!

Wprowadzenie xxv

Wszystkie przykłady w tej książce zostały przetestowane za pomocą p5.js w wersji 1.9.0, ale w większości powinny działać również z wcześniejszymi wersjami. Będę je aktualizować do najnowszej wersji. Najnowszy kod można zawsze znaleźć na stronie internetowej tej książki (https://natureofcode.com) i w powiązanym z nią repozyto-rium GitHub (https://github.com/nature-of-code).

Co trzeba wiedzieć?

Wymagania wstępne do zrozumienia materiału zawartego w tej książce można okre-ślić jako "jeden semestr nauki programowania w p5.js, Processing lub innym kreatyw-nym środowisku kodowania". Nie ma więc przeszkód, aby czytać tę książkę, ucząc się programowania w innym języku lub środowisku programistycznym.

Jeśli nigdy wcześniej nie pisaliście żadnego kodu, to możecie przeczytać tę książkę, aby poznać koncepcje i inspiracje, ale prawdopodobnie będziecie mieć trudności z kodem, ponieważ zakładam znajomość podstaw: zmiennych, instrukcji warunko-wych, pętli, funkcji, obiektów i tablic. Jeśli te pojęcia są dla was nowe, to wszelkie podstawy zawierają moje kursy Code! Programming with p5.js (https://thecodingtrain.com/p5js) i Learning Processing (https://thecodingtrain.com/processing).

Doświadczeni programiści, którzy nie pracowali jeszcze z p5.js, prawdopodobnie mogą to szybko nadrobić, sięgając po dokumentację p5.js (https://p5js.org), przeglą-dając przykłady i czytając stronę biblioteki Get Started (https://p5js.org/get-started).

Trzeba również zaznaczyć, że dość istotne jest doświadczenie w programowaniu obiektowym. W rozdziale 10 omawiam niektóre z podstaw tej tematyki, ale jeśli klasy i obiekty nie są wam znane, sugeruję obejrzenie moich samouczków wideo dotyczą-cych programowania obiektowego p5.js i Processing, dostępnych również w Coding Train (https://thecodingtrain.com/oop).

Jak czytasz tę książkę?

Czy czytasz tę książkę na Kindle? Na papierze? Na laptopie w formacie PDF? Na tab-lecie wyświetlającym animowaną wersję HTML5? A może jesteś przywiązany do krzesła, wchłaniając treść bezpośrednio do mózgu za pomocą szeregu elektrod, rurek i kaset?

Moim marzeniem zawsze było napisanie tej książki w jednym formacie (w tym przypadku w zbiorze dokumentów Notion), a następnie, po naciśnięciu magicznego przycisku (npm run build), wychodzi gotowa książka w dowolnym formacie - PDF, HTML5, wydrukowana na papierze, dla Kindle i tak dalej. Stało się to w dużej mie-rze możliwe dzięki projektowi Magic Book (https://github.com/magicbookproject), który jest strukturą open source do samodzielnego publikowania treści, pierwotnie

1

Losowość

Generowanie liczb losowych jest zbyt ważne,aby pozostawić to przypadkowi.

- Robert R. Coveyou

Tablice liczb losowych z książki A Million Random Digits with 100 000 Normal Deviates, opublikowane przez RAND Corporation

W 1947 roku RAND Corporation opublikowała dziwaczną książkę, pod podanym wyżej tytułem. Nie było to dzieło literackie ani traktat filozoficzny o losowości. Zawierała tablicę liczb losowych wygenerowanych przy użyciu symu-lacji elektronicznej za pomocą koła ruletki. Książka ta była jedną z ostatnich w serii tabel liczb losowych tworzonych od połowy lat dwudziestych do lat pięćdziesiątych XX wieku. Wraz z rozwojem szybkich komputerów generowanie liczb pseudolosowych stało się szybsze niż odczytywanie ich z tablic i w ten sposób era drukowanych tabel liczb losowych ostatecznie się zakończyła.

0

10 Rozdział 0

let w = width / randomCounts.length;

Wykreśl wyniki

for (let x = 0; x < randomCounts.length; x++) {

rect(x * w, height - randomCounts[x], w - 1, randomCounts[x]);

}

}

Zauważmy, że każdy słupek na wykresie ma nieco inną wysokość. Wielkość próbki (liczba wybieranych liczb losowych) jest mała, więc pojawiają się pewne rozbieżno-ści, gdy jedne liczby są wybierane częściej niż inne. Wraz z upływem czasu dobry generator liczb losowych wyrówna ten rozkład.

Liczby pseudolosowe

Liczby losowe z ciągu random() nie są naprawdę losowe. W istocie są pseudolo-sowe, gdyż stanowią wynik funkcji matematycznej, która po prostu symuluje losowość. Funkcja ta da z upływem czasu pewien wzorzec i przestanie wyglądać na losową. Jednak ten czas jest długi, więc funkcja random() jest losowa w wy-starczającym stopniu na potrzeby przykładów z tej książki.

Ćwiczenie 0.1

Utwórz obiekt błądzący losowo, który ma większą tendencję do poruszania się w dół i w prawo. (Rozwiązanie znajduje się w następnym podrozdziale.)

Prawdopodobieństwo i rozkłady niejednostajne

Losowość jednorodna nie zawsze jest najrozsądniejszym rozwiązaniem problemu - w szczególności w sytuacjach obejmujących tworzenie symulacji organicznych lub wyglądających naturalnie. Jednak za pomocą kilku sztuczek funkcja random() może tworzyć rozkłady niejednostajne liczb losowych, w których pewne wyniki są bar-dziej prawdopodobne od innych. Ten rodzaj rozkładu może dać ciekawe wyniki, bar-dziej przypominające naturalne.

Przypomnijcie sobie, jak zaczęliście programowanie za pomocą p5.js. Zapewne chcieliście narysować na ekranie wiele okręgów, więc powiedzieliście sobie "Już wiem! Narysuję te okręgi na losowych pozycjach, nadając im losowe wielkości i ko-lory". Wprowadzanie losowości do systemu jest bardzo rozsądnym punktem wyjścia

Losowość 11

podczas nauki podstaw grafiki komputerowej, ale w tej książce próbuję tworzyć sy-stemy modelowane tak, jak to widzimy w naturze, a losowość jednostajna nie zawsze to opisuje. Czasami trzeba nieco wpłynąć na sytuację, manipulując nieco wynikami.

Tworzenie niejednostajnego rozkładu liczb losowych będzie w tej książce wy-godne. W rozdziale 9 mamy na przykład algorytmy genetyczne, w których potrzebna jest metodologia dokonywania wyboru - które elementy populacji należy wybrać, aby przekazać ich DNA następnemu pokoleniu? Przypomina to koncepcje Darwina przetrwania najsilniejszych. Przypuśćmy, że mamy rozwijającą się populację małp. Nie każda małpa ma jednakowe szanse na reprodukcję. Symulując selekcję naturalną Darwina, nie możemy po prostu do reprodukcji wybrać losowo na rodziców dwóch małp. Te "silniejsze" będą stanowić bardziej prawdopodobny wybór. To można okre-ślić jako prawdopodobieństwo najlepiej przystosowanych.

Zatrzymajmy się tu na chwilę i spójrzmy na podstawowe zasady prawdopodo-bieństwa, abym mógł stosować w przykładach bardziej precyzyjne słowa. Zacznę od prawdopodobieństwa pojedynczego zdarzenia - prawdopodobieństwa, że dane zdarzenie nastąpi. W prawdopodobieństwie wyniki odnoszą się do wszystkich możli-wych efektów procesu losowego, a zdarzenie jest określonym rezultatem lub kombi-nacją branych pod uwagę rezultatów.

Jeśli mamy scenariusz, a którym każdy rezultat jest tak samo prawdopodobny jak inne, prawdopodobieństwo danego zdarzenia jest równe liczbie pasujących rezulta-tów podzielonej przez liczbę wszystkich możliwych wyników. Prostym przykładem jest rzut monetą: są tylko dwa możliwe wyniki, orzeł lub reszka. Jest tylko jeden orzeł, więc prawdopodobieństwo, że moneta upadnie orłem do góry to 1 podzielona przez 2: ?, czyli 50 procent.

Weźmy talię 52 kart. Prawdopodobieństwo wyciągnięcia asa jest następujące:

liczba asów/liczba kart = 4/52 = 0,077 ? 8%

Prawdopodobieństwo wyciągnięcia kara jest takie:

liczba kar/liczba kart = 13/52 = 0,25 = 25%

Możemy także obliczyć prawdopodobieństwo wielu następujących kolejno zdarzeń, mnożąc pojedyncze prawdopodobieństwo każdego zdarzenia. Na przykład poniżej mamy prawdopodobieństwo wypadnięcia reszki trzy razy z rzędu:

(1/2) × (1/2) × (1/2) = 1/8 = 0,125 = 12,5%

Pokazuje to, że w rzucie monetą reszka trzy razy z rzędu wypada średnio raz na osiem razy. Jeśli 500 razy rzucimy monetą trzy razy z rzędu, możemy oczekiwać, że rezultat w postaci trzech kolejnych reszek średnio raz na osiem prób, czyli około 63 razy.

12 Rozdział 0

Ćwiczenie 0.2

Jakie jest prawdopodobieństwo wyciągnięcia dwóch kolejnych asów z talii liczącej 52 karty, jeśli zwrócisz pierwszego asa i potasujesz talię przed drugim losowaniem? Jakie byłoby prawdopodobieństwo bez tego zwrotu?

Można na kilka sposobów wykorzystać funkcję random(), aby zastosować w kodzie pojęcia prawdopodobieństwa dla rozkładu niejednostajnego. Jedna z technik polega na wypełnieniu tablicy liczbami - z których niektóre się powtarzają - a następnie wy-borze z tej tablicy losowych elementów w celu wygenerowania zdarzeń na podstawie dokonanych wyborów:

let stuff = [1, 1, 2, 3, 3]; 1 i 3 znajdują się w tablicy dwa razy, co sprawia, że ich wybór jest bar-dziej prawdopodobny niż wybór 2

let value = random(stuff); Wybierz losowy element z tablicy

print(value);

Pięcioelementowa tablica ma dwie jedynki, więc uruchomienie tego kodu da szansę dwa na pięć, czyli 40%, wydrukowania wartości 1. Podobnie mamy 20% szansy na wydrukowanie 2 i 40% na wydrukowanie 3.

Możemy też zażądać liczby losowej (uprośćmy to, rozpatrując tylko losowe liczby zmiennoprzecinkowe z zakresu od 0 do 1) i pozwolić, aby zdarzenie następowało tylko wtedy, gdy liczba losowa będzie pochodzić z określonego zakresu. Na przykład:

let probability = 0.1; Prawdopodobieństwo 10%

let r = random(1); Losowa liczba zmiennoprzecinkowa od 0 do 1

if (r < probability) { Jeśli liczba losowa jest mniejsza niż 0,1, zaśpiewaj!

print("Sing!");

}

Jedna dziesiąta zmiennoprzecinkowych liczb od 0 do 1 jest mniejsza niż 0,1, więc ten kod będzie prowadził do śpiewania (sing) tylko w 10% przypadków.

To samo podejście możemy zastosować, aby przypisać różne wagi do różnych wy-ników. Powiedzmy, że chcemy, aby śpiewanie (singing) miało 60% szans na wystąpie-nie, tańczenie (dancing) 10% szans, a spanie (sleeping) 30% szans. Ponownie możemy wybrać liczbę losową od 0 do 1 i zobaczyć, w jakim zakresie się znajdzie:

- od 0 do 0,6 (60%) ? Śpiewanie

Losowość 13

- od 0,6 do 0,7 (10%) ? Tańczenie

- od 0,7 do 1 (30%) ? Spanie

let num = random(1);

if (num < 0.6) {

print("Sing!");

} else if (num < 0.7) {

print("Dance!");

} else {

print("Sleep!");

}

Zastosujmy teraz tę metodologię do losowego błądzenia, tak aby miało ono tendencję do ruchu w określonym kierunku.

Oto przykład obiektu Walker o następujących prawdopodobieństwach:

- szansa na ruch w górę: 20%,

- szansa na ruch w dół: 20%,

- szansa na ruch w lewo: 20%,

- szansa na ruch w prawo 40%.

Przykład 0.3. Obiekt Walker z tendencją do ruchu w prawo

step() {

let r = random(1);

if (r < 0.4) { 40% szans na ruch w prawo

this.x++;

} else if (r < 0.6) {

this.x--;

} else if (r < 0.8) {

Większa lub równa 0,6 i mniejsza niż 0,7

Wszystkie pozostałe przypadki (większa lub równa 0,7)

Jeśli liczba losowa jest mniejsza od 0,6

14 Rozdział 0

this.y++;

} else {

this.y--;

}

}

Innym powszechnym zastosowaniem tej techniki jest kontrola prawdopodobień-stwa zdarzenia, które sporadycznie ma się pojawić w naszym kodzie. Na przykład powiedzmy, że tworzymy szkic, który rozpoczyna nowe błądzenie losowe w regu-larnych odstępach czasu (co 100 ramek). Za pomocą random() możemy przypisać szansę jednego procenta, że Walker rozpocznie działanie. Wynik końcowy jest taki sam (nowy Walker średnio 1 na 100 ramek), ale zawiera w sobie przypadek co wydaje się bardziej dynamiczne i nieprzewidywalne.

Ćwiczenie 0.3

Utwórz błądzenie losowe z dynamicznym prawdopodobieństwem. Czy na przy-kład można dać 50% szans na poruszanie się w kierunku myszy? Pamiętajmy, że w p5.js można użyć mouseX i mouseY, aby ustalić bieżące położenie myszy!

Rozkład normalny liczb losowych

Innym sposobem niejednostajnego rozkładu liczb losowych jest wykorzystanie roz-kładu normalnego, w którym liczby losowe skupiają się wokół wartości średniej. Aby zobaczyć, jak jest to użyteczne, wróćmy do symulacji populacji małp i przyjmujmy, że szkic generuje tysiąc obiektów Monkey, a każdy z nich ma losową wysokość od 200 do 300 (gdyż jest to świat małp, które mają wysokość od 200 do 300 pikseli):

let h = random(200, 300);

Czy jest to dokładny algorytm do tworzenia populacji wzrostu małp? Wyobraźcie sobie zatłoczony chodnik w Nowym Jorku. Wybierzcie na ulicy osobę i może się okazać, że jej wzrost jest losowy. Nie jest to jednak rodzaj losowości, jaki funkcja random() tworzy domyślnie. Wzrost ludzi nie ma rozkładu jednostajnego. Jest wielu ludzi o średnim wzroście i są też osoby bardzo wysokie i bardzo niskie. Aby dokład-nie odzwierciedlić tę populację, losowe wzrosty w pobliżu średniej (czyli wzrosty średnie) powinny być wybierane z większym prawdopodobieństwem, zaś wzrosty skrajne (bardzo wysocy i bardzo niscy) powinny być rzadsze.

Losowość 15

Tak działa właśnie rozkład normalny (czasami zwany rozkładem Gaussa, od nazwi-ska matematyka Carla Friedricha Gaussa). Wykres tego rozkładu jest nieformalnie zwany krzywą dzwonową. Krzywa ta jest generowana za pomocą funkcji matema-tycznej, która definiuje prawdopodobieństwo dowolnie wybranej wartości jako funkcję średniej (często zapisywanej jako ?, czyli grecka literą mi) oraz odchylenia standardowego (? - grecka litera sigma).

Zapewne intuicyjnie wiecie, że średnia w przypadku wysokości od 200 do 300 wy-nosi 250. Co jednak będzie, jeśli odchylenie standardowe wyniesie 3? Lub 15? Co to oznacza dla liczb? Wskazówkę podaje nam wykres pokazany na rysunku 0.2.

Rysunek 0.2 Dwa przykłady krzywych dzwonowych rozkładu normalnego dla małego (z lewej) i dużego (z prawej) odchylenia standardowego.

Po lewej stronie mamy rozkład o bardzo małym odchyleniu standardowym, gdzie większość wartości skupia się wokół średniej (nie odbiegają wiele od standardu). Wersja po prawej ma większe odchylenie standardowe, więc wartości są bardziej roz-łożone w stosunku do średniej (mają większe odchylenie).

Liczby wyglądają następująco: biorąc pod uwagę daną populację, 68% jej członków będzie miało wartości w zakresie jednego odchylenia standardowego od średniej, 95% dwóch odchyleń standardowych, a 99,7% w zakresie trzech odchyleń standardowych. Przy odchyleniu standardowym o 5 pikseli tylko 0,3 wysokości małp będzie mniejsze niż 235 pikseli (trzy odchylenia standardowe poniżej średniej 250) lub większe niż 256 pikseli (trzy odchylenia standardowe powyżej średniej 250). Z kolei 68% wyso-kości małp będzie między 245 a 255 pikselami.

Na szczęście, aby korzystać z rozkładu normalnego liczb losowych w szkicu p5.js, nie musimy ręcznie wykonywać jakichkolwiek obliczeń. Zajmuje się tym funkcja randomGaussian() zwracająca liczby losowe o rozkładzie normalnym:

16 Rozdział 0

function draw() {

let num = randomGaussian(); Poproś o gaussowską liczbę losową

}

Obliczanie średniej i odchylenia standardowego

Mamy klasę złożoną z 10 studentów, którzy otrzymują następujące wyniki z te-stu (na 100): 85, 82, 88, 86, 85, 93, 98, 40, 73 i 83.

Średnia wynosi 81,3.

Odchylenie standardowe jest obliczane jako pierwiastek kwadratowy ze śred-niej kwadratów odchyleń od średniej. Innymi słowy, obliczamy różnicę między średnią a oceną każdej z osób i podnosimy ją do kwadratu, co daje kwadrat od-chylenia danej osoby. Następnie obliczamy średnią wszystkich tych wartości, aby otrzymać średnią wariancję. Potem wyciągamy pierwiastek kwadratowy średniej wariancji i otrzymujemy odchylenie standardowe.

Odchylenie standardowe stanowi pierwiastek z wariancji: 15,13.

Co dalej? Co będzie, jeśli celem jest na przykład określenie położenia x narysowanego kształtu?

Domyślnie funkcja randomGaussian()zwraca rozkład normalny losowych liczb do-datnich i ujemnych o średniej 0 i odchyleniu standardowym 1. Jest ona określana jako standardowy rozkład normalny. Jednak często te domyślne parametry nie działają. Powiedzmy, że chcemy na przykład losowo przypisać położenie x kształtu, wykorzy-stując rozkład normalny o średniej 320 (centralny piksel poziomy w oknie o szeroko-ści 620) i odchyleniu standardowym 60 pikseli. W tym przypadku można dostosować parametry, przekazując do funkcji randomGaussian() dwa argumenty: średnią, a po niej odchylenie standardowe.

Wynik

Różnica od średniej

Wariancja

85

85 - 81,3 = 3,7

(3,7)2 = 13,69

40

40 - 81,3 = -41,3

(-41,3)2 = 1 705,69

...

Średnia wariancja:

228,21

Losowość 17

Przykład 0.4 Rozkład gaussowski

function draw() {

let x = randomGaussian(320, 60); Rozkład normalny o średniej 320 i odchyleniu standardowym 60

noStroke();

fill(0, 10);

circle(x, 120, 16);

}

Użyłem tu argumentów do dostosowania wywołania funkcji randomGaussian(), ale zauważmy, że działania matematyczne do implementacji tego dostosowania są dość proste: musimy tylko pomnożyć wartość standardowego rozkładu normalnego przez odchylenie standardowe, a następnie dodać średnią. Innymi słowy, przypisanie x do randomGaussian(320,60) jest tym samym, co działania poniżej:

let x = 60 * randomGaussian() + 320;

Narysowanie przezroczystych okręgów jeden na drugim pozwala zobaczyć rozkład. Najciemniejszy punkt jest w pobliżu środka, gdzie skupia się większość wartości, ale często okręgi są rysowane w oddaleniu od środka, w lewo lub w prawo.

Ćwiczenie 0.4

Rozważ symulację rozprysku farby narysowanego jako zbiór kolorowych kropek. Większość farby skupia się w położeniu środkowym, ale niektóre kropki rozpry-skują się ku krawędziom. Czy możesz użyć rozkładu normalnego liczb losowych, aby wygenerować położenie kropek? Czy potrafisz wykorzystać też ten rozkład do wygenerowania palety kolorów? Spróbuj użyć suwaka, aby dostosować odchylenie standardowe.

18 Rozdział 0

Ćwiczenie 0.5

Losowe błądzenie gaussowskie jest zdefiniowane jako takie, w którym wielkość kroku (odległość, na jaką przesunie się obiekt w wyznaczonym kierunku) jest gene-rowana za pomocą rozkładu normalnego. Zaimplementuj ten rodzaj klasy Walker.

Niestandardowy rozkład liczb losowych

Nadejdzie taki moment, gdy nie będziecie chcieli jednostajnego rozkładu liczb loso-wych ani rozkładu gaussowskiego. Wyobraźcie sobie, że błądzicie losowo w poszuki-waniu pożywienia. Losowe poruszanie się w przestrzeni wydaje się rozsądną strategią, aby znaleźć coś do jedzenia. W końcu nie wiecie, gdzie się ono znajduje, więc może-cie szukać losowo, aż go znajdziecie. Mamy tu jednak problem. Jak zauważyliście, obserwując w działaniu wasz obiekt Walker, podczas losowego błądzenia wielokrotnie powraca się do już odwiedzonych miejsc. Zjawisko to nazywa się nadpróbkowaniem. Może to spowodować, że poszukiwanie pożywienia będzie bezowocne, a przynaj-mniej nieefektywne.

Jedną strategią na uniknięcie tego problemu jest częste wykonywanie dużych kro-ków. Pozwala to błądzić wokół określonych punktów, od czasu do czasu odskakując, aby zmniejszyć nadpróbkowanie. Ta odmiana błądzenia losowego jest znana jako lot Levy'ego. Wymaga ona dostosowanego zbioru prawdopodobieństw. Choć nie jest to dokładna implementacja lotu Levy'ego, możemy określić rozkład prawdopodobień-stwa w następujący sposób: im dłuży będzie krok, tym mniejsze jest prawdopodo-bieństwo jego wyboru. Krótsze kroki są bardziej prawdopodobne.

Napisałem wcześniej, że mogę wygenerować niestandardowy rozkład prawdopo-dobieństwa, wypełniając tablicę wartościami (niektóre z nich są zdublowane, aby były wybierane częściej) lub testując wyniki random(). Jednym ze sposobów imple-mentacji lotu Levy'ego może być zdefiniowanie 1-procentowej szansy, że wykonany zostanie duży krok:

let r = random(1);

if (r < 0.01) {

xstep = random(-100, 100);

ystep = random(-100, 100);

} else {

xstep = random(-1, 1);

ystep = random(-1, 1);

}

1% szansy na zrobienie dużego kroku

Losowość 19

Jednak zmniejsza to prawdopodobieństwo do ustalonej liczby możliwości: w 99% przypadków krok będzie mały, a w 1% - duży. Co będzie, jeśli zamiast tego zechcemy wprowadzić bardziej ogólną regułę, że im większa liczba, tym większe jest prawdo-podobieństwo jej wyboru? Na przykład 0,8791 byłoby bardziej prawdopodobne niż 0,8532, nawet jeśli prawdopodobieństwo będzie tylko odrobinę większe. Innymi słowy, jeśli x jest liczbą losową, prawdopodobieństwo jej wybrania można odwzoro-wać na osi y za pomocą funkcji y = x (rysunek 0.3).

Rysunek 0.3 Wykres y = x, gdzie y jest prawdopodobieństwem wybrania wartości x

Jeśli rozkład liczb losowych można wygenerować zgodnie z wykresem na rysunku 0.3, to można również wygenerować rozkład losowy zgodny z inną krzywą, którą mo-żemy zdefiniować za pomocą wzoru.

Rozwiązaniem może tu być wybranie dwóch liczb losowych zamiast jednej. Pierwsza liczba losowa jest po prostu liczbą losową. Jednak druga z liczb jest czymś, co nazywamy kwalifikującą wartością losową. Wartość ta jest wykorzystywana przez program, aby zdecydować, czy użyć pierwszej liczby, czy też odrzucić ją i wybrać ko-lejną. Liczby, które łatwiej się kwalifikują, będą wybierane częściej od tych kwalifiko-wanych rzadziej. Oto kroki (na razie rozpatruję tylko liczby losowe z przedziału od 0 do 1):

1. Wybierz liczbę losową: r1.

2. Oblicz prawdopodobieństwo p, że r1 się kwalifikuje. Spróbuj: p = r1.

3. Wybierz kolejną liczbę losową: r2.

4. Jeśli r2 jest mniejsze niż p, udało się znaleźć odpowiednią liczbę: r1!

5. Jeśli r2 nie jest mniejsze od p, wróć do kroku 1 i zacznij od początku.

Tutaj prawdopodobieństwo, że wartość losowa będzie się kwalifikować, to sama liczba losowa, jak widać z rysunku 0.3. Jeśli na przykład r1 jest równe 0,1, to mamy 10-pro-centową szansę na zakwalifikowanie. Jeśli r1 jest równe 0,83, to mamy 83-procentową

2 Rozdział 0

Oto jesteśmy na początku. Jeśli minęło trochę czasu, odkąd programujecie w języku JavaScript (lub zajmowaliście się matematyką), ten rozdział przypomni wam myśle-nie obliczeniowe. Aby zacząć podróż w dziedzinie kodowania natury, podam kilka podstawowych narzędzi do programowania symulacji: liczby losowe, rozkłady lo-sowe i szum. Spójrzcie na to jak na pierwszy (zerowy) element tablicy, która tworzy tę książkę - odświeżenie i brama do możliwości leżących przed wami.

W rozdziale 1 opisuję pojęcie wektora i to, jak służy on jako element konstrukcji sy-mulowania ruchu w całej tej książce. Ale zanim uczynię ten krok, pomyślmy przez chwilę, co oznacza, że coś porusza się na cyfrowym płótnie. Zacznę do najbardziej znanej i najprostszej symulacji ruchu: błądzenia losowego.

Błądzenie losowe

Wyobraź sobie, że stoisz na środku belki równoważni. Co 10 sekund rzucasz mo-netą. Orzeł - robisz krok do przodu. Reszka - krok do tyłu. To jest właśnie błądze-nie losowe, ścieżka zdefiniowana jako ciąg losowych kroków. Zeskakując (ostrożnie) z równoważni na podłogę, rozszerzasz błądzenie losowe z jednego wymiaru (poru-szania się tylko do przodu i do tyłu) na dwa wymiary (poruszanie się do przodu, do

20 Rozdział 0

szansę, że się zakwalifikuje. Im większa liczba, tym większe jest prawdopodobieństwo jej użycia.

Proces ten nosi nazwę algorytmu akceptacji-odrzucenia. Jest to rodzaj metody Monte Carlo (nazwanej tak ze względu na kasyno w Monte Carlo). Poniższy przy-kład przedstawia funkcję, która implementuje ten algorytm, zwracając liczbę losową z przedziału od 0 do 1.

Przykład 0.5 Rozkład akceptacji-odrzucenia

function acceptreject() {

while (true) { Wykonuj to "w nieskończoność", aż do znalezienia kwalifikującej się wartości losowej

let r1 = random(1); Wybierz wartość losową

let probability = r1; Przypisz prawdopodobieństwo

let r2 = random(1); Wybierz drugą wartość losową

If (r2 < probability) { Czy się kwalifikuje?" Jeśli tak, to skończone!

return r1;

}

}

}

Wprawdzie algorytm akceptacji-odrzucenia działa przy generowaniu wybranych rozkładów liczb losowych, ale technika ta nie jest zbyt wydajna. Może prowadzić do sporej ilości zmarnowanych obliczeń, gdy odrzucona zostaje duża ilość liczb loso-wych. Zwłaszcza w przypadku, gdy prawdopodobieństwo zakwalifikowania jest bar-dzo małe. Gdy dotrzemy w rozdziale 9 do algorytmów genetycznych, pokażę inne, bardziej optymalne podejście.

Losowość 21

Ćwiczenie 0.6

Wykorzystaj wybrane prawdopodobieństwo do zróżnicowania wielkości kroków podczas losowego błądzenia. Wielkość kroku może być określona za pomocą wpływu na zakres wartości wybieranych za pomocą losowej wartości kwalifiku-jącej. Czy potrafisz odwzorować prawdopodobieństwo na funkcję kwadratową, tworząc prawdopodobieństwo, że wybrana wartość będzie równa kwadratowi wartości?

let step = 10;

let stepx = random(-step, step);

let stepy = random(-step, step);

this.x += stepx;

this.y += stepy;

(W rozdziale 1 pokazuję, jak za pomocą wektorów efektywniej zmieniać rozmiary kroków).

Gładsze podejście z szumem Perlina

Dobry generator liczb losowych tworzy liczby, które nie mają ze sobą związku i nie ujawniają wyróżniającego je wzorca. Jednak, jak już wspomniałem, trochę losowo-ści jest pożyteczne przy programowaniu organicznych, podobnych do życiowych, zachowań, a jednostajna losowość jako jedyna podstawowa reguła nie jest wcale na-turalna. Algorytm znany jako szum Perlina, noszący nazwę od nazwiska swego wy-nalazcy Kena Perlina, bierze to pod uwagę, tworząc naturalnie uporządkowany ciąg liczb pseudolosowych, gdzie każda liczba w ciągu jest bliska poprzedniej. Tworzy to bardziej "gładkie" przejście między liczbami losowymi a bardziej organiczną postacią, niż czysty szum, co sprawia, że szum Perlina jest dobrze dostosowany do generowa-nia różnych efektów o cechach naturalnych, takich jak chmury, krajobrazy i struktu-ralnych wzorców jak np. marmury.

Aby pokazać różnicę pomiędzy szumem Perlina a losowością jednostajną, po-patrzmy na rysunek 0.4. Wykres po lewej pokazuje szum Perlina w funkcji czasu na osi x. Zwróćmy uwagę na gładkość krzywej. Wykres po prawej pokazuje szum w po-staci liczb czysto losowych. Wynik jest znacznie bardziej poszarpany. (Kod do wyge-nerowania tych wykresów jest dostępny na witrynie książki).

Rozkład jednostajny losowych wielkości kroków. Zmień to!

22 Rozdział 0

Rysunek 0.4 Wykres wartości szumu Perlina w funkcji czasu (po lewej) i wartości szumu losowego w funkcji czasu (po prawej)

Ken Perlin opracował oryginalny algorytm szumu Perlina, pracując na początku lat osiemdziesiątych XX wieku nad filmem Tron. Otrzymał potem Oskara za osiągnię-cia techniczne. Algorytm został zaprojektowany tak, aby tworzyć proceduralne teks-tury na podstawie efektów generowanych komputerowo. (Proceduralny odnosi się tu do algorytmicznego generowania elementów wizji zamiast ręcznego projektowa-nia przez artystę). Z upływem lat różni autorzy opracowali różne odcienie szumów. Niektóre godne uwagi to szum wartości, szum Worley'a i szum simpleksowy (opra-cowany w 2001 roku przez samego Perlina). Historię szumu Perlina można poznać na jego witrynie internetowej (https://mrl.nyu.edu/~perlin/doc/oscar.html), a jego różne odmiany z kolejnych lat z wideo "What Is OpenSimplex Noise?" na witrynie Coding Train (https://thecodingtrain.com/opensimplexnoise).

Biblioteka p5.js obejmuje, w funkcji o nazwie noise(), implementację klasycznego algorytmu szumu Perlina z 1983 roku. Może ona otrzymać jeden, dwa lub trzy argu-menty, gdyż szum jest obliczany w jednym, w dwóch lub w trzech wymiarach. Zacznę od pokazania szumu jednowymiarowego (1D).

Powiedzmy, że chcemy narysować na płótnie okrąg w losowym położeniu x. Z przyzwyczajenia użyjecie zapewne funkcji random():

let x = random(0, width); Losowe położenie x

circle(x, 180, 16)

Teraz zamiast losowego położenia x chcemy określić je bardziej gładko za pomocą szumu Perlina. Mogłoby się wydawać, że wystarczy zastąpić random()przez identyczne wywołanie noise(), jak w przykładzie:

Losowość 23

let x = random(0, width); Zastąpić random() przez noise()?

let x = noise(0, width); Kuszące, ale nie jest to poprawne!

circle(x, 180, 16);

Koncepcyjnie to właśnie chcemy zrobić - obliczyć wartość x, która ma zakres od 0 do szerokości zgodnej z szumem Perlina - ale implementacja ta nie jest właściwa. Podczas gdy argumenty funkcji random() określają zakres wartości między minimum a maksimum, funkcja noise() tak nie działa. Jej zakres wyjściowy jest ustalony: za-wsze zwraca wartość między 0 a 1. Za chwilę zobaczycie, że można to łatwo obejść za pomocą funkcji map() znajdującej się w p5.js, ale najpierw przeanalizujmy, czego oczekuje noise() jako argumentu.

Jednowymiarowy szum Perlina można traktować jako liniowy ciąg w czasie. Na przykład:

Czas

Wartość szumu

0

0,365

1

0,363

2

0,363

3

0,364

4

0,366

Aby uzyskać dostęp do określonej wartości szumu, musimy wybrać "chwilę czasową" i przekazać ją do funkcji noise(). Na przykład:

let n = noise(3);

Zgodnie z powyższą tabelą noise(3) zwraca wartość 0,364. Kolejnym krokiem jest użycie zmiennej dla czasu i ciągłe pytanie o wartość szumu w draw():

let t = 3;

function draw() {

let n = noise(t); Potrzebna jest wartość szumu dla określonej chwili czasowej

print(n);

}

Prawie, ale nie do końca. Ten kod drukuje w kółko tę samą wartość, gdyż wciąż pyta o wynik funkcji noise()w tej samej chwili, 3. Jeśli jednak zmienna czasowa t będzie podlegać inkrementacji, przy każdym wywołaniu funkcji otrzymamy inną wartość szumu:

24 Rozdział 0

let t = 0; Zwykle zaczynamy od czasu t = 0, choć jest to arbitralne

function draw() {

let n = noise(t);

print(n);

t += 0.01; Teraz idziemy do przodu w czasie!

}

Wybrałem przyrost t jako 0,01, ale przy innej wartości przyrostu otrzymamy inną gładkość szumu. Im większe skoki czasowe w przestrzeni szumu, tym mniej gładkie i bardziej losowe są wartości (rysunek 0.5).

Rysunek 0.5 Krótkie i długie skoki w czasie dla szumu Perlina

W kolejnych przykładach kodu z użyciem szumu Perlina zwrócimy uwagę na to, jak animacja zmienia się wraz ze zmianą wartości t.

Zakresy szumu

Gdy mamy wartości szumu z zakresu od 0 do 1, możemy odwzorować zakres do-wolnej wielkości, który pasuje do naszych potrzeb. Najłatwiejszym sposobem jest wykorzystanie funkcji map() z p5.js (rysunek 0.6). Przyjmuje ona pięć argumentów. Pierwszym jest wartość, którą chcemy odwzorować - w tym przypadku n. Po niej następuje wartość bieżącego zakresu (minimum i maksimum) oraz pożądany zakres.

W tym przypadku, przy szumie należącym do przedziału od 0 do 1, chcę naryso-wać okrąg w położeniu x zmieniającym się od 0 do szerokości płótna:

let t = 0;

function draw() {

let n = noise(t);

Losowość 25

let x = map(n, 0, 1, 0, width); Użyj map() do dostosowania za-kresu szumu Perlina

ellipse(x, 180, 16, 16);

t += 0.01; Ruch do przodu w czasie

}

Rysunek 0.6 Odwzorowanie wartość z jednego zakresu na drugi

Ten sam kod można zastosować do błądzenia losowego, przypisując odpowiednio wartości x i y do szumu Perlina. Daje to gładsze, bardziej organiczne błądzenie losowe.

Przykład 0.6 Losowe błądzenie z szumem Perlina

Jak wspomniano we wstępie, ślad w tym i w innych zrzutach ekranu ma oddać sens ruchu w na-szym szkicu.

class Walker {

constructor() {

this.tx = 0;

26 Rozdział 0

this.ty = 10000;

}

step() {

this.x = map(noise(this.tx), 0, 1, 0, width);

this.y = map(noise(this.ty), 0, 1, 0, height);

this.tx += 0.01; Ruch do przodu w czasie

this.ty += 0.01;

}

}

Zauważmy, że ten przykład wymaga nowej pary zmiennych: tx i ty. Jest tak dlatego, że muszę śledzić dwie zmienne związane z czasem - jedną dla położenia obiektu Walker na osi x, a drugą na osi y. Jednak z tym wartościami jest coś dziwnego. Dlaczego tx zaczyna się od 0, a ty od 10000?

Wartości te są wybierane arbitralnie, więc celowo zainicjowałem te dwie zmienne czasu w ten sposób, gdyż funkcja szumu jest deterministyczna: zawsze daje taki sam wynik dla określonego czasu t. Gdybym zażądał wartości szumu dla tego samego czasu t, zarówno dla x, jak i dla y, to x i y zawsze byłyby równe, co oznacza, że obiekt Walker poruszałby się tylko po przekątnej. Dlatego używam dwóch różnych prze-strzeni szumu, zaczynając od 0 dla x i od 10000 dla y, aby x i y wydawały się niezależne od siebie (rysunek 0.7).

Rysunek 0.7 Użycie różnych punktów odniesienia wzdłuż osi x, aby zróżnicować wartości szumu Perlina

W rzeczywistości nie wchodzi tu w grę żadne pojęcie czasu. Stanowi to użyteczną metaforę, aby pomóc w opisie sposobu działania funkcji szumu, ale w istocie mamy do czynienia z przestrzenią, a nie z czasem. Wykres na rysunku 0.7 pokazuje liniowy ciąg wartości szumu w przestrzeni jednowymiarowej - jest ułożony wzdłuż prostej.

Położenie x i y odwzorowane na podstawie szumu

Losowość 27

Wartości są uzyskiwane w określonym położeniu x i dlatego często w przykładach widzimy zmienną o nazwie xoff, aby pokazać punkt odniesienia x na wykresie szumu, a nie t jako czas.

Ćwiczenie 0.7

W losowym błądzeniu z szumem Perlina wyniki funkcji noise() są odwzorowane bezpośrednio na położenie obiektu Walker. Utwórz losowy obiekt Walker, ale tym razem odwzoruj wyniki funkcji noise() na wielkość kroku.

Szum dwuwymiarowy

Po analizie pojęcia wartości szumu w jednym wymiarze rozważmy, jak to będzie wyglądać w przestrzeni dwuwymiarowej. W przypadku szumu jednowymiarowego mamy ciąg wartości, w którym każda kolejna wartość jest podobna do sąsiedniej. Wyobraźmy sobie kawałek kartki (lub arkusz kalkulacyjny) z wartościami szumu jednowymiarowego napisanymi w jednym wierszu, po jednej w każdej komórce. Ponieważ wartości te występują w jednym wymiarze, każda z nich ma tylko dwóch sąsiadów: wartość poprzedzającą (po lewej) i wartość po niej (po prawej), co widać na rysunku 0.8.

Rysunek 0.8 Porównanie sąsiadujących wartości szumu Perlina w jednym wymiarze (po lewej) i w dwóch wymiarach (po prawej). Komórki są zacieniowane zgodnie z ich wartościami szumu Perlina

Szum dwuwymiarowy koncepcyjnie działa dokładnie tak samo. Różnica polega oczywiście na tym, że wartości nie są zapisane na liniowej ścieżce wzdłuż jednego wiersza papieru, ale wypełniają całą siatkę. Dana wartość będzie podobna do wszyst-kich swoich sąsiadów: powyżej, poniżej, po prawej, po lewek i wzdłuż każdej prze-kątnej, jak to pokazano po prawej stronie rysunku 0.8.

Jeśli będziemy to wizualizować, odwzorowując każdą wartość na jasność kolo-rów, dostaniemy coś, co wygląda jak chmury. Biały znajduje się obok jasnoszarego, a ten obok szarego, który z kolei sąsiaduje z ciemnoszarym, a ten jest obok czarnego, a czarny obok ciemnoszarego i tak dalej (rysunek 0.9).

28 Rozdział 0

Rysunek 0.9 Wynik szkicu p5.js pokazujący szum dwuwymiarowy, gdzie każdy piksel reprezentuje wartość szumu w odcieniach szarości

To z tego powodu odkryto szum. Jeśli dostroimy parametry i pobawimy się kolo-rami, wynikowe obrazy wyglądają jak marmur, drewno lub inna tekstura organiczna.

Szczegóły szumu

Odwołanie do szumu w p5.js wyjaśnia, że szum jest obliczany na przestrzeni kilku oktaw (ttps://p5js.org/reference/#/p5/noise). Wywołanie funkcji noi-seDetail() (https://p5js.org/reference/#/p5/noiseDetail) zmienia obie liczby oktaw i ich znaczenie względem siebie. To z kolei zmienia jakość tworzonych wartości szumu.

Jeśli chcecie za pomocą funkcji random() losowo pokolorować każdy piksel na płótnie, potrzebna jest zagłębiona pętla obiegająca wiersze i kolumny pikseli i wybierająca lo-sową jasność dla każdego z nich. Zauważmy, że w p5.js piksele są ułożone w macie-rzy w postaci czterech punktów na każdy z nich: czerwony, zielony, niebieski i alfa. Szczegóły można znaleźć w wideo z tablicą pikseli "Pixels" na witrynie Coding Train (https://thecodingtrain.com/pixels).

loadPixels();

for (let x = 0; x < width; x++) {

for (let y = 0; y < height; y++) {

let index = (x + y * width) * 4;

let bright = random(255); Losowa jasność!

Losowość 29

pixels[index] = bright;

pixels[index + 1] = bright;

pixels[index + 2] = bright;

pixels[index + 3] = 255; Ustaw wartość alfa na 255 (bez przezroczystości)

}

}

updatePixels();

Aby gładko pokolorować każdy piksel zgodnie z funkcją noise(), można zrobić to samo, lecz zamiast random() wywołać noise().

let bright = map(noise(x, y), 0, 1, 0, 255); Jasność z szumem Perlina!

Koncepcyjnie to dobry początek - kod wyznacza wartość szumu dla każdego poło-żenia (x, y) w przestrzeni dwuwymiarowej. Problem polega na tym, że nie będzie tu gładkiej, chmurowej jakości, jaka jest nam potrzebna. Zwiększanie w przestrzeni szumów o 1 od jednego piksela do drugiego jest zbyt dużym skokiem. Pamiętajmy, że przy szumie jednowymiarowym zwiększałem wartość zmiennej czasu dla każdej ramki o 0,01, a nie o 1!

Dość dobrym rozwiązaniem tego problemu jest użycie jako argumentów dla szumu innych zmiennych niż te, które są wykorzystywane w dostępie do pikseli na płótnie. Na przykład można zwiększać zmienną zwaną xoff o 0,01 przy każdym wzroście x w poziomie o 1, a zmienną yoff o 0,01 przy zmianie y o 1 w zagnieżdżonej pętli, jak to pokazano poniżej.

let xoff = 0.0; Rozpocznij od xoff równego 0

for (let x = 0; x < width; x++) {

let yoff = 0.0; Dla każdego xoff, rozpocznij od yoff równego 0

for (let y = 0; y < height; y++) {

let bright = map(noise(xoff, yoff),

0, 1, 0, 255); Użyj xoff i yoff dla noise()

let index = (x + y * width) * 4; Użyj x i y dla położenia piksela

pixels[index] = bright; Ustaw wartości czerwonego, zielo-nego, niebieskiego i alfa

pixels[index + 1] = bright;

pixels[index + 2] = bright;

pixels[index + 3] = 255;

yoff += 0.01; Przyrost yoff

Ustaw wartość czerwoną, zieloną i niebieską

Losowość 3

tyłu, w lewo i w prawo). Ponieważ są teraz cztery możliwości, trzeba dwukrotnie rzu-cić tą samą monetą, aby określić kolejny krok.

Rzut 1

Rzut 2

Wynik

Orzeł

Orzeł

Krok do przodu

Orzeł

Reszka

Krok w prawo

Reszka

Orzeł

Krok w lewo

Reszka

Reszka

Krok do tyłu

Może się to wydawać dość prostym algorytmem, ale błądzenie losowe może być wy-korzystane do modelowania różnego rodzaju zjawisk, które zachodzą w prawdziwym świecie, od ruchów molekuł gazu po karmienie zwierząt i zachowania hazardzisty spędzającego cały dzień w kasynie. Dla naszych celów błądzenie losowe jest z trzech poniższych powodów idealnym początkiem:

- Chciałbym ocenić pomysł programowania stanowiący podstawę tej książki: pro-gramowanie obiektowe (OOP, ang. object-oriented programming). Błądzenie losowe, które mam zamiar utworzyć, posłuży jako szablon do wykorzystania pro-jektu obiektowego sprawiającego, że elementy będą poruszać się po płótnie grafiki komputerowej.

- Losowe błądzenie wywołuje dwa pytania, które będę wciąż zadawał w tej książce: "Jak zdefiniować reguły, które rządzą zachowaniem naszych obiektów", a następ-nie "Jak zaimplementować te reguły w kodzie?".

- Zapewne co jakiś czas w ramach prezentowanych w książce projektów będzie-cie musieli wykazać podstawowe zrozumienie losowości, prawdopodobieństwa i szumu Perlina. Błądzenie losowe pozwoli mi na pokazanie kluczowych elemen-tów, które przydadzą się później.

Najpierw opiszę nieco programowanie obiektowe, kodując klasę Walker, aby tworzyć obiekty Walker zdolne do losowego błądzenia. Omówienie to będzie dość pobieżne. Osoby, które wcześniej nigdy nie zajmowały się programowaniem obiektowym, mogą potrzebować nieco bardziej wyczerpującego wprowadzenia. Sugeruję w takim przy-padku przerwę na zapoznanie się z fragmentem "Objects" mojego kursu wideo Code! Programming with p5.js dostępnego na witrynie https://thecodingtrain.com/objects.

30 Rozdział 0

}

xoff += 0.01; Przyrost xoff

}

Muszę się przyznać, że zrobiłem coś dość mylącego. Użyłem jednowymiarowego szumu do ustalenia dwóch zmiennych (this.x oraz this.y) do kontroli dwuwymia-rowego ruchu obiektu Walker. Następnie szybko przeszedłem do użycia szumu dwu-wymiarowego do ustawienia jednej zmiennej (bright), kontrolując jasność każdego piksela na płótnie.

Kluczowa różnica polega na tym, że dla Walker moim celem są dwie niezależne wartości szumu jednowymiarowego. Tylko przypadkiem wykorzystuję je do poru-szania się obiektów w przestrzeni dwuwymiarowej. Możemy to osiągnąć, wyko-rzystując dwa odniesienia (this.tx i this.ty) do wyciągnięcia wartości z różnych części jednowymiarowej przestrzeni szumu. W międzyczasie, w przykładzie szumu w dwóch wymiarach, zarówno xoff, jak i yoff zaczynają się od 0, gdyż szukam jed-nej wartości (jasności piksela) dla danego punktu w dwuwymiarowej przestrzeni szumu. Obiekt Walker w istocie porusza się po dwóch oddzielnych ścieżkach szumu jednowymiarowego, podczas gdy piksele są pojedynczymi wartościami w przestrzeni dwuwymiarowej.

W tym punkcie zasugerowałem tradycyjne wykorzystywanie szumu Perlina. Przypisałem gładkie wartości szumu jednowymiarowego położeniu obiektu, aby zro-bić wrażenie błądzenia losowego. Przy szumie dwuwymiarowym wygenerowałem wzorzec chmur, wykorzystując wygładzone wartości na płaszczyźnie złożonej z pik-seli. Jednak trzeba pamiętać, że wartości szumu Perlina są tylko wartościami. Nie są one nierozerwalnie związane z położeniami pikseli ani z kolorem.

Ćwiczenie 0.8

Pobaw się kolorem, noiseDetail() oraz częstotliwością przyrostów xoff i yoff, aby osiągnąć różne efekty wizualne.

Ćwiczenie 0.9

Dodaj trzeci argument do szumu, który zwiększa się raz na cykl, wykorzystując draw() do animacji szumu dwuwymiarowego.

Losowość 31

Ćwiczenie 0.10

Użyj wartości szumu jako wzgórz w krajobrazie.

Każdy z przykładów w tej książce, który zawiera zmienną, może być sterowany za pomocą szumu Perlina. Przykładowo, przy modelowaniu siły wiatru, jego siła może być sterowana szumem Perlina. To samo dotyczy kątów pomiędzy gałęziami wzorca drzewa fraktalnego lub szybkości i kierunku obiektów poruszających się w siatce, przy symulacji pola przepływów (patrz rysunek 0.10).

Rysunek 0.10 Drzewo z szumem Perlina (po lewej) i pole przepływów z szumem Perlina (po prawej)

Jednak podobnie jak można nadużywać losowości, łatwo jest wpaść w pułapkę nad-używania szumu Perlina. Jak powinien poruszać się obiekt? Szum Perlina! Jaki powi-nien być kolor? Szum Perlina! Jak szybki powinien być wzrost? Szum Perlina! Jeśli stanie się to odpowiedzią na każde pytanie, czytajcie dalej. Moim celem jest wprowa-dzanie was do wszechświata nowych możliwości poprzez definiowanie reguł swojego

32 Rozdział 0

systemu. W końcu te reguły służą wam do definiowania i im więcej możliwości macie do dyspozycji, tym łatwiej będziecie mogli dokonywać rozmyślnych, opartych na in-formacjach wyborów. Losowość i szum Perlina to tylko pierwsze gwiazdy szerokiego kreatywnego uniwersum, które będziecie analizować w tej książce.

Jak wspomniałem we wstępie, jednym ze sposobów korzystania z tej książki jest budowa jednego projektu podczas jej czytania, z dołączaniem do niego elementów z każdego kolejnego rozdziału. Jednym z możliwych projektów jest symulowanie ekosystemu. Wyobraźcie sobie populację obliczeniowych stworzeń żyjących w cy-frowym stawie i wokół niego, wchodzących ze sobą w interakcje zgodnie różnymi re-gułami. Na końcu każdego rozdziału znajdziecie taką podpowiedź. Moim celem jest danie wam pomysłów, jak przyrostowo rozwijać własny symulowany ekosystem za pomocą pojęć poznanych w danym rozdziale. Ale możecie wymyśleć coś własnego!

Losowość 33

Projekt ekosystemu

W pierwszym kroku opracuj zbiór reguł do symulacji rzeczywistego zachowania stworzenia, opierając się na zasadach błądzenia losowego lub innych ruchów sterowanych szumem. Czy możesz zasymulować zdenerwowanego owada, który lata w nieprzewidywalny sposób lub liść niesiony zmiennymi powie-wami? Zacznij od tego, jak można wyrazić osobowość tego stworzenia za po-mocą samego jego zachowania. Potem możesz pomyśleć o jego charakterystyce wizualnej.

Oto ilustracja, która może pomóc w wygenerowaniu pomysłów budowy eko-systemu na podstawie tematów opisanych w tej książce. Obserwuj, jak ilustracja ewoluuje w każdym kolejnym rozdziale w miarę wprowadzania nowych pojęć i technik.

Celem tej książki jest pokazanie algorytmów i zachowań, więc moje przykłady będą niemal zawsze obejmować tylko jeden prymitywny kształt, taki jak okrąg. Jednak spodziewam się, że macie w sobie iskrę kreatywności i zachęcam do po-stawienia sobie wyzwania zaprojektowania elementów rysowanych na płótnie. Jeśli przekładanie projektów na kod jest dla was nowością, ilustratorka książki, Zannah Marsh, napisała poradnik dotyczący rysowania na potrzeby kodu, który można znaleźć w dodatku do książki.

4 Rozdział 0

Klasa losowych obiektów Walker

W języku JavaScript obiekt to jednostka, która zawiera w sobie dane i funkcjonal-ność. W tym przypadku obiekt Walker powinien zawierać dane o swoim położeniu na płótnie i funkcjonalność w postaci zdolności do narysowania siebie lub wykonania kroku.

Klasa stanowi szablon do tworzenia konkretnych instancji obiektów. Można trak-tować klasę jak wycinarkę do ciastek, a wtedy obiekty są tymi ciastkami. Aby utwo-rzyć obiekt Walker, zacznę od zdefiniowania klasy Walker - określania, co znaczy bycie spacerowiczem*.

Spacerowicz musi mieć tylko dwa elementy danych: liczbę określającą jego po-łożenie na osi x oraz liczbę określającą jego położenie na osi y. Nadam im wartości początkowe na środku płótna, ustalając w ten sposób położenie początkowe obiektu. Mogę to zrobić za pomocą funkcji konstruktora klasy, nazwanej odpowiednio constructor(). Konstruktora można traktować jako funkcję inicjującą obiekt - setup(). Jest ona odpowiedzialna za zdefiniowanie początkowych cech obiektu, podobnie jak to robi funkcja setup() dla całego szkicu:

class Walker {

constructor() { Obiekty mają konstruktor, który je inicjuje

this.x = width / 2;

this.y = height / 2; Obiekty mają dane

}

Zwróćmy uwagę na słowo kluczowe this w celu dołączenia cech do samego nowo utworzonego obiektu: this.x oraz this.y.

Poza danymi, klasy mogą być zdefiniowane z funkcjonalnością. W tym przykła-dzie obiekt Walker ma dwie funkcje znane w kontekście programowania obiektowego jako metody. W istocie metody są funkcjami, ale różnica polega na tym, że metody są zdefiniowane wewnątrz klasy, więc są połączone z obiektem lub klasą, co nie dotyczy funkcji. Słowo kluczowe function stanowi wygodną wskazówkę: pojawi się podczas definiowania odrębnych funkcji, ale nie będzie występować w klasie. Staram się uży-wać w tej książce spójnej terminologii, ale programiści często używają wymiennie określeń funkcja i metoda.

Pierwsza metoda, show(), zawiera kod rysowania obiektu (w postaci czarnej kropki). Podczas odwoływania się do cech (zmiennych) danego obiektu nigdy nie zapominajcie o this:

* Walker to po polsku spacerowicz.

Losowość 5

show() { Obiekty mają metody

stroke(0);

point(this.x, this.y);

}

Kolejna metoda, step(), kieruje obiekt Walker do następnego kroku. Tu sprawa za-czyna być bardziej interesująca. Pamiętacie wykonywanie kroków po podłodze w lo-sowych kierunkach? Teraz do reprezentowania podłogi wykorzystam płótno p5.js. Istnieją cztery możliwe kroki. Krok w prawo można zasymulować, zwiększając x jako x++, krok w lewo - zmniejszając x w postaci x--, krok do przodu, idąc o piksel w górę (y--), a krok wstecz - idąc o piksel w dół (y++). Ale jak kod będzie wybierał jedną z tych czterech możliwości?

Wcześniej stwierdziłem, że możemy wykonać dwa rzuty monetą. Jednak w p4.js, gdy chcemy losowo wybrać jedną z opcji na liście, możemy po prostu wygenerować liczbę losową, korzystając z funkcji random(). Wybiera ona losową wartość zmienno-przecinkową (dziesiętną) w pożądanym przez nas zakresie. Tu korzystam z wartości 4, aby wskazać zakres od 0 do 4:

let choice = floor(random(4));

Konwencje kodowania

W języku JavaScript zmienne mogą być deklarowane za pomocą let lub const. Typowym podejściem jest zadeklarowanie wszystkich zmiennych za pomocą const i w razie potrzeby ich zmianę za pomocą let. W pierwszym przykładzie odpowiednie do zadeklarowania wyboru będzie const, gdyż nigdy nie otrzymuje nowej wartości podczas życia wewnątrz każdego wywołania step(). To rozróż-nienie jest ważne, ale ja biorę przykład konwencji z p5.js i deklaruję wszystkie zmienne za pomocą let.

Rozumiem, że JavaScript z ważnych powodów ma i let, i const. Jednak roz-różnienie ich może rozpraszać i być mylące dla osób początkujących. Zachęcam do dalszej analizy tego tematu i do podjęcia samodzielnej decyzji, która z me-tod deklarowania zmiennych jest najlepsza w waszych szkicach. Ponadto można poczytać więcej na ten temat w zagadnieniu #3877 w repozytorium GitHub dla p5.js (https://github.com/processing/p5.js/issues/3877).

Wybieram także korzystanie z operatora ścisłej równości (===) w języku JavaScript (i jego odpowiednika w postaci nierówności, !==). Ten operator boo-lowski testuje równość zarówno wartości, jak i typu. Na przykład 3 === ?3? da

6 Rozdział 0

w wyniku false, ponieważ porównywane są tu dwa różne typy (liczba z łańcu-chem), co wymaga przekształcenia, choć wyglądają podobnie. Z drugiej strony użycie operatora luźnej równości (==)dla 3 == '3' da wynik true, gdyż typy zo-staną przekształcone tak, aby się dały porównać. Wprawdzie luźne porównanie często działa dobrze, ale czasami może prowadzić do nieprzewidywalnych wy-ników i dlatego === jest zapewne bezpieczniejszym wyborem.

Deklaruję zmienną choice i przypisuję jej liczbę losową (całkowitą) za pomocą floor(), aby usunąć pozycje dziesiętne z losowej liczby zmiennoprzecinkowej. Z technicznego punktu widzenia liczba generowana przez random(4) leży w zakresie od zera (włącznie) do 4 (wyłączając), co oznacza, że nigdy nie może osiągnąć war-tości 4,0. Największa możliwa liczba, którą można wygenerować, leży poniżej 4 - to 3,999999999 (z taką liczbą dziewiątek, na jaką pozwala JavaScript). Liczbę tę funkcja floor() obcina do 3, usuwając część dziesiętną. Tak więc przypisuję zmiennej choice jedną z liczb 0, 1, 2 lub 4.

Następnie Walker robi odpowiedni krok (lewo, prawo, góra lub dół), zależnie od tego, jaka liczba losowa zostanie wybrana. Oto cała metoda step(), kończąca klasę Walker:

step() {

let choice = floor(random(4)); 0, 1, 2 lub 3. Wybór losowy określa krok

if (choice === 0) {

this.x++;

} else if (choice === 1) {

this.x--;

} else if (choice === 2) {

this.y++;

} else {

this.y--;

}

}

}

Gdy już napisaliśmy klasę, nadeszła pora, aby w samym szkicu umieścić konkretny obiekt Walker. Zakładając, że szukacie modelu dla pojedynczego losowego błądzenia, należy zacząć od jednej zmiennej:

let walker; Obiekt Walker

Losowość 7

Następnie w setup() tworzymy obiekt, odwołując się do nazwy klasy za pomocą ope-ratora new:

function setup() { Pamiętacie, jak działa p5.js? setup() jest wykonywane raz, gdy szkic się rozpoczyna

createCanvas(640, 240);

walker = new Walker(); Utwórz obiekt Walker

background(255);

}

Podczas każdego cyklu draw() obiekt Walker robi krok i rysuje kropkę.

Przykład 0.1 Tradycyjne błądzenie losowe

Za każdym razem, gdy pojawiają się te ramki z przykładami, kod do nich jest dostępny w edytorze sieciowym p5.js i na witrynie książki. Osoby czytające książkę offline zobaczą jedynie zrzut ekranu na wynikowym płótnie.

function draw() { Kod draw() działa w nieskończonej pętli aż do jej przerwania

walker.step(); Wywołaj funkcję względem obiektu walker

walker.show();

}

Ponieważ tło jest rysowane raz podczas działania setup(), a nie jest za każdym razem czyszczone za pomocą draw(), ścieżka losowego błądzenia jest widoczna na płótnie.

W błądzeniu losowym można wprowadzić kilka poprawek. Przede wszystkim kroki obiektu Walker są ograniczone do czterech opcji: w lewo, w prawo, w górę lub w dół. Ale każdy piksel na płótnie może mieć ośmiu możliwych sąsiadów, w tym na

8 Rozdział 0

przekątnych (patrz rysunek 0.1). Dziewiąta możliwość to pozostanie w tym samym miejscu, co także może być opcją do wyboru.

Rysunek 0.1 Kroki przy błądzeniu losowym z przekątnymi i bez nich

Aby zaimplementować obiekt Walker, który może zrobić krok do dowolnego sąsied-niego piksela (lub pozostać na miejscu), można wybrać liczbę od 0 do 8 (dziewięć możliwych wyborów). Jednak innym sposobem napisania kodu jest wybór spośród trzech możliwych kroków wzdłuż osi x (-1, 0 lub 1) oraz trzech wzdłuż osi y:

step() {

let xstep = floor(random(3)) - 1; Daje wartość -1, 0 lub 1

let ystep = floor(random(3)) - 1;

Yields -1, 0, or 1

this.x += xstep;

this.y += ystep;

}

Idąc dalej, można się pozbyć floor() i wykorzystać oryginalne liczby zmiennoprze-cinkowe z random(), aby utworzyć ciągły zakres możliwych długości kroków od -1 do 1, jak to pokazano poniżej.

step() {

let xstep = random(-1, 1);

let ystep = random(-1, 1);

from -1 to 1

this.x += xstep;

this.y += ystep;

}

Dowolna liczba zmiennoprzecin-kowa od -1 do 1

Losowość 9

Wszystkie te warianty związane z tradycyjnym losowym błądzeniem mają jeden element wspólny: w dowolnym momencie prawdopodobieństwo, że obiekt Walker wykona krok w danym kierunku jest równe prawdopodobieństwu wykonania kroku w każdym innym kierunku. Innymi słowy, jeśli mamy cztery możliwości, to jest jedna szansa na 4 (czyli 25 procent) wykonania kroku w jednym z kierunków. Przy dziewię-ciu możliwościach jest jedna szansa na 9 (około 11,1 procent).

Tak właśnie działa funkcja random(), co jest dla nas wygodne. Generator liczb lo-sowych w p5.js (który działa w tle) tworzy rozkład normalny liczb. Można przete-stować ten rozkład, licząc, ile razy wybrana zostanie liczba i pokazując te wartości na wykresie.

Przykład 0.2 Rozkład liczb losowych

let randomCounts = []; Tablica do przechowywania często-ści wyboru liczb losowych

let total = 20; Całkowita liczba zliczeń

function setup() {

createCanvas(640, 240);

for (let i = 0; i < total; i++) {

randomCounts[i] = 0;

}

}

function draw() {

background(255);

let index = floor(random(randomCounts.length));

randomCounts[index]++;

stroke(0);

fill(127);

Wybierz liczbę losową i zwiększ licznik

35

Wektory

Popełniam przestępstwo zarówno pod względem kierunku, jak i wielkości.

- Vector, Despicable Me

Wykres sztyftowy z Wysp Marshalla wystawiony w Berkeley Art. Museum (fotografia: Jim Heaphy)

Wykres sztyftowy to narzędzie do nawigacji zbudowane przez rdzenną ludność z Wysp Marshalla, położonych na Pacyfiku. To starożytne narzędzie zostało wykonane przez uważne powiązanie ze sobą środkowych części liści palmy kokosowej. Muszle wskazują na wykresie położenie wysp w regionie. Układ liści i muszli służy jako przewod-nik geograficzny, oferując abstrakcyjną reprezentację wektorów, które odzwierciedlają wzory fal oceanu i kierunek ich przepływu.

1

36 Rozdział 1

Ta książka jest o tym, jak przyglądać się otaczającemu nas światu i symulować go w postaci kodu. W pierwszej części książki zaczynam od spojrzenia na podstawy fi-zyki: jak jabłko spada z drzewa, jak kołysze się wahadło, jak Ziemia okrąża Słońce i tak dalej. Wszystko, co jest zawarte w pierwszych pięciu rozdziałach tej książki, wymaga użycia najbardziej podstawowego elementu programowania ruchu: wektora. I w tym miejscu zaczynam swoją opowieść.

Słowo vector (wektor) może znaczyć wiele rzeczy. Jest nazwą zespołu rockowego New Wave powstałego w Sacramento w Kalifornii na początku lat osiemdziesią-tych XX wieku, a także nazwą płatków śniadaniowych kanadyjskiej firmy Kellogg's. W dziedzinie epidemiologii wektor to organizm, który przenosi infekcję z jednego nosiciela na drugi. W języku programowania C++ wektor (std::vector) jest imple-mentacją struktury tablicy dynamicznie zmieniającej wielkość.

Mimo że wszystkie te definicje warto przeanalizować, nie będziemy się tu na nich skupiać. W tym rozdziale zajmujemy się wektorem euklidesowym (noszącym nazwę od nazwiska greckiego matematyka Euklidesa), znanego także jako wektor geome-tryczny. Gdy w tej książce napotykamy termin wektor, możemy zakładać, że odnosi się on do wektora euklidesowego, zdefiniowanego jako byt, który ma wielkość i kierunek.

Zwykle rysujemy wektor w postaci strzałki, jak to widać na rysunku 1.1. Kierunek wektora wskazywany jest przez strzałkę, a wielkość przez jego długość.

Wektor na rysunku 1.1 jest narysowany jako strzałka od punktu A do punktu B. Oznacza tu instrukcję, jak podróżować od A do B.

Rysunek 1.1 Wektor przedstawiony jako strzałka narysowana z punktu A do punktu B

Sens wektorów

Zanim wejdziemy głębiej w szczegóły dotyczące wektorów, chcę stworzyć przykład w p5.js, który pokazuje, dlaczego należy dbać o wektory. Jeśli śledziliście podręcz-niki dotyczące p5.js, czytaliście wprowadzenie do nich lub wprowadzenie do kursu kreatywnego kodowania (i mam nadzieję, że zrobiliście to wszystko, przygotowując

Wektory 37

się do tej książki!), zapewne w którymś miejscu dowiedzieliście się, jak napisać szkic z odbijająca się piłką.

Przykład 1.1 Odbijająca się piłka bez wektorów

let x = 100;

let y = 100;

let xspeed = 2.5;

let yspeed = 2;

function setup() {

createCanvas(640, 240);

}

function draw() {

background(255);

x = x + xspeed;

y = y + yspeed;

if (x > width || x < 0) { Sprawdź odbicia

xspeed = xspeed * -1;

}

if (y > height || y < 0) {

yspeed = yspeed * -1;

}

stroke(0); Narysuj piłkę w położeniu (x, y)

fill(127);

circle(x, y, 48);

}

Zmienne dotyczące położenia i szybkości piłki

Przesuń piłkę zgodnie z jej szybkością

38 Rozdział 1

W tym przykładzie mamy płaski dwuwymiarowy świat - puste płótno - z okrągłym wędrującym kształtem ("piłką"). Piłka ta ma takie cechy, jak położenie i szybkość, które są reprezentowane w kodzie jako zmienne:

Cecha

Nazwy zmiennych

Położenie

x i y

Prędkość

xspeed i yspeed

W bardziej wyrafinowanym szkicu możemy mieć więcej zmiennych reprezentujących inne cechy piłki i jej środowiska:

Cecha

Nazwy zmiennych

Przyspieszenie

xacceleration i yacceleration

Położenie docelowe

xtarget i ytarget

Wiatr

xwind i ywind

Tarcie

xfriction i yfriction

Możecie zauważyć, że każde pojęcie z tego świata (wiatr, położenie, przyspieszenie i inne) ma po dwie zmienne. A to jest jedynie świat dwuwymiarowy. W świecie trój-wymiarowym potrzebne by były trzy zmienne dla każdej cechy: x, y i z dla położenia, xspeed, yspeed i zspeed dla prędkości itd. Czy kod nie będzie wyglądał lepiej, jeśli go uprościć stosując mniej zmiennych? Zamiast rozpoczynać pogram czymś takim:

let x;

let y;

let xspeed;

let yspeed;

można zacząć czymś podobnym do tego:

let position;

let speed;

Pozwoli nam to potraktować cechy piłki jako wektory, a nie luźny zbiór oddzielnych wartości.

Zrobienie tego pierwszego kroku w kierunku wykorzystywania wektorów nie umożliwi wykonania czegoś nowego ani nie zmieni szkicu p5.js na pełną symulację fizyki. Jednak używanie wektorów pomoże zorganizować kod i zapewni zbiór me-tod dla powszechnych działań matematycznych, które będą wciąż potrzebne podczas programowania ruchu.

Wektory 39

We wprowadzeniu do wektorów będę się przez dłuższy czas trzymać dwóch wy-miarów (przynajmniej przez kilka pierwszych rozdziałów). Wszystkie przykłady można dość łatwo rozszerzyć na trzy wymiary lub więcej (a używana przeze mnie klasa p5.Vector dopuszcza trzy wymiary). Jednak podczas nauki podstaw dodanie zło-żoności w postaci trzeciego wymiaru niepotrzebnie będzie odwracać uwagę.

Wektory w p5.js

Pomyślmy o wektorze jak o różnicy między dwoma punktami lub instrukcji przejścia z jednego punktu do drugiego. Na przykład na rysunku 1.2 widać kilka wektorów i możliwe ich interpretacje.

Rysunek 1.2 Trzy przykłady wektorów narysowanych jako strzałki, wraz z instrukcjami przejścia na północ, południe, wchód i zachód

Wektory te można traktować w następujący sposób:

Wektor

Instrukcje

(-15, 3)

Przejdź 15 kroków na zachód, obróć się i przejdź 2 kroki na północ

(3, 4)

Przejdź 3 kroki na wschód, obróć się i przejdź 4 kroki na północ

(2, -1)

Przejdź 2 kroki na wschód, obróć się i przejdź 4 kroki na południe

Prawdopodobnie przyszedł już wam do głowy ten pomysł, gdy programowaliście ruch. Dla każdej ramki animacji (jeden cykl w pętli draw() w p5.js) podajecie instruk-cję, aby każdy obiekt zmienił położenie do nowego punktu o określoną liczbę pikseli w poziomie i w pionie. Te instrukcje stanowią w istocie wektor, jak na rysunku 1.3. Ma on zarówno wielkość (jak daleko przewędrowaliśmy?), jak i kierunek (w którą stronę poszliśmy?).

40 Rozdział 1

Rysunek 1.3 Wektor pokazujący liczbę kroków w poziomie i w pionie, aby przejść z danego miejsca do no-wego miejsca

Wektor ustala prędkość obiektu, zdefiniowaną jako współczynnik położenia obiektu względem czasu. Innymi słowy, wektor prędkości określa nowe położenie obiektu w każdej kolejnej ramce animacji, zgodnie z podstawowym algorytmem ruchu: nowe położenie jest wynikiem zastosowania prędkości do bieżącego położenia.

Jeśli prędkość jest wektorem (różnicą między dwoma punktami), to co będzie z położeniem? Czy to też jest wektor? Technicznie można argumentować, że poło-żenie nie jest wektorem, gdyż nie opisuje, jak przejść z jednego punktu do drugiego. Opisuje ono jeden punkt w przestrzeni. Niemniej jednak innym sposobem opisania położenia jest przedstawienie go jako ścieżki od początku układu współrzędnych - punktu (0, 0) - do bieżącego punktu. Jeśli potraktujemy położenie w ten sposób, staje się ono wektorem, podobnie jak prędkość (rysunek 1.4).

Na rysunku 1.4 wektory są umieszczone na płótnie grafiki komputerowej. W prze-ciwieństwie do rysunku 1.2 środek układu współrzędnych nie jest na środku, a w le-wym górnym rogu. Zamiast północy, południa, wschodu i zachodu mamy tu kierunki dodatnie i ujemne wzdłuż osi x i y (przy czym y skierowany jest w dół w kierunku dodatnim).

Rysunek 1.4 Okno graficzne z punktem (0, 0) w lewym górnym rogu, pokazujące położenie wektora położe-nia i wektora prędkości

Wektory 41

Przeanalizujmy dane dotyczące obu położeń i prędkości. W przykładzie z odbijającą się piłką oryginalnie były następujące zmienne:

Cecha

Nazwy zmiennych

Położenie

x i y

Prędkość

xspeed i yspeed

Teraz potraktuję położenie i prędkość jako wektory, przy czym każdy z nich jest re-prezentowany przez obiekt z atrybutami x i y. Jeśli miałbym sam napisać klasę Vector, zacząłbym jakoś tak:

class Vector {

constructor(x, y) {

this.x = x;

this.y = y;

}

}

Zauważmy, że ta klasa jest zaprojektowana tak, aby przechowywać takie same dane jak poprzednio - dwie liczby zmiennoprzecinkowe na wektor, czyli wartość x i war-tość y. W istocie obiekt Vector jest po prostu wygodnym sposobem przechowywa-nia dwóch wartości (lub trzech jak zobaczycie w przykładzie trójwymiarowym) pod jedną nazwą.

Okazuje się, że p5.js ma wbudowaną klasę p5.Vector, więc nie muszę sam jej pisać. Tak więc to:

let x = 100;

let y = 100;

let xspeed = 1;

let yspeed = 3.3;

staje się tym:

let position = createVector(100, 100);

let velocity = createVector(1, 3.3);

Zauważmy, że obiekty wektorów position i velocity nie są tworzone w sposób, ja-kiego można by oczekiwać, przez wywołanie funkcji konstruktora. Zamiast tego, pi-sząc nowy p5.Vector(x, y), wywołałem createVector(x, y). Funkcja createVector() jest elementem p5.js jako funkcja pomocnicza zajmująca się w tle szczegółami

42 Rozdział 1

tworzenia wektora. Poza specjalnymi okolicznościami należy zawsze tworzyć obiekty p5.Vector za pomocą create Vector(). Należy wspomnieć, że funkcje p5.js, takie jak createVector(), nie mogą być tworzone poza setup() i draw(), gdyż biblioteka nie bę-dzie jeszcze załadowana. W przykładzie 1.2 pokażę, jak do tego podejść.

Mam teraz dwa obiekty wektorowe (position i velocity), więc mogę zaimplemen-tować algorytm ruchu oparty na wektorach: position = position + velocity. W przy-kładzie 1.1, bez wektorów, kod wyglądał tak:

x = x + xspeed;

y = y + yspeed;

W idealnym świecie mógłbym przepisać to w poniższy sposób:

position = position + velocity; Dodaj wektor prędkości do wektora położenia

Jednak w języku Javascript operator + jest zarezerwowany dla podstawowych warto-ści (całkowitych, rzeczywistych i tym podobnych). Javascript nie wie, jak dodać do siebie dwa obiekty p5.Vector, podobnie jak nie wie, jak dodać dwa obiekty p5.Font lub p5.Image. Na szczęście klasa p5.Vector obejmuje metody związane z typowymi działa-niami matematycznymi.

Dodawanie wektorów

Zanim będziemy kontynuować pracę z klasą p5.Vector i metodą add(), przeanalizujmy dodawanie wektorów, korzystając z notacji stosowanej w podręcznikach do mate-matyki i fizyki. Wektory są zwykle zapisywane czcionką pogrubioną lub ze strzałką u góry. Na potrzeby tej książki, w celu rozróżnienia wektora (z wielkością i kierun-kiem) od skalara (pojedynczej wartości, jak np. liczba całkowita lub zmiennoprzecin-kowa), będę stosować notację ze strzałką:

- wektor u?

- skalar: x

Przypuśćmy, że mamy dwa wektory pokazane na rysunku 1.5.

Każda prędkość do odpowied-niego położenia