Building a web-based multiplayer game with Google Firebase
rummikub-app.web.app

Building a web-based multiplayer game with Google Firebase

Two weeks ago I decided to build rummikub-app.web.app a web-based version of a classic board/tile game. If you haven't played before, it's like a combination of rummy (collect "sets" and win when you have no cards left) and poker (players share cards on the table). I've wanted to learn Google Firebase, so what better opportunity to learn new technologies than a quarantine? ¯\_(ツ)_/¯

Goals

  1. Create a multiplayer, real-time, web-based game, and generate easy-to-share links for friends to join and play.
  2. Learn and harness the magic of Google Firebase.
  3. (optional) Unintentionally pick the hardest game to build, having never built a game before.

No alt text provided for this image


Technologies Used

Vue JS

Vue.js is a popular javascript framework. I already know it well, and didn't want to introduce too many new technologies at once (i.e. React.js).

Vuetify

Vuetify.js is a "Material Design" framework for Vue.js.

I use older, deprecated version (v1.5) on nerdydata.com, and decided this would be a good chance to learn their new v2 framework.

Firebase Realtime Database

Truly a magical database product, which deserves its own write-up. Benefits include:

  • Safely give client-side read/write access, based on simple user-based rules set up.
  • Realtime synchronized state simple callback functions in a few lines of code.
  • Supports database triggers for when objects are added/changed/deleted.
  • It even provides a simple UI, making it easy to accidentally delete data!

Firebase Functions

Same as Google Cloud Functions with a different UI. I needed some server-side logic to prevent cheating and keep some game-play secret (i.e. dealing tiles, validating/saving the board).

+1 to not paying for a server, setting up an API or docker image, manage authentication, etc.

Firebase Authentication

I've never used this service before. After 10 minutes I had "Sign in with Google" working.

Again +1 to not having to set up a server, a database to store users, configure OAuth credentials, handle redirect flow, etc.

Just call a single method to log in (and a cloud function to validate and save the data to the Realtime Database.)

Firebase Hosting

It's free static website hosting - save yourself $5/mo. in site hosting fees - all by typing:

firebase deploy --only hosting        

In theory, you don't even need to buy a domain, and can use the free "<name>.web.app".

Building the Game

1. Authentication

I started with authentication because (1) to test how to read/write user data in Firebase Realtime Database, and (2) if I couldn't get authentication working easily, I didn't want to have to create a server, and set up a database to read/write user data, and would have cut my losses early on.

Just enable the Google provider in the Authentication Console, and add two lines of code to the app to handle all login/redirect requirements

No alt text provided for this image
No alt text provided for this image

2. Game creation, and Game joining

Next up was adding two buttons to "create" and "join" games, and update each game to keep track of its players. I wanted game sharing to be easy, and found a fun library to generate unique, short, and human-readable names to help with game sharing.

On web, simply copy+paste the URL, and on mobile-web I used `navigator.share` to use the native share dialog:

No alt text provided for this image
No alt text provided for this image

3. Designing the "tile", "board", "pool", and "racks"

With users and games set up, next step was to create (1) the face-down "pool" of tiles for players to draw from, (2) each player's private "rack" where tiles are dealt, (3) the shared "board" that players can see and interact with, and (4) the small-but-important "tile".

The "Tile" class only needed a few properties. An ID (since there are two of each color/number combination), a number (or zero if a joker), and a color.

No alt text provided for this image

How tiles were formed into "groups" was less straight-forward.

Attempt #1 - create a grid of x,y coordinates that tiles can occupy. I realized early enough that this would be complex to validate/add/remove tiles from groups, and I'd be stuck with this structure if I ever wanted to change the grid-dimensions of the board.

Attempt #2 - create "tile groups" which more closely aligns with actual gameplay vs a fixed grid, and I could store and recreate the board using arrays of tile groups: [[1,2,3], [5,5,5]]

No alt text provided for this image

4. Ending a Player's Turn

"...it was at this moment..." I realized I picked a difficult game to build:

  1. I can't just save and trust the board based from what the user sends because of the high likelihood that an international hacker devotes their time cheating my game (a highly profitable investment).
  2. Newly played tiles need to be validated to ensure they originated from the player's rack (not their sleeves), and that the resulting board had no invalid groups.
  3. Inferring what number/color a joker-tile is played was tricky, since it can placed be at the start, end, or middle of a group.

The result was to send up a list of the player's "moves" from their turn; storing each move's Tile ID and destination (which group and position within the group), and then replay the moves to validate and store the new board's state.

5. Everyone loves drag-and-drop

I had a working game 🎉 but usability was lacking: click on a tile and click on a destination to move it. I knew critics would demand "drag-and-drop".

After some failed attempts at making my own draggable directive, I decided to install Shopify's (not actively maintained?) Draggable Library.

After a few failed attempts (admittedly, by my own mistakes) I tested out a few other popular libraries, failed again, and eventually got Shopify Draggable (mostly) working.

No alt text provided for this image

I had a working game (again!) 🎉 spruced up the design a bit, added a homepage to explain how to play, a quick dialog for players to share games with friends, made sure things were responsive for mobile, and deployed! All in about 2 weekends.

No alt text provided for this image


Challenges and Time Sinks

Firebase, specifically Realtime Database, has changed the way I think of database storage and client-server communication. I'm already overwhelmed by all the (mostly useless) product ideas that come to mind 😇.

While there's enough learnings from Firebase Realtime Database alone to warrant its own write-up, here were the two biggest areas that took longer than expected:

Shared Typescript Modules with Firebase Functions

I ran into lots of issues sharing common Typescript code (like the Tile and TileGroup classes) between Vue and Firebase Functions. Firebase requires all referenced code to be in its directory for when it builds and deploys.

While I eventually figured out how to use the "composite" option in Typescript to designate a module as "shareable", I couldn't find a way to deploy shared functions referenced outside of the Functions directory, and don't think Firebase Functions can handle this case easily.

My solution: `cp -r ./shared/src ./functions/src/shared` - copy it all manually before releasing.

/functions (all Firebase Functions code must be in this directory)
- tsconfig.json 
- src/
- lib/

/shared (common modules)
- tsconfig.json
- src/
- lib/

/src (Vue source)
- tsconfig.json
- views/
- App.vue
        

Firebase "request" vs "callable" functions

There are two types of Firebase Functions: "request" functions which are described in the "getting started" docs, and "callable" functions. I changed all Firebase Functions to use the "callable" type - a much better approach for apps which automatically handle authentication, and just easier to implement in general.

Firebase database rules

Firebase's Realtime Database Rules seemed counterintuitive at first, but it's really well thought out, and removes the need for a server to proxy and authorize database operations.

My advice here is to (RTFM) make sure that each key in the collection is something you have access to from the app and/or would want to filter/query by (i.e the user's UID, the game's ID, etc.)

Using their simple JSON rule builder, I can give access to the client (public website code) and enforce what each user can ".read", ".write" based on their identity.

No alt text provided for this image

In no other database would you ever allow a client (website/app) direct access to read or write from your database without some api server in-between. They handle all the validation and enforcement automatically, and default to restricted access when you don't specify a rule.

With that JSON rule schema, I enforce the following rules for requests made from the browser:

  1. Users can see their own "rack" tiles, games they're playing, and their own user profiles. Deny "write access" to any of these collections.
  2. Users can read and write to their "user_preferences" collection.
  3. Users can not see another player's tiles, or the "pool" of face-down tiles.

Firebase's documentation says it best: "While it [cascading rules logic] may not seem immediately intuitive, this is a powerful part of the rules language and allows for very complex access privileges to be implemented with minimal effort."

--

Ready to Play? Click Here!

To view or add a comment, sign in

More articles by David Bielik

  • Adding Technology Profiles to your LinkedIn Ads

    In B2B software, we all want to promote our products in front of the right companies. But, money can often be wasted on…

    1 Comment
  • Finding your Competitor's Customers

    Over the years, NerdyData has helped sales and marketing teams answer a key question for lead generation: Who are my…

Others also viewed

Explore content categories