TL;DR I spent the weekend building a game with MithrilJS and wrangling Webpack.
With a lot of people rightly practising social disatancing and following stay at home orders, there's been a huge uptick in playing games remotely, be that over video chat or otherwise.
The Drawful Situation
I was in South Africa for part of their lockdown and during this time my friend group played a lot of Drawful 2, between 7 players, 3 households and 2 timezones (plus a bunch of phones / tablets).
If you're not familiar with Drawful, the premise is straightforward and lots of fun - you're each given an object / scene to draw (on your phone). Then every round a drawing is shown to everyone playing and each player has to suggest what the drawing is. The drawing suggestions are then voted on with more points awarded to more accurate drawers and bonus points for sensible suggestions that aren't the right answer.
Drawful played remotely involves a sizeable amount of tech, each house would have a device for videocalling, a device for streaming the game (over Steam Share / Twitch) and then a device per player. For players joining a Drawful game they simply need to visit a website and enter a code (nothing extra to download or pay for).
With the experience of playing Drawful perculating through my mind, I wondered what else might be fun to play remotely...
What's the big idea?
I really like the way Drawful (and the other Jackbox games) work with players using their devices, phones or tablets etc, to play along.
I would also like to have a go at building a game. It's a chance to build something a little more fun and try out new frameworks and ways of building websites.
The Pitch
I am taking inspiration from Obama Llama - a great party game and combines a simple memory game with trivia questions, sharades and rhyming. Obama Llama is a series of small games all rolled into one so it's a great candidate to break into milestones and work through each over a series of weekends.
A few "goals" for this project:
- Each milestone should be small enough to build a functional game in a weekend.
- There should be time left over in the weekend to do a write-up.
- A new technology should be used each weekend.
The first milestone is going to be build out the simple memory game at the heart of Obama Llama.
What are the tools?
This weeks focus will be a single player memory game so the bulk of the progress will set the groundwork for the future. I have to decide on a JS Framework to build the website, work out what sort of build environment, deployments and then build a playable memory game.
Framework
For the last 4 years I have been working with ReactJS and while it is a super interesting framework it's important to step outside your comfort zone, from time to time, and try something new.
I recently read about MithrilJS on my friend, Emile's, blog and really like the prospect of a lightweight JS framework to build Single Page Applications (webapps).
I did consider VanillaJS and I am pretty keen to try out a vanilla JS and web-component based app but today is not that day.
Build Env
Github
First thing is to do is create a Github repo, vital to any software engineering project and will come in useful down the line when I want to think about automated deployments.
Naming things is hard, and thinking about the name for this game to setup a repo was harder still. I was stuck for a decent name when my girlfriend yelled out the suggestion Blue Lagoon.
Building and Serving
I am fairly used to modern web development with the yarn
, yarn start
and yarn build
song and dance at this point so in an effort to keep something familiar around I opted for Webpack.
I have used webpack to build apps before but when I say "I have used webpack" I really mean that I've used create-react-app
or a webapp template where webpack comes pre-setup.
Setting up webpack was certinaly a learning experience. I started by just trying out a few things until I got something "working". I quickly turned to various, resources and blog posts to get things setup properly, referring to the webpack docs frequently.
After ~2 hours I had something working with a folder strucutre I was happy with and the webpack-dev-server
serving the bundled file with an generated index.html. This was further enhanced when later when I started thinking about styling.
Not too bad for a mornings work and now for the more fun part.
Building a game
I haven't built much in the way of games before and I'm not really sure how to approach it other than having a picture in my head and writing down a rough idea of the game mechanics I want.
For the memory game I pictured a 6x6 grid of cards
which, when clicked or tapped, would flip to reveal the name / picture / illustration of a pair. A second tap would reveal another card and if the pair matches they would stay flipped, if not they would flip back.
There are lots of places to start with this but for me, the most interesting was to get a flipping card mechanic working.
You flipping what?
Solving the flipping animation would involve solving a bunch of problems - creating components in Mithril and passing data between them, animating the component, deciding on how to do styling (plain CSS, CSS-in-JS, inline style?)... quite a bit to think about.
A good place to start is with creating the components in Mirthril, this will allow me to start thinking about where animation happens and who is responsible for controlling behaviour or providing data.
There is a fairly decent amount of docs over at Mithril.js.org and they have a great walkthrough of how components are created, and work, with lots of CodePen live examples to go alongside.
Learning new frameworks is fun and after a bit of fiddling I was able to commit my first game component.
Let's talk about CSS
During my day-to-day work I use the react-native-web
StyleSheet which is a CSS-in-JS approach to styling. React Native for Web is an abstraction layer that can used in react projects to allow the same code to be used on the web as with native (iOS / Android).
I really like CSS-in-JS and find it useful to be able to see the whole of a component, style and structure, in one place. For this reason I wanted to find a similar approach for the Mithril project.
Mithril + j2Css
The Mithril docs used to suggest using plain CSS and offer a few suggestions on how to avoid some of the issues frequently occuring (in some part due to global scope). There is a section towards the end of the docs on CSS which recommend J2C which looked interesting and would do roughly what I wanted.
This turned out to be a mistake, although initally I seemed to make progress with "simpler" animations (things like pseudo-classes and hover worked great) I reached a roadblock when trying to add the flip animation. The issue I had was translating many combined selctors like, .flip-container.flip .flipper
, into something that worked with J2C.
I am willing to admit that I simply didn't spend enough time working with J2C so I'm not writing it off completely and it handled some CSS selectors that I never normally get to use with React Native StyleSheets.
PostCSS
The next thing to try was plain CSS, I can include a global stylesheet and be done with it. Though I still prefer having style and structure somewhat near each other when building components. The ideal solution would be smaller stylesheets located next to my component.js
:
This is made possible by PostCSS which, after a bit more webpack wrangling I added to my project. At this point I also setup auto-deployments through zeit.co so while I had spent ~3 hours going down the wrong path I could start to see the app coming together and that was really encouraging!
To flip, or not to flip
The first implementation of the flipping was done on hover (you can play with this for yourself here) which was great to test out the behaviour but useless for a game.
I was following a tutorial for the CSS flip and there was a section on making it clickable which was achived by toggling a class on the card
:
document.querySelector("#myCard").classList.toggle("flip")
While toggling the class works it's more imperative than I am used to and I wanted to find a way to do this within the framework as it would be a good chance to explore click-handlers and state.
After another ~3 hours I was able to cook, eat some dinner and then figure out scope, state and really start to get into my framework of choice.
I wired everything through and started clicking on cards, only nothing was happening...
Baby steps
It turns out I had made a crucial mistake when I first created the Mithril app by calling m.render
instead of m.mount
at the root of my app, from the docs:
The m.mount function is similar to m.render, but instead of rendering some HTML only once, it activates Mithril's auto-redrawing system.
So while it looked like the app was functioning (because it was rendering) the auto-redrawing system wasn't active which meant any changes to state in the component wouldn't re-render.
Swapping render
for mount
activated the redraw system solving the issue I had with the click-flipβ’οΈ and that was the final piece of the puzzle, now I could foucs on making something playable.
Game on
With that last little mount
/ render
hiccup resolved I could tidy up and finish the game logic. The component that was driving the matching / pairing logic was the Stage
and it would be responsible for flipping the cards and counting matches.
Initially I had thought the Card
would be in control of when it flips (based on click etc) however it ended up coupling the Card
and Stage
too closely and it would simplify the logic greatly if the Card
was a simpler component.
With the Stage
wired up to flip the cards and make a note of all matches it could also determine the end of game, to show the winner a banner, and reset the board.
A key play
In React, keys can be used to reset a components state and cause it to re-render and thankfully the same is true with the VNodes of Mithril. When the end of the game is reached I wanted a Play again?
button that could reset the board, shuffle the cards and start the game again.
I ran into a bit of an unexpected error here. It turns out that if a Mithril component renders multiple children (say a section
with a h2
and a div
) the keys must be all or none - if I want a key on one of the chilren I have to add keys to all of them. The error message for this:
βVnodes must either always have keys or never have keysβ
What I was trying to do was add a key to one of the entries but not the other. I'm highlighting this because I found the message pretty opaque and there wasn't a lot in the way of results when I searched. In the end I put a breakpoint in the code and stepped through to the component that was breaking it.
It's always fun to do a bit of debugging through framework source code, you learn loads and it's the best way to understand how it works under the hood; that said good error messages are useful too.
To finish off I spent a little bit of time writing a helper that converts "matching-pairs" into my card model representation and then thought up a bunch of pairs for testing.
Taking stock
I really enjoyed starting a project from scratch, I found the the webpack setup to be a great way to get to grips with a tool I use daily for my job but know little about.
I like the Mithril framework, I was able to get going with it fairly quickly, aside from a few gotchas, and using the Hyperscript approach (rather than JSX) means it's different enough from React. Mithril still shares enough of the declaritve patterns that I'm used to with React so I don't feel like I'm relearning from scratch.
Finally animation can be hard, CSS-in-JSS isn't always the right approach but it's always a good idea to take a break and get some food.
With that I now present to you Blue Lagoon (click to visit) ππ
Thanks for reading, look out for more in this series to see how this game evolves. Next I'm planning on investigating adding more players into the mix.
To get updates about this series, you can follow me on Twitter @mattjones
P.S This takes time to do, I spent the best part of Saturday building the game and then the bulk of Sunday writing about it - time well spent.
First appeared on my blog, last update 26 Apr 2020
Top comments (0)