Bigi Lui
2020-09-26 ⋅ 9 min read

Big2 - Breaking Down a Project Into Phases

Finding time to work on a hobby project is among one of the hard things about doing one. What makes it harder is the context you lose between the time you work on it. And when it comes to a reasonably sized project -- anything bigger than something you could complete over a weekend, with some degree of complexity that requires some planning and system design -- losing the context that was once fresh in your head could also mean losing interests/passion and abandoning ship.

The Big2 game project was like that for me. I started it a few months ago, and life got in the way and I paused on it.

When I started it, I had big plans of online play that works both synchronously & asynchronously, as well as various UI features and ideas. I started coding it all in one big sprint. I did take the time to organize code files sensibly, with clean backend/frontend separation (more on the stack and system design here) while taking advantage of Svelte's component structures; and follow best practices with the actual code.

Nevertheless, having taken a hiatus for a few months, coming back to it was hard. A lot of the system design I had in mind of client-server communication (how the server handled client connections and distribute data to each) was lost in my head and what code I had written was hard to follow.

I decided to take a step back from all of that network play code mixed in with game client/UI code, and broke down the project to different phases that I could work on separately, with each phase being small enough for me to complete over a few weeknights.

It came down to the following 3 main areas:

Game Logic

This further breaks down to two sub-points:

Pure functions that handle the logic of the Big2 game rules

Before I even split the project up to the 3 main areas, this was actually the very first part of the codebase I worked on. It made sense at the time too; I foresaw having to have game logic like this on both client side and server side (client side could quickly determine if a move is valid before a server request, and server to validate and execute a move). The actual final design turned out a little different, but this shared lib was useful nevertheless, because having a local game mode.

These are pure functions (in functional programming terms) where you provide an input value, and it runs through some logic and gives an output. No database, no state, no request context, no client.

For example, here's what a function might look like, which validates a move when everybody passed and it's your turn to play any card:

/**
 * next is a card or array of cards the player wants to play
 */
module.exports = (next) => {
  const cards = Array.isArray(next) ? next : [next];
  if (cards.length === 1) {
    //
    // Singles
    //
    return true;
  } else if (cards.length === 2) {
    //
    // Pairs
    //
    if (cards[0].order !== cards[1].order) {
      return false; // not pair
    }
    return true;
  } else if (cards.length === 3) {
    //
    // Triples
    //
    if (cards[0].order !== cards[1].order || cards[0].order !== cards[2].order) {
      return false; // not triple
    }
    return true;
  } else if (cards.length === 5) {
    //
    // Five Card Set
    //
    const fiveCardType = getFiveCardType(cards);
    if (!fiveCardType.type) {
      return false; // invalid set
    }
    return true;
  } else {
    return false; // invalid move
  }
};

All of the game logic of a game of Big2 is written as a set of a dozen or so pure functions like the one above. This includes everything from checking whether a set of five cards is a straight / flush / full-house / etc., checking if the cards you're playing is greater in power than what was played by the previous player, and so on.

Pure functions that handle players starting games and taking turns

Once the above set of pure functions for game logic is done, this is really a small subset that takes care of the flow of the game.

This set of pure functions would take in the entire Game State object, and determine whose turn is next, what happens when they take a turn or do a pass, update the Game State for everything that needs to happen, and so on.

Local Game Mode

This phase basically allowed me to flesh out the UI and frontend.

Once all of the above pure functions that handle game logic and game flow are done, working on the local game mode is completely Svelte frontend code.

The concise nature of Svelte frontend components code as well as the structures of how a Svelte web app is laid out make this just about as easy and fun as frontend could be to work on. As someone who used to advise against React and for simply using jQuery frontends for hobby projects, I have to say Svelte takes the cake in ease of development. Svelte offers the best of both worlds -- ease of getting started and concise code like a jQuery ad-hoc frontend would give, while providing the code structure and component-based design like React does that a big frontend project needs, in order to not become a spaghetti mess that a jQuery frontend codebase often becomes.

Online Game Mode

Leaving this part to the end allowed me focus on the networking aspect of it, as well as other online features like setting your name and avatar.

At this point, all game logic is done, and I have a simplified game frontend mostly working in a local game mode. Sure, the online mode will have some extra features, but at this point the UI is now in a state where I can make iterative updates, instead of starting from scratch.

This let me dive back into the websockets network game code I had when I first started the project, made sense of what was there, made necessary revamps to better work with the now more independent components of the rest of the codebase.

At the end of the day, getting the online game mode working became a lot easier and shorter than I would've anticipated. I remember at one point coming to a mental revelation of something like, "Wait, this is it. I think I'm actually only a few steps away from a fully working online game. I basically just wire up an API request of what happens when a player clicks a button to take a turn, and the rest of the websockets code that takes a request, execute it, and broadcast the updated Game State to all clients, would just handle the rest." Indeed, once I did that, the client code would receive an updated Game State, render the entire game stage (game board) correctly; and then automatically all players in the game have a fully working game.

What a satisfying feeling! I hadn't even expected the online game to be fully working that soon, but there it is.