At this point in the course, you have enough knowledge about Python to do more t

At this point in the course, you have enough knowledge about Python to do more than you think. Many simple games, for example, can be written with what we’ve already covered. So let’s do it! Let’s create a game of 2048!
Concepts being tested
Data Structures
Nested Lists
Dictionaries
Potentially Sets
Branching
Loops
Nested Loops
Functions Basics
Critical thinking, planning, & organization
So we’re all on the same page, let’s establish how the game works. The game has 4 controls:
w – move the board up
s – move the board down
a – move the board left
d – move the board right
Moving the board will slide the pieces towards the chosen direction, combining pieces with the same number if they slide into each other. Sliding this board down, for example:
╭────╮╭────╮╭────╮╭────╮
│
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│                    2
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│                        2
╰────╯╰────╯╰────╯╰────╯
results in this board:
╭────╮╭────╮╭────╮╭────╮
│
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│  2
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│                      4
╰────╯╰────╯╰────╯╰────╯
The two 2’s combine to form one 4. The game then randomly places another piece on the board, which is the 2 in the upper left of the second board. The goal of the game is to create a piece of the value 2048, in which case the player wins, and the game is over. The second way the game may terminate is if there are no more empty spaces for the game to place a new piece in, and there are no more moves for the user to take.
Our program will have one more control input:
q – Terminates the program with the message: “Goodbye”
The Program
To help alleviate some of the pressure of creating a slightly larger program, we have provided you with:
base code
utility functions
a strategy guide
Using a standardized program format allows us to automatically test your program, meaning you can get instant feedback on your program as usual. All the code you will write will be contained within the main.py module. The utilities.py module provides a few helper functions to assist in writing the program. If you are comfortable in doing so, you may write helper functions, but they are not required.
Requirements to standardize programs and enable automatic testing
As mentioned, if we agree upon a standard, we can write tests for your program and grade all submissions despite each of you having different implementations. Your program must only execute when the function “main” is called with a starting board, as seen at the bottom of the main.py template. Your program must use the “generate_piece” function provided in the “utilities” module to generate new pieces. You should call placerandom only once per piece and you should assume that placerandom will always return a valid space. Once the user quits, the modified game_board should be returned from the “main” function. Finally, before the user is allowed to take a turn, the program must place two pieces on the board.
DEV_MODE
Although a normal game of 2048 uses a random number generator to place pieces on its board, this type of input is often difficult to work with when developing or debugging new code. The base code alleviates this issue by allowing input to guide the placement of new pieces. By setting the variable “DEVMODE” to True, the program will prompt the user for column-row coordinates and a value which it will then use to place the next piece. To utilize this functionality, pass in the DEVMODE variable to the generate_piece function as a second parameter. This is optional but is a good starting ground for familiarizing yourself with the game board data structure.
Flow of the Program
Most game programs follow a rather simple flow of events. They first initialize key variables that keep track of important aspects of the game, such as clearing the board, deciding who’s turn it is, resetting the score, etc. Then the program enters the game loop: a series of events and responses that repeat until the game is over, at which point the program breaks out of the loop and the program finishes executing. Depending on your implementation, you may want to initialize and keep track of information such as who’s turn it is, if the game has been won, and certainly the state of the board.
“Single-Player” Computer Games
Although this game is technically played by only one person, the game isn’t actually a single-player game. From a coding perspective, there are two players involved: the human who controls the moves, and the computer who places random pieces on the board. This impacts how we write the logic of the game since we need to keep track of whose turn it is. The computer’s turn is fairly simple: place a piece on the board and end its turn. The player’s turn, on the other hand, is harder to track. We must consider and properly handle instances where the player does not act as we expect them to:
When the player inputs an invalid character “b”, we must re-prompt them for input until we receive a valid character (wasdq)
When the player inputs a valid move, we must verify that at least one piece on the board moved. Otherwise, the player is allowed to input another move. We must repeat this until the player inputs a move that affects the board.
Since these are two separate conditions, you should treat them as such. Don’t try to solve both checks with one solution. There are many different ways to solve this problem. Some are easier than others, so think carefully before you commit to an answer.
Initializing Data
A game of 2048 can be tracked using 4 pieces of information:
The game board
A 2-dimensional list of integers representing the cells of the board.
Our board will be limited to a 4×4 field.
Empty cells will be represented as 0’s, and all other numbers represent themselves.
The board data structure can be indexed by [row][column].
Index [0][0] is the top left of the board, [0][3] is top right, and [3][0] is the bottom left.
Whose turn it is
Computer v. user
Useful in determining if a new piece should be added, as well as when to check if the game is lost or won.
If the game has been won
Although your program may realize that the game has been won, it may not be able to take action right away, and therefore, a tracker variable can be helpful to signal the program to take action at a later time.
User input
Note: depending on your implementation, some of these may be implied information and not explicitly declared as variables.
Game-Ending Moves
Consider how the game can end. Can a game of 2048 ever be lost on the user’s turn? Since the user cannot add more pieces to the board, the answer is no. Therefore, the game can only be lost during the computer’s turn. What about winning? Can the game ever be won on the computer’s turn? For similar reasons, no. Therefore, we only need to check if the game has been won or lost after each respective turn.
Game Loop
A round of 2048 can be broken up into the following actions:
Computer’s turn
Place a new piece on the board
Check if the game is lost and act accordingly
Print the Updated Board
Remember to use the utilities.py module
User’s turn
Take input
Validate input, return to step 1 of the User’s turn if the input is invalid
Quit the program if appropriate
Update the board
If the board hasn’t changed, return to step 1 of the User’s turn
Check if the game is won and act accordingly
Note that when the game is over, the game relinquishes control back to the program by breaking out of the loop. Often, students will attempt to terminate the program all together through various calls to quit(), sys.exit(), etc. This will force the program to terminate completely, rather than allowing it to resolve its execution naturally. Do not call these functions. Resolve the program by breaking out of the loop or returning the function
Algorithms
User Moves
The heart of this program is being able to manipulate the cells of the game board to create the player moves. There are many different ways to approach this, but all of them will require you to dig deep and apply many different concepts. You might, for example, need to apply concepts such as reversing a list, list concatenation, or list/set membership (the “in” operator).
The obvious algorithm would involve moving pieces space by space across the board until no more empty spaces remain in an applied direction.
This approach may be simpler to think of, but implementing it leaves a lot of room for error.
A less obvious approach would be to delete unnecessary information from the board and add it back in after combining pieces.
Shifting the row [0, 2, 0, 4] left or right results in [2, 4, …] or […, 2, 4]. What information is important here? What can you ignore?
Most solutions will involve nested loops.
You should break the problem of updating the board down into the smaller problem of updating a single row or column, and then repeating the process for all rows or columns.
Examples:
Regardless of the specific choice – ‘w’, ‘a’, ‘s’, or ‘d’ – all user moves boil down to the same algorithm, just applied in different directions. Let’s take a look at the ‘a’ (shift left) move in-depth, which you can then apply to the remaining moves. Given the following board and the instruction ‘a’:
╭────╮╭────╮╭────╮╭────╮
│                                          4
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│                          2          4
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│.    8
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│ 2                      2                  4
╰────╯╰────╯╰────╯╰────╯
the resulting board would look like:
╭────╮╭────╮╭────╮╭────╮
│    4
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│    2                  4
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│      8
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│    4                  4
╰────╯╰────╯╰────╯╰────╯
Since each row is independent of its neighbors (hint), we can analyze them one at a time. Viewing it in its list form, the first list undergoes this transformation:
[0, 4, 0, 0] -> [4, 0, 0, 0]
The second row:
[0, 2, 4, 0] -> [2, 4, 0, 0]
Things to note
What moves? The 2 and the 4, or the 0’s?
[8, 0, 0, 0] -> [8, 0, 0, 0]
Why does the algorithm not move the 8?
[2, 2, 4, 0] -> [4, 4, 0, 0]
Does the number of 0’s change? By how many? How can you calculate this?
How did the game know not to combine the two 4’s?
Is there a way to track which pieces/cells were already used in a combine move?
[2, 2, 2, 0] -> [4, 2, 0, 0]
Why is it not [2, 4, 0, 0]?
Once you can handle a single row, is there a way to repeat the same process for the remaining 3 rows?
If you can achieve an algorithm for ‘a’, how can you modify the data or algorithm to achieve a ‘d’ move (shift right)?:
[2, 2, 4, 0] -> [0, 0, 4, 4]
If you can solve an ‘a’ move, can you modify the data or algorithm to solve a vertical move?
[0]    [0]
[2] -> [0]
[2]    [4]
[4]    [4]
Suggestion
Try solving a row by hand (pen & paper), taking one step at a time. Note any logic you use in deciding the next move. Things like “I can’t move this piece over because ____”, “It would be easier to remove ____ than to move each piece over multiple times”, or “I can’t combine this piece because ____”. Then, convert the process into code.
Game Over
The other algorithm you will need to implement is the game-over detection system.
Let’s consider the following board:
╭────╮╭────╮╭────╮╭────╮
│                        2                8                4
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│ 8                    16              4                2
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│      4              128            16                8
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│ 2                    8                4                  2
╰────╯╰────╯╰────╯╰────╯
What’s the first thing you check to tell if the game is over? Immediately, you know the game has not ended because there is at least one empty space for a new piece to be generated in.
╭────╮╭────╮╭────╮╭────╮
│      4                2                8                4
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│      8                16.              4                2
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│    4                128            16              8
╰────╯╰────╯╰────╯╰────╯
╭────╮╭────╮╭────╮╭────╮
│ 2                      8                4                2
╰────╯╰────╯╰────╯╰────╯
This second board is filled with pieces; no empty cells remain. To decide if the game is lost, you must identify any valid user moves. Again, try your hand at doing this manually, and then convert the process into code.
Hint: Attempting to apply each user move to determine if the game is over may work, but requires more effort. Can you find a way to determine if the game is over by visiting each cell only once (looping over the board only once)?
****I HAVE ATTACHED MAIN.PY AND UTILITIES.PY FILES AS WELL AS A SCREENSHOT THAT SHOWS HOW A SAMPLE GAME SHOULD RUN BELOW***
PLEASE LOOK AT THE CONCEPTS BEING TESTED AT THE VERY BEGINNING OF THE INSTRUCTIONS AS WELL TO SEE WHAT SHOULD BE IN THIS
THANK YOU