02 Aug 2022
If you were playing a game, and it flickers on every click due to page reload, that would be a poor user experience. In this project I used the Javascript fetch API on the client side to communicate with a rails backend during game play, and request a full page reload on the start of every new game.
If you would like to try out the game, it is available here and the code can be found here.
As an aside, I recently got access to DALL-E and the image below was generated with the prompt - ‘A single page web application, paper sketch’.
Before a user can begin to play, they have to sign up and confirm their email. On the home page, the user is presented with statistics about their past game sessions. There is a leaderboard and also a button to launch the game.
The idea is to play Rock Paper Scissors versus a bot. Once you make a selection, the app sends your selection to the server. On the server side, the bot makes a selection, it is compared to the player’s selection and the winner of the round is determined. The server then returns the round’s result to the client to display. After 5 rounds, the game ends and a little animation on the game container triggers to signify game end.
Why make a round trip to the server every round? You might ask. This follows from the Never Trust the Client maxim, to prevent malicious users from manipulating the outcome of the game.
Every new game starts with a full page reload. This is fine because it is the start of the game and it feels like starting off with a clean slate. For each new game request, the server creates a new game session linked to the current authenticated user. Making a full page reload for the game play would impact the user experience negatively due to flickers on reload, and hence the need to asynchronously fetch the result of each round.
Game play data like number of rounds, score and result are stored in the session object. It is reset when the game is restarted. Given that session data should be kept under 4kb, it made perfect sense to keep information like this in the session. By default, sessions in rails are encrypted but for an additional layer of security, you can always force SSL connection. This can be done by setting config.force_ssl = true
in the application’s config file.
With the way I set up the game, there is a need for a full page reload at the start of every new game. I had to disable Turbo Drive on the Launch New Game button because it doesn’t cause a full page reload and it breaks the game logic.
Devise, the authentication gem, doesn’t play well together with Turbo Drive too, therefore I disabled Turbo drive for every Devise view with a form submission.
Instead of rolling your own user authentication system, it is advisable to use battle tested solutions such as the Devise gem for authentication. Most of the edge cases and security loopholes have been covered and it is actively maintained.
The app requires the user to confirm their signup using an email and also provides a way to recover passwords, this is where action mailers come in. Mailers are somewhat similar to controllers, but one of the ways they differ is that mailers render views on email protocols as opposed to HTTP.
You have different options for sending emails like Amazon SES, Gmail, SendGrid, Mailjet etc. There are some limitations with using a personal gmail account as a mail server, therefore I went with Mailjet which has a free tier offer of 200 emails per day.
To interface with action mailer, it is as simple as adding
config.action_mailer.smtp_settings = {
address: 'in-v3.mailjet.com',
port: 587,
user_name: ENV['MAILJET_PUBLIC'],
password: ENV['MAILJET_PRIVATE'],
authentication: 'plain',
enable_starttls_auto: true
}
to the production config.
For this to work with Devise Mailers, it is necessary to set config.mailer_sender = 'no-reply@example.com'
in config/initializers/devise.rb
, where no-reply@example.com
is the sender address you have set on Mailjet.
It is just as important to specify the website’s hostname config.action_mailer.default_url_options = { host: 'https://game.chrisadebiyi.com' }
in the production config.
For my previous project - weather app, I used Digital Ocean’s app platform, however I went with Fly.io on this project. It is just as easy to use and their free tier offering is really nice for hobby projects. You will need to install their command line tool, login via the terminal and run flyclt launch
and it takes care of the rest. It will automatically detect your app platform and build a docker image, then deploy. Environment variables can be set in fly.toml
file, and for secrets and private keys you can run flyclt secrets set PRIVATE_KEY=’my-key’
to encrypt them.
The sketch was made with Figma and built with vanilla CSS. To apply page specific css, I made extensive use of namespaces because the rails asset pipeline mashes everything up into one big file. For page specific javascript, I used the content_for
method together with a named yield
.
Link to Code. Link to Live App.
Enjoy!