Op een dag 3 jaar geleden hebben we in het The Codest team een geweldig Cody spel voorbereid voor Ruby programmeurs. In het artikel van vandaag wil ik beschrijven hoe het werk aan dit project eruit zag en vooral de code van het project laten zien, die vanaf nu publiekelijk beschikbaar is op onze github.
Spelontwerp
Bij het ontwerpen van het spel was ons belangrijkste doel om een leuk amusement voor programmeurs te maken en om iets interessants te doen als onderdeel van het werk in ons bedrijf. Tot nu toe hadden we geen competenties in het maken van spellen en daarom was het voor ons een grote uitdaging. In de eerste plaats richtten we ons op wat dit spel echt was. Nadat we het eerste plan hadden bedacht, gingen we aan de slag.
Als onderdeel van het werk aan het spel besloten we een hackathon te houden en ons op te splitsen in groepen die specifieke taken uitvoerden. Met zo'n 8 uur durende werkverdeling konden we het uiterlijk van de tegenstanders in het spel, de volledige lay-out en de fundering van zowel taken als API's van het hele systeem realiseren. In de volgende fase kwamen we één keer per maand bij elkaar voor bijeenkomsten van 4 uur, waardoor we het spel in 3 bijeenkomsten konden voltooien.
Implementatie
Omdat we gespecialiseerd zijn in RubyOnRails, kozen we voor deze technologie als leidend. Het was echter niet de bedoeling dat het spel tekstueel zou zijn en daarom werd de aanpak ervan weerspiegeld in de SPA-achtige applicatie. Als onderdeel van de opdracht werkten we aan een bekende pijplijn van assets uit rails (in 2016 was er in principe niets beters) en het hele javascript gebaseerd op onze eigen code met behulp van TypeScript. In de applicatie was er een standaard verdeling van verantwoordelijkheden: Rails als asset en API-bron, javascript en gerelateerd als interactie met de gebruiker. Hier functioneerde het echter als een hybride en werden sommige weergaven gewoon gerenderd vanuit rails, terwijl sommige andere - vanuit JS.
Typescript
Het was ons eerste experiment op dit gebied. Dit waren tijden waarin mensen geloofden in het succes van CoffeScript. Het gebruik van TypeScript vereiste de introductie van een typescript-rails gem. Helaas was dit niet het laatste, omdat typescript, een statisch getypte taal, dit ook vereiste van de bibliotheken die standaard aan rails gekoppeld zijn.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/jquery.d.ts (vooral wanneer het ingebedde systeem voor activabeheer met rails wordt gebruikt).
Cody als spel vereiste veel dynamiek aan de browserkant, evenals het aanpassen van de DOM-boom. Het gebruik van TypeScript in plaats van vanilla javascript was een enorme sprong voorwaarts in de kwaliteit van de code, alleen al de aanwezigheid van klassen en inkapseling was erg verleidelijk voor ons.
API en SPA
In 2019 worden SPA-toepassingen beheerd met behulp van de prachtige React of Vue bibliotheken. In 2015 deden we het echter op een andere manier. Het eerder genoemde typescript hielp bij de implementatie van het spel, terwijl jQuery al het werk met betrekking tot het xml http-verzoek wegnam. Nu kunnen we fetch gebruiken, terwijl destijds `$ .ajax` alles was wat nodig was. Kijk eens naar onze client api!
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/services/api_client.js.ts
Als het een api was, dan moest je het authenticatieprobleem toch op de een of andere manier oplossen? Nou, dat klopt. Maar in dat geval gingen we achter (is het mogelijk om hier te schrijven - we gebruikten de band?!) de band aan en in de rails sessie creëerden we cookie_key en daarna sloegen we het op in de database. We wisten dus dat alles meer dan in orde was.
https://github.com/codesthq/cody_the_game/search?q=cookie_key&unscoped_q=cookie_key
De spelstatus werd opgeslagen in de database en informatie over hoeveel gebruikers punten hadden kwam uit de database (is het precies dezelfde database? Kunnen we het gewoon veranderen door een voornaamwoord?). ACID komt altijd van pas als er geen caching is aan de kant van het systeem;)
In het geval van de spa is dit het beste zonder de pagina te herladen. We hebben het klassiek opgelost en het html-anker was de beste oplossing zonder onnodige afhankelijkheden uit te breiden. Want wie gebruikt er nu turbolinks?
SnapSVG
Als we een spel ontwerpen, moet het alleen worden uitgebracht met geweldige graphics en animaties. In die tijd hebben we ons vele uren afgevraagd hoe we aan deze eisen konden voldoen in onze applicatie. Aan de ene kant kan het canvas wonderen verrichten, aan de andere kant is een schone html veel gemakkelijker in te halen en iedereen weet dat. Na een moeizame zoektocht naar de beste oplossing kwamen we erachter dat de combinatie van deze twee oplossingen svg is. Hiermee kun je gemakkelijk afbeeldingen in een vector presenteren, het is geschreven in de markup taal en, wat het belangrijkste is, het kan on the fly worden aangepast. Belangrijk is dat er een bibliotheek is voor svg-bestanden die op dezelfde manier werkt als jQuery en bewerkingen op de afbeelding mogelijk maakt op een uniforme manier. Dit is: http://snapsvg.io, We hebben erg goede herinneringen aan het gebruik van die oplossing.
Hieronder vindt u een voorbeeld van hoe we snap.svg hebben gebruikt:
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/intro.js.ts
Het haml-bestand zelf met het grafische skelet:
https://github.com/codesthq/cody_the_game/blob/master/app/views/game/show.html.haml
Zoals je kunt zien, is het bijna net als een normale DOM-boom en een gewone rails app!
TrustedSandbox
Nou, eindelijk hadden we API, Graphics, SPA. Maar hoe zit het met de implementatie van oplossingen die door gebruikers worden gestuurd?
Het eerste waar je aan denkt is de eval-methode, maar we zijn niet gek;) In 2016 was docker in opkomst, dus het voelde als een natuurlijke keuze. De containers zelf garandeerden geen volledige isolatie en bescherming, daarom gebruikten we een kant-en-klare oplossing in de Ruby genaamd https://github.com/vaharoni/trusted-sandbox. Hiermee kon de code beter worden beschermd voordat deze de sandbox verliet en konden de vereisten voor het besturingssysteem op een gestandaardiseerde manier worden geconfigureerd. Het was erg belangrijk om de uitvoeringstijd van de code, het benodigde geheugen en de CPU-cycli goed te beperken. Onze configuratie is hieronder beschikbaar
https://github.com/codesthq/cody_the_game/blob/master/config/trusted_sandbox.yml.example
Natuurlijk garandeerde dezelfde vertrouwde sandbox niets, daarom kwamen we met een speciale website om de code uit te voeren.
https://github.com/codesthq/cody_the_game/blob/master/app/services/task_runner/base_task.rb
Elk van de taken had zijn eigen testcase, waarmee we de juistheid van de geïmplementeerde oplossing konden verifiëren. Dit werd gedaan door de gebruikerscode in de testcase te injecteren, zodat alles geïsoleerd werd uitgevoerd.
https://github.com/codesthq/cody_the_game/blob/master/app/challenges/challenge/case.rb
Natuurlijk kostte deze actie veel tijd en tijdens het verzamelen van de antwoorden konden we het ons niet veroorloven om de sandbox te laten draaien, dus sloegen we alleen de code op in de database, creëerden we een indiening en vroegen we vervolgens, met behulp van long pooling, het eindpunt om de status van de code. Hierdoor konden we de applicatieserver ontlasten en de gegevens op de juiste manier verifiëren. Natuurlijk moesten we onszelf ook beschermen tegen het "crashen van het script" en daarom beperkten we het aantal server queries met behulp van de ttl variabele, die hieronder te zien is.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/level_controller.js.ts#L92
Samenvatting van de wedstrijd
Tot september 2011 waren de spelstatistieken als volgt:
- aantal sessies: 1945 - verzonden taken: 4476 - juiste antwoorden gestuurd: 1624 - maakte het spel af: 31
Zoals je kunt zien, begon de grootste trap in taak # 2 omdat het niet langer een gewoon 'hello world'-voorbeeld was.
Lees ook:
Een snelle duik in Ruby 2.6. Wat is er nieuw?
Het schrijven van documentatie is eenvoudig geworden dankzij VuePress
Vue.js basistutorial. Hoe begin je met dit framework?