Pewnego dnia 3 lata temu w zespole The Codest przygotowaliśmy świetną grę Cody dla programistów Ruby. W dzisiejszym artykule chciałbym opisać jak wyglądała praca nad tym projektem, a przede wszystkim pokazać kod projektu, który od teraz jest publicznie dostępny na naszym githubie.
Projektowanie gier
Projektując grę naszym głównym celem było przygotowanie fajnej rozrywki dla programistów, a także zrobienie czegoś ciekawego w ramach pracy w naszej firmie. Do tej pory nie mieliśmy żadnych kompetencji w tworzeniu gier, dlatego było to dla nas spore wyzwanie. W pierwszej kolejności skupiliśmy się na tym, czym ta gra tak naprawdę ma być. Po opracowaniu wstępnego planu przystąpiliśmy do działania.
W ramach prac nad grą zdecydowaliśmy się na hackathon i podział na grupy wykonujące konkretne zadania. Dzięki takiemu 8-godzinnemu podziałowi pracy udało nam się zrealizować wygląd przeciwników w grze, całą szatę graficzną oraz podwaliny zarówno zadań, jak i API całego systemu. W kolejnym etapie zbieraliśmy się na 4-godzinne spotkania raz w miesiącu, dzięki czemu udało nam się ukończyć grę w 3 spotkania.
Wdrożenie
Jako że specjalizujemy się w RubyOnRails, wybraliśmy tę technologię jako wiodącą. Gra nie miała być jednak tekstowa, dlatego podejście do niej znalazło odzwierciedlenie w aplikacji typu SPA. W ramach zadania pracowaliśmy nad dobrze znanym potokiem assetów z railsów (w 2016 roku w zasadzie nie było nic lepszego) oraz całą aplikacją. javascript w oparciu o nasze zastrzeżone kod z pomocą TypeScript. W aplikacji nastąpił standardowy podział obowiązków: Railsy jako źródło assetów i API, javascript i related jako interakcja z użytkownikiem. Tutaj jednak funkcjonowało to jako hybryda i część widoków była po prostu renderowana z Railsów, a część z JS.
Maszynopis
Był to nasz pierwszy eksperyment w tej dziedzinie. Były to czasy, kiedy ludzie wierzyli w sukces CoffeScript. Użycie TypeScript wymagało wprowadzenia gema typescript-rails. Niestety nie był to finał, gdyż typescript, jako język statycznie typowany, wymagał tego również od bibliotek domyślnie dołączonych do railsów.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/jquery.d.ts (szczególnie w przypadku korzystania z wbudowanego systemu zarządzania zasobami z szynami).
Cody jako gra wymagała sporej dynamiki po stronie przeglądarki, a także modyfikacji drzewa DOM. Użycie TypeScript zamiast vanilla javascript było ogromnym skokiem w jakości kodu, sama obecność klas i enkapsulacji była dla nas bardzo kusząca.
API i SPA
W 2019 r. aplikacje SPA są zarządzane za pomocą wspaniałych urządzeń React lub Vue biblioteki. Jednak w 2015 roku zrobiliśmy to w inny sposób. Wspomniany wcześniej typescript był pomocny w implementacji gry, podczas gdy jQuery cofnęło całą pracę związaną z żądaniem xml http. Teraz możemy używać fetch, podczas gdy w tamtych czasach `$ .ajax` był wszystkim, co było potrzebne do pracy. Spójrz na nasze api klienta!
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/services/api_client.js.ts
Jeśli było to api, to trzeba było jakoś rozwiązać problem uwierzytelniania, prawda? No właśnie. Ale w tym przypadku poszliśmy za (czy można tu napisać - użyliśmy zespołu?!) zespołem i w sesji rails stworzyliśmy cookie_key, a następnie zapisaliśmy go w bazie danych. Stąd wiedzieliśmy, że wszystko jest więcej niż w porządku.
https://github.com/codesthq/cody_the_game/search?q=cookie_key&unscoped_q=cookie_key
Stan gry był przechowywany w bazie danych, a informacje o tym, ilu użytkowników ma punkty, pochodziły z bazy danych (czy to ta sama baza danych? Czy możemy po prostu zmienić to zaimkiem?). ACID zawsze się przydaje, gdy nie ma buforowania po stronie systemu;)
W przypadku spa jest to najlepsze rozwiązanie bez przeładowywania strony. Rozwiązaliśmy to klasycznie i anchor html był najlepszym rozwiązaniem bez rozszerzania niepotrzebnych zależności. Bo kto by używał turbolinków?
SnapSVG
Jeśli projektujemy grę, to musi być ona wydana tylko ze świetną grafiką i animacjami. Wtedy spędzaliśmy wiele godzin zastanawiając się jak sprostać tym wymaganiom w naszej aplikacji. Z jednej strony canvas potrafi zdziałać cuda, z drugiej w czystym htmlu dużo łatwiej się połapać i każdy o tym wie. Po żmudnych poszukiwaniach najlepszego rozwiązania doszliśmy do wniosku, że połączeniem tych dwóch rozwiązań jest svg. Pozwala on w łatwy sposób prezentować grafikę w wektorze, jest napisany w języku znaczników i co najważniejsze można go modyfikować w locie. Co ważne, istnieje biblioteka dla plików svg, która działa podobnie do jQuery i pozwala na operacje na obrazie w zunifikowany sposób. Jest to: http://snapsvg.io, Mamy bardzo miłe wspomnienia związane z tym konkretnym rozwiązaniem.
Przykład użycia snap.svg można znaleźć poniżej:
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/intro.js.ts
Sam plik haml ze szkieletem graficznym:
https://github.com/codesthq/cody_the_game/blob/master/app/views/game/show.html.haml
Jak widać, jest to prawie jak normalne drzewo DOM i zwykła aplikacja railsowa!
TrustedSandbox
Cóż, w końcu mieliśmy API, Grafikę, SPA. Ale co z implementacją rozwiązań przesłanych przez użytkowników?
Pierwszą rzeczą, która przychodzi na myśl, jest metoda eval, ale nie jesteśmy szaleni;) W 2016 roku docker był na fali wznoszącej, więc wydawał się naturalnym wyborem. Same kontenery nie gwarantowały pełnej izolacji i ochrony, dlatego skorzystaliśmy z gotowego rozwiązania w Ruby o nazwie https://github.com/vaharoni/trusted-sandbox. Pozwoliło ono lepiej zabezpieczyć kod przed opuszczeniem piaskownicy i w ustandaryzowany sposób skonfigurować wymagania systemu operacyjnego. Bardzo ważne było odpowiednie ograniczenie czasu wykonywania kodu, pamięci potrzebnej do działania oraz cykli procesora. Nasza konfiguracja dostępna jest poniżej
https://github.com/codesthq/cody_the_game/blob/master/config/trusted_sandbox.yml.example
Oczywiście sama zaufana piaskownica niczego nie gwarantowała, dlatego wymyśliliśmy specjalną stronę internetową do uruchamiania kodu.
https://github.com/codesthq/cody_the_game/blob/master/app/services/task_runner/base_task.rb
Każde z zadań miało swój własny przypadek testowy, co pozwoliło nam zweryfikować poprawność zaimplementowanego rozwiązania. Dokonano tego poprzez wstrzyknięcie kodu użytkownika do przypadku testowego, tak aby wszystko było uruchamiane w izolacji.
https://github.com/codesthq/cody_the_game/blob/master/app/challenges/challenge/case.rb
Oczywiście czynność ta kosztowała sporo czasu, a podczas zbierania odpowiedzi nie mogliśmy sobie pozwolić na uruchomienie sandboxa, więc jedynie zapisywaliśmy kod w bazie danych, tworząc zgłoszenie, a następnie, korzystając z long poolingu, odpytywaliśmy endpoint w celu uzyskania statusu kodu. To pozwoliło nam odciążyć serwer aplikacji i odpowiednio zweryfikować dane. Oczywiście musieliśmy też zabezpieczyć się przed "zawaleniem skryptu", dlatego ograniczyliśmy liczbę zapytań do serwera za pomocą zmiennej ttl, co widać poniżej.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/level_controller.js.ts#L92
Podsumowanie konkursu
Do września 2011 roku statystyki gry wyglądały następująco:
- liczba sesji: 1945 - wysłane zadania: 4476 - wysłał prawidłowe odpowiedzi: 1624 - zakończył grę: 31
Jak widać, największe schody zaczęły się w zadaniu # 2, ponieważ nie był to już zwykły przykład hello world.
Czytaj także:
Szybkie zapoznanie się z Ruby 2.6. Co nowego?
Pisanie dokumentacji stało się łatwe dzięki VuePress
Samouczek dotyczący podstaw Vue.js. Jak zacząć pracę z tym frameworkiem?