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!
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.
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?