Dość powszechne na forum Warsztatu jest pytanie: z czego się uczyć? – programowania w ogóle i gamedevu w szczególności. Chodzi tu po prostu o źródła wiedzy. I chociaż (częściowa) odpowiedź znajduje się w warsztatowej Wiki, to pewnie podobne zapytania wciąż będą się pojawiać. Nie ma się tu specjalnie czemu dziwić.
Zupełnie inaczej jest, jeśli owo ‘uczenie się’ występuje w trochę innym kontekście. “Czy mogę nauczyć się programowania, jeśli …?”, “Czy powinienem wykonywać ćwiczenia z książki X?” albo najgorsze z tych pytań: “Ile czasu poświęcacie dziennie na naukę programowania?”. Natychmiast służę oczywiście odpowiedzią na nie: dokładnie trzy godziny, między 19 a 22, przez dwa dni w tygodniu, tj. we wtorki i piątki. I bardzo, bardzo przy tym pilnuję, aby tego czasu nauki nie skracać nawet o minutę!… Czy ta odpowiedź jest satysfakcjonująca? Przypuszczam, że niezbyt ;]
A mówiąc już trochę poważniej… Jeśli nie zajmujemy się programowaniem zawodowo, to jest to pewnie nasze hobby – czyli coś, co robimy dla przyjemności. Chodzi o tworzenie czegoś własnego, eksperymentowanie z różnymi technologiami, a także – nie ma co ukrywać – uczenie się czegoś nowego. Jednak mówimy tu o uczeniu się rzeczy, które chcemy, kiedy chcemy i jak długo chcemy – wedle naszego własnego uznania, a nie czegoś w rodzaju “planu treningowego'”, ułożonego na podstawie odpowiedzi ludzi z jakiegoś forum internetowego (z całym szacunkiem dla Warsztatu oczywiście). Trudno przecież oczekiwać, że powiedzą nam oni, co będzie nam bardziej odpowiadało; do tego trzeba dojść samodzielnie.
Czy więc właściwa odpowiedź na wspomniane pytanie istnieje? Oczywiście, że tak. Brzmi ona: na naukę programowania przeznaczamy tyle czasu, ile chcemy i/lub możemy. Tak po prostu.
Były takie prognozy, że w Internecie wkrótce nie będzie już prawie niczego za darmo, a za większość usług czy nawet informacji trzeba będzie płacić. W rzeczywistości można obecnie znaleźć w sieci coraz bardziej wyrafinowane i wyspecjalizowane usługi często całkowicie (lub niemal całkowicie) darmowe. Nie chodzi tu już tylko o webhosting czy konto e-mail. Przeciwnie, nietrudno natrafić na oferty skierowane bezpośrednio do… programistów – zawodowców i amatorów.
Istnieją bowiem serwisy, które nieodpłatnie udostępniają swoim użytkownikom repozytoria, na których mogą oni umieszczać swoje projekty. Tak tak, chodzi tutaj o hostowanie systemów kontroli wersji, w szczególności najpopularniejszego: Subversion (SVN). Wystarczy zarejestrować się w jednym z takich serwisów, a otrzymujemy możliwość założenia swojego własnego repozytorium i nadania praw dostępu do niego innym użytkownikom. Obowiązują oczywiście pewne ograniczenia, dotyczące najczęściej ilości miejsca na naszą twórczość lub liczby osób, które możemy zaprosić do współpracy. Na potrzeby własne, szkolne i uczelniane często są one jednak możliwe do zaakceptowania. A jak nie, to pozostają jeszcze wersje płatne – bo nie ma co ukrywać, że to o przyciągnięcie do oferty komercyjnej w całym tym ambarasie chodzi :)
Darowanemu koniowi nie ma co jednak zaglądać za bardzo w zęby. Jeśli bowiem potrzebujemy narzędzia do zorganizowania pracy grupowej (albo nawet swojej własnej) nad niewielkim projektem, to serwisy w rodzaju Beanstalk, XP-Dev czy Assembla mogą być bardzo dobrym rozwiązaniem.
Niejeden początkujący programista C++ natknął się pewnie na prosty problem: jak zdefiniować zmienną, która będzie dostępna w całym programie? Sprawa nie jest oczywista z tego względu, że w języku tym nie występuje w ogóle pojęcie ‘projektu’; każdy moduł .cpp jest kompilowany osobno i dopiero linker składa je wszystkie w jeden plik wykonywalny. O tym jednak łatwo zapomnieć, jeśli używamy współczesnych środowisk programistycznych.
Warto więc wiedzieć, że w C++ tak naprawdę nie ma zmiennych globalnych. Symulują je zmienne łączone zewnętrznie – takie, które definiujemy w jednym module .cpp, a odwołujemy się do nich z innych modułów przy pomocy deklaracji zapowiadających extern
. Te muszą być oczywiście umieszczone we wspólnych plikach nagłówkowych.
Powyższy sposób jest jedynym na uzyskanie zmiennych dostępnych z wielu plików .cpp. Jeśli coś w nim pokręcimy, możemy otrzymać albo kod niekompilujący się (jeżeli nie dołączymy deklaracji extern
), ale – co gorsza – uruchamiający się, ale działający zupełnie inaczej (jeśli w headerze damy normalną definicje zmiennej, każdy dołączający go moduł otrzyma jej własną niezależną kopię).
Na dzisiaj przewidziałem ciekawostki z nieco innej niż zwykle beczki :) Chciałem mianowicie pokazać dwa przykłady na to, jak intuicyjnie całkiem proste pojęcie matematyczne w rzeczywistości jest bardzo podatne na niewłaściwe zrozumienie. Chodzi tutaj o zwykłe prawdopodobieństwo – czyli szansę na zajście jakiegoś zdarzenia.
Pierwszy przykład jest z gatunku rozrywkowo-medialnych i związany jest z pewnym teleturniejem, który zresztą był kiedyś emitowany także w Polsce. Oto w pewnym jego etapie uczestnik jest konfrontowany z trzema zasłoniętymi bramkami, z których jedna zawiera nagrodę, a dwie pozostałe są puste. Spośród tej trójki gracz wybiera jedną bramkę, by stać się właścicielem jej ewentualnej zawartości. Trik polega na tym, że gospodarz programu – już po wyborze gracza – odsłania jedną z pozostałych bramek, która okazuje się być pusta. Zgodnie z regułami teleturnieju gracz może w tym momencie zmienić swój wybór i wskazać trzecią bramkę zamiast tej wybranej pierwotnie. Pytanie brzmi: czy taka zamiana mu się opłaca?
Część ludzi stwierdziłaby zapewne, że nie ma to znaczenia, bo szansa wygranej przecież i tak wynosi 1 do 3, bo tylko za jedną bramką jest nagroda. Inni mogliby uznać, że po odsłonięciu jednej bramki wybieramy już spośród dwóch, więc nasze szansę rosną do 50% – też niezależnie od tego, czy zmienimy swój pierwotny wybór czy nie… A jak jest naprawdę?
Może się to wydawać niedorzeczne, jednak będąc na miejscu gracza, powinniśmy zawsze zmienić swój wybór. Mało tego, w ten sposób nasze szanse na wygraną rosną dokładnie dwukrotnie! Jak to możliwe?… Otóż kluczowe w wyjaśnieniu tego zjawiska jest zauważenie, że gospodarz programu zawsze odsłoni bramkę pustą i niewybraną przez gracza (w innym przypadku gra skończyłaby się od razu i w ogóle nie byłoby możliwości zmiany). Dlatego też opłaca się dokonać zmiany, bo w ten sposób w istocie odwracamy prawdopodobieństwo wygranej i przegranej. Możliwe są bowiem dwie sytuacje:
Jak wiadomo prawdopodobieństwo dobrego wyboru spośród trzech bramek wynosi 1/3, a złego – 2/3. Takie są też odpowiednie prawdopodobieństwa dwóch powyższych scenariuszy; innymi słowy, zmiana bramki powoduje, że prawdopodobieństwo wygranej rośnie z początkowego 1/3 do 2/3.
Jeśli ktoś ma nadal wątpliwości, to nie ma czym się martwić – podobno wielu matematyków też ma kłopoty z ogarnięciem tego paradoksu :) Wydaje mi się, że rzecz w tkwi w błędnym określeniu, co tak naprawdę jest tutaj zdarzeniem losowym. Jeśli za takie będziemy uważali zarówno początkowy wybór, jak i zmianę, to rzeczywiście mogą być kłopoty z dojściem do poprawnych wniosków. Zamiast tego całą grę powinno się traktować jako jedno doświadczenie losowe, ze z góry ustalonym scenariuszem.
A co z drugim przykładem? Jest na szczęście nieco prostszy i dotyczy koncepcji zdarzeń (nie)zależnych. Oto kilka pytań dotykających tej kwestii, dla których odpowiedzi “intuicyjne” są zwykle błędne:
Nie, prawdopodobieństwo wpadnięcia kulki w czarne pole to cały czas 1/2. Nie, liczba 42 może wypaść z takim samym prawdopodobieństwem dzisiaj, jak i w każdym innym losowaniu. Nie, zabicie n potworów Y wcale nie da nam prawie-pewności dostania Z – o ile mówimy o zwyczajowym użyciu słowa ‘prawie’ ;)
Wszędzie tutaj mamy bowiem do czynienia z sekwencją zdarzeń całkowicie niezależnych, których wyniki nie wpływają na siebie. Zatem nie ma znaczenia to, ile razy wcześniej kręciliśmy ruletką, ile wysłaliśmy już w życiu kuponów Lotto i ile wrażych monstrów zdołaliśmy zaszlachtować – prawdopodobieństwo zajścia interesującego nas zdarzenia w kolejnej próbie jest zawsze identyczne. Możemy aczkolwiek spróbować policzyć, na ile jest to prawdopodobne dla ciągu k kolejnych prób. Otóż ze schematu Bernoulliego wynika, że szansa na sukces wynosi wtedy
1 – (1 – p)k
jeśli dla pojedynczej próby prawdopodobieństwo wynosi p. Stąd dla p = 1/n i k = n otrzymujemy (po kilku, jak to by powiedzieli matematycy, “trywialnych przekształceniach ;D) wynik: (e – 1)/e; nieco ponad 63%. To trochę mało jak na prawie-pewność, czyż nie? ;]
Jak więc widać na przykładach, szansa na to, że opacznie zrozumiemy sytuację, w której zastosowania mają pojęcia ‘szansy’ czy ‘prawdopodobieństwa’ (używane tutaj jako synonimy) jest – nomen omen dosyć duża. Może dlatego w szkole niespecjalnie przepadałem za tym działem matematyki :)