Week 3 Class Exercise: Building The 2048 Game Together

Chris Tralie

Overview / Logistics

The purpose of this class exercise is to teach you how to write and test methods that do very specific tasks, and then to see how they all fit together to accomplish a complex task. This should nail home that point that you should always split complex programs up into simpler, small tasks and test them each rigorously before combining them into a whole, and also that it's possible to create specifications for code that are precise enough that different people can work on different parts of the same program. As a bonus, you will get some great practice with 2D arrays, which will help you on the next assignment

Students will split up into groups to specialize on one small piece of the logic for the game 2048, and we will then put them all together to make a complete game that we play as a class

You can obtain the code from github with the command

git clone https://github.com/ursinus-cs174-s2022/Week3_2048.git

You'l be editing the code in game.cpp. Some groups may use the function rand(), which I implemented in randutils.cpp to return a random number between 0 and 1. I've provided a makefile, so you can build the code with make and run it with ./game

Learning Objectives

  • Write methods to fit a specification
  • Access and modify elements in 2D array
  • Write loops that work in concert with 2D arrays

Background: 2048

2048 is a simple but surprisingly addictive game that was all the rage about eight years ago. It also happens to be at the right level of complexity for where we are now with our coding skills. Click here to read the rules for the game, and please play the game yourself below with the 4 arrow keys to familiarize yourself with the rules (Javascript implementation courtesy of Kunal Mohta)

Programming Tasks

There are many ways to implement the logic in this game, but we will be breaking it down into a set of very specific tasks that each group will work on. Here are the different tasks that each group will implement. Each group should have a designated coder who shares their screen, a note-taker who keeps track of the approaches that were tried and the difficulties that the group ran into.

  1. Group 1: addRandom() and makeRandomGrid()
  2. Group 2: moveRight()
  3. Group 3: moveDown()
  4. Group 4: combineRight()
  5. Group 5: combineDown()
  6. Group 6: flipHorizontally() and flipVertically()
  7. Group 7: isFull() and isOver()

Students should test their methods rigorously before declaring victory. You may want to fill in the method makeMyOwnGrid() to fill in specific example to test your code on to make sure it handles all cases. I've given some examples below in the different task sections that you should verify give the right outputs. As an example of how you might setup a grid, the following code

Prints out a grid that looks like this


Group 1: addRandom() and makeRandomGrid()

The addRandom() method should turn exactly one empty cell anywhere on the board at random into either a 2 or a 4. The method should not overwrite a cell that is already occupied. Below is an example of two calls of addRandom() in a row. The first one adds a 4 in the upper right, and the second one adds a 2 on the left of the second row.

printBoard()printBoard()

Once you are finished this method, you should create another method makeRandomGrid(int numInitial) that takes one integer, which is the number of random 2s or 4s to put down on the grid in random positions. This should be as simple as calling your makeRandomGrid() function numInitial times in a row.

To aid you, use the rand() method that I provided, which returns random numbers between 0 and 1.


Group 2: moveRight()

Fill in the method moveRight() to take the nonzero elements in the current board and move them all to the right so that there are no gaps and everything is touching the right of the board. Don't worry about combining adjacent elements that are the same; that will be the job of another group.

As an example, consider a call of moveRight() on the board below

moveRight()

Group 3: moveDown()

Fill in the method moveDown() to take the nonzero elements in the current board and move them all down so there are no gaps and everything is touching the bottom. Don't worry about combining adjacent elements that are the same; that will be the job of another group.

As an example, consider a call of moveDown() on the board below

moveDown()

Group 4: combineRight()

Fill in the method combineRight() to combine all adjacent numbers that are the same in a particular row. There are three cases to consider

  1. There are two adjacent elements that are the same. In this case, put a zero in the place of the first one, and replace the second one with twice its value
  2. There are three in a row. In this case, put a zero in the first place to make it empty, keep the original value in the second place, and put twice the original value in the third place.
  3. There are 4 in a row. Put zeros in the first two, and put twice the value in the second two.

Below is an example of the effect this would have on a particular board

combineRight()

Group 5: combineDown()

Fill in the method combineDown() to combine all adjacent numbers that are the same in a particular column. There are three cases to consider

  1. There are two adjacent elements that are the same. In this case, put a zero in the place of the first one, and replace the second one with twice its value
  2. There are three in a row. In this case, put a zero in the first place to make it empty, keep the original value in the second place, and put twice the original value in the third place.
  3. There are 4 in a row. Put zeros in the first two, and put twice the value in the second two.

Below is an example of the effect this would have on a particular board

combineDown()

Group 6: flipHorizontally() and flipVertically()

Fill in the method flipHorizontally() to flip all of the elements in the board to the from left/right, right/left in a mirror reflection. Here's an example of the effect of this on a particular board

flipHorizontally()

Then fill in the method flipVertically() to flip all of the elements in the board from up/down, down/up in a mirror reflection.Here's an example of the effect of this on a particular board

flipVertically()

Group 7: isFull() and isOver()

Fill in the method isFull(), which returns true if the board is full, or false otherwise. Then, if time permits, implement the method isOver() that returns true if the the board is full and it is not possible to make any moves. or false otherwise.

For example, here is a board that is full but in which it is still possible to make a move (isFull() is true but isOver() is false)

And here is a board that is full and where no more moves are possible, so the game should be over (isFull() is true and isOver() is true)