Blackout - podliczanie punktów i CRON

W pierwszym wpisie o Blackoucie – silniku gier strategiczno-ekonomicznych via WWW przedstawiłem zaledwie szkic moich notatek. Dlatego też, zgodnie z obietnicą postaram się przybliżyć bardziej jeden z aspektów.

Ostatnio wziąłem na warsztat kwestię w xNovie dość kluczową, podliczanie punktów graczy. Podliczanie w xNovie punktów jest czynnością wykonywaną z panelu administracyjnego, która w założeniu raz dziennie ma policzyć punkty dla każdego gracza i sojuszu w grze, aby potem zapisać je w bazie i przedstawiać np. w zakładce „Statystyki”. Pierwszą sprawą jaka powinna zaintrygować każdego jest pytanie o to, dlaczego taka sprawa nie jest wykonywana automatycznie za pomocą CRON-a. Szczerze mówiąc nie wiem :D Wiem za to, że w xNovie cały skrypt odpowiedzialny za to jest wepchnięty jako zakładka panelu administracyjnego, dlatego nie za bardzo jest możliwość podpięcia tego do CRON-a. Trzeba by to wydłubać do osobnego pliku.

Dlatego też w Blackoucie podliczanie punktów służących do tworzenia statystyk zostało po prostu umieszczone w funkcji BuildStats() (konwencja nazewnictwa zgodna z resztą funkcji w xNovie, żeby nie robić bałaganu większego niż już jest). W tym momencie miejsce wywołania tej operacji (panel administracyjny/CRON) nie ma żadnego znaczenia. Całe podliczenie statystyk sprowadza się do wywołania jednej funkcji.

Budowanie statystyk zostało w xNovie rozbite na dwa pliki: admin/statbuilder.php i admin/statfunctions.php. Cztery funkcje znajdujące się w drugim pliku uprościłem lekko (bo standardowo, jak to w xNovie, generowały trochę nigdzie nie używanych danych) i jako, że są używane tylko przy podliczaniu punktów, przerzuciłem je do pliku z funkcją BuildStats()

Jeśli chodzi o objętość kodu. W xNovie: plik statbuilder.php – 308 linijek, statfunctions.php – 87. Razem daje to 395 linii kodu. U mnie plik spełniający funkcje dwóch powyższych, czyli BuildStats.php zajmuje 232 linijki.

Jedną z kilku przyczyn takiej różnicy w długości kodu jest to, co znalazłem na końcu statbuilder.php – otóż na koniec pliku do podliczania punktów doklejono na chama (nie zgadza się nawet poziom wcięć, choć to w xNovie norma) kod do usuwania nieaktywnych użytkowników. Najlepsze jest to, że w xNovie istnieje już funkcja do usuwania użytkownika razem ze wszystkimi potrzebnymi do tego rzeczami (usuwanie planet, flot, sojuszy itd. itd.)

Wprowadziłem też kilka usprawnień w tabeli która przechowuje statystyki graczy i sojuszów. Zacząłem przede wszystkim od skasowania zbędnych pól - ich liczba zmniejszyła się 25 z do 19, spadł więc też rozmiar trzymanych danych. Najlepszym dowodem na brak myślenia jest fakt, że data podliczenia statystyk, która jest jednakowa w całej tabeli jest przechowywana przy każdym jej rekordzie. A ich może być sporo - po jednym na każdego gracza i na każdy sojusz w grze.

Jeśli chodzi o drugą część wpisu, czyli CRON-a. W oryginalnej xNovie było wiele czynności, które można by wykonywać z jego pomocą - na czele oczywiście podliczanie punktów. Wstępnie w Blackoucie zdecydowałem się aby skrypt odpalany CRON-em realizował następujące zadania: podliczanie punktów, usuwanie graczy, którym minął czas na aktywację konta, w przyszłości także podliczanie rekordów (będą cache'owane i generowane raz na dobę) i wykonywanie innych cyklicznych czynności związanych z przewidywanymi nowymi możliwościami w grze. Oczywiście każda czynność wydzielona do funkcji, tak aby problemem nie było na przykład wywołanie jej z panelu admina.

I to na tyle w tym wpisie, siadam do kodu :)

Blackout - przedstawienie projektu

Już sporo czasu upłynęło od ostatniego wpisu na moim blogu, tak więc stwierdziłem, że nadszedł czas na podzielenie się częścią informacji o tym nad czym właśnie pracuję.

Być może pamiętacie poprzedni wpis  „recenzujący” skrypt xNova służący do tworzenia własnych klonów gry przeglądarkowej Ogame. Postanowiłem przepisać go po swojemu i uzyskać z niego w miarę uniwersalny silnik gier MMORPG via WWW. Zależy mi na tym, aby nie był ściśle ukierunkowany na grę dziejącą się w kosmosie, żeby po dokonaniu zmian w szablonach, plikach językowych i ewentualnie lekkich zmianach mechaniki móc na nim postawić dowolną inną grę strategiczno-ekonomiczną. Skrypt zdecydowałem się ochrzcić Blackout (nazwę, tak jak zazwyczaj wymyślił Rhino, chwała mu za to). Może nie jest to nazwa super oryginalna, ale bardzo chciałem uniknąć kolejnego potworka o nazwie "Super Game Uber-pro Elo Melo Maka-Faka Engine".

Jak wskazują daty modyfikacji niektórych plików – przepisywanie skryptów zacząłem dziesiątego lutego tego roku. Tak więc całkiem przypadkiem dziś mija miesiąc – sądzę, że to niezły pretekst do opisana tego co udało mi się zrobić i tego co mnie jeszcze czeka.

Tak więc: zacząłem od przygotowania najbardziej potrzebnych elementów do rozpoczęcia pisania. Poprawiona klasa baz danych, wzorowana lekko na tej użytej jeszcze w Iron CMS-ie, bardzo prosty logger błędów i króciutki system obsługi szablonów (nie ma chyba nawet 60 linijek).

Zacząłem rzecz jasna od napisania plików dołączanych do każdej strony – mamy więc znany z oryginalnej xNovy plik common.php – w przebudowanej formie, ale spełnia identyczne w założeniu zadanie – wykonuje czynności potrzebne przy wczytaniu każdej strony (np. sprawdzenie czy gracz nie jest zbanowany). W moim silniku pozostały jeszcze resztki ze sławetnego pliku todofleetcontrol.php (możecie o nim przeczytać w tym wpisie, punkt 10). Na razie okroiłem go tylko i przemianowałem na pure_evil.php – sądzę, że to bardziej adekwatna nazwa :D. Potem zostanie usunięty, a każda podstrona będzie ładować tylko to, czego faktycznie potrzebuje.

Jeśli chodzi o zakładki, które dla użytkownika są najważniejsze to:’

  • Podgląd – gotowy w 80%, nie gotowe opuszczanie kolonii i zmienianie nazwy planety
  • Budynki, laboratorium, stocznia, obrona – odtworzone działanie i wygląd z oryginalnej xNovy, mocna przebudowa planowana na następną wersję
  • Oficerowie – gotowe w 100%, jeśli nie liczyć części niedokończonych tłumaczeń
  • Handlarz – gotowy w 20%
  • Sojusz – na początku próbowałem wzorować się na oryginalnym alliance.php jednak po 4 godzinach stwierdziłem, że nie ma to żadnego sensu. Aktualna koncepcja jest rozrysowana na kartce i czeka na realizację
  • Flota – działa sama czynność wysyłania flot, w większości działa misja walki
  • Wiadomości – napisane całkowicie od nowa – jest tam kilka rzeczy do dorobienia i poprawki, ale ogólnie mówiąc plik jest prawie gotowy
  • Galaktyka – ciekawa historia: przerażony ilością zapytań jakie generuje ta zakładka (średnio 50 na odsłonę, przy 1 planecie w pokazywanym systemie) i ilością funkcji jakich używa tylko ta zakładka (17 plików – 1200 linijek) postanowiłem napisać go od zera. Zapytania w pętli zastąpione jednym podwójnym JOIN-em, wszystko ładnie umieszczone w jednym pliku – sądzę, że w wersji ostatecznej plik nie przekroczy 300 linijek
  • Imperium, Surowce, Technologia – gotowe
  • Rekordy – nawet nie zaczęte
  • Statystyki – prawdopodobnie działają, lecz to się okaże dopiero po zrobieniu skryptu do przeliczania punktów
  • Szukaj – gotowe
  • Zbanowani – gotowe, przebudowana tabela trzymająca bany, użyto relacji
  • Chat – gotowy, tylko trzeba go ujednolicić w jeden plik i ogarnąć system BBCode – jeden dla całej gry
  • Kontakt – działa
  • Opcje – mocno odchudzone, nie działa jeszcze urlop i usuwanie konta
  • Panel administracyjny – grubsza sprawa, temat na osobny wpis

Tak, wiem, w większości zrobiłem sobie z tego wpisu kolejne wcielenie notatek odnośnie skryptu, mam jednak nadzieję, że umieszczenie tego publicznie pomoże mi się zmotywować do dalszej pracy.

Jak widać, wiele jeszcze przede mną, myśląc o kilku plikach robi mi się słabo :D, ale jakoś to będzie… Więcej konkretów, mniej suchych notatek obiecuję już w przyszłym wpisie.

Przegląd skryptów genialnych #3 - xNova

Witajcie, w kontynuacji dawno zaczętej serii, w której przyglądam się największym „perełkom” pośród skryptów PHP. Dzisiaj pod nóż pójdzie coś specjalnego, prawdziwy as wśród asów. Drodzy Państwo, przywitajcie xNovę.

Najpierw krótko o samym skrypcie. xNova to skrypt, który ma umożliwić tworzenie gier MMO via WWW wyglądających dokładnie jak OGame (mówimy tutaj o starej wersji, bo od czasu wydania ocenianej wersji skryptu, OGame doczekał się całkowitej przebudowy interfejsu).

Ciężko jest nawet powiedzieć jaką wersję będę opisywał, bo xNova od wieków nie posiada oficjalnej strony/forum/bloga. Tak więc namnożyło się bez liku wersji (najczęściej po prostu przeróbek prywatnych osób, które połatały 3% bugów i zoptymalizowały kilka zapytań). Wersja którą mam, była opisana jako „xNova 0.3 Multilanguage”, ale zdefiniowana wewnątrz skryptu stała VERSION miała wartość 0.8, więc mamy niezły rozrzut.

W związku z ilością bugów oraz masą amatorsko tworzonych „nowych wersji” najczęstszymi poradami na bugi zamieszczonymi na „profesjonalnych suportach xNovy” nie są wcale poprawki błędnej linijki, tylko zdanie „podmień sobie plik z wersji 1.1A/5.5/x.x by Ktośtam” – to też bardzo skutecznie tworzy chaos w plikach.

No, ale przejdźmy do właściwej części wpisu:

  1. Już patrząc w strukturę katalogów możemy mieć pewne pojęcie o skrypcie. W katalogu głównym umieszczono foldery scripts i js. Oba zawierają skrypty JavaScripts, część z nich jest identyczna i po analizie można dojść do tego, że około 12 z plików nie jest nigdzie używanych.

  2. Wygląd skryptu opiera się na dwóch folderach: templates, w którym leżą pliki dla systemu szablonów i skins, w którym leżą obrazki i pliki CSS. Bardzo piękne założenie, szkoda tylko , że poza tymi folderami w katalogu głównym mamy jeszcze foldery css i images, więc zmiana szablonu nie daje możliwości pełnej modyfikacji wyglądu bez modyfikacji zawartości tych folderów. Ponadto wg mnie pliki JavaScript też powinny być zależne od wybranego theme’u, a więc leżeć w jego katalogu.

  3. Przejdźmy do instalacji. Uruchamiam folder z xNovą a tam bardzo prosty do zdiagnozowania błąd. Skrypt wymaga włączonej dyrektywy short_open_tag Wracamy po szybkim restarcie serwera :)

  4. Po zmianie konfiguracji próbuję jeszcze raz i co widzę? Błąd połączenia z bazą danych zamiast instalatora. W pliku index.php jest odpowiednia instrukcja, która przekierowuje do instalatora jeśli config.php jest pusty. Niestety, ktoś udostępniając tą paczkę ludziom nie pomyślał i zostawił swojego wypełnionego configa. Tak więc poprawmy jego błędy i wyczyśćmy plik config.php. Do trzech razy sztuka…

  5. Próbujemy jeszcze raz i oto naszym oczom ukazuje się przepiękny instalator… po francusku. Szkoda tylko, że silnik posiada możliwość zmiany języka w całej grze, w instalatorze także. Niestety nikt nie pomyślał nad możliwością wyboru języka instalacji.

  6. Lecąc na czuja wypełniamy tabelkę z danymi do DB, standardowy układ jak w każdym skrypcie. Ukazuje nam się podstrona z pociesznym napisem „La base de donnée a bien été installée!”. Szkoda tylko, że ktoś jej pożarł całego CSS-a.

  7. Styl powraca, tworzymy konto admina i mamy rejestrację z głowy. Czas się zalogować.

  8. Bez problemu loguję się do gry, a tam wszystko po… tak, zgadliście, po francusku. Instalator z automatu ustawia adminowi język na francuski. Trzeba go przestawić w opcjach konta/w bazie danych.

  9. Teraz, kiedy gra już działa, przyjrzyjmy się plikom: widać wyraźnie, że każda z podstron najpierw dołącza plik common.php, a więc to jemu przyjrzymy się na początku. Co my tam mamy? Widać, że istnieje możliwość ustawienia własnych ścieżek do kilku folderów oraz domyślnego języka gry. Tylko dlaczego to nie jest w configu? Potem widzimy dołączanie kilku plików, w tym todofleetcontrol.php.

  10. Ten plik jest na tyle wyjątkowy, że zdecydowałem się go pokazać w całości:

    <?php
    /**
    *
    *
    * @version 1
    * @copyright 2008 By Chlorel for XNova
    */
    
    // Fonctions deja 'au propre'
    include($ugamela_root_path . 'includes/functions/FlyingFleetHandler.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseAttack.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseStay.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseStayAlly.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseTransport.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseSpy.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseRecycling.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseDestruction.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseColonisation.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MissionCaseExpedition.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SendSimpleMessage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SpyTarget.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/RestoreFleetToPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/StoreGoodsToPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckPlanetBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckPlanetUsedFields.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CreateOneMoonRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CreateOnePlanetRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertJavaScriptChronoApplet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsTechnologieAccessible.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetBuildingTime.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetRestPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetElementPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetBuildingPrice.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsElementBuyable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckCookies.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ChekUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertGalaxyScripts.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyCheckFunctions.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyRows.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetPhalanxRange.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetMissileRange.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPos.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowPlanetName.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowMoon.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowDebris.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowava.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowAlly.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyRowActions.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxySelector.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyMISelector.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyTitles.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GalaxyLegendPopup.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowGalaxyFooter.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetMaxConstructibleElements.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/GetElementRessources.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ElementBuildListBox.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ElementBuildListQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/FleetBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/DefensesBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ResearchBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BatimentBuildingPage.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckLabSettingsInQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/InsertBuildListScript.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/AddBuildingToQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/HandleTechnologieBuild.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildingSavePlanetRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildingSaveUserRecord.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/RemoveBuildingFromQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CancelBuildingFromQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SetNextQueueElementOnTop.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/ShowTopNavigationBar.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SetSelectedPlanet.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MessageForm.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/PlanetResourceUpdate.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildFlyingFleetTable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SendNewPassword.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/HandleElementBuildingQueue.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/UpdatePlanetBatimentQueueList.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/IsOfficierAccessible.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/CheckInputStrings.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/MipCombatEngine.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/DeleteSelectedUser.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/SortUserPlanets.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildFleetEventTable.'.$phpEx);
    include($ugamela_root_path . 'includes/functions/BuildRessourcePage.'.$phpEx);
    
    ?>
    

    Co my tu mamy? Bezmyślne dołączanie 78 plików za każdym wywołaniem strony. Nawet gdy się logujemy, rejestrujemy, oglądamy podstronę kontakt itd. silnik ładuje 78 plików potrzebnych tylko w grze.

  11. Kolejnym plikiem ładowanym w common.php jest plik /includes/db.php. On też jest w pewien sposób wyjątkowy: jedyne co robi to include’uje kolejny (jeden) plik. Gdzie tu logika?

  12. Wymieniony wyżej plik includes/db.php dołącza plik db/mysql.php. Jego zawartość też jest warta przytoczenia:

    <?
    function doquery($query, $table, $fetch = false){
    global $numqueries,$link,$debug,$ugamela_root_path;
    require($ugamela_root_path.'config.php');if(!$link)
    {$link = mysql_connect($dbsettings["server"], $dbsettings["user"],
    $dbsettings["pass"]) or
    $debug->error(mysql_error()."
    $query","SQL Error");
    
    mysql_select_db($dbsettings["name"]) or $debug->error(mysql_error()."
    $query","SQL Error");
    mysql_query("SET NAMES latin2");
    echo mysql_error();}
    
    $sql = str_replace("{{table}}", $dbsettings["prefix"].$table, $query);
    $sqlquery = mysql_query($sql) or
    $debug->error(mysql_error()."
    $sql
    ","SQL Error");
    
    unset($dbsettings);
    $numqueries++;
    $arr = debug_backtrace();
    $file = end(explode('/',$arr[1]['file']));
    $line = $arr[1]['line'];
    $debug->add("
    Query $numqueries:$query$file($line)$table$fetch
    
    ");
    
    if($fetch)
    {$sqlrow = mysql_fetch_array($sqlquery);
    return $sqlrow;
    }else{return $sqlquery;}}
    ?>
    

    Pierwszą rzeczą jaka rzuciła mi się w oczy poza genialnym układem kodu był fakt, że przy każdym zapytaniu jest dołączany config.php. No ktoś tam nie ma mózgu… nie można po prostu dołączyć go w common.php? Tak samo ewentualne łączenie powinno się odbywać nie funkcji wykonującej zapytania tylko tam.

  13. W folderze includes znajduje się plik databaseinfos.php, który na pierwszy rzut oka jest używany do tworzenia tabel przez instalator, a krótka analiza wykazuje, że używany jest tylko tam. W takim wypadku, czy nie logiczniej byłoby go usunąć automatycznie po zakończeniu instalacji (tak jak zresztą cały skrypt). Ponadto prościej byłoby chyba, gdyby znajdował się po prostu w folderze install.

  14. Samo nazewnictwo plików jest lekko mówiąc dziwne. Czy to mówiąc o sposobie zapisu (add_moon.php, ale już activeplanet.php), czy o języku (errors.php, paneladmina.php, verband.php). Do wyboru, do koloru!

  15. Jeśli chodzi o bazę danych, to widać jak na dłoni, że była dorabiana po kawałku zależnie od konieczności. Jednak nikt nie zadał sobie trudu, aby spojrzeć na nią całościowo, przeprojektować i choćby pousuwać zbędne pola z tabel czy ustawić bardziej optymalne typy dla pól. Jako najlepszy przykład można podać fakt, że informacje o występujących w grze Księżycach są trzymane w dwóch tabelach, przy czym jedna zawiera po prostu część danych z pierwszej tabeli. Niestety, aby to poprawić trzeba dokonać dość mocnej przeróbki skryptu, a mało kto ma tyle zawzięcia.

Powyższy skrypt jest bardzo wymownym przykładem na to jak nie powinno się tworzyć jakichkolwiek aplikacji. Zero organizacji pracy i początkowych założeń daje właśnie takie efekty. Można tylko współczuć ludziom którzy z wielkim zapałem chcą zrobić "super wypasionom grem na xNovie" nie zaglądając nawet do "jej wnętrza".