CS 142 Project 2
- Game description
- Before you begin
- Starter code
- Guide to the project
- Functions you will write
- Testing your program
- What to turn in
- Challenges
- Hints and tips
- Other games
You will write a program that allows the user to play 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 are neighbors of the one you selected (and neighbors of the neighbors, etc) disappear, and new ones drop from the top to take their place. You earn points for every gumdrop that disappears.
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:
- The game should ask the user how big the board should be, in rows and columns.
- The game should ask the user how many points to play to.
- The game should draw a board of the specified size, filled with colored circles representing gumdrops. I find that using four different colors for gumdrops works well.
- The game should let the user click the gumdrops (the colored circles). With every click, the program should check if the gumdrop clicked on has at least one neighbor (upper, lower, left, or right) of the same color. If it does, then all gumdrops connected to the original one of the same color should disappear. Here, “connected” means a neighbor of the same color, or neighbors of neighbors of the same color, or neighbors of neighbors of neighbors, etc. In other words, the single entire region of the same color should disappear. “Neighbors” means left, right, above, or below, not diagonal.
- When a region of gumdrops disappears, if there are any gumdrops above the region, then they should fall as far as they can downward, until they hit other gumdrops in the same column (simulating gravity).
- Once all the gumdrops have fallen down, there will still be open areas of the board. These should be filled with new gumdrops. (You do not have to simulate gravity to have the new gumdrops “fall in” from off the top of the screen; they can just appear.)
- 10 points are accumulated for each gumdrop eliminated in a region. For example, if you eliminate 3 gumdrops at once, that earns 30 points. However, bonuses are given for larger regions of gumdrops eliminated at once. For regions at least 5 gumdrops big, each gumdrop earns 50 points. So a region of 5 gumdrops all disappearing at once earns 250 points. For regions at least 10 gumdrops big, you earn 100 points per gumdrop. For instance, if you can eliminate 10 gumdrops at once, that earns 1000 points!
- When you reach the threshold for points entered at the beginning of the program, the game ends.
Before you begin
This project is designed to test your knowledge of the following concepts:
- object-oriented programming basics
- two-dimensional arrays
- graphics using SimpleCanvas
- separating the internal representation of a computer program from how the user interacts with the program
This project builds heavily on the tic-tac-toe lab, especially in the objectives in the list above.
In particular, think about the following before beginning to code:
- In a two-dimensional array, how do you determine the number of rows and the number of columns?
- In a two-dimensional array, given a the indices of a particular cell (the row and column), how would you access the cell above? Below? How about the cells to the left and right?
- Continuing from the previous question, how would you determine if the cell above (or below/left/right) exists? That is, how would you detect if a given cell is on the edge of the board and might not have a particular neighboring cell at all?
- If you wanted to simulate moving an element in a 2-dimensional array from one cell to a neighboring cell, how would you do it?
Starter code
Make sure you create the project in a place on your computer where you can find it! I suggest making a new subfolder in your CS 142 projects folder.
First, make a new project in IntelliJ. Then, download this starter code. Unzip the file and copy the files (or drag and drop them) into the src
folder in your new project.
In the starter code you will find three files:
GumdropGatherer.java
is where you will write your code. A few methods are already written for you.SimpleCanvas.java
is the SimpleCanvas code that you don’t need to worry about.BouncingBall.java
is a demonstration on using thepause()
method inSimpleCanvas
to make an animation. You will probably want to usepause()
when you start implementing the gravity part of the project, so you can use this as an example.
Guide to the project
You should read this whole guide before beginning.
This project is set up similarly to the Tic-Tac-Toe lab. The starter code uses a number of familiar functions, including handleMouseClick()
and draw()
.
Representing a gumdrop board
We learned in the tic-tac-toe lab that the computer’s internal representation of the state of a game does not have to match what the user sees; in tic-tac-toe, the “internal representation” of the game board was a 3x3 two-dimensional array of characters, filled with X’s and O’s, while the “external representation,” or what the user sees, was a 300 by 300 graphical canvas filled with colored circles. This concept is fundamental in computer science, and we will explore it more in this project.
Here, the board of gumdrops will be internally represented by a two-dimensional array of integers (int[][]
). The user, however, will not see these integers; they will see a graphical canvas filled with colored circles. Here is the way in which the integers correspond to the colored circles:
- If
board[row][col]
contains a zero, it means that square on the board is empty. - If
board[row][col]
contains an integer 1–4 (inclusive), that means that square contains a gumdrop. The specific number 1–4 corresponds to the color of the gumdrop (you can pick the colors; I used red/green/blue/yellow). - If
board[row][col]
contains a negative integer 1–4, that means that square contains a marked gumdrop. Marked gumdrops are ones that are about to be removed, and are drawn in white. (This concept is explained more in-depth later.)
In other words, just like the tic-tac-toe lab used char
s to represent the empty spaces, X’s, and O’s on the board, we will use int
s in a similar way: zero = empty space, positive numbers = gumdrops, negative numbers = “marked” gumdrops.
Drawing the board on the canvas
In tic-tac-toe we used a square size of 100-by-100 because the game board was only 3 by 3, which then scaled up to a 300 x 300 sized canvas. Because the game board for this project can be larger (it’s more fun if it’s larger), I suggest using a gumdrop size of 40-by-40 instead. In other words, instead of multiplying and dividing by 100, you will multiply and divide by 40.
Marking gumdrops for removal
When a gumdrop is clicked on, we need to figure out the region of gumdrops that will be removed. The way we will do this is by “marking” gumdrops for removal in an iterative process. When a gumdrop is first clicked on, your code should change its number to its negative equivalent. Remember that the gumdrop colors are stored internally by positive integers 1–4. So when a gumdrop is clicked on, it will be marked for removal by changing its integer to the corresponding negative integer. That is, “1” becomes “-1”, “2” becomes “-2”, etc.
Finding the region of gumdrops to remove
Remember that when a gumdrop is clicked, the entire contiguous region of gumdrops connected to the initially-clicked gumdrop should be removed. This will be done in two phases. First, we will find the region of gumdrops to be removed by “spreading” the marked gumdrop (now a negative number) to all neighboring gumdrops above, below, left, and right that are the same color as the marked gumdrop. We do this over and over again, as long as there are more gumdrops to mark. Note that this iterative spreading process is not shown visually; the user should only see the final result after the spreading can’t go any farther.
Functions you will write
The game begins in the
main()
method, like all Java programs. There are instructions there explaining the general algorithm. To be clear, you should not write all of your code insidemain()
!I’ve given you the function definitions for a number of other functions that you will write. They are described here, and also in Javadoc comments in the code.
void draw(SimpleCanvas canvas, int[][] board)
: This function is similar to the tic-tac-toe drawing function. The main differences are that this board is a flexible size (can be any number of rows and columns, where tic-tac-toe was always 3-by-3) and this board holds integers rather than chars. Otherwise the logic of the function is very similar.int[] handleMouseClick(SimpleCanvas canvas, int[][] board)
: This function is also similar to the tic-tac-toe function. The main difference is that this function returns an integer array with the coordinates of the mouse click.boolean hasMatchingNeighbor(int[][] board, int row, int col)
: This function returns aboolean
indicating whether or not the gumdrop at (row, col) has a neighboring gumdrop of the same color. This function will be used inhandleMouseClick
to detect if a gumdrop that the player clicked on has a matching neighbor of the same color. If there isn’t a matching neighbor, the function will returnfalse
, and the game won’t allow the user to select that gumdrop.boolean spreadMarkedOnce(int[][] board)
: After “marking” the chosen gumdrop by switching its number to its negative value, this function should “spread” the negative value by checking all the neighbors (up/down/left/right) of any marked gumdrop and marking neighbors with an identical number (color). This function returns true if at least one gumdrop changed from postive to negative, false otherwise. This can be called in a loop (inmain
) until no more marking happens. Hints are below.int removeMarkedGumdrops(int[][] board)
: After all the gumdrops needing to be marked are marked, this function will remove them (turn the negative numbers into zeroes). Remember, gumdrops are never “truly” removed, we just turn them back into zeroes which indicates empty squares. This function returns the number of gumdrops removed.boolean gravityOnce(int[][] board)
: After removing marked gumdrops, this function will lower lower any “floating” gumdrops one space vertically. This function returns true if at least one gumdrop was lowered, false otherwise. This function will be called in a loop inmain
so the user can see the gumdrops fall.int calcPoints(int gumdrops)
: This function calculates how many points the player earns for removing however many gumdrops they removed.int fillEmptySquresRandomly(int[][] board)
: This function is called after removing marked gumdrops to place new random gumdrops in the empty squares. Returns the number of gumdrops added to the board (you will need this number to calculate the points earned).
Additionally, I have given you a function called printBoard
that shows the contents of the gumdrop board as numbers. This is very helpful when debugging, as the draw
function can be finicky to use.
Testing your program
You should test your program thoroughly to make sure it works. You will do this by writing “test functions.” A test function is a function designed to test a different function, that will run a number of test cases on the function being tested. You can call these test functions from main.
You must write test functions for hasMatchingNeighbor()
, gravityOnce()
and spreadMarkedOnce()
. Additional test functions are highly recommended, but not required.
For instance, to test gravityOnce()
, you might write this:
public static void testGravityOnce()
{
int[][] board1 =
{ {1, 3, 2, 1},
{1, 0, 0, 3},
{2, 2, 0, 0},
{0, 0, 0, 1} };
gravity(board1);
printBoard(board1);
}
And then you would verify that your test code prints
[0, 0, 0, 0]
[1, 3, 2, 1]
[1, 0, 0, 3]
[2, 2, 0, 1]
What to turn in
Through Canvas, turn in your
GumdropGatherer.java
file.
Additionally, upload a text file answering the following questions:- What bugs and conceptual difficulties did you encounter? How did you overcome them? What did you learn?
- Describe any serious problems you encountered while writing the program (larger things than the previous question).
- Describe whatever help (if any) that you received. Don’t include readings, lectures, and exercises, but do include any help from other sources, such as websites or people (including classmates and friends) and attribute them by name.
- Did you do any of the challenges (see below)? If so, explain what you did.
- List any other feedback you have. Feel free to provide any feedback on how much you learned from doing the assignment, and whether you enjoyed doing it.
Challenges
- Add additional functionality to the game like you might find in games like Candy Crush Saga, Angry Birds, Toon Blast, etc.
- For example, make up different kinds of “items” that can appear in squares on the board. Like a bomb that explodes neighboring squares, or a present that earns you bonus points.
- Make the same-color-neighbor concept work with diagonal directions (so regions can extend diagonally as well as horizontally or vertically).
Hints and tips
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.
Finding the region of gumdrops that should disappear when one is clicked
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 Java do the command
board[2][1] *= -1
which changes the board to this:board = { {4, 3, 2, 1}, {1, 4, 3, 3}, {2, -4, 4, 4}, {3, 4, 4, 1} };
Then we call
spreadMarkedOnce(board)
which changes the board to this:board = { {4, 3, 2, 1}, {1, -4, 3, 3}, {2, -4, -4, 4}, {3, -4, 4, 1} };
The function call above will return
true
, meaning at least one square was changed. Notice how the -4 has spread to three additional squares. Then we callspreadMarkedOnce(board)
again. Now the board changes to:board = { {4, 3, 2, 1}, {1, -4, 3, 3}, {2, -4, -4, -4}, {3, -4, -4, 1} };
and the function call returns
true
, since we changed two more 4s into -4s. Then we wouldspreadMarked(board)
one more time, but the board wouldn’t change, because there are no more 4s to change into -4s. Note that the 4 in the upper left corner doesn’t change to -4 because it’s not directly next to any of the -4s. So the function returnsfalse
(and we can therefore stop calling it).Simulating gravity
This, along with the
spreadMarkedOnce
function, are the two most challenging parts of the program. You will write a function calledgravityOnce
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
gravityOnce
function returntrue
if any gumdrops were moved. That way, you can callgravityOnce
over and over in a loop until it returnsfalse
(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.
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
gravityOnce(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
true
`, because at least one number moved.We can then call
gravityOnce(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
true
. 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
true
. At this point all the pieces are as low as possible, but the function must be called one more time to returnfalse
in order to determine that.
Other games
- If you don’t like this game, you can make a different one. The requirements are that it must involve a customizable-size board, and it must involve some concept where you examine the “neighbors” of the squares on the board.
- Some ideas are: Minesweeper, Connect 4, 2048, Candy Crush, Angry Birds, …
- You must clear your idea with me first.