Gra nigdy nie układa łamigłówki ręcznie. Wyhodowuje ją z jednej liczby, a potem rozgrywa tę łamigłówkę sama przeciwko sobie, żeby upewnić się, że nie jest ani trywialna, ani niemożliwa, zanim w ogóle ją zobaczysz.
Napisane i zredagowane po angielsku. Ta polska wersja powstała w wyniku tłumaczenia maszynowego; tam, gdzie liczy się precyzja, rozstrzygający jest angielski oryginał. Przeczytaj oryginał po angielsku →
Poziom w Contraption nie jest nigdzie zapisany. Nie istnieje plik, w którym stałoby, gdzie siedzi bramka ani jak rozmieszczone są półki. Zamiast tego cały poziom, punkt startowy piłki, pozycja bramki, liczba i rozmieszczenie stałych ścian, zestaw części, które dostajesz do ręki, jest obliczany z jednej liczby początkowej zwanej ziarnem. Podaj generatorowi to samo ziarno, a otrzymasz, bajt w bajt, tę samą łamigłówkę. 1
Jest to możliwe, bo jedynym źródłem „losowości” generatora jest mały, deterministyczny generator liczb pseudolosowych. Contraption używa mulberry32, zwięzłego 32-bitowego generatora, którego autorem jest Tommy Ettinger; poznasz go po charakterystycznej stałej 0x6d2b79f5, dodawanej do stanu przy każdym losowaniu. 2 Jest szybki, mieści się w kilku linijkach i, co kluczowe, wcale nie jest losowy: to ustalony ciąg matematyczny, który jedynie wygląda na potasowany. Daj mu punkt startowy, a będzie w nieskończoność produkował ten sam strumień liczb. Generator odczytuje potem ten strumień, żeby zdecydować, gdzie co umieścić.
Tryb Codziennie opiera się na tym determinizmie, żeby zrobić coś społecznego. Jego ziarno nie jest losowe, lecz wytwarzane z kalendarza. Gra bierze dzisiejszą datę w czasie uniwersalnym, zapisuje ją w krótkim ciągu znaków w rodzaju „contraption:2026-7-28” i przepuszcza ten tekst przez funkcję skrótu, żeby sprowadzić go do jednej 32-bitowej liczby. 1
Tą funkcją skrótu jest FNV-1a, dobrze znany niekryptograficzny mikser: zaczyna od stałego przesunięcia, dziesiętnie 2166136261, czyli 0x811c9dc5, i dla każdego znaku wplata literę operacją alternatywy wykluczającej (XOR), a następnie mnoży wynik przez liczbę pierwszą FNV, 16777619. 3 Zostaje liczba, która jest stała dla danego dnia i zupełnie inna z dnia na dzień, więc zmiana jednego znaku w dacie całkowicie rozrzuca ziarno. Skoro kalendarz jest ten sam dla wszystkich, to samo dotyczy ziarna, a więc i łamigłówki. Dwoje nieznajomych po przeciwnych stronach świata otwiera tryb Codziennie i staje przed identycznym polem, a właśnie to sprawia, że porównywanie wyników ma sens.
Codzienne wyzwanie jest uczciwe nie dlatego, że serwer wysyła wszystkim ten sam poziom, lecz dlatego, że każda kopia gry niezależnie oblicza na nowo ten sam poziom z tej samej daty.
Losowa geometria jest łatwa; dobra losowa geometria już nie. Generator, który po prostu rozrzuca bramkę i kilka półek, czasem wyprodukuje poziom, w którym piłka upuszczona z góry sama spada prosto do bramki. To nie łamigłówka, to przerywnik filmowy. Dlatego zanim Contraption zaakceptuje kandydata na świat, przeprowadza cichy test, który w kodzie nazywa się preSolved: symuluje poziom z zerową liczbą umieszczonych części i sprawdza, czy goła piłka dociera do bramki samą siłą grawitacji. Jeśli tak, poziom trafia do kosza, a generator losuje od nowa. 1
To subtelne odwrócenie tego, jak zwykle myśli się o silniku fizycznym. Ta sama funkcja simulate(), która odgrywa twoją maszynę, zostaje w chwili generowania skierowana do wewnątrz, żeby skontrolować trudność samego poziomu. Silnik jest zarazem sędzią twojego rozwiązania i kontrolerem jakości łamigłówki.
Odrzucenie zbyt łatwego poziomu to tylko połowa roboty. Drugim zagrożeniem jest poziom niemożliwy, z bramką schowaną tam, gdzie nie dosięgnie żaden układ części. Contraption zabezpiecza się przed tym małym solverem typu brute force o nazwie plausiblySolvable. Nie myśli on sprytnie. Po prostu próbuje. 1
Konkretnie: przesuwa jedną rampę po siatce pozycji rozpiętej nad polem i w każdym miejscu próbuje jej pod czterema różnymi nachyleniami. Dla każdego z tych próbnych ustawień uruchamia pełną symulację i pyta: czy ta jedna rampa doprowadziła piłkę do domu? Gdy tylko któreś ustawienie się powiedzie, poziom zostaje uznany za rozwiązywalny i trafia do ciebie. Jeśli po całym przeszukaniu nie zadziała żadne, generator woli odrzucić poziom i spróbować ze świeżym ziarnem.
| Parametr przeszukania | Zakres | Znaczenie |
|---|---|---|
| Rampa X | 15 → 85, krok 14 | kolumny w poprzek pola |
| Rampa Y | 30 → 120, krok 18 | wiersze w dół pola |
| Wypróbowane kąty | 4 | ±0.45 i ±0.8 radiana |
| Test na ustawienie | 1 pełna symulacja | czy piłka dociera do bramki? |
Warto uczciwie powiedzieć, czego to dowodzi, bo kod też jest w tej sprawie uczciwy: komentarz nazywa to „tanim sprawdzeniem wiarygodności … nie wyczerpującym”. Poziom, który przechodzi test, na pewno ma co najmniej jedno toporne rozwiązanie z użyciem jednej rampy. Poziom, który go oblewa, wciąż może dać się rozwiązać wentylatorem, przekładnią albo sprytniej ustawioną rampą, której siatka nigdy nie wypróbowała, więc generator nie traktuje porażki jako dowodu niemożliwości. Zachowuje pierwszy znaleziony nietrywialny świat jako rezerwę, a jeśli czterdzieści prób minie bez czystego rozwiązania jedną częścią, wysyła tę rezerwę, zamiast zapętlać się w nieskończoność. Wynikiem jest pragmatyczna gwarancja: nigdy darmowa wygrana, prawie zawsze prawdziwa droga do środka i nigdy nieskończone czekanie na „idealny” poziom. 1
Spójrz z dystansu, a generator poziomów Contraption okaże się czystym przykładem bardzo starego i bardzo ogólnego wzorca rozwiązywania problemów: generuj i testuj. Zaproponuj losowego kandydata; sprawdź go pod kątem swoich wymagań; zachowaj, jeśli przejdzie, odrzuć i zaproponuj nowego, jeśli nie. To pętla stojąca za zawartością proceduralną w niezliczonych grach, za dużą częścią obliczeń ewolucyjnych i, przy odrobinie przymrużenia oka, za samym doborem naturalnym: mutacja proponuje, środowisko testuje, ocaleni trwają. 4
Wersję z Contraption czyni elegancką to, że test nie jest tanim zastępnikiem grywalności, on jest grywalnością. Generator nie szacuje, czy poziom jest uczciwy, licząc półki albo mierząc odległości. Dosłownie rozgrywa poziom, i to dwa razy: raz pusty, żeby upewnić się, że nie jest prezentem, i raz z próbną rampą, żeby upewnić się, że ścieżka istnieje. Ta sama deterministyczna fizyka, która osądzi twoje rozwiązanie, jest fizyką, która sprawdziła łamigłówkę. Nie ma osobnego „walidatora poziomów”, który mógłby rozjechać się z prawdziwymi regułami, bo istnieje tylko jeden zestaw reguł, używany do wszystkiego.
A ponieważ wszystko to jest deterministyczne, kontrola jest wolna od szczęścia w obie strony. Codzienne ziarno nie może się zmówić, żeby dać jednemu graczowi łatwiejsze pole niż innemu, i nie może dać nikomu pola nieuczciwego, bo zanim poziom stanie się wyzwaniem, przetrwał już rozegranie, przez grę, przeciwko samej sobie.