Um dia, há 3 anos, na equipa The Codest, preparámos um grande jogo de Cody para programadores Ruby. No artigo de hoje, gostaria de descrever como foi o trabalho neste projeto e, acima de tudo, mostrar-vos o código do projeto, que a partir de agora está disponível publicamente no nosso github.
Conceção de jogos
Ao conceber o jogo, o nosso principal objetivo era preparar um entretenimento divertido para os programadores, bem como fazer algo interessante como parte do trabalho na nossa empresa. Até à data, não tínhamos tido quaisquer competências na criação de jogos, razão pela qual o jogo nós um desafio significativo. Em primeiro lugar, concentrámo-nos no que era realmente este jogo. Depois de elaborarmos o plano inicial, pusemos mãos à obra.
Como parte do trabalho no jogo, decidimos fazer uma hackathon e dividirmo-nos em grupos que realizam tarefas específicas. Com esta divisão de trabalho de 8 horas, conseguimos perceber o aspeto dos adversários no jogo, todo o layout e a base das tarefas e APIs de todo o sistema. Durante a fase seguinte, reunimo-nos em encontros de 4 horas uma vez por mês, pelo que conseguimos terminar o jogo em 3 encontros.
Implementação
Como somos especializados em RubyOnRails, escolhemos a tecnologia como a principal. No entanto, o jogo não se destinava a ser textual e, por isso, a abordagem ao mesmo reflectiu-se na aplicação do tipo SPA. Como parte da tarefa, trabalhámos num conhecido pipeline de activos de carris (em 2016 não havia nada melhor em princípio) e todo o javascript com base nas nossas código com a ajuda de TypeScript. Na aplicação, havia uma divisão padrão de responsabilidades: Rails como ativo e fonte de API, javascript e afins como interação com o utilizador. Aqui, no entanto, funcionou como um híbrido e algumas visualizações foram simplesmente renderizadas a partir do Rails, enquanto outras - a partir do JS.
Tipografia
Foi a nossa primeira experiência neste domínio. Era uma época em que as pessoas acreditavam no sucesso do CoffeScript. O uso do TypeScript exigiu a introdução de uma gem typescript-rails. Infelizmente, isso não foi o final, pois o typescript, sendo uma linguagem estaticamente tipada, também exigia isso das bibliotecas anexadas por padrão ao rails.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/jquery.d.ts (especialmente quando se utiliza o sistema de gestão de activos incorporado com carris).
O Cody como jogo exigia muita dinâmica do lado do browser, bem como a modificação da árvore DOM. Utilizar o TypeScript em vez do javascript normal foi um grande salto na qualidade do código, a própria presença de classes e encapsulamento foi muito tentadora para nós.
API e SPA
Em 2019, as aplicações SPA são geridas utilizando o magnífico React ou Vue bibliotecas. No entanto, em 2015, fizemo-lo de uma forma diferente. O typescript mencionado anteriormente foi útil na implementação do jogo, enquanto o jQuery revogou todo o trabalho relacionado com o pedido http xml. Agora podemos usar o fetch, enquanto naquela época o `$ .ajax` era tudo o que era necessário para o trabalho. Dê uma olhada na nossa api cliente!
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/services/api_client.js.ts
Se era uma API, então tinha de resolver o problema da autenticação de alguma forma, não era? Bem, é verdade. Mas nesse caso, fomos atrás (é possível escrever aqui - usámos a banda?!) da banda e na sessão rails criámos cookie_key e depois guardámo-la na base de dados. Portanto, sabíamos que estava tudo bem.
https://github.com/codesthq/cody_the_game/search?q=cookie_key&unscoped_q=cookie_key
O estado do jogo estava armazenado na base de dados e a informação sobre quantos utilizadores tinham pontos vinha da base de dados (é a mesma base de dados? Podemos trocá-la por um pronome?). O ACID dá sempre jeito quando não há cache do lado do sistema;)
No caso do spa, é a melhor solução sem recarregar a página. Resolvemo-lo de forma clássica e a âncora html foi a melhor solução sem expandir dependências desnecessárias. Porque quem é que usaria turbolinks?
SnapSVG
Se concebermos um jogo, este só pode ser lançado com excelentes gráficos e animações. Na altura, passámos muitas horas a pensar como satisfazer essas exigências na nossa aplicação. Por um lado, o canvas pode fazer milagres, por outro, num html limpo é muito mais fácil de apanhar e toda a gente sabe isso. Depois de uma busca minuciosa pela melhor solução, descobrimos que a combinação destas duas soluções é o svg. Permite-lhe apresentar facilmente gráficos num vetor, está escrito na linguagem de marcação e, o que é mais importante, pode ser modificado em tempo real. É importante notar que existe uma biblioteca para ficheiros svg que funciona de forma semelhante ao jQuery e permite operações na imagem de uma forma unificada. Isto é: http://snapsvg.io, temos boas recordações dessa utilização específica da solução.
Um exemplo de como utilizámos o snap.svg pode ser encontrado abaixo:
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/intro.js.ts
O próprio ficheiro haml com o esqueleto gráfico:
https://github.com/codesthq/cody_the_game/blob/master/app/views/game/show.html.haml
Como pode ver, é quase como uma árvore DOM normal e uma aplicação rails normal!
TrustedSandbox
Bem, finalmente tínhamos API, Gráficos, SPA. Mas e a implementação das soluções enviadas pelos utilizadores?
A primeira coisa que nos vem à cabeça é o método eval, mas nós não somos loucos;) Em 2016, o método doca estava a aumentar, pelo que pareceu uma escolha natural. Os próprios contentores não garantiam um isolamento e uma proteção completos, pelo que utilizámos uma solução pronta no Rubi chamado https://github.com/vaharoni/trusted-sandbox. Permitiu proteger melhor o código antes de sair da sandbox e configurar de forma padronizada os requisitos do sistema operativo. Era muito importante limitar adequadamente o tempo de execução do código, a memória necessária para operar e os ciclos da CPU. A nossa configuração está disponível abaixo
https://github.com/codesthq/cody_the_game/blob/master/config/trusted_sandbox.yml.example
É claro que a mesma caixa de areia de confiança não garantia nada, e foi por isso que criámos um sítio Web especial para executar o código.
https://github.com/codesthq/cody_the_game/blob/master/app/services/task_runner/base_task.rb
Cada uma das tarefas tinha o seu próprio caso de teste, o que nos permitiu verificar a correção da solução implementada. Isto foi feito injectando o código do utilizador no caso de teste, de modo a que tudo fosse executado isoladamente.
https://github.com/codesthq/cody_the_game/blob/master/app/challenges/challenge/case.rb
Naturalmente, esta ação custou bastante tempo e, enquanto recolhíamos as respostas, não nos podíamos dar ao luxo de executar a caixa de areia, pelo que apenas guardámos o código na base de dados, criando uma submissão e, em seguida, utilizando o long pooling, pedimos ao endpoint para obter o estado do código. Isto permitiu-nos aliviar o servidor de aplicações e verificar os dados de forma adequada. É claro que também tínhamos de nos proteger contra o "bloqueio do script" e, por isso, limitámos o número de consultas ao servidor utilizando a variável ttl, que pode ser vista abaixo.
https://github.com/codesthq/cody_the_game/blob/master/app/assets/javascripts/level_controller.js.ts#L92
Resumo do concurso
Até setembro de 2011, as estatísticas do jogo eram as seguintes:
- número de sessões: 1945 - tarefas enviadas: 4476 - enviou respostas corretas: 1624 - terminou o jogo: 31
Como se pode ver, a maior escada começou na tarefa # 2, porque já não era um exemplo vulgar de hello world.
Leia também:
Um mergulho rápido no Ruby 2.6. O que há de novo?
Escrever documentação tornou-se fácil graças ao VuePress
Tutorial básico do Vue.js. Como começar com esta estrutura?