En dag for 3 år siden forberedte vi i The Codest-teamet et fantastisk Cody-spil for Ruby-programmører. I dagens artikel vil jeg gerne beskrive, hvordan arbejdet med dette projekt så ud, og frem for alt vise dig koden til projektet, som fra nu af er offentligt tilgængelig på vores github.
Design af spil
Da vi designede spillet, var vores hovedmål at lave sjov underholdning for programmører og at lave noget interessant som en del af arbejdet i vores virksomhed. Hidtil havde vi ikke haft nogen kompetencer i at skabe spil, og derfor var det en stor udfordring for os. I første omgang fokuserede vi på, hvad dette spil egentlig var. Efter at have udarbejdet den første plan gik vi i gang.
Som en del af arbejdet med spillet besluttede vi at tage et hackathon og dele os op i grupper, der udførte specifikke opgaver. Med en sådan 8-timers arbejdsdeling var vi i stand til at realisere modstandernes udseende i spillet, hele layoutet og grundlaget for både opgaver og API'er i hele systemet. I den næste fase samledes vi til 4-timers møder en gang om måneden, og det lykkedes os at færdiggøre spillet på 3 møder.
Implementering
Da vi er specialiserede i RubyOnRails, valgte vi teknologien som den førende. Det var dog ikke meningen, at spillet skulle være tekstbaseret, og derfor blev tilgangen til det afspejlet i en applikation af SPA-typen. Som en del af opgaven arbejdede vi på en velkendt pipeline af aktiver fra rails (i 2016 var der i princippet ikke noget bedre) og hele javascript baseret på vores egenudviklede Kode med hjælp fra TypeScript. I applikationen var der en standard ansvarsfordeling: Rails som aktiv og API-kilde, javascript og lignende som interaktion med brugeren. Her fungerede det dog som en hybrid, og nogle visninger blev simpelthen gengivet fra rails, mens nogle af de andre - fra JS.
Manuskript
Det var vores første eksperiment på dette område. Det var dengang, folk troede på CoffeScripts succes. Brug af TypeScript krævede introduktion af en typescript-rails gem. Desværre var dette ikke det sidste, da typescript, som er et statisk typet sprog, også krævede dette fra de biblioteker, der som standard er knyttet til rails.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/jquery.d.ts (især når man bruger det indbyggede asset management-system med rails).
Cody som spil krævede en masse dynamik på browsersiden samt ændring af DOM's træ. At bruge TypeScript i stedet for vaniljejavascript var et stort spring i kodens kvalitet, og selve tilstedeværelsen af klasser og indkapsling var meget fristende for os.
API og SPA
I 2019 styres SPA-applikationer ved hjælp af de fantastiske React- eller Vue-biblioteker. Men i 2015 gjorde vi det på en anden måde. Det tidligere nævnte typescript var nyttigt i implementeringen af spillet, mens jQuery tilbagekaldte alt arbejdet i forbindelse med xml http-anmodningen. Nu kan vi bruge fetch, mens `$ .ajax` dengang var alt, hvad der var brug for til jobbet. Tag et kig på vores klient-api!
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/services/api_client.js.ts
Hvis det var api, måtte du løse autentificeringsproblemet på en eller anden måde, ikke sandt? Jo, det er rigtigt. Men i det tilfælde gik vi efter (er det muligt at skrive her - vi brugte bandet?!) bandet, og i rails-sessionen oprettede vi cookie_key og gemte den bagefter i databasen. Derfor vidste vi, at alt var mere end fint.
https://github.com/codesthq/cody_the_game/search?q=cookie_key&unscoped_q=cookie_key
Spillets status blev gemt i databasen, og oplysninger om, hvor mange brugere der havde point, kom fra databasen (er det den samme database? Kan vi ikke bare ændre det med et pronomen?). ACID er altid praktisk, når der ikke er nogen caching på systemsiden ;)
I spaens tilfælde er det den bedste løsning uden at genindlæse siden. Vi har løst det på klassisk vis, og html-ankeret var den bedste løsning uden at udvide unødvendige afhængigheder. For hvem ville bruge turbolinks?
SnapSVG
Hvis vi designer et spil, må det kun udgives med fantastisk grafik og animationer. Dengang brugte vi mange timer på at spekulere over, hvordan vi kunne opfylde disse krav i vores applikation. På den ene side kan lærredet udrette mirakler, på den anden side er ren html meget lettere at indhente, og det ved alle. Efter en omhyggelig søgen efter den bedste løsning fandt vi ud af, at kombinationen af disse to løsninger er svg. Det giver dig mulighed for nemt at præsentere grafik i en vektor, det er skrevet i markup-sproget, og hvad der er det vigtigste, det kan ændres undervejs. Det er vigtigt, at der findes et bibliotek til svg-filer, der fungerer på samme måde som jQuery og tillader operationer på billedet på en ensartet måde. Dette er: http://snapsvg.io, Vi har meget gode minder om den særlige brug af løsningen.
Et eksempel på, hvordan vi brugte snap.svg, kan du se nedenfor:
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/intro.js.ts
Selve haml-filen med det grafiske skelet:
https://github.com/codesthq/cody_the_game/blob/master/app/views/game/show.html.haml
Som du kan se, er det næsten som et normalt DOM-træ og en almindelig rails-app!
TrustedSandbox
Endelig havde vi API, grafik og SPA. Men hvad med implementeringen af de løsninger, som brugerne sender?
Det første, man tænker på, er eval-metoden, men vi er ikke skøre ;) Tilbage i 2016 var docker på vej frem, så det føltes som et naturligt valg. Containerne i sig selv garanterede ikke fuldstændig isolation og beskyttelse, og derfor brugte vi en færdig løsning i Ruby kaldet https://github.com/vaharoni/trusted-sandbox. Det gjorde det muligt at beskytte koden bedre, før den forlod sandkassen, og på en standardiseret måde konfigurere kravene til operativsystemet. Det var meget vigtigt at begrænse kodens udførelsestid, den nødvendige hukommelse og CPU-cyklusserne korrekt. Vores konfiguration er tilgængelig nedenfor
https://github.com/codesthq/cody_the_game/blob/master/config/trusted_sandbox.yml.example
Den samme betroede sandkasse garanterede selvfølgelig ikke noget, og derfor fandt vi på en særlig hjemmeside til at køre koden.
https://github.com/codesthq/cody_the_game/blob/master/app/services/task_runner/base_task.rb
Hver af opgaverne havde sin egen testcase, som gjorde det muligt for os at kontrollere, at den implementerede løsning var korrekt. Dette blev gjort ved at injicere brugerkoden i testcasen, så alt blev kørt isoleret.
https://github.com/codesthq/cody_the_game/blob/master/app/challenges/challenge/case.rb
Denne handling kostede selvfølgelig en hel del tid, og mens vi indsamlede svarene, havde vi ikke råd til at køre sandkassen, så vi gemte kun koden i databasen, oprettede en submission og bad derefter endpointet om at få kodestatus ved hjælp af long pooling. Det gav os mulighed for at aflaste applikationsserveren og verificere dataene på passende vis. Vi var selvfølgelig også nødt til at beskytte os mod at "crashe scriptet", og derfor begrænsede vi antallet af serverforespørgsler ved hjælp af ttl-variablen, som kan ses nedenfor.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/level_controller.js.ts#L92
Resumé af konkurrencen
Indtil september 2011 var spilstatistikken som følger:
- antal sessioner: 1945 - sendte opgaver: 4476 - sendte korrekte svar: 1624 - sluttede kampen: 31
Som du kan se, startede den største trappe i opgave # 2, fordi det ikke længere var et almindeligt hello world-eksempel.
Læs også her:
Et hurtigt dyk ned i Ruby 2.6. Hvad er det nye?
Det er blevet nemt at skrive dokumentation takket være VuePress
Grundlæggende vejledning i Vue.js. Hvordan kommer man i gang med dette framework?