Processing/Geometria
Bez matematyki ani rusz
edytujNa szczęście matematyka, o której tu mowa nie jest skomplikowana i w praktyce okazuje się łatwa do stosowania. W tym rozdziale mówić będziemy jedynie o podstawowych operacjach, a w przyszłych więcej o trygonometrii i podobnych wynalazkach. Teraz jednak powróćmy do podstaw - w programowaniu nawet dodawanie ma kilka postaci.
Operacje przypisania... i dodawanie
edytujZacznijmy od tzw. operacji przypisania, czyli nadawania zmiennej jakiejś wartości. Rzecz wydaje się prosta, ale przyjrzyjmy się poniższemu kodowi - czy wszystkie operacje są oczywiste?
int x = 3;
int y = 0;
y = x + 6;
y = y + 1;
println( "Wartość zmiennej y to " + y );
Na pewno pojawi się pytanie, co to za funkcja na końcu i co oznacza tam dodawanie. Wyjaśnijmy to od razu. Funkcja println wypisuje w dolnej, czarnej częsci okna Processingu wszystko, o co ją poprosimy. W naszym przypadku chcielibyśmy, żeby wypisała na ekranie wartość zmiennej y. W tym celu dodajemy napis "Wartość zmiennej y to " do zmiennej y. Co Processing z tym zrobi? odczyta, jaka jest wartość zmiennej y i doklei ją do tego napisu. W ten sposób dostaniemy:
Wartość zmiennej y to 10
Do tego tematu wrócimy jeszcze w dalszych rozdziałach. W tym momencie warto pamiętać, że jeśli chcialibyśmy podejrzeć, jakie wartości znajdują się w naszym programie to możemy wypisać je w oknie konsoli (czarny obszar na dole okna Processingu) za pomocą funckji println.
Wracamy do dodawania i przypisywania. Dwie pierwsze linie kodu są proste. Tworzymy zmienną typu całkowitego i nadajemy jej nową wartość. Co dzieje się w liniach 4 i 5?
x = x + 1 to nie to samo, co x = x + 1
edytujNa lekcjach matematyki uczyliśmy się, że:
x = x + 1
x - x = 1
0 = 1
Równianie sprzeczne
W programowaniu jest trochę inaczej - tutaj prawa i lewa strona nie są wymienne. Processing wie, że najpierw ma wykonać wszystkie działania po prawej stronie znaku równości, a następnie przekazać ich wartość temu, co znajduje się po lewej stronie znaku równości. W poprzednim przykładzie (linie 4-5) mieliśmy zatem taką kolejność wykonuwania kodu:
// Linia numer 4: y = x + 6;
Liczę wartość wyrażenia po prawej stronie --> x + 6
zmienna x --> 3
3 + 6 --> 9
Przypisuję zmiennej y (obojętnie jaką wartość miała wcześniej) wartość 9
// Linia numer 5: y = y + 1;
Liczę wartość wyrażenia po prawej stronie --> y + 1
zmienna y --> 9
9 + 1 --> 10
Przypisuję zmiennej y (obojętnie jaką wartość miała wcześniej) wartość 10
Można zatem powiedzieć, że - w odróżnieniu od matematyki - w programowaniu znak = oznacza: temu, co znajduje się po lewej stronie znaku, przypisz (nadaj) wartość wyrażenia znajdującego się po prawej stronie znaku. Znaczy to, że Processing widząc linię kodu zawierającą znak = czyta ją od znaku = do końca w prawo, a następnie cofa się jeden krok w lewo. W sumie to proste.
Matematyka dla leniwych
edytujJeśli w programowaniu można napisać coś skrótem, to pewnie da się napisać to skrótem. Często jest tak, jak w ostatnim przykładzie - chcielibyśmy dodać do zmiennej jakąś wartość:
int x = 8;
// jakis kod
// zwieksz wartosc zmiennej x o 5
x = x + 5;
Taką operację można zapisać następującym skrótem:
// dodaj 5 do aktualnej wartosci zmiennej x
x += 5;
Co więcej - takie skróty dotyczą wszytkich podstawowych operacji matematycznych.
int x = 2;
// dodaj 3 do aktualnej wartosci zmiennej x
x += 3;
// odejmij 1 od aktualnej wartosci zmiennej x
x -= 1;
// pomnóż aktualną wartość zmiennej x przez 3
x *= 3;
// podziel aktualną wartość zmiennej x przez 2
x /= 2;
println( "Wartość zmiennej x to " + x );
Wartość zmiennej powinna wynosić 6.
Skrót skrótu
edytujWyobraźmy sobie taką sytuację. Piszemy program, który odtwarza plik filmowy (a będziemy taki pisać w dalszej części podręcznika). Naciskamy play i klatka po klatce film wyświetlany jest na ekranie. Żeby wiedzieć, czy znajdujemy się już w połowie, czy dopiero na początku filmu, mamy zmienną frameCounter (licznik klatek), która zwiększa się o jeden po każdej wyświetlonej klatce filmu. Nasz algorytm mógłby wyglądać tak:
wyświetl nową klatkę filmu
frameCounter += 1;
I tak w kółko. Programiści wynaleźli na taką operację specjalną nazwę i bardzo przydatny skrót, który ma dwie wersje*:
frameCounter++; // to jest inkrementacja
frameCounter--; // a to jest dekrementacja
W pierwszym przypadku dodajemu 1 do wartości zmiennej frameCount, w drugim przypadku 1 od niej odejmujemy. W pewnym sensie efekt działania jest identyczny dla poniższych lini kodu:
frameCounter++;
frameCounter += 1;
Wbrew pozorom inkrementacja to bardzo przydatna sprawa i od następnego rozdziału korzystać z niej będziemy bezustannie!!
* - tak naprawdę instnieją cztery wersje, a nie dwie. Pozostałe dwie poznamy jednak później, bo w chwili obecnej nie mają one dla nas większego znaczenia.
Trzy przez dwa
edytujDzielenie to nie fizyka kwantowa - trzy przez dwa to przecież półtora.
int result = 3 / 2;
println( "Trzy przez dwa to " + result );
Jak to 1?! Czyżby się Processing pomylił?! Przyjrzyjmy się dokładnie powyższemu fragmentowi kodu. Jak widać dzielimy liczby całkowite i w dodatku zapisujemy wynik w zmiennej cełkowitej. Processing nie miał szansy pokazania nam tego, co po przecinku - prosimy o liczbę całkowitą, dostajemy całkowitą! Spróbujmy zatem zmienić zmienną result na liczbę z przecinkiem:
float result = 3 / 2;
println( "Trzy przez dwa to " + result );
1.0?! O co chodzi?! Znów jednak przyjrzyjmy się dokładniej przykładowi i zastanówmy, jak Processing wykonuje pierwszą linię kodu - krok po kroku:
// float result = 3 / 2;
Widzę operację przypisania (=) - zaczynam od prawej strony znaku =
3 / 2 --> dzielenie:
3 to liczba całkowita
2 to też liczba całkowita
wynik jest zatem liczbą całkowitą --> 1
Cofam się o krok i wykonuję operację przypisania:
result --> zmienna z przecinkiem
1 --> liczba całkowita
przekonwertuj liczbę całkowitą na z przecinkiem, żeby pasowała do zmiennej result
1 --> 1.0
result --> 1.0
Uff... Trochę to pogmatwane. Lekcja jednak z tego taka, że jeśli wykonujemy dowolną operację matematyczną na liczbach całkowitych, wynik tej operacji będzie całkowity - nawet jeśli w ten sposób stracimy dokładność obliczeń. Jak się później okaże czasami bywa to bardzo przydatne. Pamiętajmy bowiem, że działamy na monitorze podzielonym na piksele i nie ma piksela numer 2.4 ani współrzędnej (24.7, 61.9).
Jak jednak poradzić sobie z naszym prostym dzieleniem? Rozwiązanie jest następujące: z obu stron znaku przypisania (=) musimy mieć liczbę z przecinkiem.
float result = 3.0 / 2.0;
println( "Trzy przez dwa to tak naprawdę " + result );
Udało się!! Processing policzył z przecinkami wynik i przypisał go zmiennej przecinkowej wartość tych obliczeń. Czy to znaczy, że musimy wszystkie operacje z przecinkiem pisać z zerami? I tak i nie. Można wykorzystać taką cechę Processingu, że gdy w operacji matematycznej pojawia się choćby jedna liczba z przecinkiem, Processing automatycznie zamienia tę drugą na przecinkową. Należy jednak na to uważać, bo...
Kto pierwszy, kto drugi
edytujProcessing liczy proste wyrażenia tak jak my robimy to na lekcjach matematyki: najpierw to, co w nawiasach, później mnożenie i dzielenie, a na końcu dodawania i odejmowanie. Zmiany z liczb całkowitych na przecinkowe dokonuje się z zachowaniem tych zasad, co w praktyce oznacza, że:
float result = 3 / 2 + 0.1;
println( "Coś tu chyba nie gra: " + result );
Wyszło 1.1 zamiast 1.6. Dlaczego? Dlatego, że Processing najpierw podzielił dwie liczby całkowite i dostał całkowity wynik (1), po czym dodał ten wynik do liczby z przecinkiem. W tymc celu zamienił 1 na 1.0 i dodał 0.1. Wszystko gra. Tylko, że nie o to nam chodziło. Należałoby zatem napisać tak:
float result = 3 / 2.0 + 0.1;
println( "Teraz chyba gra: + " result );
Łatwo tu o pomyłki. Często będzie tak, że nasz program będzie działał, ale to, co zobaczymy na ekranie nie będzie odpowiadało naszym oczekiwaniom. W wielu przypadkach okaże się, że miało być 1.5 * 2, a wyszło 1 * 2, bo gdzieś wcześniej wykonaliśmy operacje na liczbach całkowitych, a nie przecinkowych.