What is Flipping Bananas?

Flipping Bananas is a puzzle game with similarities to Minesweeper and Picross. The goal of the game is to help Geoffrey the Monkey find bananas. You are given a board with 25 squares. Under each square there is either a banana (or a bunch of bananas) or Spike the Dog. You are trying to find all the bunches of bananas (bunches have 2 or 3 bananas), while avoiding Spike. Once you find all the bunches of bananas the round is over. If you flip over a square where Spike is hiding you lose a life, and if you lose all your lives the game is over.

Why did I build this?

This game was inspired by a mini game inside of Pokemon HeartGold. I've grown up playing Pokemon games (I still play because I'm still growing up), and the early Pokemon games all had a Game Corner where you could play mini games, such as slot machines. When they made HeartGold they replaced the slot machines with a game called Voltorb Flip.

Screenshot of voltorb flip

I spent a great deal of time inside Pokemon just playing this mini game. I often times would load up Pokemon HeartGold just to play Voltorb Flip. I recently got the urge to play the game again so I starting thinking about how I could make this game myself. That way I could play it whenever I wanted to without needing to load up Pokemon. Not that I'm going to stop playing Pokemon but it would be nice to not have to load up Pokemon and travel to Goldenrod City just to play this game. I did find this recreation online, so I could have just played that but building things is fun.

Deciding how to build the game

I thought about how to go about building the game for a couple days. I thought the hardest part would be writing the code to actually generate the different boards, but this ended up being pretty easy. I'll talk about that a bit later.

I also gave some thought to what framework I wanted to use to build the game. I have been using Svelte a lot lately so that was a consideration. But I ended going with Remix. Remix is a React framework that has some similarities to Svelte. I decided to go with Remix because I had never used it and it had been a long time since I really spent much time building anything with React, and I missed it.

I also decided to build the gameplay a little bit different than Voltorb Flip. For example in Voltorb Flip there are no lives and the leveling works a little different.

Generating the game boards

Now that I've figured out what framework I wanted to use and had some general ideas of how I wanted to build the game, it was time to actually build the game.

Generating the game boards was a lot easier than I initially thought. Basically I built the game with ten levels. You can play past level 10 but all the levels past 10 use the same logic. I originally started out with just one level but the one level was either too difficult or too easy (more on that later) so I built out ten levels and made it progressively more difficult as you advanced to higher levels.

To create the levels I started with a levels object that consists of the data for all ten levels. In the following example I show level 4, which will always have ten squares with Spike, nine squares with one banana, three squares with a bunch of two bananas, and three squares with a bunch of three bananas (25 squares total). But the location of Spike and the bananas will change from game to game.

javascript

export const levels = {
  ...
  4: {
    '0': 10,
    '1': 9,
    '2': 3,
    '3': 3,
  },
  ...
};

Next I take the levels object shown above and create a game board for whatever level the player is on. In this game I create a board for level 4.

javascript

// import levels object from above
import { levels } from './levels';

// create a new game starting at level 1 or whatever level the player is on
export default function createGame(lvl = 1) {
  const board = [];

  // if the current level is higher than the last defined level in the levels object - just use that last level
  if (lvl >= levels.length) lvl = levels.length;

  // take the level data and create an array with values for the squares
  const choices = Object.keys(levels[lvl]).reduce((acc, curr) => {
    for (let i = 0; i < levels[lvl][curr]; i++) {
      acc.push(parseInt(curr));
    }
    return acc;
  }, []);

  // shuffle up the values to randomize the game
  function shuffle(array) {
    for (let i = array.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
  };

  const shuffledChoices = shuffle(choices);

  // create the game board

  // create five rows
  for (let _row = 0; _row < 5; _row++) {
    const row = [];
    
    // create five columns in each row
    for (let _col = 0; _col < 5; _col++) {
      // assign the first value in shuffledChoices to the current square
      let value = shuffledChoices[0];
      
      // remove the first value in shuffledChoices so during the next loop we can grab the first value again
      shuffledChoices.shift();
      row.push(value);
    }

    // once all five squares in the row have been assigned values add the row to the game board
    board.push(row);
  }

  /* an example of what a level 4 game board might look like - zeros represent spike
    board: [
      [ 1, 2, 0, 0, 0 ],
      [ 1, 3, 1, 1, 3 ],
      [ 0, 0, 1, 2, 1 ],
      [ 0, 0, 0, 0, 3 ],
      [ 1, 1, 1, 2, 0 ]
    ] 
  */
  return board;
}

Original Level Was Either Too Difficult or Too Easy

I originally built the game to be only one level, using a weighting system to determine what gets assigned to a square. But this ended up being far too random. One game you might have Spike showing up a handful of times in 25 squares and the next game he might show up 15 times. So I decided to be a little more intentional with how many times Spike and the different bunches of bananas show up.

javascript

// original method for creating a level

// this would determine the likelihood of a value being assigned to a square
const weights = {
    '0': 7,
    '1': 4,
    '2': 3,
    '3': 2,
  };

// create the array of choices
const choices = Object.keys(weights).reduce((acc, curr) => {
  for (let i = 0; i < weights[curr]; i++) {
    acc.push(parseInt(curr));
  }
  return acc;
}, []);

// pick a random value from choices
choices[Math.floor(Math.random()*choices.length)];

Checking the values

Now that I have the game board created I can check that board array whenever a player checks under a square. To do that correctly I need to know what square is being checked. Each square is built with a button element that has the row number and column number assigned to it as a data-attribute.

jsx

export default function Button({ row, col }) {
  return (
    <button
      type="button"
      onClick={handleCheckValue}
      className="absolute inset-0 z-10"
      data-row={row}
      data-col={col}
     ></button>
  );
}

So when a player checks under a square I can check the board array for the value assigned to the square, rather than putting the value in the html.

javascript

// get the row and col values from the button
const { row, col } = e.target.dataset;

// check the board for the square value
const val = game.board[row][col];

return val;

Wrapping Up

There's a lot more that I could talk about but I don't want to 😅.

The game also keeps score, has lives that you lose and gain, and more. All the game data is stored inside React state. I decided to display the game data to the player in a way similar to what you'll see in Galaga and Galaxian. Two other games that I played a lot growing up.

I thought the most interesting part of building this game was the logic to generate the different game boards. Not nearly as difficult or complex as I thought it would be.

Now that I have the game built I can play whenever I want without having to load up Pokemon HeartGold! Although I definitely will still play but now I can focus solely on becoming the very best, like no one ever was.