Project 9

2D Game: Gumdrop Gatherer

For your final project, you will make a "Candy Crush Saga"-style game that we are calling "Gumdrop Gatherer." (Let me know if you can come up with a better name for this...) In the game, you are presented with a two-dimensional grid of gumdrops, and you must gather them in a certain way to collect as many points as possible. You can use the mouse to click on any gumdrop you want, as long as it has at least one neighboring gumdrop of the same color. At that point, all the gumdrops of that color touching the one you selected disappear, and new ones drop from the top to take their place. You earn points for every gumdrop that disappears.

Partners

You may work with a partner for this project. If you choose to do so, only turn in one copy of the project with both of your names in the comments at the beginning of the code.

Alternate games

If this game doesn't particularly interest you, you may create any two-dimensional game of your choice of comparable programming difficulty. See the end of this document for requirements and suggested games. You must get the instructor's approval before starting work on your project if you are not writing the Gumdrop Gatherer program.

Game Description

A video is worth a thousand words, so let's take a look at a demonstration.

Your game doesn't have to work exactly like mine, but it must meet the following requirements:

Suggested steps

You may design the program in any way you see fit. However, you must use at least three functions. Otherwise, the specific design decisions are up to you. That said, I have some suggestions that might help.

The biggest hint is to use the tic-tac-toe program we wrote in class as an overall guide and model. Though the game mechanics itself are different between tic-tac-toe and this game, the programs can be designed in a rather similar fashion. Both use a two-dimensional list to represent the game board, they wait for the user to click the mouse on the board, and then the board updates in some fashion.

Specific suggestions:

The game logic should work like this:

Later in this document there are specific suggestions for functions which you can use as more guidance if you'd like.

What to do

FAQ

Challenges

Other games

Function guidance

Here are some ideas on functions to write. You don't have to write these; you can pick and choose, or make up totally new ones.

Creating an empty game board board

make_empty_board(rows, cols): Copy this function from tic-tac-toe. It makes a completely blank board of zeroes.

Filling a game board with random numbers

I suggest having a function called fill_board(board) that iterates through the game board and replaces every zero in it with a random number between 1 and 4 (inclusive). This can be used in two different places in your program. First, it can be used at the very beginning of the program to make a completely random starting board of gumdrops. Then, it can be used when there is a blank region of the board, after some gumdrops have disappeared, that should be filled with new gumdrops.

Checking to see if a gumdrop is next to one of the same color

This is a slightly tricky one because it involves checking the neighboring squares on the game board. What makes it tricky is that if the gumdrop is on the border of the board, then all four neighbors (up, down, left, right) may not exist.

Try writing this function:

has_identical_neighbor(board, row, col): This function will be used to see if a gumdrop that the user clicked on has a neighbor of the same color. Gumdrops that don't have any neighbors of the same color can't be eliminated, so this function should check the upper, lower, left, and right neighbors of the (row, col) gumdrop to see if any of those four neighbors are the same color. Note: for gumdrops on the border of the board, there may not be four neighbors to check.

Examples:

Imagine a board like this:

  board = [[1, 3, 2, 1],
             [1, 4, 3, 3],
             [2, 4, 1, 3]]
   

has_identical_neighbor(board, 0, 0) should return True because the 1 in the upper left corner has another 1 below it.

has_identical_neighbor(board, 2, 1) should return True because the 4 in the bottom row has another 4 above it.

has_identical_neighbor(board, 1, 2) should return True because the 3 in the middle row has another 3 next to it.

has_identical_neighbor(board, 2, 2) should return False because the 1 in the bottom row has no other 1 next to it.

Finding the region of gumdrops that should disappear when one is clicked

In the demo video above, we use the color white to show the gumdrops that are about to disappear. To make this happen, we will use a -1 to represent these gumdrops.

When a user clicks on a gumdrop, first check if the gumdrop has a neighbor of the same color (call your has_identical_neighbor function). If there is a same-colored neighbor, change the gumdrop from whatever number it is to -1. This indicates that it will disappear. Then we will "spread" this -1 to all neighboring gumdrops that have the same color as the original one.

Try writing this function:

spread(board, num): This function should "spread" any -1s in the board to their upper, lower, left, and right neighbors, if those neighbors have the same number as the num parameter. This sounds harder than it is. To do this, use a standard nested-for loop to iterate through the board. Whenever you find a -1, first check to see if any of the four neighbors have the number num in them. If they do, overwrite the neighbor with a -1. Return the number of numbers changed. Note: since this function only looks at immediate neighbors of the -1s, it might take a few calls to this function to finish spreading the -1s.

Examples:

Imagine a board like this:

  board = [[4, 3, 2, 1],
           [1, 4, 3, 3],
           [2, 4, 4, 4],
           [3, 4, 4, 1]]

Imagine the user clicks on the gumdrop at row 2 and column 1. First, we have Python do the command board[2][1] = -1 which changes the board to this:

  board = [[4, 3, 2, 1],
           [1, 4, 3, 3],
           [2, -1, 4, 4],
           [3, 4, 4, 1]]

Then we call spread(board, 4) which changes the board to this:

  board = [[4, 3, 2, 1],
           [1, -1, 3, 3],
           [2, -1, -1, 4],
           [3, -1, 4, 1]]

The function call above will return 3, meaning three 4's were changed to -1s. Notice how the -1 has spread to three additional squares. Then we call spread(board, 4) again. Now the board changes to:

  board = [[4, 3, 2, 1],
           [1, -1, 3, 3],
           [2, -1, -1, -1],
           [3, -1, -1, 1]]

and the function call returns 2, since changed two more 4s into -1s. Then we would call spread(board, 4) one more time, but the board wouldn't change, because there are no more 4s to change into -1s. Note that the 4 in the upper left corner doesn't change to -1 because it's not directly next to any of the -1s. So the function returns zero (and we can therefore stop calling it).

Replacing the -1s with 0s

You can write a simple function that replaces all the -1s with zeros. The only reason the -1s were there in the first place was so we could draw them in white for a split second (the comment above about time.sleep()) and then remove them from the board by replacing them with zeros.

I called my function replace(board). I also had it return the number of -1s replaced by zeroes, which I then used to assign the points that the user earned. I wrote a function called calc_points(numreplaced) to do this.

Simulating gravity

This, along with the previous function, are the two most challenging parts of the program. I suggest writing a function called gravity(board) that goes through every square of the board and looks for a situation where there is a square with a number in it, and a square with a zero below it. The 0 indicates a blank space, so the number should be lowered into the blank space (where the zero is now), and the number replaced with a zero. This simulates gravity dropping each gumdrop into the square below.

Like the function above, have the gravity function return the number of gumdrops moved. That way, you can call gravity over and over in a loop until it returns zero (which means all of the gumdrops have been lowered into their final positions).

To write this function, use the standard nested for loops, but have the row loop run backwards, to examine the rows from the bottom up. That will make the gravity effect look nicer (this isn't required, but it makes it look prettier).

Examples:

Imagine a board like this:

  board = [[1, 3, 2, 1],
           [1, 0, 0, 3],
           [2, 2, 0, 0],
           [0, 0, 0, 1]]

After calling gravity(board), the board will look like:

  board = [[0, 0, 0, 0], 
           [1, 3, 2, 1], 
           [1, 0, 0, 3], 
           [2, 2, 0, 1]]

and the function returns 8, because 8 numbers have moved.

We can then call gravity(board) again to get:

  board = [[0, 0, 0, 0], 
           [1, 0, 0, 1], 
           [1, 3, 2, 3], 
           [2, 2, 0, 1]]

and the function returns 2. We call it one more time:

  board = [[0, 0, 0, 0], 
           [1, 0, 0, 1], 
           [1, 3, 0, 3], 
           [2, 2, 2, 1]]

and it returns 1. At this point all the pieces are as low as possible.