Javascript — How to generate Candy Crush and Bejeweled grid with no matches

Dennis Wang
4 min readMar 8, 2020
Source: https://zone.msn.com/en/bejeweled/

By Dennis Wang

Did you ever play Bejeweled before? Bejeweled is the world’s #1 puzzle game. I played Candy Crush Saga a lot when I was in high school. Now I’m a software developer with variety of technologies across web and mobile app development. Today I would like to share my simple short code to find and remove matches from a randomly generated candy grid.

The main dependency used in this project is lodash.

First, we’re going to define constant values indicating jewels.

const COLOR = {  RED: '#E23822',  BLUE: '#3192F1',  PURPLE: '#5630D8',  YELLOW: '#FFD449',  GREEN: '#144B14',  SILK: '#E3E3E3',  GOLD: '#F9A620'}const JEWELS = [  COLOR.RED,  COLOR.BLUE,  COLOR.PURPLE,  COLOR.YELLOW,  COLOR.GREEN,  COLOR.YELLOW,  COLOR.SILK,  COLOR.GOLD]

Next, we import some lodash util functions.

import { difference, indexOf, isEmpty, random, size, union } from 'lodash'

Next, we create the generateGrid function, which produces a grid of jewels from a list of jewel prototypes and required grid dimension.

const generateGrid = (prototypes, col, row) => {  if (isEmpty(prototypes)) return []  col = parseInt(col)  row = parseInt(row)  if (col <= 0 || row <= 0) throw Error('Invalid props')  var grid = []  for (var i = 0; i < row; i++) {    var eachRow = []    for (var j = 0; j < col; j++) {    eachRow.push(prototypes[random(size(prototypes) - 1)])  }  grid.push(eachRow)}

Actually the above code generates a grid randomly, so that it possibly has matches inside the grid. Now we have to find all matches by surfing the grid both horizontally and vertically.

So the next thing to do is create another function to find the matches. It returns a list of positions of matched jewels with no duplication.

const findMatches = grid => {  var matches = []  var groups = []  for (var i = 0; i < grid.length; i++) {    var tempArr = grid[i]    groups = []    for (var j = 0; j < tempArr.length; j++) {      if (j < tempArr.length - 2) {        if (grid[i][j] && grid[i][j + 1] && grid[i][j + 2]) {          if (grid[i][j] === grid[i][j + 1] && grid[i][j + 1] === grid[i][j + 2]) {            if (!isEmpty(groups) && indexOf(groups, grid[i][j]) === -1) {              matches.push(groups)              groups = []            }            indexOf(groups, grid[i][j]) === -1 && groups.push({ row: i, col: j })
indexOf(groups, grid[i][j + 1]) === -1 && groups.push({ row: i, col: j + 1 })
indexOf(groups, grid[i][j + 2]) === -1 && groups.push({ row: i, col: j + 2 }) } } } } !isEmpty(groups) && matches.push(groups) } for (j = 0; j < grid.length; j++) { tempArr = grid[j] groups = [] for (i = 0; i < tempArr.length; i++) { if (i < tempArr.length - 2) { if (grid[i][j] && grid[i + 1][j] && grid[i + 2][j]) { if (grid[i][j] === grid[i + 1][j] && grid[i + 1][j] === grid[i + 2][j]) { if (!isEmpty(groups) && indexOf(groups, grid[i][j]) === -1) { matches.push(groups) groups = [] } indexOf(groups, grid[i][j]) === -1 && groups.push({ row: i, col: j }) indexOf(groups, grid[i + 1][j]) === -1 && groups.push({ row: i + 1, col: j }) indexOf(groups, grid[i + 2][j]) === -1 && groups.push({ row: i + 2, col: j }) } } } } !isEmpty(groups) && matches.push(groups) } var result = [] for (i = 0; i < matches.length; i++) { const row = matches[i] result = union(result, row) }
return result}

Now we can remove all matches from the grid by calling the above function. What we need to do is not remove. They have to be replaced with other jewels in order to avoid additional matches. To do that, we’re going to select a jewel based on the neighbors around each matched cell. The following code does the work for us. Add the following lines to your generateGrid function.

const matches = findMatches(grid)if (isEmpty(matches)) return gridfor (i = 0; i < matches.length; i++) {  var neighbors = []  matches[i].col > 0 && neighbors.push({ row: matches[i].row, col: matches[i].col - 1 })  matches[i].col < col - 1 && neighbors.push({ row: matches[i].row, col: matches[i].col + 1 })  matches[i].row > 0 && neighbors.push({ row: matches[i].row - 1, col: matches[i].col })  matches[i].row < row - 1 && neighbors.push({ row: matches[i].row + 1, col: matches[i].col })  const candidateJewels = difference(prototypes, neighbors.map(neighbor => grid[neighbor.row][neighbor.col]))  if (!isEmpty(candidateJewels)) {    grid[matches[i].row][matches[i].col] =    candidateJewels[random(size(candidateJewels) - 1)]  }}return grid

Finally, you invoke the generateGrid function to have a valid grid with no matches.

console.log(generateGrid(JEWELS, 8, 8))

If you’re interested in its visualization, please visit my Github repo.

Source: https://github.com/proIT324/bejeweled-grid/blob/master/screenshots/screenshot002.png

Thank you so much for reading my post!

--

--

Dennis Wang

I'm a professional full-stack techie with multi years of proven successful startup experience.